diff --git a/README.md b/README.md index 3411e89..4681a43 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,33 @@ use node_compressed_generator.js to build "node_compressed_generator.js" from ge index.html is the release version of mbed-blockly. +## Version + +The current implementation supports mbed OS 2 only. Mbed OS 5 will be considered in the future release. + +## Enhanced feature to core library of blockly + +To make Blockly support code generation of C code, we add a few js files under the folder "c_core" without changing the original code of blockly. + +instances.js +field_variable.js +field_instance.js +block.js +blockly.js + +## Keep up-to-date with upstream library + +To make the development up-to-date, I should manually download the latest version of [https://github.com/google/closure-compiler-js](google-closure-compiler) and [https://github.com/google/blockly](google-blockly). The former is easier to handle since all I should do is downloading the compressed library and extract it. +For the latter (blockly), I only need parts of it. Therefore, I should manually download the following parts: + +* `core/*` +* `blocks/*` +* `msg/js/en.js` + ## Reference * [https://os.mbed.com/platforms/ST-Nucleo-F103RB/](https://os.mbed.com/platforms/ST-Nucleo-F103RB/) hardware information * [https://developer-sjc-indigo-border.mbed.org/cookbook/Homepage](https://developer-sjc-indigo-border.mbed.org/cookbook/Homepage) SDK API reference -* [https://developers.google.com/blockly/guides/overview](https://developers.google.com/blockly/guides/overview) Google Blockly reference \ No newline at end of file +* [https://developers.google.com/blockly/guides/overview](https://developers.google.com/blockly/guides/overview) Google Blockly reference diff --git a/blockly_compressed.js b/blockly_compressed.js index bf1ed6c..b3cf47c 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -2,51 +2,37 @@ 'use strict'; var COMPILED=!0,goog=goog||{}; -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.checkStringArgs=function(a,b,c){if(null==a)throw new TypeError("The 'this' value for String.prototype."+c+" must not be null or undefined");if(b instanceof RegExp)throw new TypeError("First argument to String.prototype."+c+" must not be a regular expression");return a+""};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1; -$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this); -$jscomp.polyfill=function(a,b,c,d){if(b){c=$jscomp.global;a=a.split(".");for(d=0;da||1342177279>>=1)b+=b;return d}},"es6","es3");$jscomp.findInternal=function(a,b,c){a instanceof String&&(a=String(a));for(var d=a.length,e=0;ea||1342177279>>=1)b+=b;return d}},"es6","es3");$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,c){return $jscomp.findInternal(this,a,c).v}},"es6","es3"); $jscomp.polyfill("String.prototype.endsWith",function(a){return a?a:function(a,c){var b=$jscomp.checkStringArgs(this,a,"endsWith");a+="";void 0===c&&(c=b.length);c=Math.max(0,Math.min(c|0,b.length));for(var e=a.length;0=e}},"es6","es3"); $jscomp.polyfill("String.prototype.startsWith",function(a){return a?a:function(a,c){var b=$jscomp.checkStringArgs(this,a,"startsWith");a+="";var e=b.length,f=a.length;c=Math.max(0,Math.min(c|0,b.length));for(var g=0;g=f}},"es6","es3");var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a}; goog.isNumber=function(a){return"number"==typeof a};goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; -goog.define=function(a,b){COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&void 0===goog.global.CLOSURE_UNCOMPILED_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?b=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&void 0===goog.global.CLOSURE_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(b=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en"; -goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1;goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)}; -goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; -goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.");if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module."); -goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)};goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null; -goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_};goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; +goog.define=function(a,b){if(!COMPILED){var c=goog.global.CLOSURE_UNCOMPILED_DEFINES,d=goog.global.CLOSURE_DEFINES;c&&void 0===c.nodeType&&Object.prototype.hasOwnProperty.call(c,a)?b=c[a]:d&&void 0===d.nodeType&&Object.prototype.hasOwnProperty.call(d,a)&&(b=d[a])}goog.exportPath_(a,b)};goog.DEBUG=!1;goog.LOCALE="en";goog.TRUSTED_SITE=!0;goog.STRICT_MODE_COMPATIBLE=!1;goog.DISALLOW_TEST_ONLY_CODE=COMPILED&&!goog.DEBUG;goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING=!1; +goog.provide=function(a){if(goog.isInModuleLoader_())throw Error("goog.provide can not be used within a goog.module.");if(!COMPILED&&goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');goog.constructNamespace_(a)};goog.constructNamespace_=function(a,b){if(!COMPILED){delete goog.implicitNamespaces_[a];for(var c=a;(c=c.substring(0,c.lastIndexOf(".")))&&!goog.getObjectByName(c);)goog.implicitNamespaces_[c]=!0}goog.exportPath_(a,b)};goog.VALID_MODULE_RE_=/^[a-zA-Z_$][a-zA-Z0-9._$]*$/; +goog.module=function(a){if(!goog.isString(a)||!a||-1==a.search(goog.VALID_MODULE_RE_))throw Error("Invalid module identifier");if(!goog.isInModuleLoader_())throw Error("Module "+a+" has been loaded incorrectly. Note, modules cannot be loaded as normal scripts. They require some kind of pre-processing step. You're likely trying to load a module via a script tag or as a part of a concatenated bundle without rewriting the module. For more info see: https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide."); +if(goog.moduleLoaderState_.moduleName)throw Error("goog.module may only be called once per module.");goog.moduleLoaderState_.moduleName=a;if(!COMPILED){if(goog.isProvided_(a))throw Error('Namespace "'+a+'" already declared.');delete goog.implicitNamespaces_[a]}};goog.module.get=function(a){return goog.module.getInternal_(a)}; +goog.module.getInternal_=function(a){if(!COMPILED){if(a in goog.loadedModules_)return goog.loadedModules_[a];if(!goog.implicitNamespaces_[a])return a=goog.getObjectByName(a),null!=a?a:null}return null};goog.moduleLoaderState_=null;goog.isInModuleLoader_=function(){return null!=goog.moduleLoaderState_}; +goog.module.declareLegacyNamespace=function(){if(!COMPILED&&!goog.isInModuleLoader_())throw Error("goog.module.declareLegacyNamespace must be called from within a goog.module");if(!COMPILED&&!goog.moduleLoaderState_.moduleName)throw Error("goog.module must be called prior to goog.module.declareLegacyNamespace.");goog.moduleLoaderState_.declareLegacyNamespace=!0}; goog.setTestOnly=function(a){if(goog.DISALLOW_TEST_ONLY_CODE)throw a=a||"",Error("Importing test-only code into non-debug environment"+(a?": "+a:"."));};goog.forwardDeclare=function(a){};COMPILED||(goog.isProvided_=function(a){return a in goog.loadedModules_||!goog.implicitNamespaces_[a]&&goog.isDefAndNotNull(goog.getObjectByName(a))},goog.implicitNamespaces_={"goog.module":!0}); -goog.getObjectByName=function(a,b){a=a.split(".");b=b||goog.global;for(var c;c=a.shift();)if(goog.isDefAndNotNull(b[c]))b=b[c];else return null;return b};goog.globalize=function(a,b){b=b||goog.global;for(var c in a)b[c]=a[c]}; -goog.addDependency=function(a,b,c,d){if(goog.DEPENDENCIES_ENABLED){var e;a=a.replace(/\\/g,"/");var f=goog.dependencies_;d&&"boolean"!==typeof d||(d=d?{module:"goog"}:{});for(var g=0;e=b[g];g++)f.nameToPath[e]=a,f.loadFlags[a]=d;for(d=0;b=c[d];d++)a in f.requires||(f.requires[a]={}),f.requires[a][b]=!0}};goog.ENABLE_DEBUG_LOADER=!0;goog.logToConsole_=function(a){goog.global.console&&goog.global.console.error(a)}; -goog.require=function(a){if(!COMPILED){goog.ENABLE_DEBUG_LOADER&&goog.IS_OLD_IE_&&goog.maybeProcessDeferredDep_(a);if(goog.isProvided_(a)){if(goog.isInModuleLoader_())return goog.module.getInternal_(a)}else if(goog.ENABLE_DEBUG_LOADER){var b=goog.getPathFromDeps_(a);if(b)goog.writeScripts_(b);else throw a="goog.require could not find: "+a,goog.logToConsole_(a),Error(a);}return null}};goog.basePath="";goog.nullFunction=function(){}; -goog.abstractMethod=function(){throw Error("unimplemented abstract method");};goog.addSingletonGetter=function(a){a.instance_=void 0;a.getInstance=function(){if(a.instance_)return a.instance_;goog.DEBUG&&(goog.instantiatedSingletons_[goog.instantiatedSingletons_.length]=a);return a.instance_=new a}};goog.instantiatedSingletons_=[];goog.LOAD_MODULE_USING_EVAL=!0;goog.SEAL_MODULE_EXPORTS=goog.DEBUG;goog.loadedModules_={};goog.DEPENDENCIES_ENABLED=!COMPILED&&goog.ENABLE_DEBUG_LOADER;goog.TRANSPILE="detect"; -goog.TRANSPILER="transpile.js"; -goog.DEPENDENCIES_ENABLED&&(goog.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{},written:{},deferred:{}},goog.inHtmlDocument_=function(){var a=goog.global.document;return null!=a&&"write"in a},goog.findBasePath_=function(){if(goog.isDef(goog.global.CLOSURE_BASE_PATH)&&goog.isString(goog.global.CLOSURE_BASE_PATH))goog.basePath=goog.global.CLOSURE_BASE_PATH;else if(goog.inHtmlDocument_()){var a=goog.global.document,b=a.currentScript;a=b?[b]:a.getElementsByTagName("SCRIPT");for(b=a.length- -1;0<=b;--b){var c=a[b].src,d=c.lastIndexOf("?");d=-1==d?c.length:d;if("base.js"==c.substr(d-7,7)){goog.basePath=c.substr(0,d-7);break}}}},goog.importScript_=function(a,b){(goog.global.CLOSURE_IMPORT_SCRIPT||goog.writeScriptTag_)(a,b)&&(goog.dependencies_.written[a]=!0)},goog.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.oldIeWaiting_=!1,goog.importProcessedScript_=function(a,b,c){goog.importScript_("",'goog.retrieveAndExec_("'+a+'", '+b+", "+c+");")},goog.queuedModules_= -[],goog.wrapModule_=function(a,b){return goog.LOAD_MODULE_USING_EVAL&&goog.isDef(goog.global.JSON)?"goog.loadModule("+goog.global.JSON.stringify(b+"\n//# sourceURL\x3d"+a+"\n")+");":'goog.loadModule(function(exports) {"use strict";'+b+"\n;return exports});\n//# sourceURL\x3d"+a+"\n"},goog.loadQueuedModules_=function(){var a=goog.queuedModules_.length;if(0>>0);goog.uidCounter_=0;goog.getHashCode=goog.getUid; goog.removeHashCode=goog.removeUid;goog.cloneObject=function(a){var b=goog.typeOf(a);if("object"==b||"array"==b){if(a.clone)return a.clone();b="array"==b?[]:{};for(var c in a)b[c]=goog.cloneObject(a[c]);return b}return a};goog.bindNative_=function(a,b,c){return a.call.apply(a.bind,arguments)}; goog.bindJs_=function(a,b,c){if(!a)throw Error();if(2Number(a[1])?!1:b('(()\x3d\x3e{"use strict";class X{constructor(){if(new.target!\x3dString)throw 1;this.x\x3d42}}let q\x3dReflect.construct(X,[],String);if(q.x!\x3d42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a\x3d\x3d2)continue;function f(z\x3d{a}){let a\x3d0;return z.a}{function f(){return 0;}}return f()\x3d\x3d3}})()')}); -a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 \x3d\x3d 4")});a("es8",function(){return b("async () \x3d\x3e 1, true")});return c};/* +goog.DEPENDENCIES_ENABLED&&(goog.inHtmlDocument_=function(){var a=goog.global.document;return null!=a&&"write"in a},goog.findBasePath_=function(){if(goog.isDef(goog.global.CLOSURE_BASE_PATH)&&goog.isString(goog.global.CLOSURE_BASE_PATH))goog.basePath=goog.global.CLOSURE_BASE_PATH;else if(goog.inHtmlDocument_()){var a=goog.global.document,b=a.currentScript;a=b?[b]:a.getElementsByTagName("SCRIPT");for(b=a.length-1;0<=b;--b){var c=a[b].src,d=c.lastIndexOf("?");d=-1==d?c.length:d;if("base.js"==c.substr(d- +7,7)){goog.basePath=c.substr(0,d-7);break}}}},goog.findBasePath_(),goog.Transpiler=function(){this.requiresTranspilation_=null},goog.Transpiler.prototype.createRequiresTranspilation_=function(){function a(a,b){d?c[a]=!0:b()?c[a]=!1:d=c[a]=!0}function b(a){try{return!!eval(a)}catch(g){return!1}}var c={es3:!1},d=!1,e=goog.global.navigator&&goog.global.navigator.userAgent?goog.global.navigator.userAgent:"";a("es5",function(){return b("[1,].length\x3d\x3d1")});a("es6",function(){var a=e.match(/Edge\/(\d+)(\.\d)*/i); +return a&&15>Number(a[1])?!1:b('(()\x3d\x3e{"use strict";class X{constructor(){if(new.target!\x3dString)throw 1;this.x\x3d42}}let q\x3dReflect.construct(X,[],String);if(q.x!\x3d42||!(q instanceof String))throw 1;for(const a of[2,3]){if(a\x3d\x3d2)continue;function f(z\x3d{a}){let a\x3d0;return z.a}{function f(){return 0;}}return f()\x3d\x3d3}})()')});a("es6-impl",function(){return!0});a("es7",function(){return b("2 ** 2 \x3d\x3d 4")});a("es8",function(){return b("async () \x3d\x3e 1, true")});a("es_next", +function(){return b("({...rest} \x3d {}), true")});return c},goog.Transpiler.prototype.needsTranspile=function(a){if("always"==goog.TRANSPILE)return!0;if("never"==goog.TRANSPILE)return!1;this.requiresTranspilation_||(this.requiresTranspilation_=this.createRequiresTranspilation_());if(a in this.requiresTranspilation_)return this.requiresTranspilation_[a];throw Error("Unknown language mode: "+a);},goog.Transpiler.prototype.transpile=function(a,b){return goog.transpile_(a,b)},goog.transpiler_=new goog.Transpiler, +goog.DebugLoader=function(){this.dependencies_={loadFlags:{},nameToPath:{},requires:{},visited:{},written:{},deferred:{}};this.oldIeWaiting_=!1;this.queuedModules_=[];this.lastNonModuleScriptIndex_=0},goog.DebugLoader.IS_OLD_IE_=!(goog.global.atob||!goog.global.document||!goog.global.document.all),goog.DebugLoader.prototype.earlyProcessLoad=function(a){goog.DebugLoader.IS_OLD_IE_&&this.maybeProcessDeferredDep_(a)},goog.DebugLoader.prototype.load=function(a){var b=this.getPathFromDeps_(a);if(b){var c= +[],d={},e=this.dependencies_,f=this,g=function(a){if(!(a in e.written||a in e.visited)){e.visited[a]=!0;if(a in e.requires)for(var b in e.requires[a])if(!f.isProvided(b))if(b in e.nameToPath)g(e.nameToPath[b]);else throw Error("Undefined nameToPath for "+b);a in d||(d[a]=!0,c.push(a))}};g(b);for(a=0;a=a||"\u0080"<=a&&"\ufffd">=a};goog.string.stripNewlines=function(a){return a.replace(/(\r\n|\r|\n)+/g," ")};goog.string.canonicalizeNewlines=function(a){return a.replace(/(\r\n|\r|\n)/g,"\n")}; -goog.string.normalizeWhitespace=function(a){return a.replace(/\xa0|\s/g," ")};goog.string.normalizeSpaces=function(a){return a.replace(/\xa0|[ \t]+/g," ")};goog.string.collapseBreakingSpaces=function(a){return a.replace(/[\t\r\n ]+/g," ").replace(/^[\t\r\n ]+|[\t\r\n ]+$/g,"")};goog.string.trim=goog.TRUSTED_SITE&&String.prototype.trim?function(a){return a.trim()}:function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")};goog.string.trimLeft=function(a){return a.replace(/^[\s\xa0]+/,"")}; -goog.string.trimRight=function(a){return a.replace(/[\s\xa0]+$/,"")};goog.string.caseInsensitiveCompare=function(a,b){a=String(a).toLowerCase();b=String(b).toLowerCase();return a/g;goog.string.QUOT_RE_=/"/g;goog.string.SINGLE_QUOTE_RE_=/'/g;goog.string.NULL_RE_=/\x00/g;goog.string.E_RE_=/e/g;goog.string.ALL_RE_=goog.string.DETECT_DOUBLE_ESCAPING?/[\x00&<>"'e]/:/[\x00&<>"']/;goog.string.unescapeEntities=function(a){return goog.string.contains(a,"\x26")?!goog.string.FORCE_NON_DOM_HTML_UNESCAPING&&"document"in goog.global?goog.string.unescapeEntitiesUsingDom_(a):goog.string.unescapePureXmlEntities_(a):a}; -goog.string.unescapeEntitiesWithDocument=function(a,b){return goog.string.contains(a,"\x26")?goog.string.unescapeEntitiesUsingDom_(a,b):a}; -goog.string.unescapeEntitiesUsingDom_=function(a,b){var c={"\x26amp;":"\x26","\x26lt;":"\x3c","\x26gt;":"\x3e","\x26quot;":'"'};var d=b?b.createElement("div"):goog.global.document.createElement("div");return a.replace(goog.string.HTML_ENTITY_PATTERN_,function(a,b){var e=c[a];if(e)return e;"#"==b.charAt(0)&&(b=Number("0"+b.substr(1)),isNaN(b)||(e=String.fromCharCode(b)));e||(d.innerHTML=a+" ",e=d.firstChild.nodeValue.slice(0,-1));return c[a]=e})}; -goog.string.unescapePureXmlEntities_=function(a){return a.replace(/&([^;]+);/g,function(a,c){switch(c){case "amp":return"\x26";case "lt":return"\x3c";case "gt":return"\x3e";case "quot":return'"';default:return"#"!=c.charAt(0)||(c=Number("0"+c.substr(1)),isNaN(c))?a:String.fromCharCode(c)}})};goog.string.HTML_ENTITY_PATTERN_=/&([^;\s<&]+);?/g;goog.string.whitespaceEscape=function(a,b){return goog.string.newLineToBr(a.replace(/ /g," \x26#160;"),b)}; -goog.string.preserveSpaces=function(a){return a.replace(/(^|[\n ]) /g,"$1"+goog.string.Unicode.NBSP)};goog.string.stripQuotes=function(a,b){for(var c=b.length,d=0;db&&(a=a.substring(0,b-3)+"...");c&&(a=goog.string.htmlEscape(a));return a}; -goog.string.truncateMiddle=function(a,b,c,d){c&&(a=goog.string.unescapeEntities(a));if(d&&a.length>b){d>b&&(d=b);var e=a.length-d;a=a.substring(0,b-d)+"..."+a.substring(e)}else a.length>b&&(d=Math.floor(b/2),e=a.length-d,a=a.substring(0,d+b%2)+"..."+a.substring(e));c&&(a=goog.string.htmlEscape(a));return a};goog.string.specialEscapeChars_={"\x00":"\\0","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\x0B":"\\x0B",'"':'\\"',"\\":"\\\\","\x3c":"\x3c"};goog.string.jsEscapeCache_={"'":"\\'"}; -goog.string.quote=function(a){a=String(a);for(var b=['"'],c=0;ce?d:goog.string.escapeChar(d))}b.push('"');return b.join("")};goog.string.escapeString=function(a){for(var b=[],c=0;cb)var c=a;else{if(256>b){if(c="\\x",16>b||256b&&(c+="0");c+=b.toString(16).toUpperCase()}return goog.string.jsEscapeCache_[a]=c};goog.string.contains=function(a,b){return-1!=a.indexOf(b)}; -goog.string.caseInsensitiveContains=function(a,b){return goog.string.contains(a.toLowerCase(),b.toLowerCase())};goog.string.countOf=function(a,b){return a&&b?a.split(b).length-1:0};goog.string.removeAt=function(a,b,c){var d=a;0<=b&&bb?1:0};goog.string.hashCode=function(a){for(var b=0,c=0;c>>0;return b};goog.string.uniqueStringCounter_=2147483648*Math.random()|0;goog.string.createUniqueString=function(){return"goog_"+goog.string.uniqueStringCounter_++}; -goog.string.toNumber=function(a){var b=Number(a);return 0==b&&goog.string.isEmptyOrWhitespace(a)?NaN:b};goog.string.isLowerCamelCase=function(a){return/^[a-z]+([A-Z][a-z]*)*$/.test(a)};goog.string.isUpperCamelCase=function(a){return/^([A-Z][a-z]*)+$/.test(a)};goog.string.toCamelCase=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};goog.string.toSelectorCase=function(a){return String(a).replace(/([A-Z])/g,"-$1").toLowerCase()}; -goog.string.toTitleCase=function(a,b){b=goog.isString(b)?goog.string.regExpEscape(b):"\\s";return a.replace(new RegExp("(^"+(b?"|["+b+"]+":"")+")([a-z])","g"),function(a,b,e){return b+e.toUpperCase()})};goog.string.capitalize=function(a){return String(a.charAt(0)).toUpperCase()+String(a.substr(1)).toLowerCase()};goog.string.parseInt=function(a){isFinite(a)&&(a=String(a));return goog.isString(a)?/^\s*-?0x/i.test(a)?parseInt(a,16):parseInt(a,10):NaN}; -goog.string.splitLimit=function(a,b,c){a=a.split(b);for(var d=[];0c&&(c=e)}return-1==c?a:a.slice(c+1)}; -goog.string.editDistance=function(a,b){var c=[],d=[];if(a==b)return 0;if(!a.length||!b.length)return Math.max(a.length,b.length);for(var e=0;ec?Math.max(0,a.length+c):c;if(goog.isString(a))return goog.isString(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc&&(c=Math.max(0,a.length+c));if(goog.isString(a))return goog.isString(b)&&1==b.length?a.lastIndexOf(b,c):-1;for(;0<=c;c--)if(c in a&&a[c]===b)return c;return-1}; goog.array.forEach=goog.NATIVE_ARRAY_PROTOTYPES&&(goog.array.ASSUME_NATIVE_FUNCTIONS||Array.prototype.forEach)?function(a,b,c){goog.asserts.assert(null!=a.length);Array.prototype.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=goog.isString(a)?a.split(""):a,f=0;fthis.MAX_UNDO&&this.undoStack_.unshift());for(var b=0,c;c=this.listeners_[b];b++)c(a)}; Blockly.Workspace.prototype.getBlockById=function(a){return this.blockDB_[a]||null};Blockly.Workspace.WorkspaceDB_=Object.create(null);Blockly.Workspace.getById=function(a){return Blockly.Workspace.WorkspaceDB_[a]||null};Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear;Blockly.Workspace.prototype.clearUndo=Blockly.Workspace.prototype.clearUndo;Blockly.Workspace.prototype.addChangeListener=Blockly.Workspace.prototype.addChangeListener; -Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;goog.labs={};goog.labs.userAgent={};goog.labs.userAgent.util={};goog.labs.userAgent.util.getNativeUserAgentString_=function(){var a=goog.labs.userAgent.util.getNavigator_();return a&&(a=a.userAgent)?a:""};goog.labs.userAgent.util.getNavigator_=function(){return goog.global.navigator};goog.labs.userAgent.util.userAgent_=goog.labs.userAgent.util.getNativeUserAgentString_();goog.labs.userAgent.util.setUserAgent=function(a){goog.labs.userAgent.util.userAgent_=a||goog.labs.userAgent.util.getNativeUserAgentString_()}; +Blockly.Workspace.prototype.removeChangeListener=Blockly.Workspace.prototype.removeChangeListener;goog.string={};goog.string.DETECT_DOUBLE_ESCAPING=!1;goog.string.FORCE_NON_DOM_HTML_UNESCAPING=!1;goog.string.Unicode={NBSP:"\u00a0"};goog.string.startsWith=function(a,b){return 0==a.lastIndexOf(b,0)};goog.string.endsWith=function(a,b){var c=a.length-b.length;return 0<=c&&a.indexOf(b,c)==c};goog.string.caseInsensitiveStartsWith=function(a,b){return 0==goog.string.caseInsensitiveCompare(b,a.substr(0,b.length))}; +goog.string.caseInsensitiveEndsWith=function(a,b){return 0==goog.string.caseInsensitiveCompare(b,a.substr(a.length-b.length,b.length))};goog.string.caseInsensitiveEquals=function(a,b){return a.toLowerCase()==b.toLowerCase()};goog.string.subs=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1=a||"\u0080"<=a&&"\ufffd">=a};goog.string.stripNewlines=function(a){return a.replace(/(\r\n|\r|\n)+/g," ")};goog.string.canonicalizeNewlines=function(a){return a.replace(/(\r\n|\r|\n)/g,"\n")}; +goog.string.normalizeWhitespace=function(a){return a.replace(/\xa0|\s/g," ")};goog.string.normalizeSpaces=function(a){return a.replace(/\xa0|[ \t]+/g," ")};goog.string.collapseBreakingSpaces=function(a){return a.replace(/[\t\r\n ]+/g," ").replace(/^[\t\r\n ]+|[\t\r\n ]+$/g,"")};goog.string.trim=goog.TRUSTED_SITE&&String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};goog.string.trimLeft=function(a){return a.replace(/^[\s\xa0]+/,"")}; +goog.string.trimRight=function(a){return a.replace(/[\s\xa0]+$/,"")};goog.string.caseInsensitiveCompare=function(a,b){a=String(a).toLowerCase();b=String(b).toLowerCase();return a/g;goog.string.QUOT_RE_=/"/g;goog.string.SINGLE_QUOTE_RE_=/'/g;goog.string.NULL_RE_=/\x00/g;goog.string.E_RE_=/e/g;goog.string.ALL_RE_=goog.string.DETECT_DOUBLE_ESCAPING?/[\x00&<>"'e]/:/[\x00&<>"']/;goog.string.unescapeEntities=function(a){return goog.string.contains(a,"\x26")?!goog.string.FORCE_NON_DOM_HTML_UNESCAPING&&"document"in goog.global?goog.string.unescapeEntitiesUsingDom_(a):goog.string.unescapePureXmlEntities_(a):a}; +goog.string.unescapeEntitiesWithDocument=function(a,b){return goog.string.contains(a,"\x26")?goog.string.unescapeEntitiesUsingDom_(a,b):a}; +goog.string.unescapeEntitiesUsingDom_=function(a,b){var c={"\x26amp;":"\x26","\x26lt;":"\x3c","\x26gt;":"\x3e","\x26quot;":'"'};var d=b?b.createElement("div"):goog.global.document.createElement("div");return a.replace(goog.string.HTML_ENTITY_PATTERN_,function(a,b){var e=c[a];if(e)return e;"#"==b.charAt(0)&&(b=Number("0"+b.substr(1)),isNaN(b)||(e=String.fromCharCode(b)));e||(d.innerHTML=a+" ",e=d.firstChild.nodeValue.slice(0,-1));return c[a]=e})}; +goog.string.unescapePureXmlEntities_=function(a){return a.replace(/&([^;]+);/g,function(a,c){switch(c){case "amp":return"\x26";case "lt":return"\x3c";case "gt":return"\x3e";case "quot":return'"';default:return"#"!=c.charAt(0)||(c=Number("0"+c.substr(1)),isNaN(c))?a:String.fromCharCode(c)}})};goog.string.HTML_ENTITY_PATTERN_=/&([^;\s<&]+);?/g;goog.string.whitespaceEscape=function(a,b){return goog.string.newLineToBr(a.replace(/ /g," \x26#160;"),b)}; +goog.string.preserveSpaces=function(a){return a.replace(/(^|[\n ]) /g,"$1"+goog.string.Unicode.NBSP)};goog.string.stripQuotes=function(a,b){for(var c=b.length,d=0;db&&(a=a.substring(0,b-3)+"...");c&&(a=goog.string.htmlEscape(a));return a}; +goog.string.truncateMiddle=function(a,b,c,d){c&&(a=goog.string.unescapeEntities(a));if(d&&a.length>b){d>b&&(d=b);var e=a.length-d;a=a.substring(0,b-d)+"..."+a.substring(e)}else a.length>b&&(d=Math.floor(b/2),e=a.length-d,a=a.substring(0,d+b%2)+"..."+a.substring(e));c&&(a=goog.string.htmlEscape(a));return a};goog.string.specialEscapeChars_={"\x00":"\\0","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\x0B":"\\x0B",'"':'\\"',"\\":"\\\\","\x3c":"\x3c"};goog.string.jsEscapeCache_={"'":"\\'"}; +goog.string.quote=function(a){a=String(a);for(var b=['"'],c=0;ce?d:goog.string.escapeChar(d))}b.push('"');return b.join("")};goog.string.escapeString=function(a){for(var b=[],c=0;cb)var c=a;else{if(256>b){if(c="\\x",16>b||256b&&(c+="0");c+=b.toString(16).toUpperCase()}return goog.string.jsEscapeCache_[a]=c};goog.string.contains=function(a,b){return-1!=a.indexOf(b)}; +goog.string.caseInsensitiveContains=function(a,b){return goog.string.contains(a.toLowerCase(),b.toLowerCase())};goog.string.countOf=function(a,b){return a&&b?a.split(b).length-1:0};goog.string.removeAt=function(a,b,c){var d=a;0<=b&&bb?1:0};goog.string.hashCode=function(a){for(var b=0,c=0;c>>0;return b};goog.string.uniqueStringCounter_=2147483648*Math.random()|0;goog.string.createUniqueString=function(){return"goog_"+goog.string.uniqueStringCounter_++}; +goog.string.toNumber=function(a){var b=Number(a);return 0==b&&goog.string.isEmptyOrWhitespace(a)?NaN:b};goog.string.isLowerCamelCase=function(a){return/^[a-z]+([A-Z][a-z]*)*$/.test(a)};goog.string.isUpperCamelCase=function(a){return/^([A-Z][a-z]*)+$/.test(a)};goog.string.toCamelCase=function(a){return String(a).replace(/\-([a-z])/g,function(a,c){return c.toUpperCase()})};goog.string.toSelectorCase=function(a){return String(a).replace(/([A-Z])/g,"-$1").toLowerCase()}; +goog.string.toTitleCase=function(a,b){b=goog.isString(b)?goog.string.regExpEscape(b):"\\s";return a.replace(new RegExp("(^"+(b?"|["+b+"]+":"")+")([a-z])","g"),function(a,b,e){return b+e.toUpperCase()})};goog.string.capitalize=function(a){return String(a.charAt(0)).toUpperCase()+String(a.substr(1)).toLowerCase()};goog.string.parseInt=function(a){isFinite(a)&&(a=String(a));return goog.isString(a)?/^\s*-?0x/i.test(a)?parseInt(a,16):parseInt(a,10):NaN}; +goog.string.splitLimit=function(a,b,c){a=a.split(b);for(var d=[];0c&&(c=e)}return-1==c?a:a.slice(c+1)}; +goog.string.editDistance=function(a,b){var c=[],d=[];if(a==b)return 0;if(!a.length||!b.length)return Math.max(a.length,b.length);for(var e=0;egoog.i18n.bidi.rtlDetectionThreshold_?goog.i18n.bidi.Dir.RTL:goog.i18n.bidi.Dir.LTR}; goog.i18n.bidi.detectRtlDirectionality=function(a,b){return goog.i18n.bidi.estimateDirection(a,b)==goog.i18n.bidi.Dir.RTL};goog.i18n.bidi.setElementDirAndAlign=function(a,b){a&&(b=goog.i18n.bidi.toDir(b))&&(a.style.textAlign=b==goog.i18n.bidi.Dir.RTL?goog.i18n.bidi.RIGHT:goog.i18n.bidi.LEFT,a.dir=b==goog.i18n.bidi.Dir.RTL?"rtl":"ltr")}; goog.i18n.bidi.setElementDirByTextDirectionality=function(a,b){switch(goog.i18n.bidi.estimateDirection(b)){case goog.i18n.bidi.Dir.LTR:a.dir="ltr";break;case goog.i18n.bidi.Dir.RTL:a.dir="rtl";break;default:a.removeAttribute("dir")}};goog.i18n.bidi.DirectionalString=function(){};goog.html.TrustedResourceUrl=function(){this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_="";this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_=goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_};goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString=!0;goog.html.TrustedResourceUrl.prototype.getTypedStringValue=function(){return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_}; -goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString=!0;goog.html.TrustedResourceUrl.prototype.getDirection=function(){return goog.i18n.bidi.Dir.LTR};goog.DEBUG&&(goog.html.TrustedResourceUrl.prototype.toString=function(){return"TrustedResourceUrl{"+this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_+"}"}); +goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString=!0;goog.html.TrustedResourceUrl.prototype.getDirection=function(){return goog.i18n.bidi.Dir.LTR};goog.html.TrustedResourceUrl.prototype.cloneWithParams=function(a){var b=goog.html.TrustedResourceUrl.unwrap(this),c=/\?/.test(b)?"\x26":"?",d;for(d in a)for(var e=goog.isArray(a[d])?a[d]:[a[d]],f=0;f=goog.debug.MAX_STACK_DEPTH){b.push("[...long stack...]");break}}a&&d>=a?b.push("[...reached max depth limit...]"):b.push("[end]");return b.join("")}; +goog.debug.MAX_STACK_DEPTH=50;goog.debug.getNativeStackTrace_=function(a){var b=Error();if(Error.captureStackTrace)return Error.captureStackTrace(b,a),String(b.stack);try{throw b;}catch(c){b=c}return(a=b.stack)?String(a):null};goog.debug.getStacktrace=function(a){var b;goog.debug.FORCE_SLOPPY_STACKS||(b=goog.debug.getNativeStackTrace_(a||goog.debug.getStacktrace));b||(b=goog.debug.getStacktraceHelper_(a||arguments.callee.caller,[]));return b}; +goog.debug.getStacktraceHelper_=function(a,b){var c=[];if(goog.array.contains(b,a))c.push("[...circular reference...]");else if(a&&b.length=a.keyCode)a.keyCode=-1}catch(b){}};goog.events.BrowserEvent.prototype.getBrowserEvent=function(){return this.event_};goog.events.Listenable=function(){};goog.events.Listenable.IMPLEMENTED_BY_PROP="closure_listenable_"+(1E6*Math.random()|0);goog.events.Listenable.addImplementation=function(a){a.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP]=!0};goog.events.Listenable.isImplementedBy=function(a){return!(!a||!a[goog.events.Listenable.IMPLEMENTED_BY_PROP])};goog.events.ListenableKey=function(){};goog.events.ListenableKey.counter_=0;goog.events.ListenableKey.reserveKey=function(){return++goog.events.ListenableKey.counter_};goog.events.Listener=function(a,b,c,d,e,f){goog.events.Listener.ENABLE_MONITORING&&(this.creationStack=Error().stack);this.listener=a;this.proxy=b;this.src=c;this.type=d;this.capture=!!e;this.handler=f;this.key=goog.events.ListenableKey.reserveKey();this.removed=this.callOnce=!1};goog.events.Listener.ENABLE_MONITORING=!1;goog.events.Listener.prototype.markAsRemoved=function(){this.removed=!0;this.handler=this.src=this.proxy=this.listener=null};goog.events.ListenerMap=function(a){this.src=a;this.listeners={};this.typeCount_=0};goog.events.ListenerMap.prototype.getTypeCount=function(){return this.typeCount_};goog.events.ListenerMap.prototype.getListenerCount=function(){var a=0,b;for(b in this.listeners)a+=this.listeners[b].length;return a}; +a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.platformModifierKey=goog.userAgent.MAC?a.metaKey:a.ctrlKey;this.pointerId=a.pointerId||0;this.pointerType=goog.events.BrowserEvent.getPointerType_(a);this.state=a.state;this.event_=a;a.defaultPrevented&&this.preventDefault()}; +goog.events.BrowserEvent.prototype.isButton=function(a){return goog.events.BrowserFeature.HAS_W3C_BUTTON?this.event_.button==a:"click"==this.type?a==goog.events.BrowserEvent.MouseButton.LEFT:!!(this.event_.button&goog.events.BrowserEvent.IE_BUTTON_MAP[a])};goog.events.BrowserEvent.prototype.isMouseActionButton=function(){return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT)&&!(goog.userAgent.WEBKIT&&goog.userAgent.MAC&&this.ctrlKey)}; +goog.events.BrowserEvent.prototype.stopPropagation=function(){goog.events.BrowserEvent.superClass_.stopPropagation.call(this);this.event_.stopPropagation?this.event_.stopPropagation():this.event_.cancelBubble=!0}; +goog.events.BrowserEvent.prototype.preventDefault=function(){goog.events.BrowserEvent.superClass_.preventDefault.call(this);var a=this.event_;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};goog.events.BrowserEvent.prototype.getBrowserEvent=function(){return this.event_}; +goog.events.BrowserEvent.getPointerType_=function(a){return goog.isString(a.pointerType)?a.pointerType:goog.events.BrowserEvent.IE_POINTER_TYPE_MAP[a.pointerType]||""};goog.events.Listenable=function(){};goog.events.Listenable.IMPLEMENTED_BY_PROP="closure_listenable_"+(1E6*Math.random()|0);goog.events.Listenable.addImplementation=function(a){a.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP]=!0};goog.events.Listenable.isImplementedBy=function(a){return!(!a||!a[goog.events.Listenable.IMPLEMENTED_BY_PROP])};goog.events.ListenableKey=function(){};goog.events.ListenableKey.counter_=0;goog.events.ListenableKey.reserveKey=function(){return++goog.events.ListenableKey.counter_};goog.events.Listener=function(a,b,c,d,e,f){goog.events.Listener.ENABLE_MONITORING&&(this.creationStack=Error().stack);this.listener=a;this.proxy=b;this.src=c;this.type=d;this.capture=!!e;this.handler=f;this.key=goog.events.ListenableKey.reserveKey();this.removed=this.callOnce=!1};goog.events.Listener.ENABLE_MONITORING=!1;goog.events.Listener.prototype.markAsRemoved=function(){this.removed=!0;this.handler=this.src=this.proxy=this.listener=null};goog.events.ListenerMap=function(a){this.src=a;this.listeners={};this.typeCount_=0};goog.events.ListenerMap.prototype.getTypeCount=function(){return this.typeCount_};goog.events.ListenerMap.prototype.getListenerCount=function(){var a=0,b;for(b in this.listeners)a+=this.listeners[b].length;return a}; goog.events.ListenerMap.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.listeners[f];a||(a=this.listeners[f]=[],this.typeCount_++);var g=goog.events.ListenerMap.findListenerIndex_(a,b,d,e);-1a?Blockly.Types.LARGE_NUMBER:Blockly.Types.NUMBER):Blockly.Types.regExpFloat_.test(a)?Blockly.Types.DECIMAL:Blockly.Types.NULL};Blockly.StaticTyping=function(){this.varTypeDict=Object.create(null);this.pendingVarTypeDict=Object.create(null)}; -Blockly.StaticTyping.prototype.collectVarsWithTypes=function(a){this.varTypeDict=Object.create(null);this.pendingVarTypeDict=Object.create(null);a=Blockly.StaticTyping.getAllStatementsOrdered(a);for(var b=0;b=goog.debug.MAX_STACK_DEPTH){b.push("[...long stack...]");break}}a&&d>=a?b.push("[...reached max depth limit...]"):b.push("[end]");return b.join("")}; -goog.debug.MAX_STACK_DEPTH=50;goog.debug.getNativeStackTrace_=function(a){var b=Error();if(Error.captureStackTrace)return Error.captureStackTrace(b,a),String(b.stack);try{throw b;}catch(c){b=c}return(a=b.stack)?String(a):null};goog.debug.getStacktrace=function(a){var b;goog.debug.FORCE_SLOPPY_STACKS||(b=goog.debug.getNativeStackTrace_(a||goog.debug.getStacktrace));b||(b=goog.debug.getStacktraceHelper_(a||arguments.callee.caller,[]));return b}; -goog.debug.getStacktraceHelper_=function(a,b){var c=[];if(goog.array.contains(b,a))c.push("[...circular reference...]");else if(a&&b.lengtha?Blockly.Types.LARGE_NUMBER:Blockly.Types.NUMBER):Blockly.Types.regExpFloat_.test(a)?Blockly.Types.DECIMAL:Blockly.Types.NULL};Blockly.StaticTyping=function(){this.varTypeDict=Object.create(null);this.pendingVarTypeDict=Object.create(null)}; +Blockly.StaticTyping.prototype.collectVarsWithTypes=function(a){this.varTypeDict=Object.create(null);this.pendingVarTypeDict=Object.create(null);a=Blockly.StaticTyping.getAllStatementsOrdered(a);for(var b=0;bvar goog = undefined;'); - // Load fresh Closure Library. - document.write(''); - document.write(''); -} +// Do not edit this file; automatically generated by build.py. +'use strict'; + +this.IS_NODE_JS = !!(typeof module !== 'undefined' && module.exports); +this.BLOCKLY_DIR = (function(root) { + if (!root.IS_NODE_JS) { + // Find name of current directory. + var scripts = document.getElementsByTagName('script'); + var re = new RegExp('(.+)[\/]blockly_(.*)uncompressed\.js$'); + for (var i = 0, script; script = scripts[i]; i++) { + var match = re.exec(script.src); + if (match) { + return match[1]; + } + } + alert('Could not detect Blockly\'s directory name.'); + } + return ''; +})(this); +this.BLOCKLY_BOOT = function(root) { + if (root.IS_NODE_JS) { + require('google-closure-library'); + } else if (typeof goog == 'undefined') { + alert('Error: Closure not found. Read this:\n' + + 'developers.google.com/blockly/guides/modify/web/closure'); + } + var dir = ''; + if (root.IS_NODE_JS) { + dir = 'blockly'; + } else { + dir = this.BLOCKLY_DIR.match(/[^\/]+$/)[0]; + } + // Execute after Closure has loaded. +goog.addDependency("../../../mbed_code_generator_v2/c_core/c_blockly.js", ['Blockly.C_Blockly'], ['Blockly.StaticTyping', 'Blockly.Macro', 'Blockly.FieldMacro']); +goog.addDependency("../../../mbed_code_generator_v2/c_core/field_macro.js", ['Blockly.FieldMacro'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.Macro', 'Blockly.utils', 'goog.string']); +goog.addDependency("../../../mbed_code_generator_v2/c_core/macro.js", ['Blockly.Macro'], ['Blockly.Workspace', 'goog.string']); +goog.addDependency("../../../mbed_code_generator_v2/c_core/static_typing.js", ['Blockly.StaticTyping'], ['Blockly.Block', 'Blockly.Type', 'Blockly.Types', 'Blockly.Workspace', 'goog.asserts']); +goog.addDependency("../../../mbed_code_generator_v2/c_core/type.js", ['Blockly.Type'], ['goog.asserts']); +goog.addDependency("../../../mbed_code_generator_v2/c_core/types.js", ['Blockly.Types'], ['Blockly.Type']); +goog.addDependency("../../../" + dir + "/core/block.js", ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Comment', 'Blockly.Connection', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Mutator', 'Blockly.Warning', 'Blockly.Workspace', 'Blockly.Xml', 'goog.array', 'goog.asserts', 'goog.math.Coordinate', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/block_drag_surface.js", ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/block_dragger.js", ['Blockly.BlockDragger'], ['Blockly.DraggedConnectionManager', 'goog.math.Coordinate', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/block_render_svg.js", ['Blockly.BlockSvg.render'], ['Blockly.BlockSvg', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/block_svg.js", ['Blockly.BlockSvg'], ['Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Grid', 'Blockly.RenderedConnection', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.utils', 'goog.Timer', 'goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/blockly.js", ['Blockly'], ['Blockly.BlockSvg.render', 'Blockly.Events', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldTextInput', 'Blockly.FieldNumber', 'Blockly.FieldVariable', 'Blockly.Generator', 'Blockly.Msg', 'Blockly.Procedures', 'Blockly.Toolbox', 'Blockly.Touch', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'goog.color', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/blocks.js", ['Blockly.Blocks'], []); +goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Touch', 'Blockly.Workspace', 'goog.dom', 'goog.math', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/bubble_dragger.js", ['Blockly.BubbleDragger'], ['goog.math.Coordinate', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Icon', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/connection.js", ['Blockly.Connection'], ['goog.asserts', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']); +goog.addDependency("../../../" + dir + "/core/constants.js", ['Blockly.constants'], []); +goog.addDependency("../../../" + dir + "/core/contextmenu.js", ['Blockly.ContextMenu'], ['Blockly.utils', 'Blockly.utils.uiMenu', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem']); +goog.addDependency("../../../" + dir + "/core/css.js", ['Blockly.Css'], []); +goog.addDependency("../../../" + dir + "/core/dragged_connection_manager.js", ['Blockly.DraggedConnectionManager'], ['Blockly.RenderedConnection', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/events.js", ['Blockly.Events'], ['goog.array', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/extensions.js", ['Blockly.Extensions'], ['Blockly.Mutator', 'Blockly.utils', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/field.js", ['Blockly.Field'], ['Blockly.Gesture', 'goog.asserts', 'goog.dom', 'goog.math.Size', 'goog.style', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_angle.js", ['Blockly.FieldAngle'], ['Blockly.FieldTextInput', 'goog.math', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_checkbox.js", ['Blockly.FieldCheckbox'], ['Blockly.Field']); +goog.addDependency("../../../" + dir + "/core/field_colour.js", ['Blockly.FieldColour'], ['Blockly.Field', 'Blockly.utils', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.ColorPicker']); +goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDate'], ['Blockly.Field', 'Blockly.utils', 'goog.date', 'goog.date.DateTime', 'goog.dom', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.style', 'goog.ui.DatePicker']); +goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'Blockly.utils', 'Blockly.utils.uiMenu', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.Tooltip', 'goog.dom', 'goog.math.Size']); +goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']); +goog.addDependency("../../../" + dir + "/core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Field', 'Blockly.Msg', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'goog.asserts', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/flyout_base.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.FlyoutButton', 'Blockly.Gesture', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/flyout_button.js", ['Blockly.FlyoutButton'], ['goog.dom', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/flyout_dragger.js", ['Blockly.FlyoutDragger'], ['Blockly.WorkspaceDragger', 'goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/flyout_horizontal.js", ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.FlyoutButton', 'Blockly.Flyout', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/flyout_vertical.js", ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Flyout', 'Blockly.FlyoutButton', 'Blockly.utils', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/generator.js", ['Blockly.Generator'], ['Blockly.Block', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/gesture.js", ['Blockly.Gesture'], ['Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.constants', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/grid.js", ['Blockly.Grid'], ['Blockly.utils', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/icon.js", ['Blockly.Icon'], ['goog.dom', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/inject.js", ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.Grid', 'Blockly.Options', 'Blockly.WorkspaceSvg', 'Blockly.WorkspaceDragSurfaceSvg', 'goog.dom', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/input.js", ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/msg.js", ['Blockly.Msg'], []); +goog.addDependency("../../../" + dir + "/core/mutator.js", ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'goog.Timer', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/names.js", ['Blockly.Names'], []); +goog.addDependency("../../../" + dir + "/core/options.js", ['Blockly.Options'], []); +goog.addDependency("../../../" + dir + "/core/procedures.js", ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.Field', 'Blockly.Names', 'Blockly.Workspace']); +goog.addDependency("../../../" + dir + "/core/rendered_connection.js", ['Blockly.RenderedConnection'], ['Blockly.Connection']); +goog.addDependency("../../../" + dir + "/core/scrollbar.js", ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['goog.dom', 'goog.events']); +goog.addDependency("../../../" + dir + "/core/toolbox.js", ['Blockly.Toolbox'], ['Blockly.Flyout', 'Blockly.HorizontalFlyout', 'Blockly.Touch', 'Blockly.VerticalFlyout', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.BrowserFeature', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.math.Rect', 'goog.style', 'goog.ui.tree.TreeControl', 'goog.ui.tree.TreeNode']); +goog.addDependency("../../../" + dir + "/core/tooltip.js", ['Blockly.Tooltip'], ['goog.dom', 'goog.dom.TagName']); +goog.addDependency("../../../" + dir + "/core/touch.js", ['Blockly.Touch'], ['goog.events', 'goog.events.BrowserFeature', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/touch_gesture.js", ['Blockly.TouchGesture'], ['Blockly.Gesture', 'goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/trashcan.js", ['Blockly.Trashcan'], ['goog.Timer', 'goog.dom', 'goog.math', 'goog.math.Rect']); +goog.addDependency("../../../" + dir + "/core/ui_menu_utils.js", ['Blockly.utils.uiMenu'], []); +goog.addDependency("../../../" + dir + "/core/utils.js", ['Blockly.utils'], ['Blockly.Touch', 'goog.dom', 'goog.events.BrowserFeature', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/variable_map.js", ['Blockly.VariableMap'], []); +goog.addDependency("../../../" + dir + "/core/variable_model.js", ['Blockly.VariableModel'], ['goog.string']); +goog.addDependency("../../../" + dir + "/core/variables.js", ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.constants', 'Blockly.VariableModel', 'Blockly.Workspace', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/variables_dynamic.js", ['Blockly.VariablesDynamic'], ['Blockly.Variables', 'Blockly.Blocks', 'Blockly.constants', 'Blockly.VariableModel', 'goog.string']); +goog.addDependency("../../../" + dir + "/core/warning.js", ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Icon']); +goog.addDependency("../../../" + dir + "/core/widgetdiv.js", ['Blockly.WidgetDiv'], ['Blockly.Css', 'goog.dom', 'goog.dom.TagName', 'goog.style']); +goog.addDependency("../../../" + dir + "/core/workspace.js", ['Blockly.Workspace'], ['Blockly.VariableMap', 'goog.array', 'goog.math']); +goog.addDependency("../../../" + dir + "/core/workspace_audio.js", ['Blockly.WorkspaceAudio'], []); +goog.addDependency("../../../" + dir + "/core/workspace_drag_surface_svg.js", ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', 'goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("../../../" + dir + "/core/workspace_dragger.js", ['Blockly.WorkspaceDragger'], ['goog.math.Coordinate', 'goog.asserts']); +goog.addDependency("../../../" + dir + "/core/workspace_svg.js", ['Blockly.WorkspaceSvg'], ['Blockly.ConnectionDB', 'Blockly.constants', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Touch', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.ZoomControls', 'goog.array', 'goog.dom', 'goog.math.Coordinate', 'goog.userAgent']); +goog.addDependency("../../../" + dir + "/core/xml.js", ['Blockly.Xml'], ['goog.asserts', 'goog.dom']); +goog.addDependency("../../../" + dir + "/core/zoom_controls.js", ['Blockly.ZoomControls'], ['Blockly.Touch', 'goog.dom']); +goog.addDependency("../../alltests.js", [], []); +goog.addDependency("../../browser_capabilities.js", [], []); +goog.addDependency("../../doc/js/article.js", [], []); +goog.addDependency("../../protractor.conf.js", [], []); +goog.addDependency("../../protractor_spec.js", [], []); +goog.addDependency("../../third_party/closure/goog/base.js", [], []); +goog.addDependency("../../third_party/closure/goog/caja/string/html/htmlparser.js", ['goog.string.html', 'goog.string.html.HtmlParser', 'goog.string.html.HtmlParser.EFlags', 'goog.string.html.HtmlParser.Elements', 'goog.string.html.HtmlParser.Entities', 'goog.string.html.HtmlSaxHandler'], []); +goog.addDependency("../../third_party/closure/goog/caja/string/html/htmlparser_test.js", [], ['goog.string.html.HtmlParser', 'goog.string.html.HtmlSaxHandler', 'goog.testing.LooseMock', 'goog.testing.jsunit']); +goog.addDependency("../../third_party/closure/goog/deps.js", [], []); +goog.addDependency("../../third_party/closure/goog/dojo/dom/query.js", ['goog.dom.query'], ['goog.array', 'goog.dom', 'goog.functions', 'goog.string', 'goog.userAgent']); +goog.addDependency("../../third_party/closure/goog/dojo/dom/query_test.js", ['goog.dom.query_test'], ['goog.dom', 'goog.dom.query', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("../../third_party/closure/goog/loremipsum/text/loremipsum.js", ['goog.text.LoremIpsum'], ['goog.array', 'goog.math', 'goog.string', 'goog.structs.Map', 'goog.structs.Set']); +goog.addDependency("../../third_party/closure/goog/loremipsum/text/loremipsum_test.js", [], ['goog.testing.PseudoRandom', 'goog.testing.jsunit', 'goog.text.LoremIpsum']); +goog.addDependency("../../third_party/closure/goog/mochikit/async/deferred.js", ['goog.async.Deferred', 'goog.async.Deferred.AlreadyCalledError', 'goog.async.Deferred.CanceledError'], ['goog.Promise', 'goog.Thenable', 'goog.array', 'goog.asserts', 'goog.debug.Error']); +goog.addDependency("../../third_party/closure/goog/mochikit/async/deferred_async_test.js", [], ['goog.async.Deferred', 'goog.testing.jsunit']); +goog.addDependency("../../third_party/closure/goog/mochikit/async/deferred_test.js", [], ['goog.Promise', 'goog.Thenable', 'goog.async.Deferred', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("../../third_party/closure/goog/mochikit/async/deferredlist.js", ['goog.async.DeferredList'], ['goog.async.Deferred']); +goog.addDependency("../../third_party/closure/goog/mochikit/async/deferredlist_test.js", [], ['goog.array', 'goog.async.Deferred', 'goog.async.DeferredList', 'goog.testing.jsunit']); +goog.addDependency("../../third_party/closure/goog/svgpan/svgpan.js", ['svgpan.SvgPan'], ['goog.Disposable', 'goog.events', 'goog.events.EventType', 'goog.events.MouseWheelHandler']); +goog.addDependency("../bin/generate_closure_unit_tests/generate_closure_unit_tests.js", [], []); +goog.addDependency("a11y/aria/announcer.js", ['goog.a11y.aria.Announcer'], ['goog.Disposable', 'goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.object']); +goog.addDependency("a11y/aria/announcer_test.js", ['goog.a11y.aria.AnnouncerTest'], ['goog.a11y.aria', 'goog.a11y.aria.Announcer', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.State', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.iframe', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("a11y/aria/aria.js", ['goog.a11y.aria'], ['goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.a11y.aria.datatables', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.object', 'goog.string']); +goog.addDependency("a11y/aria/aria_test.js", ['goog.a11y.ariaTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit']); +goog.addDependency("a11y/aria/attributes.js", ['goog.a11y.aria.AutoCompleteValues', 'goog.a11y.aria.CheckedValues', 'goog.a11y.aria.DropEffectValues', 'goog.a11y.aria.ExpandedValues', 'goog.a11y.aria.GrabbedValues', 'goog.a11y.aria.InvalidValues', 'goog.a11y.aria.LivePriority', 'goog.a11y.aria.OrientationValues', 'goog.a11y.aria.PressedValues', 'goog.a11y.aria.RelevantValues', 'goog.a11y.aria.SelectedValues', 'goog.a11y.aria.SortValues', 'goog.a11y.aria.State'], []); +goog.addDependency("a11y/aria/datatables.js", ['goog.a11y.aria.datatables'], ['goog.a11y.aria.State', 'goog.object']); +goog.addDependency("a11y/aria/roles.js", ['goog.a11y.aria.Role'], []); +goog.addDependency("array/array.js", ['goog.array'], ['goog.asserts']); +goog.addDependency("array/array_test.js", ['goog.arrayTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("asserts/asserts.js", ['goog.asserts', 'goog.asserts.AssertionError'], ['goog.debug.Error', 'goog.dom.NodeType']); +goog.addDependency("asserts/asserts_test.js", ['goog.assertsTest'], ['goog.asserts', 'goog.asserts.AssertionError', 'goog.dom', 'goog.dom.TagName', 'goog.string', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("async/animationdelay.js", ['goog.async.AnimationDelay'], ['goog.Disposable', 'goog.events', 'goog.functions']); +goog.addDependency("async/animationdelay_test.js", [], []); +goog.addDependency("async/conditionaldelay.js", ['goog.async.ConditionalDelay'], ['goog.Disposable', 'goog.async.Delay']); +goog.addDependency("async/conditionaldelay_test.js", ['goog.async.ConditionalDelayTest'], ['goog.async.ConditionalDelay', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("async/debouncer.js", ['goog.async.Debouncer'], ['goog.Disposable', 'goog.Timer']); +goog.addDependency("async/debouncer_test.js", ['goog.async.DebouncerTest'], ['goog.array', 'goog.async.Debouncer', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("async/delay.js", ['goog.Delay', 'goog.async.Delay'], ['goog.Disposable', 'goog.Timer']); +goog.addDependency("async/delay_test.js", ['goog.async.DelayTest'], ['goog.async.Delay', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("async/freelist.js", ['goog.async.FreeList'], []); +goog.addDependency("async/freelist_test.js", ['goog.async.FreeListTest'], ['goog.async.FreeList', 'goog.testing.jsunit']); +goog.addDependency("async/nexttick.js", ['goog.async.nextTick', 'goog.async.throwException'], ['goog.debug.entryPointRegistry', 'goog.dom.TagName', 'goog.functions', 'goog.labs.userAgent.browser', 'goog.labs.userAgent.engine']); +goog.addDependency("async/nexttick_test.js", ['goog.async.nextTickTest'], ['goog.Promise', 'goog.Timer', 'goog.async.nextTick', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.dom', 'goog.dom.TagName', 'goog.labs.userAgent.browser', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("async/run.js", ['goog.async.run'], ['goog.async.WorkQueue', 'goog.async.nextTick', 'goog.async.throwException']); +goog.addDependency("async/run_test.js", ['goog.async.runTest'], ['goog.async.run', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("async/throttle.js", ['goog.Throttle', 'goog.async.Throttle'], ['goog.Disposable', 'goog.Timer']); +goog.addDependency("async/throttle_test.js", ['goog.async.ThrottleTest'], ['goog.async.Throttle', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("async/workqueue.js", ['goog.async.WorkItem', 'goog.async.WorkQueue'], ['goog.asserts', 'goog.async.FreeList']); +goog.addDependency("async/workqueue_test.js", ['goog.async.WorkQueueTest'], ['goog.async.WorkQueue', 'goog.testing.jsunit']); +goog.addDependency("base.js", [], []); +goog.addDependency("base_debug_loader_test.js", ['goog.baseDebugLoaderTest'], ['goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("base_module_test.js", [], []); +goog.addDependency("base_test.js", ['goog.baseTest'], ['goog.Promise', 'goog.Timer', 'goog.dom', 'goog.dom.TagName', 'goog.object', 'goog.test_module', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("bootstrap/nodejs.js", [], []); +goog.addDependency("bootstrap/webworkers.js", [], []); +goog.addDependency("color/alpha.js", ['goog.color.alpha'], ['goog.color']); +goog.addDependency("color/alpha_test.js", ['goog.color.alphaTest'], ['goog.array', 'goog.color', 'goog.color.alpha', 'goog.testing.jsunit']); +goog.addDependency("color/color.js", ['goog.color', 'goog.color.Hsl', 'goog.color.Hsv', 'goog.color.Rgb'], ['goog.color.names', 'goog.math']); +goog.addDependency("color/color_test.js", ['goog.colorTest'], ['goog.array', 'goog.color', 'goog.color.names', 'goog.testing.jsunit']); +goog.addDependency("color/names.js", ['goog.color.names'], []); +goog.addDependency("crypt/aes.js", ['goog.crypt.Aes'], ['goog.asserts', 'goog.crypt.BlockCipher']); +goog.addDependency("crypt/aes_test.js", ['goog.crypt.AesTest'], ['goog.crypt', 'goog.crypt.Aes', 'goog.testing.jsunit']); +goog.addDependency("crypt/arc4.js", ['goog.crypt.Arc4'], ['goog.asserts']); +goog.addDependency("crypt/arc4_test.js", ['goog.crypt.Arc4Test'], ['goog.array', 'goog.crypt.Arc4', 'goog.testing.jsunit']); +goog.addDependency("crypt/base64.js", ['goog.crypt.base64'], ['goog.asserts', 'goog.crypt', 'goog.string', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("crypt/base64_test.js", ['goog.crypt.base64Test'], ['goog.crypt', 'goog.crypt.base64', 'goog.testing.jsunit']); +goog.addDependency("crypt/basen.js", ['goog.crypt.baseN'], []); +goog.addDependency("crypt/basen_test.js", ['goog.crypt.baseNTest'], ['goog.crypt.baseN', 'goog.testing.jsunit']); +goog.addDependency("crypt/blobhasher.js", ['goog.crypt.BlobHasher', 'goog.crypt.BlobHasher.EventType'], ['goog.asserts', 'goog.events.EventTarget', 'goog.fs', 'goog.log']); +goog.addDependency("crypt/blobhasher_test.js", ['goog.crypt.BlobHasherTest'], ['goog.crypt', 'goog.crypt.BlobHasher', 'goog.crypt.Md5', 'goog.events', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("crypt/blockcipher.js", ['goog.crypt.BlockCipher'], []); +goog.addDependency("crypt/bytestring_perf.js", ['goog.crypt.byteArrayToStringPerf'], ['goog.array', 'goog.dom', 'goog.testing.PerformanceTable']); +goog.addDependency("crypt/cbc.js", ['goog.crypt.Cbc'], ['goog.array', 'goog.asserts', 'goog.crypt', 'goog.crypt.BlockCipher']); +goog.addDependency("crypt/cbc_test.js", ['goog.crypt.CbcTest'], ['goog.crypt', 'goog.crypt.Aes', 'goog.crypt.Cbc', 'goog.testing.jsunit']); +goog.addDependency("crypt/crypt.js", ['goog.crypt'], ['goog.array', 'goog.asserts']); +goog.addDependency("crypt/crypt_test.js", ['goog.cryptTest'], ['goog.crypt', 'goog.string', 'goog.testing.jsunit']); +goog.addDependency("crypt/ctr.js", ['goog.crypt.Ctr'], ['goog.array', 'goog.asserts', 'goog.crypt']); +goog.addDependency("crypt/ctr_test.js", ['goog.crypt.CtrTest'], ['goog.crypt', 'goog.crypt.Aes', 'goog.crypt.Ctr', 'goog.testing.jsunit']); +goog.addDependency("crypt/hash.js", ['goog.crypt.Hash'], []); +goog.addDependency("crypt/hash32.js", ['goog.crypt.hash32'], ['goog.crypt']); +goog.addDependency("crypt/hash32_test.js", ['goog.crypt.hash32Test'], ['goog.crypt.hash32', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("crypt/hashtester.js", ['goog.crypt.hashTester'], ['goog.array', 'goog.crypt', 'goog.dom', 'goog.dom.TagName', 'goog.testing.PerformanceTable', 'goog.testing.PseudoRandom', 'goog.testing.asserts']); +goog.addDependency("crypt/hmac.js", ['goog.crypt.Hmac'], ['goog.crypt.Hash']); +goog.addDependency("crypt/hmac_test.js", ['goog.crypt.HmacTest'], ['goog.crypt.Hmac', 'goog.crypt.Sha1', 'goog.crypt.hashTester', 'goog.testing.jsunit']); +goog.addDependency("crypt/md5.js", ['goog.crypt.Md5'], ['goog.crypt.Hash']); +goog.addDependency("crypt/md5_test.js", ['goog.crypt.Md5Test'], ['goog.crypt', 'goog.crypt.Md5', 'goog.crypt.hashTester', 'goog.testing.jsunit']); +goog.addDependency("crypt/pbkdf2.js", ['goog.crypt.pbkdf2'], ['goog.array', 'goog.asserts', 'goog.crypt', 'goog.crypt.Hmac', 'goog.crypt.Sha1']); +goog.addDependency("crypt/pbkdf2_test.js", ['goog.crypt.pbkdf2Test'], ['goog.crypt', 'goog.crypt.pbkdf2', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("crypt/sha1.js", ['goog.crypt.Sha1'], ['goog.crypt.Hash']); +goog.addDependency("crypt/sha1_test.js", ['goog.crypt.Sha1Test'], ['goog.crypt', 'goog.crypt.Sha1', 'goog.crypt.hashTester', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("crypt/sha2.js", ['goog.crypt.Sha2'], ['goog.array', 'goog.asserts', 'goog.crypt.Hash']); +goog.addDependency("crypt/sha224.js", ['goog.crypt.Sha224'], ['goog.crypt.Sha2']); +goog.addDependency("crypt/sha224_test.js", ['goog.crypt.Sha224Test'], ['goog.crypt', 'goog.crypt.Sha224', 'goog.crypt.hashTester', 'goog.testing.jsunit']); +goog.addDependency("crypt/sha256.js", ['goog.crypt.Sha256'], ['goog.crypt.Sha2']); +goog.addDependency("crypt/sha256_test.js", ['goog.crypt.Sha256Test'], ['goog.crypt', 'goog.crypt.Sha256', 'goog.crypt.hashTester', 'goog.testing.jsunit']); +goog.addDependency("crypt/sha2_64bit.js", ['goog.crypt.Sha2_64bit'], ['goog.array', 'goog.asserts', 'goog.crypt.Hash', 'goog.math.Long']); +goog.addDependency("crypt/sha2_64bit_test.js", ['goog.crypt.Sha2_64bit_test'], ['goog.array', 'goog.crypt', 'goog.crypt.Sha384', 'goog.crypt.Sha512', 'goog.crypt.Sha512_256', 'goog.crypt.hashTester', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("crypt/sha384.js", ['goog.crypt.Sha384'], ['goog.crypt.Sha2_64bit']); +goog.addDependency("crypt/sha512.js", ['goog.crypt.Sha512'], ['goog.crypt.Sha2_64bit']); +goog.addDependency("crypt/sha512_256.js", ['goog.crypt.Sha512_256'], ['goog.crypt.Sha2_64bit']); +goog.addDependency("cssom/cssom.js", ['goog.cssom', 'goog.cssom.CssRuleType'], ['goog.array', 'goog.dom', 'goog.dom.TagName']); +goog.addDependency("cssom/cssom_test.js", ['goog.cssomTest'], ['goog.array', 'goog.cssom', 'goog.cssom.CssRuleType', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("cssom/iframe/style.js", ['goog.cssom.iframe.style'], ['goog.asserts', 'goog.cssom', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.string', 'goog.style', 'goog.userAgent']); +goog.addDependency("cssom/iframe/style_test.js", ['goog.cssom.iframe.styleTest'], ['goog.cssom', 'goog.cssom.iframe.style', 'goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("datasource/datamanager.js", ['goog.ds.DataManager'], ['goog.ds.BasicNodeList', 'goog.ds.DataNode', 'goog.ds.Expr', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.Map']); +goog.addDependency("datasource/datasource.js", ['goog.ds.BaseDataNode', 'goog.ds.BasicNodeList', 'goog.ds.DataNode', 'goog.ds.DataNodeList', 'goog.ds.EmptyNodeList', 'goog.ds.LoadState', 'goog.ds.SortedNodeList', 'goog.ds.Util', 'goog.ds.logger'], ['goog.array', 'goog.log']); +goog.addDependency("datasource/datasource_test.js", ['goog.ds.JsDataSourceTest'], ['goog.dom.xml', 'goog.ds.DataManager', 'goog.ds.JsDataSource', 'goog.ds.SortedNodeList', 'goog.ds.XmlDataSource', 'goog.testing.jsunit']); +goog.addDependency("datasource/expr.js", ['goog.ds.Expr'], ['goog.ds.BasicNodeList', 'goog.ds.EmptyNodeList', 'goog.string']); +goog.addDependency("datasource/expr_test.js", ['goog.ds.ExprTest'], ['goog.ds.DataManager', 'goog.ds.Expr', 'goog.ds.JsDataSource', 'goog.testing.jsunit']); +goog.addDependency("datasource/fastdatanode.js", ['goog.ds.AbstractFastDataNode', 'goog.ds.FastDataNode', 'goog.ds.FastListNode', 'goog.ds.PrimitiveFastDataNode'], ['goog.ds.DataManager', 'goog.ds.DataNodeList', 'goog.ds.EmptyNodeList', 'goog.string']); +goog.addDependency("datasource/fastdatanode_test.js", ['goog.ds.FastDataNodeTest'], ['goog.array', 'goog.ds.DataManager', 'goog.ds.Expr', 'goog.ds.FastDataNode', 'goog.testing.jsunit']); +goog.addDependency("datasource/jsdatasource.js", ['goog.ds.JsDataSource', 'goog.ds.JsPropertyDataSource'], ['goog.ds.BaseDataNode', 'goog.ds.BasicNodeList', 'goog.ds.DataManager', 'goog.ds.DataNode', 'goog.ds.EmptyNodeList', 'goog.ds.LoadState']); +goog.addDependency("datasource/jsondatasource.js", ['goog.ds.JsonDataSource'], ['goog.Uri', 'goog.dom', 'goog.dom.TagName', 'goog.ds.DataManager', 'goog.ds.JsDataSource', 'goog.ds.LoadState', 'goog.ds.logger', 'goog.log']); +goog.addDependency("datasource/jsxmlhttpdatasource.js", ['goog.ds.JsXmlHttpDataSource'], ['goog.Uri', 'goog.ds.DataManager', 'goog.ds.FastDataNode', 'goog.ds.LoadState', 'goog.ds.logger', 'goog.events', 'goog.log', 'goog.net.EventType', 'goog.net.XhrIo']); +goog.addDependency("datasource/jsxmlhttpdatasource_test.js", ['goog.ds.JsXmlHttpDataSourceTest'], ['goog.ds.JsXmlHttpDataSource', 'goog.testing.TestQueue', 'goog.testing.jsunit', 'goog.testing.net.XhrIo']); +goog.addDependency("datasource/xmldatasource.js", ['goog.ds.XmlDataSource', 'goog.ds.XmlHttpDataSource'], ['goog.Uri', 'goog.dom.NodeType', 'goog.dom.xml', 'goog.ds.BasicNodeList', 'goog.ds.DataManager', 'goog.ds.DataNode', 'goog.ds.LoadState', 'goog.ds.logger', 'goog.log', 'goog.net.XhrIo', 'goog.string']); +goog.addDependency("date/date.js", ['goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval', 'goog.date.month', 'goog.date.weekDay'], ['goog.asserts', 'goog.date.DateLike', 'goog.i18n.DateTimeSymbols', 'goog.string']); +goog.addDependency("date/date_test.js", ['goog.dateTest'], ['goog.array', 'goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval', 'goog.date.month', 'goog.date.weekDay', 'goog.i18n.DateTimeSymbols', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.platform', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("date/datelike.js", ['goog.date.DateLike'], []); +goog.addDependency("date/daterange.js", ['goog.date.DateRange', 'goog.date.DateRange.Iterator', 'goog.date.DateRange.StandardDateRangeKeys'], ['goog.date.Date', 'goog.date.Interval', 'goog.iter.Iterator', 'goog.iter.StopIteration']); +goog.addDependency("date/daterange_test.js", ['goog.date.DateRangeTest'], ['goog.date.Date', 'goog.date.DateRange', 'goog.date.Interval', 'goog.i18n.DateTimeSymbols', 'goog.testing.jsunit']); +goog.addDependency("date/duration.js", ['goog.date.duration'], ['goog.i18n.DateTimeFormat', 'goog.i18n.MessageFormat']); +goog.addDependency("date/duration_test.js", ['goog.date.durationTest'], ['goog.date.duration', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("date/relative.js", ['goog.date.relative', 'goog.date.relative.TimeDeltaFormatter', 'goog.date.relative.Unit'], ['goog.i18n.DateTimeFormat', 'goog.i18n.DateTimePatterns']); +goog.addDependency("date/relative_test.js", ['goog.date.relativeTest'], ['goog.date.relativeCommonTests']); +goog.addDependency("date/relativecommontests.js", ['goog.date.relativeCommonTests'], ['goog.date.DateTime', 'goog.date.relative', 'goog.i18n.DateTimeFormat', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("date/relativewithplurals.js", ['goog.date.relativeWithPlurals'], ['goog.date.relative', 'goog.date.relative.Unit', 'goog.i18n.MessageFormat']); +goog.addDependency("date/relativewithplurals_test.js", ['goog.date.relativeWithPluralsTest'], ['goog.date.relative', 'goog.date.relativeCommonTests', 'goog.date.relativeWithPlurals', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_bn', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_fa']); +goog.addDependency("date/utcdatetime.js", ['goog.date.UtcDateTime'], ['goog.date', 'goog.date.Date', 'goog.date.DateTime', 'goog.date.Interval']); +goog.addDependency("date/utcdatetime_test.js", ['goog.date.UtcDateTimeTest'], ['goog.date.Interval', 'goog.date.UtcDateTime', 'goog.date.month', 'goog.date.weekDay', 'goog.testing.jsunit']); +goog.addDependency("db/cursor.js", ['goog.db.Cursor'], ['goog.async.Deferred', 'goog.db.Error', 'goog.db.KeyRange', 'goog.debug', 'goog.events.EventTarget']); +goog.addDependency("db/db.js", ['goog.db', 'goog.db.BlockedCallback', 'goog.db.UpgradeNeededCallback'], ['goog.asserts', 'goog.async.Deferred', 'goog.db.Error', 'goog.db.IndexedDb', 'goog.db.Transaction']); +goog.addDependency("db/db_test.js", ['goog.dbTest'], ['goog.Disposable', 'goog.Promise', 'goog.array', 'goog.db', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.IndexedDb', 'goog.db.KeyRange', 'goog.db.Transaction', 'goog.events', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent.product']); +goog.addDependency("db/error.js", ['goog.db.DomErrorLike', 'goog.db.Error', 'goog.db.Error.ErrorCode', 'goog.db.Error.ErrorName', 'goog.db.Error.VersionChangeBlockedError'], ['goog.asserts', 'goog.debug.Error']); +goog.addDependency("db/index.js", ['goog.db.Index'], ['goog.async.Deferred', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.KeyRange', 'goog.debug']); +goog.addDependency("db/indexeddb.js", ['goog.db.IndexedDb'], ['goog.db.Error', 'goog.db.ObjectStore', 'goog.db.Transaction', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget']); +goog.addDependency("db/keyrange.js", ['goog.db.KeyRange'], []); +goog.addDependency("db/objectstore.js", ['goog.db.ObjectStore'], ['goog.async.Deferred', 'goog.db.Cursor', 'goog.db.Error', 'goog.db.Index', 'goog.db.KeyRange', 'goog.debug', 'goog.events']); +goog.addDependency("db/transaction.js", ['goog.db.Transaction', 'goog.db.Transaction.TransactionMode'], ['goog.async.Deferred', 'goog.db.Error', 'goog.db.ObjectStore', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget']); +goog.addDependency("debug/console.js", ['goog.debug.Console'], ['goog.debug.LogManager', 'goog.debug.Logger', 'goog.debug.TextFormatter']); +goog.addDependency("debug/console_test.js", ['goog.debug.ConsoleTest'], ['goog.debug.Console', 'goog.debug.LogRecord', 'goog.debug.Logger', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("debug/debug.js", ['goog.debug'], ['goog.array', 'goog.debug.errorcontext', 'goog.userAgent']); +goog.addDependency("debug/debug_test.js", ['goog.debugTest'], ['goog.debug', 'goog.debug.errorcontext', 'goog.structs.Set', 'goog.testing.jsunit']); +goog.addDependency("debug/debugwindow.js", ['goog.debug.DebugWindow'], ['goog.debug.HtmlFormatter', 'goog.debug.LogManager', 'goog.debug.Logger', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.string.Const', 'goog.structs.CircularBuffer', 'goog.userAgent']); +goog.addDependency("debug/debugwindow_test.js", ['goog.debug.DebugWindowTest'], ['goog.debug.DebugWindow', 'goog.testing.jsunit']); +goog.addDependency("debug/devcss/devcss.js", ['goog.debug.DevCss', 'goog.debug.DevCss.UserAgent'], ['goog.asserts', 'goog.cssom', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.string', 'goog.userAgent']); +goog.addDependency("debug/devcss/devcss_test.js", ['goog.debug.DevCssTest'], ['goog.debug.DevCss', 'goog.style', 'goog.testing.jsunit']); +goog.addDependency("debug/devcss/devcssrunner.js", ['goog.debug.devCssRunner'], ['goog.debug.DevCss']); +goog.addDependency("debug/divconsole.js", ['goog.debug.DivConsole'], ['goog.debug.HtmlFormatter', 'goog.debug.LogManager', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.string.Const', 'goog.style']); +goog.addDependency("debug/enhanceerror_test.js", ['goog.debugEnhanceErrorTest'], ['goog.debug', 'goog.testing.jsunit']); +goog.addDependency("debug/entrypointregistry.js", ['goog.debug.EntryPointMonitor', 'goog.debug.entryPointRegistry'], ['goog.asserts']); +goog.addDependency("debug/entrypointregistry_test.js", ['goog.debug.entryPointRegistryTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.testing.jsunit']); +goog.addDependency("debug/error.js", ['goog.debug.Error'], []); +goog.addDependency("debug/error_test.js", ['goog.debug.ErrorTest'], ['goog.debug.Error', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("debug/errorcontext.js", ['goog.debug.errorcontext'], []); +goog.addDependency("debug/errorcontext_test.js", [], ['goog.testing.jsunit']); +goog.addDependency("debug/errorhandler.js", ['goog.debug.ErrorHandler', 'goog.debug.ErrorHandler.ProtectedFunctionError'], ['goog.Disposable', 'goog.asserts', 'goog.debug', 'goog.debug.EntryPointMonitor', 'goog.debug.Error', 'goog.debug.Trace']); +goog.addDependency("debug/errorhandler_async_test.js", ['goog.debug.ErrorHandlerAsyncTest'], ['goog.Promise', 'goog.debug.ErrorHandler', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("debug/errorhandler_test.js", ['goog.debug.ErrorHandlerTest'], ['goog.debug.ErrorHandler', 'goog.testing.MockControl', 'goog.testing.jsunit']); +goog.addDependency("debug/errorhandlerweakdep.js", ['goog.debug.errorHandlerWeakDep'], []); +goog.addDependency("debug/errorreporter.js", ['goog.debug.ErrorReporter', 'goog.debug.ErrorReporter.ExceptionEvent'], ['goog.asserts', 'goog.debug', 'goog.debug.Error', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.debug.errorcontext', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.log', 'goog.net.XhrIo', 'goog.object', 'goog.string', 'goog.uri.utils', 'goog.userAgent']); +goog.addDependency("debug/errorreporter_test.js", ['goog.debug.ErrorReporterTest'], ['goog.debug.Error', 'goog.debug.ErrorReporter', 'goog.debug.errorcontext', 'goog.events', 'goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("debug/fancywindow.js", ['goog.debug.FancyWindow'], ['goog.array', 'goog.asserts', 'goog.debug.DebugWindow', 'goog.debug.LogManager', 'goog.debug.Logger', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("debug/formatter.js", ['goog.debug.Formatter', 'goog.debug.HtmlFormatter', 'goog.debug.TextFormatter'], ['goog.debug', 'goog.debug.Logger', 'goog.debug.RelativeTimeProvider', 'goog.html.SafeHtml', 'goog.html.SafeUrl', 'goog.html.uncheckedconversions', 'goog.string.Const']); +goog.addDependency("debug/formatter_test.js", ['goog.debug.FormatterTest'], ['goog.debug.HtmlFormatter', 'goog.debug.LogRecord', 'goog.debug.Logger', 'goog.html.SafeHtml', 'goog.testing.jsunit']); +goog.addDependency("debug/fpsdisplay.js", ['goog.debug.FpsDisplay'], ['goog.asserts', 'goog.async.AnimationDelay', 'goog.dom', 'goog.dom.TagName', 'goog.ui.Component']); +goog.addDependency("debug/fpsdisplay_test.js", ['goog.debug.FpsDisplayTest'], ['goog.Timer', 'goog.debug.FpsDisplay', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("debug/logbuffer.js", ['goog.debug.LogBuffer'], ['goog.asserts', 'goog.debug.LogRecord']); +goog.addDependency("debug/logbuffer_test.js", ['goog.debug.LogBufferTest'], ['goog.debug.LogBuffer', 'goog.debug.Logger', 'goog.testing.jsunit']); +goog.addDependency("debug/logger.js", ['goog.debug.LogManager', 'goog.debug.Loggable', 'goog.debug.Logger', 'goog.debug.Logger.Level'], ['goog.array', 'goog.asserts', 'goog.debug', 'goog.debug.LogBuffer', 'goog.debug.LogRecord']); +goog.addDependency("debug/logger_test.js", ['goog.debug.LoggerTest'], ['goog.debug.LogManager', 'goog.debug.Logger', 'goog.testing.jsunit']); +goog.addDependency("debug/logrecord.js", ['goog.debug.LogRecord'], []); +goog.addDependency("debug/logrecordserializer.js", ['goog.debug.logRecordSerializer'], ['goog.debug.LogRecord', 'goog.debug.Logger', 'goog.json', 'goog.object']); +goog.addDependency("debug/logrecordserializer_test.js", ['goog.debug.logRecordSerializerTest'], ['goog.debug.LogRecord', 'goog.debug.Logger', 'goog.debug.logRecordSerializer', 'goog.testing.jsunit']); +goog.addDependency("debug/relativetimeprovider.js", ['goog.debug.RelativeTimeProvider'], []); +goog.addDependency("debug/tracer.js", ['goog.debug.StopTraceDetail', 'goog.debug.Trace'], ['goog.array', 'goog.asserts', 'goog.debug.Logger', 'goog.iter', 'goog.log', 'goog.structs.Map', 'goog.structs.SimplePool']); +goog.addDependency("debug/tracer_test.js", ['goog.debug.TraceTest'], ['goog.array', 'goog.debug.Trace', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("defineclass_test.js", ['goog.defineClassTest'], ['goog.testing.jsunit']); +goog.addDependency("delegate/delegateregistry.js", [], []); +goog.addDependency("delegate/delegateregistry_test.js", [], ['goog.testing.jsunit']); +goog.addDependency("delegate/delegates.js", [], []); +goog.addDependency("delegate/delegates_test.js", [], ['goog.testing.jsunit']); +goog.addDependency("demos/autocompleteremotedata.js", [], []); +goog.addDependency("demos/autocompleterichremotedata.js", [], []); +goog.addDependency("demos/editor/deps.js", [], []); +goog.addDependency("demos/editor/helloworld.js", ['goog.demos.editor.HelloWorld'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']); +goog.addDependency("demos/editor/helloworld_test.js", ['goog.demos.editor.HelloWorldTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.demos.editor.HelloWorld', 'goog.testing.editor.TestHelper', 'goog.testing.editor.FieldMock']); +goog.addDependency("demos/editor/helloworlddialog.js", ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent'], ['goog.dom.TagName', 'goog.events.Event', 'goog.string', 'goog.ui.editor.AbstractDialog']); +goog.addDependency("demos/editor/helloworlddialog_test.js", ['goog.demos.editor.HelloWorldDialogTest'], ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent', 'goog.dom.DomHelper', 'goog.events.EventHandler', 'goog.testing.LooseMock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.AbstractDialog.EventType']); +goog.addDependency("demos/editor/helloworlddialogplugin.js", ['goog.demos.editor.HelloWorldDialogPlugin', 'goog.demos.editor.HelloWorldDialogPlugin.Command'], ['goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range', 'goog.functions', 'goog.ui.editor.AbstractDialog']); +goog.addDependency("demos/editor/helloworlddialogplugin_test.js", ['goog.demos.editor.HelloWorldDialogPluginTest'], ['goog.demos.editor.HelloWorldDialog', 'goog.demos.editor.HelloWorldDialog.OkEvent', 'goog.demos.editor.HelloWorldDialogPlugin', 'goog.demos.editor.HelloWorldDialogPlugin.Command', 'goog.dom', 'goog.dom.NodeType', 'goog.testing.ExpectedFailures', 'goog.testing.MockControl', 'goog.testing.MockRange', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.editor.dom', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.userAgent']); +goog.addDependency("demos/graphics/tigerdata.js", [], []); +goog.addDependency("demos/samplecomponent.js", ['goog.demos.SampleComponent'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.ui.Component']); +goog.addDependency("demos/tree/testdata.js", [], []); +goog.addDependency("demos/xpc/xpcdemo.js", ['xpcdemo'], ['goog.Uri', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.html.SafeHtml', 'goog.log', 'goog.log.Level', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel']); +goog.addDependency("deps.js", [], []); +goog.addDependency("disposable/disposable.js", ['goog.Disposable', 'goog.dispose', 'goog.disposeAll'], ['goog.disposable.IDisposable']); +goog.addDependency("disposable/disposable_test.js", ['goog.DisposableTest'], ['goog.Disposable', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("disposable/idisposable.js", ['goog.disposable.IDisposable'], []); +goog.addDependency("dom/abstractmultirange.js", ['goog.dom.AbstractMultiRange'], ['goog.array', 'goog.dom', 'goog.dom.AbstractRange', 'goog.dom.TextRange']); +goog.addDependency("dom/abstractrange.js", ['goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.SavedCaretRange', 'goog.dom.TagIterator', 'goog.userAgent']); +goog.addDependency("dom/abstractrange_test.js", ['goog.dom.AbstractRangeTest'], ['goog.dom', 'goog.dom.AbstractRange', 'goog.dom.Range', 'goog.dom.TagName', 'goog.testing.jsunit']); +goog.addDependency("dom/animationframe/animationframe.js", ['goog.dom.animationFrame', 'goog.dom.animationFrame.Spec', 'goog.dom.animationFrame.State'], ['goog.dom.animationFrame.polyfill']); +goog.addDependency("dom/animationframe/animationframe_test.js", ['goog.dom.AnimationFrameTest'], ['goog.dom.animationFrame', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("dom/animationframe/polyfill.js", ['goog.dom.animationFrame.polyfill'], []); +goog.addDependency("dom/annotate.js", ['goog.dom.annotate', 'goog.dom.annotate.AnnotateFn'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.object']); +goog.addDependency("dom/annotate_test.js", ['goog.dom.annotateTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.annotate', 'goog.html.SafeHtml', 'goog.testing.jsunit']); +goog.addDependency("dom/asserts.js", ['goog.dom.asserts'], ['goog.asserts']); +goog.addDependency("dom/asserts_test.js", ['goog.dom.assertsTest'], ['goog.dom.asserts', 'goog.testing.StrictMock', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/attr.js", ['goog.dom.Attr'], []); +goog.addDependency("dom/browserfeature.js", ['goog.dom.BrowserFeature'], ['goog.userAgent']); +goog.addDependency("dom/browserrange/abstractrange.js", ['goog.dom.browserrange.AbstractRange'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter', 'goog.math.Coordinate', 'goog.string', 'goog.string.StringBuffer', 'goog.userAgent']); +goog.addDependency("dom/browserrange/browserrange.js", ['goog.dom.browserrange', 'goog.dom.browserrange.Error'], ['goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.browserrange.GeckoRange', 'goog.dom.browserrange.IeRange', 'goog.dom.browserrange.OperaRange', 'goog.dom.browserrange.W3cRange', 'goog.dom.browserrange.WebKitRange', 'goog.userAgent']); +goog.addDependency("dom/browserrange/browserrange_test.js", ['goog.dom.browserrangeTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.browserrange', 'goog.html.testing', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/browserrange/geckorange.js", ['goog.dom.browserrange.GeckoRange'], ['goog.dom.browserrange.W3cRange']); +goog.addDependency("dom/browserrange/ierange.js", ['goog.dom.browserrange.IeRange'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.browserrange.AbstractRange', 'goog.log', 'goog.string']); +goog.addDependency("dom/browserrange/operarange.js", ['goog.dom.browserrange.OperaRange'], ['goog.dom.browserrange.W3cRange']); +goog.addDependency("dom/browserrange/w3crange.js", ['goog.dom.browserrange.W3cRange'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeEndpoint', 'goog.dom.TagName', 'goog.dom.browserrange.AbstractRange', 'goog.string', 'goog.userAgent']); +goog.addDependency("dom/browserrange/webkitrange.js", ['goog.dom.browserrange.WebKitRange'], ['goog.dom.RangeEndpoint', 'goog.dom.browserrange.W3cRange', 'goog.userAgent']); +goog.addDependency("dom/bufferedviewportsizemonitor.js", ['goog.dom.BufferedViewportSizeMonitor'], ['goog.asserts', 'goog.async.Delay', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType']); +goog.addDependency("dom/bufferedviewportsizemonitor_test.js", ['goog.dom.BufferedViewportSizeMonitorTest'], ['goog.dom.BufferedViewportSizeMonitor', 'goog.dom.ViewportSizeMonitor', 'goog.events', 'goog.events.EventType', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit']); +goog.addDependency("dom/classes.js", ['goog.dom.classes'], ['goog.array']); +goog.addDependency("dom/classes_test.js", ['goog.dom.classes_test'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classes', 'goog.testing.jsunit']); +goog.addDependency("dom/classlist.js", ['goog.dom.classlist'], ['goog.array']); +goog.addDependency("dom/classlist_test.js", ['goog.dom.classlist_test'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit']); +goog.addDependency("dom/controlrange.js", ['goog.dom.ControlRange', 'goog.dom.ControlRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.AbstractMultiRange', 'goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TagWalkType', 'goog.dom.TextRange', 'goog.iter.StopIteration', 'goog.userAgent']); +goog.addDependency("dom/controlrange_test.js", ['goog.dom.ControlRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.RangeType', 'goog.dom.TagName', 'goog.dom.TextRange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/dataset.js", ['goog.dom.dataset'], ['goog.labs.userAgent.browser', 'goog.string', 'goog.userAgent.product']); +goog.addDependency("dom/dataset_test.js", ['goog.dom.datasetTest'], ['goog.dom', 'goog.dom.dataset', 'goog.testing.jsunit']); +goog.addDependency("dom/dom.js", ['goog.dom', 'goog.dom.Appendable', 'goog.dom.DomHelper'], ['goog.array', 'goog.asserts', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.uncheckedconversions', 'goog.math.Coordinate', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.userAgent']); +goog.addDependency("dom/dom_compile_test.js", ['goog.dom.DomCompileTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit']); +goog.addDependency("dom/dom_test.js", ['goog.dom.dom_test'], ['goog.array', 'goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.html.SafeUrl', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("dom/fontsizemonitor.js", ['goog.dom.FontSizeMonitor', 'goog.dom.FontSizeMonitor.EventType'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']); +goog.addDependency("dom/fontsizemonitor_test.js", ['goog.dom.FontSizeMonitorTest'], ['goog.dom', 'goog.dom.FontSizeMonitor', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/forms.js", ['goog.dom.forms'], ['goog.dom.InputType', 'goog.dom.TagName', 'goog.structs.Map', 'goog.window']); +goog.addDependency("dom/forms_test.js", ['goog.dom.formsTest'], ['goog.dom', 'goog.dom.forms', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("dom/fullscreen.js", ['goog.dom.fullscreen', 'goog.dom.fullscreen.EventType'], ['goog.dom', 'goog.userAgent']); +goog.addDependency("dom/fullscreen_test.js", ['goog.dom.fullscreen_test'], ['goog.dom.DomHelper', 'goog.dom.fullscreen', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("dom/htmlelement.js", ['goog.dom.HtmlElement'], []); +goog.addDependency("dom/iframe.js", ['goog.dom.iframe'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.TrustedResourceUrl', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("dom/iframe_test.js", ['goog.dom.iframeTest'], ['goog.dom', 'goog.dom.iframe', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("dom/inputtype.js", ['goog.dom.InputType'], []); +goog.addDependency("dom/inputtype_test.js", ['goog.dom.InputTypeTest'], ['goog.dom.InputType', 'goog.object', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/iter.js", ['goog.dom.iter.AncestorIterator', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator'], ['goog.iter.Iterator', 'goog.iter.StopIteration']); +goog.addDependency("dom/iter_test.js", ['goog.dom.iterTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.iter.AncestorIterator', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/multirange.js", ['goog.dom.MultiRange', 'goog.dom.MultiRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.AbstractMultiRange', 'goog.dom.AbstractRange', 'goog.dom.RangeIterator', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TextRange', 'goog.iter', 'goog.iter.StopIteration', 'goog.log']); +goog.addDependency("dom/multirange_test.js", ['goog.dom.MultiRangeTest'], ['goog.dom', 'goog.dom.MultiRange', 'goog.dom.Range', 'goog.iter', 'goog.testing.jsunit']); +goog.addDependency("dom/nodeiterator.js", ['goog.dom.NodeIterator'], ['goog.dom.TagIterator']); +goog.addDependency("dom/nodeiterator_test.js", ['goog.dom.NodeIteratorTest'], ['goog.dom', 'goog.dom.NodeIterator', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/nodeoffset.js", ['goog.dom.NodeOffset'], ['goog.Disposable', 'goog.dom.TagName']); +goog.addDependency("dom/nodeoffset_test.js", ['goog.dom.NodeOffsetTest'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.testing.jsunit']); +goog.addDependency("dom/nodetype.js", ['goog.dom.NodeType'], []); +goog.addDependency("dom/pattern/abstractpattern.js", ['goog.dom.pattern.AbstractPattern'], ['goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/allchildren.js", ['goog.dom.pattern.AllChildren'], ['goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/callback/callback.js", ['goog.dom.pattern.callback'], ['goog.dom', 'goog.dom.TagWalkType', 'goog.iter']); +goog.addDependency("dom/pattern/callback/counter.js", ['goog.dom.pattern.callback.Counter'], []); +goog.addDependency("dom/pattern/callback/test.js", ['goog.dom.pattern.callback.Test'], ['goog.iter.StopIteration']); +goog.addDependency("dom/pattern/childmatches.js", ['goog.dom.pattern.ChildMatches'], ['goog.dom.pattern.AllChildren', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/endtag.js", ['goog.dom.pattern.EndTag'], ['goog.dom.TagWalkType', 'goog.dom.pattern.Tag']); +goog.addDependency("dom/pattern/fulltag.js", ['goog.dom.pattern.FullTag'], ['goog.dom.pattern.MatchType', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.Tag']); +goog.addDependency("dom/pattern/matcher.js", ['goog.dom.pattern.Matcher'], ['goog.dom.TagIterator', 'goog.dom.pattern.MatchType', 'goog.iter']); +goog.addDependency("dom/pattern/matcher_test.js", ['goog.dom.pattern.matcherTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.pattern.EndTag', 'goog.dom.pattern.FullTag', 'goog.dom.pattern.Matcher', 'goog.dom.pattern.Repeat', 'goog.dom.pattern.Sequence', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.callback.Counter', 'goog.dom.pattern.callback.Test', 'goog.iter.StopIteration', 'goog.testing.jsunit']); +goog.addDependency("dom/pattern/nodetype.js", ['goog.dom.pattern.NodeType'], ['goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/pattern.js", ['goog.dom.pattern', 'goog.dom.pattern.MatchType'], []); +goog.addDependency("dom/pattern/pattern_test.js", ['goog.dom.patternTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagWalkType', 'goog.dom.pattern.AllChildren', 'goog.dom.pattern.ChildMatches', 'goog.dom.pattern.EndTag', 'goog.dom.pattern.FullTag', 'goog.dom.pattern.MatchType', 'goog.dom.pattern.NodeType', 'goog.dom.pattern.Repeat', 'goog.dom.pattern.Sequence', 'goog.dom.pattern.StartTag', 'goog.dom.pattern.Text', 'goog.testing.jsunit']); +goog.addDependency("dom/pattern/repeat.js", ['goog.dom.pattern.Repeat'], ['goog.dom.NodeType', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/sequence.js", ['goog.dom.pattern.Sequence'], ['goog.dom.NodeType', 'goog.dom.pattern', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/pattern/starttag.js", ['goog.dom.pattern.StartTag'], ['goog.dom.TagWalkType', 'goog.dom.pattern.Tag']); +goog.addDependency("dom/pattern/tag.js", ['goog.dom.pattern.Tag'], ['goog.dom.pattern', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType', 'goog.object']); +goog.addDependency("dom/pattern/text.js", ['goog.dom.pattern.Text'], ['goog.dom.NodeType', 'goog.dom.pattern', 'goog.dom.pattern.AbstractPattern', 'goog.dom.pattern.MatchType']); +goog.addDependency("dom/range.js", ['goog.dom.Range'], ['goog.dom', 'goog.dom.AbstractRange', 'goog.dom.BrowserFeature', 'goog.dom.ControlRange', 'goog.dom.MultiRange', 'goog.dom.NodeType', 'goog.dom.TextRange']); +goog.addDependency("dom/range_test.js", ['goog.dom.RangeTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeType', 'goog.dom.TagName', 'goog.dom.TextRange', 'goog.dom.browserrange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/rangeendpoint.js", ['goog.dom.RangeEndpoint'], []); +goog.addDependency("dom/safe.js", ['goog.dom.safe', 'goog.dom.safe.InsertAdjacentHtmlPosition'], ['goog.asserts', 'goog.dom.asserts', 'goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const']); +goog.addDependency("dom/safe_test.js", ['goog.dom.safeTest'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.dom.safe.InsertAdjacentHtmlPosition', 'goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.testing', 'goog.string', 'goog.string.Const', 'goog.testing', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/savedcaretrange.js", ['goog.dom.SavedCaretRange'], ['goog.array', 'goog.dom', 'goog.dom.SavedRange', 'goog.dom.TagName', 'goog.string']); +goog.addDependency("dom/savedcaretrange_test.js", ['goog.dom.SavedCaretRangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.SavedCaretRange', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/savedrange.js", ['goog.dom.SavedRange'], ['goog.Disposable', 'goog.log']); +goog.addDependency("dom/savedrange_test.js", ['goog.dom.SavedRangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/selection.js", ['goog.dom.selection'], ['goog.dom.InputType', 'goog.string', 'goog.userAgent']); +goog.addDependency("dom/selection_test.js", ['goog.dom.selectionTest'], ['goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.selection', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("dom/tagiterator.js", ['goog.dom.TagIterator', 'goog.dom.TagWalkType'], ['goog.dom', 'goog.dom.NodeType', 'goog.iter.Iterator', 'goog.iter.StopIteration']); +goog.addDependency("dom/tagiterator_test.js", ['goog.dom.TagIteratorTest'], ['goog.dom', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.dom.TagWalkType', 'goog.iter', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/tagname.js", ['goog.dom.TagName'], ['goog.dom.HtmlElement']); +goog.addDependency("dom/tagname_test.js", ['goog.dom.TagNameTest'], ['goog.dom.TagName', 'goog.object', 'goog.testing.jsunit']); +goog.addDependency("dom/tags.js", ['goog.dom.tags'], ['goog.object']); +goog.addDependency("dom/tags_test.js", ['goog.dom.tagsTest'], ['goog.dom.tags', 'goog.testing.jsunit']); +goog.addDependency("dom/textassert.js", ['goog.dom.textAssert'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName']); +goog.addDependency("dom/textassert_test.js", [], []); +goog.addDependency("dom/textrange.js", ['goog.dom.TextRange'], ['goog.array', 'goog.dom', 'goog.dom.AbstractRange', 'goog.dom.RangeType', 'goog.dom.SavedRange', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.dom.browserrange', 'goog.string', 'goog.userAgent']); +goog.addDependency("dom/textrange_test.js", ['goog.dom.TextRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.Range', 'goog.dom.TextRange', 'goog.math.Coordinate', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("dom/textrangeiterator.js", ['goog.dom.TextRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeIterator', 'goog.dom.TagName', 'goog.iter.StopIteration']); +goog.addDependency("dom/textrangeiterator_test.js", ['goog.dom.TextRangeIteratorTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("dom/uri.js", [], []); +goog.addDependency("dom/uri_test.js", [], []); +goog.addDependency("dom/vendor.js", ['goog.dom.vendor'], ['goog.string', 'goog.userAgent']); +goog.addDependency("dom/vendor_test.js", ['goog.dom.vendorTest'], ['goog.array', 'goog.dom.vendor', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']); +goog.addDependency("dom/viewportsizemonitor.js", ['goog.dom.ViewportSizeMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size']); +goog.addDependency("dom/viewportsizemonitor_test.js", ['goog.dom.ViewportSizeMonitorTest'], ['goog.dom.ViewportSizeMonitor', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("dom/xml.js", ['goog.dom.xml'], ['goog.dom', 'goog.dom.NodeType', 'goog.userAgent']); +goog.addDependency("dom/xml_test.js", ['goog.dom.xmlTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.xml', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/browserfeature.js", ['goog.editor.BrowserFeature'], ['goog.editor.defines', 'goog.labs.userAgent.browser', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("editor/browserfeature_test.js", ['goog.editor.BrowserFeatureTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit']); +goog.addDependency("editor/clicktoeditwrapper.js", ['goog.editor.ClickToEditWrapper'], ['goog.Disposable', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.range', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventType']); +goog.addDependency("editor/clicktoeditwrapper_test.js", ['goog.editor.ClickToEditWrapperTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.ClickToEditWrapper', 'goog.editor.SeamlessField', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit']); +goog.addDependency("editor/command.js", ['goog.editor.Command'], []); +goog.addDependency("editor/contenteditablefield.js", ['goog.editor.ContentEditableField'], ['goog.asserts', 'goog.editor.Field', 'goog.log']); +goog.addDependency("editor/contenteditablefield_test.js", ['goog.editor.ContentEditableFieldTest'], ['goog.dom', 'goog.editor.ContentEditableField', 'goog.editor.field_test', 'goog.html.SafeHtml', 'goog.testing.jsunit']); +goog.addDependency("editor/defines.js", ['goog.editor.defines'], []); +goog.addDependency("editor/field.js", ['goog.editor.Field', 'goog.editor.Field.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.array', 'goog.asserts', 'goog.async.Delay', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.safe', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.editor.node', 'goog.editor.range', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.log', 'goog.log.Level', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("editor/field_test.js", ['goog.editor.field_test'], ['goog.array', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.range', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.html.SafeHtml', 'goog.testing.LooseMock', 'goog.testing.MockClock', 'goog.testing.dom', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("editor/focus.js", ['goog.editor.focus'], ['goog.dom.selection']); +goog.addDependency("editor/focus_test.js", ['goog.editor.focusTest'], ['goog.dom.selection', 'goog.editor.BrowserFeature', 'goog.editor.focus', 'goog.testing.jsunit']); +goog.addDependency("editor/icontent.js", ['goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo'], ['goog.dom', 'goog.editor.BrowserFeature', 'goog.style', 'goog.userAgent']); +goog.addDependency("editor/icontent_test.js", ['goog.editor.icontentTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/link.js", ['goog.editor.Link'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.node', 'goog.editor.range', 'goog.string', 'goog.string.Unicode', 'goog.uri.utils', 'goog.uri.utils.ComponentIndex']); +goog.addDependency("editor/link_test.js", ['goog.editor.LinkTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Link', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/node.js", ['goog.editor.node'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.iter.ChildIterator', 'goog.dom.iter.SiblingIterator', 'goog.iter', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.userAgent']); +goog.addDependency("editor/node_test.js", ['goog.editor.nodeTest'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.editor.node', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugin.js", ['goog.editor.Plugin'], ['goog.events.EventTarget', 'goog.functions', 'goog.log', 'goog.object', 'goog.reflect', 'goog.userAgent']); +goog.addDependency("editor/plugin_test.js", ['goog.editor.PluginTest'], ['goog.editor.Field', 'goog.editor.Plugin', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/abstractbubbleplugin.js", ['goog.editor.plugins.AbstractBubblePlugin'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.Plugin', 'goog.editor.style', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.actionEventWrapper', 'goog.functions', 'goog.string.Unicode', 'goog.ui.Component', 'goog.ui.editor.Bubble', 'goog.userAgent']); +goog.addDependency("editor/plugins/abstractbubbleplugin_test.js", ['goog.editor.plugins.AbstractBubblePluginTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.functions', 'goog.style', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.editor.Bubble', 'goog.userAgent']); +goog.addDependency("editor/plugins/abstractdialogplugin.js", ['goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.plugins.AbstractDialogPlugin.EventType'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.range', 'goog.events', 'goog.ui.editor.AbstractDialog']); +goog.addDependency("editor/plugins/abstractdialogplugin_test.js", ['goog.editor.plugins.AbstractDialogPluginTest'], ['goog.dom', 'goog.dom.SavedRange', 'goog.dom.TagName', 'goog.editor.Field', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.events.Event', 'goog.events.EventHandler', 'goog.functions', 'goog.html.SafeHtml', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.AbstractDialog', 'goog.userAgent']); +goog.addDependency("editor/plugins/abstracttabhandler.js", ['goog.editor.plugins.AbstractTabHandler'], ['goog.editor.Plugin', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("editor/plugins/abstracttabhandler_test.js", ['goog.editor.plugins.AbstractTabHandlerTest'], ['goog.editor.Field', 'goog.editor.plugins.AbstractTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/basictextformatter.js", ['goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.BasicTextFormatter.COMMAND'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.editor.style', 'goog.iter', 'goog.iter.StopIteration', 'goog.log', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.editor.messages', 'goog.userAgent']); +goog.addDependency("editor/plugins/basictextformatter_test.js", ['goog.editor.plugins.BasicTextFormatterTest'], ['goog.array', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.BasicTextFormatter', 'goog.html.SafeHtml', 'goog.object', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.LooseMock', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("editor/plugins/blockquote.js", ['goog.editor.plugins.Blockquote'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Plugin', 'goog.editor.node', 'goog.functions', 'goog.log']); +goog.addDependency("editor/plugins/blockquote_test.js", ['goog.editor.plugins.BlockquoteTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.plugins.Blockquote', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit']); +goog.addDependency("editor/plugins/emoticons.js", ['goog.editor.plugins.Emoticons'], ['goog.dom.TagName', 'goog.editor.Plugin', 'goog.editor.range', 'goog.functions', 'goog.ui.emoji.Emoji', 'goog.userAgent']); +goog.addDependency("editor/plugins/emoticons_test.js", ['goog.editor.plugins.EmoticonsTest'], ['goog.Uri', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.editor.Field', 'goog.editor.plugins.Emoticons', 'goog.testing.jsunit', 'goog.ui.emoji.Emoji', 'goog.userAgent']); +goog.addDependency("editor/plugins/enterhandler.js", ['goog.editor.plugins.EnterHandler'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.plugins.Blockquote', 'goog.editor.range', 'goog.editor.style', 'goog.events.KeyCodes', 'goog.functions', 'goog.object', 'goog.string', 'goog.userAgent']); +goog.addDependency("editor/plugins/enterhandler_test.js", ['goog.editor.plugins.EnterHandlerTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.Blockquote', 'goog.editor.plugins.EnterHandler', 'goog.editor.range', 'goog.events', 'goog.events.KeyCodes', 'goog.html.testing', 'goog.testing.ExpectedFailures', 'goog.testing.MockClock', 'goog.testing.dom', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/firststrong.js", ['goog.editor.plugins.FirstStrong'], ['goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.i18n.bidi', 'goog.i18n.uChar', 'goog.iter', 'goog.userAgent']); +goog.addDependency("editor/plugins/firststrong_test.js", ['goog.editor.plugins.FirstStrongTest'], ['goog.dom.Range', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.plugins.FirstStrong', 'goog.editor.range', 'goog.events.KeyCodes', 'goog.html.testing', 'goog.testing.MockClock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/headerformatter.js", ['goog.editor.plugins.HeaderFormatter'], ['goog.editor.Command', 'goog.editor.Plugin', 'goog.userAgent']); +goog.addDependency("editor/plugins/headerformatter_test.js", ['goog.editor.plugins.HeaderFormatterTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.HeaderFormatter', 'goog.events.BrowserEvent', 'goog.testing.LooseMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/linkbubble.js", ['goog.editor.plugins.LinkBubble', 'goog.editor.plugins.LinkBubble.Action'], ['goog.array', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.functions', 'goog.string', 'goog.style', 'goog.ui.editor.messages', 'goog.uri.utils', 'goog.window']); +goog.addDependency("editor/plugins/linkbubble_test.js", ['goog.editor.plugins.LinkBubbleTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.Link', 'goog.editor.plugins.LinkBubble', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.string', 'goog.style', 'goog.testing.FunctionMock', 'goog.testing.PropertyReplacer', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/linkdialogplugin.js", ['goog.editor.plugins.LinkDialogPlugin'], ['goog.array', 'goog.dom', 'goog.editor.Command', 'goog.editor.plugins.AbstractDialogPlugin', 'goog.events.EventHandler', 'goog.functions', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.LinkDialog', 'goog.uri.utils']); +goog.addDependency("editor/plugins/linkdialogplugin_test.js", ['goog.ui.editor.plugins.LinkDialogTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Link', 'goog.editor.plugins.LinkDialogPlugin', 'goog.html.SafeHtml', 'goog.string', 'goog.string.Unicode', 'goog.testing.MockControl', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.editor.dom', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.LinkDialog', 'goog.userAgent']); +goog.addDependency("editor/plugins/linkshortcutplugin.js", ['goog.editor.plugins.LinkShortcutPlugin'], ['goog.editor.Command', 'goog.editor.Plugin']); +goog.addDependency("editor/plugins/linkshortcutplugin_test.js", ['goog.editor.plugins.LinkShortcutPluginTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Field', 'goog.editor.plugins.BasicTextFormatter', 'goog.editor.plugins.LinkBubble', 'goog.editor.plugins.LinkShortcutPlugin', 'goog.events.KeyCodes', 'goog.testing.PropertyReplacer', 'goog.testing.dom', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent.product']); +goog.addDependency("editor/plugins/listtabhandler.js", ['goog.editor.plugins.ListTabHandler'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.plugins.AbstractTabHandler', 'goog.iter']); +goog.addDependency("editor/plugins/listtabhandler_test.js", ['goog.editor.plugins.ListTabHandlerTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.plugins.ListTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit']); +goog.addDependency("editor/plugins/loremipsum.js", ['goog.editor.plugins.LoremIpsum'], ['goog.asserts', 'goog.dom', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.node', 'goog.functions', 'goog.html.SafeHtml', 'goog.userAgent']); +goog.addDependency("editor/plugins/loremipsum_test.js", ['goog.editor.plugins.LoremIpsumTest'], ['goog.dom', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.plugins.LoremIpsum', 'goog.html.SafeHtml', 'goog.string.Unicode', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/removeformatting.js", ['goog.editor.plugins.RemoveFormatting'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.range', 'goog.string', 'goog.userAgent']); +goog.addDependency("editor/plugins/removeformatting_test.js", ['goog.editor.plugins.RemoveFormattingTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.plugins.RemoveFormatting', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.dom', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("editor/plugins/spacestabhandler.js", ['goog.editor.plugins.SpacesTabHandler'], ['goog.dom.TagName', 'goog.editor.plugins.AbstractTabHandler', 'goog.editor.range']); +goog.addDependency("editor/plugins/spacestabhandler_test.js", ['goog.editor.plugins.SpacesTabHandlerTest'], ['goog.dom', 'goog.dom.Range', 'goog.editor.plugins.SpacesTabHandler', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.functions', 'goog.testing.StrictMock', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit']); +goog.addDependency("editor/plugins/tableeditor.js", ['goog.editor.plugins.TableEditor'], ['goog.array', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Plugin', 'goog.editor.Table', 'goog.editor.node', 'goog.editor.range', 'goog.object', 'goog.userAgent']); +goog.addDependency("editor/plugins/tableeditor_test.js", ['goog.editor.plugins.TableEditorTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.plugins.TableEditor', 'goog.object', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.JsUnitException', 'goog.testing.TestCase', 'goog.testing.editor.FieldMock', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/tagonenterhandler.js", ['goog.editor.plugins.TagOnEnterHandler'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.Command', 'goog.editor.node', 'goog.editor.plugins.EnterHandler', 'goog.editor.range', 'goog.editor.style', 'goog.events.KeyCodes', 'goog.functions', 'goog.string.Unicode', 'goog.style', 'goog.userAgent']); +goog.addDependency("editor/plugins/tagonenterhandler_test.js", ['goog.editor.plugins.TagOnEnterHandlerTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.plugins.TagOnEnterHandler', 'goog.events.KeyCodes', 'goog.html.SafeHtml', 'goog.string.Unicode', 'goog.testing.dom', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/plugins/undoredo.js", ['goog.editor.plugins.UndoRedo'], ['goog.dom', 'goog.dom.NodeOffset', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.Command', 'goog.editor.Field', 'goog.editor.Plugin', 'goog.editor.node', 'goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoState', 'goog.events', 'goog.events.EventHandler', 'goog.log', 'goog.object']); +goog.addDependency("editor/plugins/undoredo_test.js", ['goog.editor.plugins.UndoRedoTest'], ['goog.array', 'goog.dom', 'goog.dom.browserrange', 'goog.editor.Field', 'goog.editor.plugins.LoremIpsum', 'goog.editor.plugins.UndoRedo', 'goog.events', 'goog.functions', 'goog.html.SafeHtml', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock', 'goog.testing.jsunit']); +goog.addDependency("editor/plugins/undoredomanager.js", ['goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoManager.EventType'], ['goog.editor.plugins.UndoRedoState', 'goog.events', 'goog.events.EventTarget']); +goog.addDependency("editor/plugins/undoredomanager_test.js", ['goog.editor.plugins.UndoRedoManagerTest'], ['goog.editor.plugins.UndoRedoManager', 'goog.editor.plugins.UndoRedoState', 'goog.events', 'goog.testing.StrictMock', 'goog.testing.jsunit']); +goog.addDependency("editor/plugins/undoredostate.js", ['goog.editor.plugins.UndoRedoState'], ['goog.events.EventTarget']); +goog.addDependency("editor/plugins/undoredostate_test.js", ['goog.editor.plugins.UndoRedoStateTest'], ['goog.editor.plugins.UndoRedoState', 'goog.testing.jsunit']); +goog.addDependency("editor/range.js", ['goog.editor.range', 'goog.editor.range.Point'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.dom.RangeEndpoint', 'goog.dom.SavedCaretRange', 'goog.editor.node', 'goog.editor.style', 'goog.iter', 'goog.userAgent']); +goog.addDependency("editor/range_test.js", ['goog.editor.rangeTest'], ['goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.range', 'goog.editor.range.Point', 'goog.string', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("editor/seamlessfield.js", ['goog.editor.SeamlessField'], ['goog.cssom.iframe.style', 'goog.dom', 'goog.dom.Range', 'goog.dom.TagName', 'goog.dom.safe', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.icontent', 'goog.editor.icontent.FieldFormatInfo', 'goog.editor.icontent.FieldStyleInfo', 'goog.editor.node', 'goog.events', 'goog.events.EventType', 'goog.html.SafeHtml', 'goog.log', 'goog.style']); +goog.addDependency("editor/seamlessfield_test.js", ['goog.editor.seamlessfield_test'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.Range', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Field', 'goog.editor.SeamlessField', 'goog.events', 'goog.functions', 'goog.html.SafeHtml', 'goog.style', 'goog.testing.MockClock', 'goog.testing.MockRange', 'goog.testing.jsunit']); +goog.addDependency("editor/style.js", ['goog.editor.style'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.object', 'goog.style', 'goog.userAgent']); +goog.addDependency("editor/style_test.js", ['goog.editor.styleTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.style', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.style', 'goog.testing.LooseMock', 'goog.testing.jsunit', 'goog.testing.mockmatchers']); +goog.addDependency("editor/table.js", ['goog.editor.Table', 'goog.editor.TableCell', 'goog.editor.TableRow'], ['goog.asserts', 'goog.dom', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.log', 'goog.string.Unicode', 'goog.style']); +goog.addDependency("editor/table_test.js", ['goog.editor.TableTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.Table', 'goog.testing.jsunit']); +goog.addDependency("events/actioneventwrapper.js", ['goog.events.actionEventWrapper'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.EventWrapper', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("events/actioneventwrapper_test.js", ['goog.events.actionEventWrapperTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.events', 'goog.events.EventHandler', 'goog.events.KeyCodes', 'goog.events.actionEventWrapper', 'goog.testing.events', 'goog.testing.jsunit']); +goog.addDependency("events/actionhandler.js", ['goog.events.ActionEvent', 'goog.events.ActionHandler', 'goog.events.ActionHandler.EventType', 'goog.events.BeforeActionEvent'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("events/actionhandler_test.js", ['goog.events.ActionHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.ActionHandler', 'goog.testing.events', 'goog.testing.jsunit']); +goog.addDependency("events/browserevent.js", ['goog.events.BrowserEvent', 'goog.events.BrowserEvent.MouseButton', 'goog.events.BrowserEvent.PointerType'], ['goog.debug', 'goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventType', 'goog.reflect', 'goog.userAgent']); +goog.addDependency("events/browserevent_test.js", ['goog.events.BrowserEventTest'], ['goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.math.Coordinate', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/browserfeature.js", ['goog.events.BrowserFeature'], ['goog.userAgent']); +goog.addDependency("events/event.js", ['goog.events.Event', 'goog.events.EventLike'], ['goog.Disposable', 'goog.events.EventId']); +goog.addDependency("events/event_test.js", ['goog.events.EventTest'], ['goog.events.Event', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.testing.jsunit']); +goog.addDependency("events/eventhandler.js", ['goog.events.EventHandler'], ['goog.Disposable', 'goog.events', 'goog.object']); +goog.addDependency("events/eventhandler_test.js", ['goog.events.EventHandlerTest'], ['goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("events/eventid.js", ['goog.events.EventId'], []); +goog.addDependency("events/events.js", ['goog.events', 'goog.events.CaptureSimulationMode', 'goog.events.Key', 'goog.events.ListenableType'], ['goog.asserts', 'goog.debug.entryPointRegistry', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.Listenable', 'goog.events.ListenerMap']); +goog.addDependency("events/events_test.js", ['goog.eventsTest'], ['goog.asserts.AssertionError', 'goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.BrowserFeature', 'goog.events.CaptureSimulationMode', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.Listener', 'goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("events/eventtarget.js", ['goog.events.EventTarget'], ['goog.Disposable', 'goog.asserts', 'goog.events', 'goog.events.Event', 'goog.events.Listenable', 'goog.events.ListenerMap', 'goog.object']); +goog.addDependency("events/eventtarget_test.js", ['goog.events.EventTargetTest'], ['goog.events.EventTarget', 'goog.events.Listenable', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.testing.jsunit']); +goog.addDependency("events/eventtarget_via_googevents_test.js", ['goog.events.EventTargetGoogEventsTest'], ['goog.events', 'goog.events.EventTarget', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.testing', 'goog.testing.jsunit']); +goog.addDependency("events/eventtarget_via_w3cinterface_test.js", ['goog.events.EventTargetW3CTest'], ['goog.events.EventTarget', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.testing.jsunit']); +goog.addDependency("events/eventtargettester.js", ['goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType'], ['goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.testing.asserts', 'goog.testing.recordFunction']); +goog.addDependency("events/eventtype.js", ['goog.events.EventType', 'goog.events.PointerFallbackEventType'], ['goog.events.BrowserFeature', 'goog.userAgent']); +goog.addDependency("events/eventtype_test.js", ['goog.events.EventTypeTest'], ['goog.events.BrowserFeature', 'goog.events.EventType', 'goog.events.PointerFallbackEventType', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/eventwrapper.js", ['goog.events.EventWrapper'], []); +goog.addDependency("events/filedrophandler.js", ['goog.events.FileDropHandler', 'goog.events.FileDropHandler.EventType'], ['goog.array', 'goog.dom', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.log', 'goog.log.Level']); +goog.addDependency("events/filedrophandler_test.js", ['goog.events.FileDropHandlerTest'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.FileDropHandler', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/focushandler.js", ['goog.events.FocusHandler', 'goog.events.FocusHandler.EventType'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.userAgent']); +goog.addDependency("events/imehandler.js", ['goog.events.ImeHandler', 'goog.events.ImeHandler.Event', 'goog.events.ImeHandler.EventType'], ['goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("events/imehandler_test.js", ['goog.events.ImeHandlerTest'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.ImeHandler', 'goog.events.KeyCodes', 'goog.object', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/inputhandler.js", ['goog.events.InputHandler', 'goog.events.InputHandler.EventType'], ['goog.Timer', 'goog.dom.TagName', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("events/inputhandler_test.js", ['goog.events.InputHandlerTest'], ['goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("events/keycodes.js", ['goog.events.KeyCodes'], ['goog.userAgent']); +goog.addDependency("events/keycodes_test.js", ['goog.events.KeyCodesTest'], ['goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/keyhandler.js", ['goog.events.KeyEvent', 'goog.events.KeyHandler', 'goog.events.KeyHandler.EventType'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.userAgent']); +goog.addDependency("events/keyhandler_test.js", ['goog.events.KeyEventTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/keynames.js", ['goog.events.KeyNames'], []); +goog.addDependency("events/keys.js", ['goog.events.Keys'], []); +goog.addDependency("events/listenable.js", ['goog.events.Listenable', 'goog.events.ListenableKey'], ['goog.events.EventId']); +goog.addDependency("events/listenable_test.js", ['goog.events.ListenableTest'], ['goog.events.Listenable', 'goog.testing.jsunit']); +goog.addDependency("events/listener.js", ['goog.events.Listener'], ['goog.events.ListenableKey']); +goog.addDependency("events/listenermap.js", ['goog.events.ListenerMap'], ['goog.array', 'goog.events.Listener', 'goog.object']); +goog.addDependency("events/listenermap_test.js", ['goog.events.ListenerMapTest'], ['goog.dispose', 'goog.events', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.events.ListenerMap', 'goog.testing.jsunit']); +goog.addDependency("events/mousewheelhandler.js", ['goog.events.MouseWheelEvent', 'goog.events.MouseWheelHandler', 'goog.events.MouseWheelHandler.EventType'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.math', 'goog.style', 'goog.userAgent']); +goog.addDependency("events/mousewheelhandler_test.js", ['goog.events.MouseWheelHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.MouseWheelEvent', 'goog.events.MouseWheelHandler', 'goog.functions', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/onlinehandler.js", ['goog.events.OnlineHandler', 'goog.events.OnlineHandler.EventType'], ['goog.Timer', 'goog.events.BrowserFeature', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.net.NetworkStatusMonitor']); +goog.addDependency("events/onlinelistener_test.js", ['goog.events.OnlineHandlerTest'], ['goog.events', 'goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.OnlineHandler', 'goog.net.NetworkStatusMonitor', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("events/pastehandler.js", ['goog.events.PasteHandler', 'goog.events.PasteHandler.EventType', 'goog.events.PasteHandler.State'], ['goog.Timer', 'goog.async.ConditionalDelay', 'goog.events.BrowserEvent', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.log', 'goog.userAgent']); +goog.addDependency("events/pastehandler_test.js", ['goog.events.PasteHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.PasteHandler', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("events/wheelevent.js", ['goog.events.WheelEvent'], ['goog.asserts', 'goog.events.BrowserEvent']); +goog.addDependency("events/wheelhandler.js", ['goog.events.WheelHandler'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.WheelEvent', 'goog.style', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("events/wheelhandler_test.js", ['goog.events.WheelHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.WheelEvent', 'goog.events.WheelHandler', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("format/emailaddress.js", ['goog.format.EmailAddress'], ['goog.string']); +goog.addDependency("format/emailaddress_test.js", ['goog.format.EmailAddressTest'], ['goog.array', 'goog.format.EmailAddress', 'goog.testing.jsunit']); +goog.addDependency("format/format.js", ['goog.format'], ['goog.i18n.GraphemeBreak', 'goog.string', 'goog.userAgent']); +goog.addDependency("format/format_test.js", ['goog.formatTest'], ['goog.dom', 'goog.dom.TagName', 'goog.format', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("format/htmlprettyprinter.js", ['goog.format.HtmlPrettyPrinter', 'goog.format.HtmlPrettyPrinter.Buffer'], ['goog.dom.TagName', 'goog.object', 'goog.string.StringBuffer']); +goog.addDependency("format/htmlprettyprinter_test.js", ['goog.format.HtmlPrettyPrinterTest'], ['goog.format.HtmlPrettyPrinter', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("format/internationalizedemailaddress.js", ['goog.format.InternationalizedEmailAddress'], ['goog.format.EmailAddress', 'goog.string']); +goog.addDependency("format/internationalizedemailaddress_test.js", ['goog.format.InternationalizedEmailAddressTest'], ['goog.array', 'goog.format.InternationalizedEmailAddress', 'goog.testing.jsunit']); +goog.addDependency("format/jsonprettyprinter.js", ['goog.format.JsonPrettyPrinter', 'goog.format.JsonPrettyPrinter.SafeHtmlDelimiters', 'goog.format.JsonPrettyPrinter.TextDelimiters'], ['goog.html.SafeHtml', 'goog.json', 'goog.json.Serializer', 'goog.string', 'goog.string.format']); +goog.addDependency("format/jsonprettyprinter_test.js", ['goog.format.JsonPrettyPrinterTest'], ['goog.format.JsonPrettyPrinter', 'goog.testing.jsunit']); +goog.addDependency("fs/entry.js", ['goog.fs.DirectoryEntry', 'goog.fs.DirectoryEntry.Behavior', 'goog.fs.Entry', 'goog.fs.FileEntry'], []); +goog.addDependency("fs/entryimpl.js", ['goog.fs.DirectoryEntryImpl', 'goog.fs.EntryImpl', 'goog.fs.FileEntryImpl'], ['goog.array', 'goog.async.Deferred', 'goog.fs.DirectoryEntry', 'goog.fs.Entry', 'goog.fs.Error', 'goog.fs.FileEntry', 'goog.fs.FileWriter', 'goog.functions', 'goog.string']); +goog.addDependency("fs/error.js", ['goog.fs.DOMErrorLike', 'goog.fs.Error', 'goog.fs.Error.ErrorCode'], ['goog.asserts', 'goog.debug.Error', 'goog.object', 'goog.string']); +goog.addDependency("fs/filereader.js", ['goog.fs.FileReader', 'goog.fs.FileReader.EventType', 'goog.fs.FileReader.ReadyState'], ['goog.async.Deferred', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.ProgressEvent']); +goog.addDependency("fs/filesaver.js", ['goog.fs.FileSaver', 'goog.fs.FileSaver.EventType', 'goog.fs.FileSaver.ReadyState'], ['goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.ProgressEvent']); +goog.addDependency("fs/filesystem.js", ['goog.fs.FileSystem'], []); +goog.addDependency("fs/filesystemimpl.js", ['goog.fs.FileSystemImpl'], ['goog.fs.DirectoryEntryImpl', 'goog.fs.FileSystem']); +goog.addDependency("fs/filewriter.js", ['goog.fs.FileWriter'], ['goog.fs.Error', 'goog.fs.FileSaver']); +goog.addDependency("fs/fs.js", ['goog.fs'], ['goog.array', 'goog.async.Deferred', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.fs.FileSystemImpl', 'goog.fs.url', 'goog.userAgent']); +goog.addDependency("fs/fs_test.js", ['goog.fsTest'], ['goog.Promise', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.fs.FileSaver', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("fs/progressevent.js", ['goog.fs.ProgressEvent'], ['goog.events.Event']); +goog.addDependency("fs/url.js", ['goog.fs.url'], []); +goog.addDependency("fs/url_test.js", ['goog.urlTest'], ['goog.fs.url', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("functions/functions.js", ['goog.functions'], []); +goog.addDependency("functions/functions_test.js", ['goog.functionsTest'], ['goog.array', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("fx/abstractdragdrop.js", ['goog.fx.AbstractDragDrop', 'goog.fx.AbstractDragDrop.EventType', 'goog.fx.DragDropEvent', 'goog.fx.DragDropItem'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style']); +goog.addDependency("fx/abstractdragdrop_test.js", ['goog.fx.AbstractDragDropTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.functions', 'goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit']); +goog.addDependency("fx/anim/anim.js", ['goog.fx.anim', 'goog.fx.anim.Animated'], ['goog.async.AnimationDelay', 'goog.async.Delay', 'goog.object']); +goog.addDependency("fx/anim/anim_test.js", ['goog.fx.animTest'], ['goog.async.AnimationDelay', 'goog.async.Delay', 'goog.events', 'goog.functions', 'goog.fx.Animation', 'goog.fx.anim', 'goog.object', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("fx/animation.js", ['goog.fx.Animation', 'goog.fx.Animation.EventType', 'goog.fx.Animation.State', 'goog.fx.AnimationEvent'], ['goog.array', 'goog.asserts', 'goog.events.Event', 'goog.fx.Transition', 'goog.fx.TransitionBase', 'goog.fx.anim', 'goog.fx.anim.Animated']); +goog.addDependency("fx/animation_test.js", ['goog.fx.AnimationTest'], ['goog.events', 'goog.fx.Animation', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("fx/animationqueue.js", ['goog.fx.AnimationParallelQueue', 'goog.fx.AnimationQueue', 'goog.fx.AnimationSerialQueue'], ['goog.array', 'goog.asserts', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.fx.TransitionBase']); +goog.addDependency("fx/animationqueue_test.js", ['goog.fx.AnimationQueueTest'], ['goog.events', 'goog.fx.Animation', 'goog.fx.AnimationParallelQueue', 'goog.fx.AnimationSerialQueue', 'goog.fx.Transition', 'goog.fx.anim', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("fx/css3/fx.js", ['goog.fx.css3'], ['goog.fx.css3.Transition']); +goog.addDependency("fx/css3/transition.js", ['goog.fx.css3.Transition'], ['goog.Timer', 'goog.asserts', 'goog.fx.TransitionBase', 'goog.style', 'goog.style.transition']); +goog.addDependency("fx/css3/transition_test.js", ['goog.fx.css3.TransitionTest'], ['goog.dispose', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.fx.Transition', 'goog.fx.css3.Transition', 'goog.style.transition', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("fx/cssspriteanimation.js", ['goog.fx.CssSpriteAnimation'], ['goog.fx.Animation']); +goog.addDependency("fx/cssspriteanimation_test.js", ['goog.fx.CssSpriteAnimationTest'], ['goog.fx.CssSpriteAnimation', 'goog.math.Box', 'goog.math.Size', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("fx/dom.js", ['goog.fx.dom', 'goog.fx.dom.BgColorTransform', 'goog.fx.dom.ColorTransform', 'goog.fx.dom.Fade', 'goog.fx.dom.FadeIn', 'goog.fx.dom.FadeInAndShow', 'goog.fx.dom.FadeOut', 'goog.fx.dom.FadeOutAndHide', 'goog.fx.dom.PredefinedEffect', 'goog.fx.dom.Resize', 'goog.fx.dom.ResizeHeight', 'goog.fx.dom.ResizeWidth', 'goog.fx.dom.Scroll', 'goog.fx.dom.Slide', 'goog.fx.dom.SlideFrom', 'goog.fx.dom.Swipe'], ['goog.color', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.style', 'goog.style.bidi']); +goog.addDependency("fx/dragdrop.js", ['goog.fx.DragDrop'], ['goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem']); +goog.addDependency("fx/dragdropgroup.js", ['goog.fx.DragDropGroup'], ['goog.dom', 'goog.fx.AbstractDragDrop', 'goog.fx.DragDropItem']); +goog.addDependency("fx/dragdropgroup_test.js", ['goog.fx.DragDropGroupTest'], ['goog.events', 'goog.fx.DragDropGroup', 'goog.testing.jsunit']); +goog.addDependency("fx/dragger.js", ['goog.fx.DragEvent', 'goog.fx.Dragger', 'goog.fx.Dragger.EventType'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.style', 'goog.style.bidi', 'goog.userAgent']); +goog.addDependency("fx/dragger_test.js", ['goog.fx.DraggerTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Rect', 'goog.style.bidi', 'goog.testing.StrictMock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("fx/draglistgroup.js", ['goog.fx.DragListDirection', 'goog.fx.DragListGroup', 'goog.fx.DragListGroup.EventType', 'goog.fx.DragListGroupEvent'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Coordinate', 'goog.string', 'goog.style']); +goog.addDependency("fx/draglistgroup_test.js", ['goog.fx.DragListGroupTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.Event', 'goog.events.EventType', 'goog.fx.DragEvent', 'goog.fx.DragListDirection', 'goog.fx.DragListGroup', 'goog.fx.Dragger', 'goog.math.Coordinate', 'goog.object', 'goog.testing.events', 'goog.testing.jsunit']); +goog.addDependency("fx/dragscrollsupport.js", ['goog.fx.DragScrollSupport'], ['goog.Disposable', 'goog.Timer', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.style']); +goog.addDependency("fx/dragscrollsupport_test.js", ['goog.fx.DragScrollSupportTest'], ['goog.fx.DragScrollSupport', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit']); +goog.addDependency("fx/easing.js", ['goog.fx.easing'], []); +goog.addDependency("fx/easing_test.js", ['goog.fx.easingTest'], ['goog.fx.easing', 'goog.testing.jsunit']); +goog.addDependency("fx/fx.js", ['goog.fx'], ['goog.asserts', 'goog.fx.Animation', 'goog.fx.Animation.EventType', 'goog.fx.Animation.State', 'goog.fx.AnimationEvent', 'goog.fx.Transition.EventType', 'goog.fx.easing']); +goog.addDependency("fx/fx_test.js", ['goog.fxTest'], ['goog.fx.Animation', 'goog.object', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("fx/transition.js", ['goog.fx.Transition', 'goog.fx.Transition.EventType'], []); +goog.addDependency("fx/transitionbase.js", ['goog.fx.TransitionBase', 'goog.fx.TransitionBase.State'], ['goog.events.EventTarget', 'goog.fx.Transition']); +goog.addDependency("graphics/abstractgraphics.js", ['goog.graphics.AbstractGraphics'], ['goog.dom', 'goog.graphics.AffineTransform', 'goog.graphics.Element', 'goog.graphics.EllipseElement', 'goog.graphics.Fill', 'goog.graphics.Font', 'goog.graphics.GroupElement', 'goog.graphics.Path', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.Stroke', 'goog.graphics.StrokeAndFillElement', 'goog.graphics.TextElement', 'goog.math.Coordinate', 'goog.math.Size', 'goog.style', 'goog.ui.Component']); +goog.addDependency("graphics/affinetransform.js", ['goog.graphics.AffineTransform'], []); +goog.addDependency("graphics/affinetransform_test.js", ['goog.graphics.AffineTransformTest'], ['goog.graphics', 'goog.graphics.AffineTransform', 'goog.testing.jsunit']); +goog.addDependency("graphics/canvaselement.js", ['goog.graphics.CanvasEllipseElement', 'goog.graphics.CanvasGroupElement', 'goog.graphics.CanvasImageElement', 'goog.graphics.CanvasPathElement', 'goog.graphics.CanvasRectElement', 'goog.graphics.CanvasTextElement'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.graphics.EllipseElement', 'goog.graphics.Font', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.Path', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement', 'goog.html.SafeHtml', 'goog.html.uncheckedconversions', 'goog.math', 'goog.string', 'goog.string.Const']); +goog.addDependency("graphics/canvasgraphics.js", ['goog.graphics.CanvasGraphics'], ['goog.dom.TagName', 'goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.CanvasEllipseElement', 'goog.graphics.CanvasGroupElement', 'goog.graphics.CanvasImageElement', 'goog.graphics.CanvasPathElement', 'goog.graphics.CanvasRectElement', 'goog.graphics.CanvasTextElement', 'goog.graphics.Font', 'goog.graphics.SolidFill', 'goog.math.Size', 'goog.style']); +goog.addDependency("graphics/canvasgraphics_test.js", ['goog.graphics.CanvasGraphicsTest'], ['goog.dom', 'goog.graphics.CanvasGraphics', 'goog.graphics.SolidFill', 'goog.graphics.Stroke', 'goog.testing.jsunit']); +goog.addDependency("graphics/element.js", ['goog.graphics.Element'], ['goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.events.Listenable', 'goog.graphics.AffineTransform', 'goog.math']); +goog.addDependency("graphics/ellipseelement.js", ['goog.graphics.EllipseElement'], ['goog.graphics.StrokeAndFillElement']); +goog.addDependency("graphics/ext/coordinates.js", ['goog.graphics.ext.coordinates'], ['goog.string']); +goog.addDependency("graphics/ext/coordinates_test.js", ['goog.graphics.ext.coordinatesTest'], ['goog.graphics', 'goog.graphics.ext.coordinates', 'goog.testing.jsunit']); +goog.addDependency("graphics/ext/element.js", ['goog.graphics.ext.Element'], ['goog.events.EventTarget', 'goog.functions', 'goog.graphics.ext.coordinates']); +goog.addDependency("graphics/ext/element_test.js", ['goog.graphics.ext.ElementTest'], ['goog.graphics', 'goog.graphics.ext', 'goog.testing.StrictMock', 'goog.testing.jsunit']); +goog.addDependency("graphics/ext/ellipse.js", ['goog.graphics.ext.Ellipse'], ['goog.graphics.ext.StrokeAndFillElement']); +goog.addDependency("graphics/ext/ext.js", ['goog.graphics.ext'], ['goog.graphics.ext.Ellipse', 'goog.graphics.ext.Graphics', 'goog.graphics.ext.Group', 'goog.graphics.ext.Image', 'goog.graphics.ext.Rectangle', 'goog.graphics.ext.Shape', 'goog.graphics.ext.coordinates']); +goog.addDependency("graphics/ext/graphics.js", ['goog.graphics.ext.Graphics'], ['goog.events', 'goog.events.EventType', 'goog.graphics', 'goog.graphics.ext.Group']); +goog.addDependency("graphics/ext/group.js", ['goog.graphics.ext.Group'], ['goog.array', 'goog.graphics.ext.Element']); +goog.addDependency("graphics/ext/image.js", ['goog.graphics.ext.Image'], ['goog.graphics.ext.Element']); +goog.addDependency("graphics/ext/path.js", ['goog.graphics.ext.Path'], ['goog.graphics.AffineTransform', 'goog.graphics.Path', 'goog.math.Rect']); +goog.addDependency("graphics/ext/path_test.js", ['goog.graphics.ext.PathTest'], ['goog.graphics', 'goog.graphics.ext.Path', 'goog.testing.graphics', 'goog.testing.jsunit']); +goog.addDependency("graphics/ext/rectangle.js", ['goog.graphics.ext.Rectangle'], ['goog.graphics.ext.StrokeAndFillElement']); +goog.addDependency("graphics/ext/shape.js", ['goog.graphics.ext.Shape'], ['goog.graphics.ext.StrokeAndFillElement']); +goog.addDependency("graphics/ext/strokeandfillelement.js", ['goog.graphics.ext.StrokeAndFillElement'], ['goog.graphics.ext.Element']); +goog.addDependency("graphics/fill.js", ['goog.graphics.Fill'], []); +goog.addDependency("graphics/font.js", ['goog.graphics.Font'], []); +goog.addDependency("graphics/graphics.js", ['goog.graphics'], ['goog.dom', 'goog.graphics.CanvasGraphics', 'goog.graphics.SvgGraphics', 'goog.graphics.VmlGraphics', 'goog.userAgent']); +goog.addDependency("graphics/groupelement.js", ['goog.graphics.GroupElement'], ['goog.graphics.Element']); +goog.addDependency("graphics/imageelement.js", ['goog.graphics.ImageElement'], ['goog.graphics.Element']); +goog.addDependency("graphics/lineargradient.js", ['goog.graphics.LinearGradient'], ['goog.asserts', 'goog.graphics.Fill']); +goog.addDependency("graphics/path.js", ['goog.graphics.Path', 'goog.graphics.Path.Segment'], ['goog.array', 'goog.graphics.AffineTransform', 'goog.math']); +goog.addDependency("graphics/path_test.js", ['goog.graphics.PathTest'], ['goog.array', 'goog.math', 'goog.graphics.Path', 'goog.graphics.AffineTransform', 'goog.testing.graphics', 'goog.testing.jsunit']); +goog.addDependency("graphics/pathelement.js", ['goog.graphics.PathElement'], ['goog.graphics.StrokeAndFillElement']); +goog.addDependency("graphics/paths.js", ['goog.graphics.paths'], ['goog.graphics.Path', 'goog.math.Coordinate']); +goog.addDependency("graphics/paths_test.js", ['goog.graphics.pathsTest'], ['goog.dom', 'goog.graphics', 'goog.graphics.paths', 'goog.testing.jsunit']); +goog.addDependency("graphics/rectelement.js", ['goog.graphics.RectElement'], ['goog.graphics.StrokeAndFillElement']); +goog.addDependency("graphics/solidfill.js", ['goog.graphics.SolidFill'], ['goog.graphics.Fill']); +goog.addDependency("graphics/solidfill_test.js", ['goog.graphics.SolidFillTest'], ['goog.graphics.SolidFill', 'goog.testing.jsunit']); +goog.addDependency("graphics/stroke.js", ['goog.graphics.Stroke'], []); +goog.addDependency("graphics/strokeandfillelement.js", ['goog.graphics.StrokeAndFillElement'], ['goog.graphics.Element']); +goog.addDependency("graphics/svgelement.js", ['goog.graphics.SvgEllipseElement', 'goog.graphics.SvgGroupElement', 'goog.graphics.SvgImageElement', 'goog.graphics.SvgPathElement', 'goog.graphics.SvgRectElement', 'goog.graphics.SvgTextElement'], ['goog.dom', 'goog.graphics.EllipseElement', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement']); +goog.addDependency("graphics/svggraphics.js", ['goog.graphics.SvgGraphics'], ['goog.Timer', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.Font', 'goog.graphics.LinearGradient', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.Stroke', 'goog.graphics.SvgEllipseElement', 'goog.graphics.SvgGroupElement', 'goog.graphics.SvgImageElement', 'goog.graphics.SvgPathElement', 'goog.graphics.SvgRectElement', 'goog.graphics.SvgTextElement', 'goog.math', 'goog.math.Size', 'goog.style', 'goog.userAgent']); +goog.addDependency("graphics/svggraphics_test.js", ['goog.graphics.SvgGraphicsTest'], ['goog.dom', 'goog.graphics.SvgGraphics', 'goog.testing.graphics', 'goog.testing.jsunit']); +goog.addDependency("graphics/textelement.js", ['goog.graphics.TextElement'], ['goog.graphics.StrokeAndFillElement']); +goog.addDependency("graphics/vmlelement.js", ['goog.graphics.VmlEllipseElement', 'goog.graphics.VmlGroupElement', 'goog.graphics.VmlImageElement', 'goog.graphics.VmlPathElement', 'goog.graphics.VmlRectElement', 'goog.graphics.VmlTextElement'], ['goog.dom', 'goog.graphics.EllipseElement', 'goog.graphics.GroupElement', 'goog.graphics.ImageElement', 'goog.graphics.PathElement', 'goog.graphics.RectElement', 'goog.graphics.TextElement']); +goog.addDependency("graphics/vmlgraphics.js", ['goog.graphics.VmlGraphics'], ['goog.array', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.graphics.AbstractGraphics', 'goog.graphics.Font', 'goog.graphics.LinearGradient', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.VmlEllipseElement', 'goog.graphics.VmlGroupElement', 'goog.graphics.VmlImageElement', 'goog.graphics.VmlPathElement', 'goog.graphics.VmlRectElement', 'goog.graphics.VmlTextElement', 'goog.html.uncheckedconversions', 'goog.math', 'goog.math.Size', 'goog.reflect', 'goog.string', 'goog.string.Const', 'goog.style', 'goog.userAgent']); +goog.addDependency("history/event.js", ['goog.history.Event'], ['goog.events.Event', 'goog.history.EventType']); +goog.addDependency("history/eventtype.js", ['goog.history.EventType'], []); +goog.addDependency("history/history.js", ['goog.History', 'goog.History.Event', 'goog.History.EventType'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.safe', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.history.Event', 'goog.history.EventType', 'goog.html.SafeHtml', 'goog.html.TrustedResourceUrl', 'goog.html.uncheckedconversions', 'goog.labs.userAgent.device', 'goog.memoize', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("history/history_test.js", ['goog.HistoryTest'], ['goog.History', 'goog.dispose', 'goog.dom', 'goog.html.TrustedResourceUrl', 'goog.string.Const', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("history/html5history.js", ['goog.history.Html5History', 'goog.history.Html5History.TokenTransformer'], ['goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.history.Event']); +goog.addDependency("history/html5history_test.js", ['goog.history.Html5HistoryTest'], ['goog.Timer', 'goog.events', 'goog.events.EventType', 'goog.history.EventType', 'goog.history.Html5History', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.recordFunction']); +goog.addDependency("html/cssspecificity.js", [], []); +goog.addDependency("html/cssspecificity_test.js", [], []); +goog.addDependency("html/flash.js", ['goog.html.flash'], ['goog.asserts', 'goog.html.SafeHtml']); +goog.addDependency("html/flash_test.js", ['goog.html.flashTest'], ['goog.html.SafeHtml', 'goog.html.TrustedResourceUrl', 'goog.html.flash', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/legacyconversions.js", ['goog.html.legacyconversions'], ['goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl']); +goog.addDependency("html/legacyconversions_test.js", ['goog.html.legacyconversionsTest'], ['goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.legacyconversions', 'goog.testing.jsunit']); +goog.addDependency("html/safehtml.js", ['goog.html.SafeHtml'], ['goog.array', 'goog.asserts', 'goog.dom.TagName', 'goog.dom.tags', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.labs.userAgent.browser', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/safehtml_test.js", ['goog.html.safeHtmlTest'], ['goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.testing', 'goog.i18n.bidi.Dir', 'goog.labs.userAgent.browser', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/safehtmlformatter.js", ['goog.html.SafeHtmlFormatter'], ['goog.asserts', 'goog.dom.tags', 'goog.html.SafeHtml', 'goog.string']); +goog.addDependency("html/safehtmlformatter_test.js", ['goog.html.safeHtmlFormatterTest'], ['goog.html.SafeHtml', 'goog.html.SafeHtmlFormatter', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("html/safescript.js", ['goog.html.SafeScript'], ['goog.asserts', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/safescript_test.js", ['goog.html.safeScriptTest'], ['goog.html.SafeScript', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/safestyle.js", ['goog.html.SafeStyle'], ['goog.array', 'goog.asserts', 'goog.html.SafeUrl', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/safestyle_test.js", ['goog.html.safeStyleTest'], ['goog.html.SafeStyle', 'goog.html.SafeUrl', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/safestylesheet.js", ['goog.html.SafeStyleSheet'], ['goog.array', 'goog.asserts', 'goog.html.SafeStyle', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/safestylesheet_test.js", ['goog.html.safeStyleSheetTest'], ['goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/safeurl.js", ['goog.html.SafeUrl'], ['goog.asserts', 'goog.fs.url', 'goog.html.TrustedResourceUrl', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.string', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/safeurl_test.js", ['goog.html.safeUrlTest'], ['goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.safeUrlTestVectors', 'goog.i18n.bidi.Dir', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("html/safeurl_test_vectors.js", ['goog.html.safeUrlTestVectors'], []); +goog.addDependency("html/sanitizer/attributewhitelist.js", ['goog.html.sanitizer.AttributeSanitizedWhitelist', 'goog.html.sanitizer.AttributeWhitelist'], []); +goog.addDependency("html/sanitizer/csssanitizer.js", ['goog.html.sanitizer.CssSanitizer'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.html.CssSpecificity', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.sanitizer.noclobber', 'goog.html.uncheckedconversions', 'goog.object', 'goog.string', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("html/sanitizer/csssanitizer_test.js", ['goog.html.CssSanitizerTest'], ['goog.array', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.sanitizer.CssSanitizer', 'goog.html.testing', 'goog.string', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("html/sanitizer/elementweakmap.js", [], []); +goog.addDependency("html/sanitizer/elementweakmap_test.js", [], []); +goog.addDependency("html/sanitizer/htmlsanitizer.js", ['goog.html.sanitizer.HtmlSanitizer', 'goog.html.sanitizer.HtmlSanitizer.Builder', 'goog.html.sanitizer.HtmlSanitizerAttributePolicy', 'goog.html.sanitizer.HtmlSanitizerPolicy', 'goog.html.sanitizer.HtmlSanitizerPolicyContext', 'goog.html.sanitizer.HtmlSanitizerPolicyHints', 'goog.html.sanitizer.HtmlSanitizerUrlPolicy'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.sanitizer.AttributeSanitizedWhitelist', 'goog.html.sanitizer.AttributeWhitelist', 'goog.html.sanitizer.CssSanitizer', 'goog.html.sanitizer.SafeDomTreeProcessor', 'goog.html.sanitizer.TagBlacklist', 'goog.html.sanitizer.TagWhitelist', 'goog.html.sanitizer.noclobber', 'goog.html.uncheckedconversions', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("html/sanitizer/htmlsanitizer_test.js", ['goog.html.HtmlSanitizerTest'], ['goog.array', 'goog.dom', 'goog.functions', 'goog.html.SafeHtml', 'goog.html.SafeUrl', 'goog.html.sanitizer.HtmlSanitizer', 'goog.html.sanitizer.HtmlSanitizer.Builder', 'goog.html.sanitizer.TagWhitelist', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("html/sanitizer/noclobber.js", [], []); +goog.addDependency("html/sanitizer/noclobber_test.js", [], []); +goog.addDependency("html/sanitizer/safedomtreeprocessor.js", [], []); +goog.addDependency("html/sanitizer/safedomtreeprocessor_test.js", [], []); +goog.addDependency("html/sanitizer/tagblacklist.js", ['goog.html.sanitizer.TagBlacklist'], []); +goog.addDependency("html/sanitizer/tagwhitelist.js", ['goog.html.sanitizer.TagWhitelist'], []); +goog.addDependency("html/sanitizer/unsafe.js", ['goog.html.sanitizer.unsafe'], ['goog.asserts', 'goog.html.sanitizer.HtmlSanitizer.Builder', 'goog.string', 'goog.string.Const']); +goog.addDependency("html/sanitizer/unsafe_test.js", ['goog.html.UnsafeTest'], ['goog.html.SafeHtml', 'goog.html.sanitizer.AttributeWhitelist', 'goog.html.sanitizer.HtmlSanitizer', 'goog.html.sanitizer.TagWhitelist', 'goog.html.sanitizer.unsafe', 'goog.string.Const', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("html/silverlight.js", ['goog.html.silverlight'], ['goog.html.SafeHtml', 'goog.html.TrustedResourceUrl', 'goog.html.flash', 'goog.string.Const']); +goog.addDependency("html/silverlight_test.js", ['goog.html.silverlightTest'], ['goog.html.SafeHtml', 'goog.html.TrustedResourceUrl', 'goog.html.silverlight', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/testing.js", ['goog.html.testing'], ['goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.testing.mockmatchers.ArgumentMatcher']); +goog.addDependency("html/textextractor.js", ['goog.html.textExtractor'], ['goog.array', 'goog.dom.TagName', 'goog.html.sanitizer.HtmlSanitizer', 'goog.object', 'goog.userAgent']); +goog.addDependency("html/textextractor_test.js", [], []); +goog.addDependency("html/trustedresourceurl.js", ['goog.html.TrustedResourceUrl'], ['goog.asserts', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.string.Const', 'goog.string.TypedString']); +goog.addDependency("html/trustedresourceurl_test.js", ['goog.html.trustedResourceUrlTest'], ['goog.html.TrustedResourceUrl', 'goog.i18n.bidi.Dir', 'goog.object', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/uncheckedconversions.js", ['goog.html.uncheckedconversions'], ['goog.asserts', 'goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const']); +goog.addDependency("html/uncheckedconversions_test.js", ['goog.html.uncheckedconversionsTest'], ['goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.uncheckedconversions', 'goog.i18n.bidi.Dir', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("html/utils.js", ['goog.html.utils'], ['goog.string']); +goog.addDependency("html/utils_test.js", ['goog.html.UtilsTest'], ['goog.array', 'goog.dom.TagName', 'goog.html.utils', 'goog.object', 'goog.testing.jsunit']); +goog.addDependency("i18n/bidi.js", ['goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.DirectionalString', 'goog.i18n.bidi.Format'], []); +goog.addDependency("i18n/bidi_test.js", ['goog.i18n.bidiTest'], ['goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.testing.jsunit']); +goog.addDependency("i18n/bidiformatter.js", ['goog.i18n.BidiFormatter'], ['goog.html.SafeHtml', 'goog.i18n.bidi', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.Format']); +goog.addDependency("i18n/bidiformatter_test.js", ['goog.i18n.BidiFormatterTest'], ['goog.html.SafeHtml', 'goog.html.testing', 'goog.i18n.BidiFormatter', 'goog.i18n.bidi.Dir', 'goog.i18n.bidi.Format', 'goog.testing.jsunit']); +goog.addDependency("i18n/charlistdecompressor.js", ['goog.i18n.CharListDecompressor'], ['goog.array', 'goog.i18n.uChar']); +goog.addDependency("i18n/charlistdecompressor_test.js", ['goog.i18n.CharListDecompressorTest'], ['goog.i18n.CharListDecompressor', 'goog.testing.jsunit']); +goog.addDependency("i18n/charpickerdata.js", ['goog.i18n.CharPickerData'], []); +goog.addDependency("i18n/collation.js", ['goog.i18n.collation'], []); +goog.addDependency("i18n/collation_test.js", ['goog.i18n.collationTest'], ['goog.i18n.collation', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("i18n/compactnumberformatsymbols.js", ['goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.CompactNumberFormatSymbols_af', 'goog.i18n.CompactNumberFormatSymbols_am', 'goog.i18n.CompactNumberFormatSymbols_ar', 'goog.i18n.CompactNumberFormatSymbols_ar_DZ', 'goog.i18n.CompactNumberFormatSymbols_az', 'goog.i18n.CompactNumberFormatSymbols_be', 'goog.i18n.CompactNumberFormatSymbols_bg', 'goog.i18n.CompactNumberFormatSymbols_bn', 'goog.i18n.CompactNumberFormatSymbols_br', 'goog.i18n.CompactNumberFormatSymbols_bs', 'goog.i18n.CompactNumberFormatSymbols_ca', 'goog.i18n.CompactNumberFormatSymbols_chr', 'goog.i18n.CompactNumberFormatSymbols_cs', 'goog.i18n.CompactNumberFormatSymbols_cy', 'goog.i18n.CompactNumberFormatSymbols_da', 'goog.i18n.CompactNumberFormatSymbols_de', 'goog.i18n.CompactNumberFormatSymbols_de_AT', 'goog.i18n.CompactNumberFormatSymbols_de_CH', 'goog.i18n.CompactNumberFormatSymbols_el', 'goog.i18n.CompactNumberFormatSymbols_en', 'goog.i18n.CompactNumberFormatSymbols_en_AU', 'goog.i18n.CompactNumberFormatSymbols_en_CA', 'goog.i18n.CompactNumberFormatSymbols_en_GB', 'goog.i18n.CompactNumberFormatSymbols_en_IE', 'goog.i18n.CompactNumberFormatSymbols_en_IN', 'goog.i18n.CompactNumberFormatSymbols_en_SG', 'goog.i18n.CompactNumberFormatSymbols_en_US', 'goog.i18n.CompactNumberFormatSymbols_en_ZA', 'goog.i18n.CompactNumberFormatSymbols_es', 'goog.i18n.CompactNumberFormatSymbols_es_419', 'goog.i18n.CompactNumberFormatSymbols_es_ES', 'goog.i18n.CompactNumberFormatSymbols_es_MX', 'goog.i18n.CompactNumberFormatSymbols_es_US', 'goog.i18n.CompactNumberFormatSymbols_et', 'goog.i18n.CompactNumberFormatSymbols_eu', 'goog.i18n.CompactNumberFormatSymbols_fa', 'goog.i18n.CompactNumberFormatSymbols_fi', 'goog.i18n.CompactNumberFormatSymbols_fil', 'goog.i18n.CompactNumberFormatSymbols_fr', 'goog.i18n.CompactNumberFormatSymbols_fr_CA', 'goog.i18n.CompactNumberFormatSymbols_ga', 'goog.i18n.CompactNumberFormatSymbols_gl', 'goog.i18n.CompactNumberFormatSymbols_gsw', 'goog.i18n.CompactNumberFormatSymbols_gu', 'goog.i18n.CompactNumberFormatSymbols_haw', 'goog.i18n.CompactNumberFormatSymbols_he', 'goog.i18n.CompactNumberFormatSymbols_hi', 'goog.i18n.CompactNumberFormatSymbols_hr', 'goog.i18n.CompactNumberFormatSymbols_hu', 'goog.i18n.CompactNumberFormatSymbols_hy', 'goog.i18n.CompactNumberFormatSymbols_id', 'goog.i18n.CompactNumberFormatSymbols_in', 'goog.i18n.CompactNumberFormatSymbols_is', 'goog.i18n.CompactNumberFormatSymbols_it', 'goog.i18n.CompactNumberFormatSymbols_iw', 'goog.i18n.CompactNumberFormatSymbols_ja', 'goog.i18n.CompactNumberFormatSymbols_ka', 'goog.i18n.CompactNumberFormatSymbols_kk', 'goog.i18n.CompactNumberFormatSymbols_km', 'goog.i18n.CompactNumberFormatSymbols_kn', 'goog.i18n.CompactNumberFormatSymbols_ko', 'goog.i18n.CompactNumberFormatSymbols_ky', 'goog.i18n.CompactNumberFormatSymbols_ln', 'goog.i18n.CompactNumberFormatSymbols_lo', 'goog.i18n.CompactNumberFormatSymbols_lt', 'goog.i18n.CompactNumberFormatSymbols_lv', 'goog.i18n.CompactNumberFormatSymbols_mk', 'goog.i18n.CompactNumberFormatSymbols_ml', 'goog.i18n.CompactNumberFormatSymbols_mn', 'goog.i18n.CompactNumberFormatSymbols_mo', 'goog.i18n.CompactNumberFormatSymbols_mr', 'goog.i18n.CompactNumberFormatSymbols_ms', 'goog.i18n.CompactNumberFormatSymbols_mt', 'goog.i18n.CompactNumberFormatSymbols_my', 'goog.i18n.CompactNumberFormatSymbols_nb', 'goog.i18n.CompactNumberFormatSymbols_ne', 'goog.i18n.CompactNumberFormatSymbols_nl', 'goog.i18n.CompactNumberFormatSymbols_no', 'goog.i18n.CompactNumberFormatSymbols_no_NO', 'goog.i18n.CompactNumberFormatSymbols_or', 'goog.i18n.CompactNumberFormatSymbols_pa', 'goog.i18n.CompactNumberFormatSymbols_pl', 'goog.i18n.CompactNumberFormatSymbols_pt', 'goog.i18n.CompactNumberFormatSymbols_pt_BR', 'goog.i18n.CompactNumberFormatSymbols_pt_PT', 'goog.i18n.CompactNumberFormatSymbols_ro', 'goog.i18n.CompactNumberFormatSymbols_ru', 'goog.i18n.CompactNumberFormatSymbols_sh', 'goog.i18n.CompactNumberFormatSymbols_si', 'goog.i18n.CompactNumberFormatSymbols_sk', 'goog.i18n.CompactNumberFormatSymbols_sl', 'goog.i18n.CompactNumberFormatSymbols_sq', 'goog.i18n.CompactNumberFormatSymbols_sr', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn', 'goog.i18n.CompactNumberFormatSymbols_sv', 'goog.i18n.CompactNumberFormatSymbols_sw', 'goog.i18n.CompactNumberFormatSymbols_ta', 'goog.i18n.CompactNumberFormatSymbols_te', 'goog.i18n.CompactNumberFormatSymbols_th', 'goog.i18n.CompactNumberFormatSymbols_tl', 'goog.i18n.CompactNumberFormatSymbols_tr', 'goog.i18n.CompactNumberFormatSymbols_uk', 'goog.i18n.CompactNumberFormatSymbols_ur', 'goog.i18n.CompactNumberFormatSymbols_uz', 'goog.i18n.CompactNumberFormatSymbols_vi', 'goog.i18n.CompactNumberFormatSymbols_zh', 'goog.i18n.CompactNumberFormatSymbols_zh_CN', 'goog.i18n.CompactNumberFormatSymbols_zh_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_TW', 'goog.i18n.CompactNumberFormatSymbols_zu'], []); +goog.addDependency("i18n/compactnumberformatsymbolsext.js", ['goog.i18n.CompactNumberFormatSymbolsExt', 'goog.i18n.CompactNumberFormatSymbols_af_NA', 'goog.i18n.CompactNumberFormatSymbols_af_ZA', 'goog.i18n.CompactNumberFormatSymbols_agq', 'goog.i18n.CompactNumberFormatSymbols_agq_CM', 'goog.i18n.CompactNumberFormatSymbols_ak', 'goog.i18n.CompactNumberFormatSymbols_ak_GH', 'goog.i18n.CompactNumberFormatSymbols_am_ET', 'goog.i18n.CompactNumberFormatSymbols_ar_001', 'goog.i18n.CompactNumberFormatSymbols_ar_AE', 'goog.i18n.CompactNumberFormatSymbols_ar_BH', 'goog.i18n.CompactNumberFormatSymbols_ar_DJ', 'goog.i18n.CompactNumberFormatSymbols_ar_EG', 'goog.i18n.CompactNumberFormatSymbols_ar_EH', 'goog.i18n.CompactNumberFormatSymbols_ar_ER', 'goog.i18n.CompactNumberFormatSymbols_ar_IL', 'goog.i18n.CompactNumberFormatSymbols_ar_IQ', 'goog.i18n.CompactNumberFormatSymbols_ar_JO', 'goog.i18n.CompactNumberFormatSymbols_ar_KM', 'goog.i18n.CompactNumberFormatSymbols_ar_KW', 'goog.i18n.CompactNumberFormatSymbols_ar_LB', 'goog.i18n.CompactNumberFormatSymbols_ar_LY', 'goog.i18n.CompactNumberFormatSymbols_ar_MA', 'goog.i18n.CompactNumberFormatSymbols_ar_MR', 'goog.i18n.CompactNumberFormatSymbols_ar_OM', 'goog.i18n.CompactNumberFormatSymbols_ar_PS', 'goog.i18n.CompactNumberFormatSymbols_ar_QA', 'goog.i18n.CompactNumberFormatSymbols_ar_SA', 'goog.i18n.CompactNumberFormatSymbols_ar_SD', 'goog.i18n.CompactNumberFormatSymbols_ar_SO', 'goog.i18n.CompactNumberFormatSymbols_ar_SS', 'goog.i18n.CompactNumberFormatSymbols_ar_SY', 'goog.i18n.CompactNumberFormatSymbols_ar_TD', 'goog.i18n.CompactNumberFormatSymbols_ar_TN', 'goog.i18n.CompactNumberFormatSymbols_ar_XB', 'goog.i18n.CompactNumberFormatSymbols_ar_YE', 'goog.i18n.CompactNumberFormatSymbols_as', 'goog.i18n.CompactNumberFormatSymbols_as_IN', 'goog.i18n.CompactNumberFormatSymbols_asa', 'goog.i18n.CompactNumberFormatSymbols_asa_TZ', 'goog.i18n.CompactNumberFormatSymbols_ast', 'goog.i18n.CompactNumberFormatSymbols_ast_ES', 'goog.i18n.CompactNumberFormatSymbols_az_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_az_Cyrl_AZ', 'goog.i18n.CompactNumberFormatSymbols_az_Latn', 'goog.i18n.CompactNumberFormatSymbols_az_Latn_AZ', 'goog.i18n.CompactNumberFormatSymbols_bas', 'goog.i18n.CompactNumberFormatSymbols_bas_CM', 'goog.i18n.CompactNumberFormatSymbols_be_BY', 'goog.i18n.CompactNumberFormatSymbols_bem', 'goog.i18n.CompactNumberFormatSymbols_bem_ZM', 'goog.i18n.CompactNumberFormatSymbols_bez', 'goog.i18n.CompactNumberFormatSymbols_bez_TZ', 'goog.i18n.CompactNumberFormatSymbols_bg_BG', 'goog.i18n.CompactNumberFormatSymbols_bm', 'goog.i18n.CompactNumberFormatSymbols_bm_ML', 'goog.i18n.CompactNumberFormatSymbols_bn_BD', 'goog.i18n.CompactNumberFormatSymbols_bn_IN', 'goog.i18n.CompactNumberFormatSymbols_bo', 'goog.i18n.CompactNumberFormatSymbols_bo_CN', 'goog.i18n.CompactNumberFormatSymbols_bo_IN', 'goog.i18n.CompactNumberFormatSymbols_br_FR', 'goog.i18n.CompactNumberFormatSymbols_brx', 'goog.i18n.CompactNumberFormatSymbols_brx_IN', 'goog.i18n.CompactNumberFormatSymbols_bs_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_bs_Cyrl_BA', 'goog.i18n.CompactNumberFormatSymbols_bs_Latn', 'goog.i18n.CompactNumberFormatSymbols_bs_Latn_BA', 'goog.i18n.CompactNumberFormatSymbols_ca_AD', 'goog.i18n.CompactNumberFormatSymbols_ca_ES', 'goog.i18n.CompactNumberFormatSymbols_ca_FR', 'goog.i18n.CompactNumberFormatSymbols_ca_IT', 'goog.i18n.CompactNumberFormatSymbols_ccp', 'goog.i18n.CompactNumberFormatSymbols_ccp_BD', 'goog.i18n.CompactNumberFormatSymbols_ccp_IN', 'goog.i18n.CompactNumberFormatSymbols_ce', 'goog.i18n.CompactNumberFormatSymbols_ce_RU', 'goog.i18n.CompactNumberFormatSymbols_cgg', 'goog.i18n.CompactNumberFormatSymbols_cgg_UG', 'goog.i18n.CompactNumberFormatSymbols_chr_US', 'goog.i18n.CompactNumberFormatSymbols_ckb', 'goog.i18n.CompactNumberFormatSymbols_ckb_IQ', 'goog.i18n.CompactNumberFormatSymbols_ckb_IR', 'goog.i18n.CompactNumberFormatSymbols_cs_CZ', 'goog.i18n.CompactNumberFormatSymbols_cy_GB', 'goog.i18n.CompactNumberFormatSymbols_da_DK', 'goog.i18n.CompactNumberFormatSymbols_da_GL', 'goog.i18n.CompactNumberFormatSymbols_dav', 'goog.i18n.CompactNumberFormatSymbols_dav_KE', 'goog.i18n.CompactNumberFormatSymbols_de_BE', 'goog.i18n.CompactNumberFormatSymbols_de_DE', 'goog.i18n.CompactNumberFormatSymbols_de_IT', 'goog.i18n.CompactNumberFormatSymbols_de_LI', 'goog.i18n.CompactNumberFormatSymbols_de_LU', 'goog.i18n.CompactNumberFormatSymbols_dje', 'goog.i18n.CompactNumberFormatSymbols_dje_NE', 'goog.i18n.CompactNumberFormatSymbols_dsb', 'goog.i18n.CompactNumberFormatSymbols_dsb_DE', 'goog.i18n.CompactNumberFormatSymbols_dua', 'goog.i18n.CompactNumberFormatSymbols_dua_CM', 'goog.i18n.CompactNumberFormatSymbols_dyo', 'goog.i18n.CompactNumberFormatSymbols_dyo_SN', 'goog.i18n.CompactNumberFormatSymbols_dz', 'goog.i18n.CompactNumberFormatSymbols_dz_BT', 'goog.i18n.CompactNumberFormatSymbols_ebu', 'goog.i18n.CompactNumberFormatSymbols_ebu_KE', 'goog.i18n.CompactNumberFormatSymbols_ee', 'goog.i18n.CompactNumberFormatSymbols_ee_GH', 'goog.i18n.CompactNumberFormatSymbols_ee_TG', 'goog.i18n.CompactNumberFormatSymbols_el_CY', 'goog.i18n.CompactNumberFormatSymbols_el_GR', 'goog.i18n.CompactNumberFormatSymbols_en_001', 'goog.i18n.CompactNumberFormatSymbols_en_150', 'goog.i18n.CompactNumberFormatSymbols_en_AG', 'goog.i18n.CompactNumberFormatSymbols_en_AI', 'goog.i18n.CompactNumberFormatSymbols_en_AS', 'goog.i18n.CompactNumberFormatSymbols_en_AT', 'goog.i18n.CompactNumberFormatSymbols_en_BB', 'goog.i18n.CompactNumberFormatSymbols_en_BE', 'goog.i18n.CompactNumberFormatSymbols_en_BI', 'goog.i18n.CompactNumberFormatSymbols_en_BM', 'goog.i18n.CompactNumberFormatSymbols_en_BS', 'goog.i18n.CompactNumberFormatSymbols_en_BW', 'goog.i18n.CompactNumberFormatSymbols_en_BZ', 'goog.i18n.CompactNumberFormatSymbols_en_CC', 'goog.i18n.CompactNumberFormatSymbols_en_CH', 'goog.i18n.CompactNumberFormatSymbols_en_CK', 'goog.i18n.CompactNumberFormatSymbols_en_CM', 'goog.i18n.CompactNumberFormatSymbols_en_CX', 'goog.i18n.CompactNumberFormatSymbols_en_CY', 'goog.i18n.CompactNumberFormatSymbols_en_DE', 'goog.i18n.CompactNumberFormatSymbols_en_DG', 'goog.i18n.CompactNumberFormatSymbols_en_DK', 'goog.i18n.CompactNumberFormatSymbols_en_DM', 'goog.i18n.CompactNumberFormatSymbols_en_ER', 'goog.i18n.CompactNumberFormatSymbols_en_FI', 'goog.i18n.CompactNumberFormatSymbols_en_FJ', 'goog.i18n.CompactNumberFormatSymbols_en_FK', 'goog.i18n.CompactNumberFormatSymbols_en_FM', 'goog.i18n.CompactNumberFormatSymbols_en_GD', 'goog.i18n.CompactNumberFormatSymbols_en_GG', 'goog.i18n.CompactNumberFormatSymbols_en_GH', 'goog.i18n.CompactNumberFormatSymbols_en_GI', 'goog.i18n.CompactNumberFormatSymbols_en_GM', 'goog.i18n.CompactNumberFormatSymbols_en_GU', 'goog.i18n.CompactNumberFormatSymbols_en_GY', 'goog.i18n.CompactNumberFormatSymbols_en_HK', 'goog.i18n.CompactNumberFormatSymbols_en_IL', 'goog.i18n.CompactNumberFormatSymbols_en_IM', 'goog.i18n.CompactNumberFormatSymbols_en_IO', 'goog.i18n.CompactNumberFormatSymbols_en_JE', 'goog.i18n.CompactNumberFormatSymbols_en_JM', 'goog.i18n.CompactNumberFormatSymbols_en_KE', 'goog.i18n.CompactNumberFormatSymbols_en_KI', 'goog.i18n.CompactNumberFormatSymbols_en_KN', 'goog.i18n.CompactNumberFormatSymbols_en_KY', 'goog.i18n.CompactNumberFormatSymbols_en_LC', 'goog.i18n.CompactNumberFormatSymbols_en_LR', 'goog.i18n.CompactNumberFormatSymbols_en_LS', 'goog.i18n.CompactNumberFormatSymbols_en_MG', 'goog.i18n.CompactNumberFormatSymbols_en_MH', 'goog.i18n.CompactNumberFormatSymbols_en_MO', 'goog.i18n.CompactNumberFormatSymbols_en_MP', 'goog.i18n.CompactNumberFormatSymbols_en_MS', 'goog.i18n.CompactNumberFormatSymbols_en_MT', 'goog.i18n.CompactNumberFormatSymbols_en_MU', 'goog.i18n.CompactNumberFormatSymbols_en_MW', 'goog.i18n.CompactNumberFormatSymbols_en_MY', 'goog.i18n.CompactNumberFormatSymbols_en_NA', 'goog.i18n.CompactNumberFormatSymbols_en_NF', 'goog.i18n.CompactNumberFormatSymbols_en_NG', 'goog.i18n.CompactNumberFormatSymbols_en_NL', 'goog.i18n.CompactNumberFormatSymbols_en_NR', 'goog.i18n.CompactNumberFormatSymbols_en_NU', 'goog.i18n.CompactNumberFormatSymbols_en_NZ', 'goog.i18n.CompactNumberFormatSymbols_en_PG', 'goog.i18n.CompactNumberFormatSymbols_en_PH', 'goog.i18n.CompactNumberFormatSymbols_en_PK', 'goog.i18n.CompactNumberFormatSymbols_en_PN', 'goog.i18n.CompactNumberFormatSymbols_en_PR', 'goog.i18n.CompactNumberFormatSymbols_en_PW', 'goog.i18n.CompactNumberFormatSymbols_en_RW', 'goog.i18n.CompactNumberFormatSymbols_en_SB', 'goog.i18n.CompactNumberFormatSymbols_en_SC', 'goog.i18n.CompactNumberFormatSymbols_en_SD', 'goog.i18n.CompactNumberFormatSymbols_en_SE', 'goog.i18n.CompactNumberFormatSymbols_en_SH', 'goog.i18n.CompactNumberFormatSymbols_en_SI', 'goog.i18n.CompactNumberFormatSymbols_en_SL', 'goog.i18n.CompactNumberFormatSymbols_en_SS', 'goog.i18n.CompactNumberFormatSymbols_en_SX', 'goog.i18n.CompactNumberFormatSymbols_en_SZ', 'goog.i18n.CompactNumberFormatSymbols_en_TC', 'goog.i18n.CompactNumberFormatSymbols_en_TK', 'goog.i18n.CompactNumberFormatSymbols_en_TO', 'goog.i18n.CompactNumberFormatSymbols_en_TT', 'goog.i18n.CompactNumberFormatSymbols_en_TV', 'goog.i18n.CompactNumberFormatSymbols_en_TZ', 'goog.i18n.CompactNumberFormatSymbols_en_UG', 'goog.i18n.CompactNumberFormatSymbols_en_UM', 'goog.i18n.CompactNumberFormatSymbols_en_US_POSIX', 'goog.i18n.CompactNumberFormatSymbols_en_VC', 'goog.i18n.CompactNumberFormatSymbols_en_VG', 'goog.i18n.CompactNumberFormatSymbols_en_VI', 'goog.i18n.CompactNumberFormatSymbols_en_VU', 'goog.i18n.CompactNumberFormatSymbols_en_WS', 'goog.i18n.CompactNumberFormatSymbols_en_XA', 'goog.i18n.CompactNumberFormatSymbols_en_ZM', 'goog.i18n.CompactNumberFormatSymbols_en_ZW', 'goog.i18n.CompactNumberFormatSymbols_eo', 'goog.i18n.CompactNumberFormatSymbols_es_AR', 'goog.i18n.CompactNumberFormatSymbols_es_BO', 'goog.i18n.CompactNumberFormatSymbols_es_BR', 'goog.i18n.CompactNumberFormatSymbols_es_BZ', 'goog.i18n.CompactNumberFormatSymbols_es_CL', 'goog.i18n.CompactNumberFormatSymbols_es_CO', 'goog.i18n.CompactNumberFormatSymbols_es_CR', 'goog.i18n.CompactNumberFormatSymbols_es_CU', 'goog.i18n.CompactNumberFormatSymbols_es_DO', 'goog.i18n.CompactNumberFormatSymbols_es_EA', 'goog.i18n.CompactNumberFormatSymbols_es_EC', 'goog.i18n.CompactNumberFormatSymbols_es_GQ', 'goog.i18n.CompactNumberFormatSymbols_es_GT', 'goog.i18n.CompactNumberFormatSymbols_es_HN', 'goog.i18n.CompactNumberFormatSymbols_es_IC', 'goog.i18n.CompactNumberFormatSymbols_es_NI', 'goog.i18n.CompactNumberFormatSymbols_es_PA', 'goog.i18n.CompactNumberFormatSymbols_es_PE', 'goog.i18n.CompactNumberFormatSymbols_es_PH', 'goog.i18n.CompactNumberFormatSymbols_es_PR', 'goog.i18n.CompactNumberFormatSymbols_es_PY', 'goog.i18n.CompactNumberFormatSymbols_es_SV', 'goog.i18n.CompactNumberFormatSymbols_es_UY', 'goog.i18n.CompactNumberFormatSymbols_es_VE', 'goog.i18n.CompactNumberFormatSymbols_et_EE', 'goog.i18n.CompactNumberFormatSymbols_eu_ES', 'goog.i18n.CompactNumberFormatSymbols_ewo', 'goog.i18n.CompactNumberFormatSymbols_ewo_CM', 'goog.i18n.CompactNumberFormatSymbols_fa_AF', 'goog.i18n.CompactNumberFormatSymbols_fa_IR', 'goog.i18n.CompactNumberFormatSymbols_ff', 'goog.i18n.CompactNumberFormatSymbols_ff_CM', 'goog.i18n.CompactNumberFormatSymbols_ff_GN', 'goog.i18n.CompactNumberFormatSymbols_ff_MR', 'goog.i18n.CompactNumberFormatSymbols_ff_SN', 'goog.i18n.CompactNumberFormatSymbols_fi_FI', 'goog.i18n.CompactNumberFormatSymbols_fil_PH', 'goog.i18n.CompactNumberFormatSymbols_fo', 'goog.i18n.CompactNumberFormatSymbols_fo_DK', 'goog.i18n.CompactNumberFormatSymbols_fo_FO', 'goog.i18n.CompactNumberFormatSymbols_fr_BE', 'goog.i18n.CompactNumberFormatSymbols_fr_BF', 'goog.i18n.CompactNumberFormatSymbols_fr_BI', 'goog.i18n.CompactNumberFormatSymbols_fr_BJ', 'goog.i18n.CompactNumberFormatSymbols_fr_BL', 'goog.i18n.CompactNumberFormatSymbols_fr_CD', 'goog.i18n.CompactNumberFormatSymbols_fr_CF', 'goog.i18n.CompactNumberFormatSymbols_fr_CG', 'goog.i18n.CompactNumberFormatSymbols_fr_CH', 'goog.i18n.CompactNumberFormatSymbols_fr_CI', 'goog.i18n.CompactNumberFormatSymbols_fr_CM', 'goog.i18n.CompactNumberFormatSymbols_fr_DJ', 'goog.i18n.CompactNumberFormatSymbols_fr_DZ', 'goog.i18n.CompactNumberFormatSymbols_fr_FR', 'goog.i18n.CompactNumberFormatSymbols_fr_GA', 'goog.i18n.CompactNumberFormatSymbols_fr_GF', 'goog.i18n.CompactNumberFormatSymbols_fr_GN', 'goog.i18n.CompactNumberFormatSymbols_fr_GP', 'goog.i18n.CompactNumberFormatSymbols_fr_GQ', 'goog.i18n.CompactNumberFormatSymbols_fr_HT', 'goog.i18n.CompactNumberFormatSymbols_fr_KM', 'goog.i18n.CompactNumberFormatSymbols_fr_LU', 'goog.i18n.CompactNumberFormatSymbols_fr_MA', 'goog.i18n.CompactNumberFormatSymbols_fr_MC', 'goog.i18n.CompactNumberFormatSymbols_fr_MF', 'goog.i18n.CompactNumberFormatSymbols_fr_MG', 'goog.i18n.CompactNumberFormatSymbols_fr_ML', 'goog.i18n.CompactNumberFormatSymbols_fr_MQ', 'goog.i18n.CompactNumberFormatSymbols_fr_MR', 'goog.i18n.CompactNumberFormatSymbols_fr_MU', 'goog.i18n.CompactNumberFormatSymbols_fr_NC', 'goog.i18n.CompactNumberFormatSymbols_fr_NE', 'goog.i18n.CompactNumberFormatSymbols_fr_PF', 'goog.i18n.CompactNumberFormatSymbols_fr_PM', 'goog.i18n.CompactNumberFormatSymbols_fr_RE', 'goog.i18n.CompactNumberFormatSymbols_fr_RW', 'goog.i18n.CompactNumberFormatSymbols_fr_SC', 'goog.i18n.CompactNumberFormatSymbols_fr_SN', 'goog.i18n.CompactNumberFormatSymbols_fr_SY', 'goog.i18n.CompactNumberFormatSymbols_fr_TD', 'goog.i18n.CompactNumberFormatSymbols_fr_TG', 'goog.i18n.CompactNumberFormatSymbols_fr_TN', 'goog.i18n.CompactNumberFormatSymbols_fr_VU', 'goog.i18n.CompactNumberFormatSymbols_fr_WF', 'goog.i18n.CompactNumberFormatSymbols_fr_YT', 'goog.i18n.CompactNumberFormatSymbols_fur', 'goog.i18n.CompactNumberFormatSymbols_fur_IT', 'goog.i18n.CompactNumberFormatSymbols_fy', 'goog.i18n.CompactNumberFormatSymbols_fy_NL', 'goog.i18n.CompactNumberFormatSymbols_ga_IE', 'goog.i18n.CompactNumberFormatSymbols_gd', 'goog.i18n.CompactNumberFormatSymbols_gd_GB', 'goog.i18n.CompactNumberFormatSymbols_gl_ES', 'goog.i18n.CompactNumberFormatSymbols_gsw_CH', 'goog.i18n.CompactNumberFormatSymbols_gsw_FR', 'goog.i18n.CompactNumberFormatSymbols_gsw_LI', 'goog.i18n.CompactNumberFormatSymbols_gu_IN', 'goog.i18n.CompactNumberFormatSymbols_guz', 'goog.i18n.CompactNumberFormatSymbols_guz_KE', 'goog.i18n.CompactNumberFormatSymbols_gv', 'goog.i18n.CompactNumberFormatSymbols_gv_IM', 'goog.i18n.CompactNumberFormatSymbols_ha', 'goog.i18n.CompactNumberFormatSymbols_ha_GH', 'goog.i18n.CompactNumberFormatSymbols_ha_NE', 'goog.i18n.CompactNumberFormatSymbols_ha_NG', 'goog.i18n.CompactNumberFormatSymbols_haw_US', 'goog.i18n.CompactNumberFormatSymbols_he_IL', 'goog.i18n.CompactNumberFormatSymbols_hi_IN', 'goog.i18n.CompactNumberFormatSymbols_hr_BA', 'goog.i18n.CompactNumberFormatSymbols_hr_HR', 'goog.i18n.CompactNumberFormatSymbols_hsb', 'goog.i18n.CompactNumberFormatSymbols_hsb_DE', 'goog.i18n.CompactNumberFormatSymbols_hu_HU', 'goog.i18n.CompactNumberFormatSymbols_hy_AM', 'goog.i18n.CompactNumberFormatSymbols_id_ID', 'goog.i18n.CompactNumberFormatSymbols_ig', 'goog.i18n.CompactNumberFormatSymbols_ig_NG', 'goog.i18n.CompactNumberFormatSymbols_ii', 'goog.i18n.CompactNumberFormatSymbols_ii_CN', 'goog.i18n.CompactNumberFormatSymbols_is_IS', 'goog.i18n.CompactNumberFormatSymbols_it_CH', 'goog.i18n.CompactNumberFormatSymbols_it_IT', 'goog.i18n.CompactNumberFormatSymbols_it_SM', 'goog.i18n.CompactNumberFormatSymbols_it_VA', 'goog.i18n.CompactNumberFormatSymbols_ja_JP', 'goog.i18n.CompactNumberFormatSymbols_jgo', 'goog.i18n.CompactNumberFormatSymbols_jgo_CM', 'goog.i18n.CompactNumberFormatSymbols_jmc', 'goog.i18n.CompactNumberFormatSymbols_jmc_TZ', 'goog.i18n.CompactNumberFormatSymbols_ka_GE', 'goog.i18n.CompactNumberFormatSymbols_kab', 'goog.i18n.CompactNumberFormatSymbols_kab_DZ', 'goog.i18n.CompactNumberFormatSymbols_kam', 'goog.i18n.CompactNumberFormatSymbols_kam_KE', 'goog.i18n.CompactNumberFormatSymbols_kde', 'goog.i18n.CompactNumberFormatSymbols_kde_TZ', 'goog.i18n.CompactNumberFormatSymbols_kea', 'goog.i18n.CompactNumberFormatSymbols_kea_CV', 'goog.i18n.CompactNumberFormatSymbols_khq', 'goog.i18n.CompactNumberFormatSymbols_khq_ML', 'goog.i18n.CompactNumberFormatSymbols_ki', 'goog.i18n.CompactNumberFormatSymbols_ki_KE', 'goog.i18n.CompactNumberFormatSymbols_kk_KZ', 'goog.i18n.CompactNumberFormatSymbols_kkj', 'goog.i18n.CompactNumberFormatSymbols_kkj_CM', 'goog.i18n.CompactNumberFormatSymbols_kl', 'goog.i18n.CompactNumberFormatSymbols_kl_GL', 'goog.i18n.CompactNumberFormatSymbols_kln', 'goog.i18n.CompactNumberFormatSymbols_kln_KE', 'goog.i18n.CompactNumberFormatSymbols_km_KH', 'goog.i18n.CompactNumberFormatSymbols_kn_IN', 'goog.i18n.CompactNumberFormatSymbols_ko_KP', 'goog.i18n.CompactNumberFormatSymbols_ko_KR', 'goog.i18n.CompactNumberFormatSymbols_kok', 'goog.i18n.CompactNumberFormatSymbols_kok_IN', 'goog.i18n.CompactNumberFormatSymbols_ks', 'goog.i18n.CompactNumberFormatSymbols_ks_IN', 'goog.i18n.CompactNumberFormatSymbols_ksb', 'goog.i18n.CompactNumberFormatSymbols_ksb_TZ', 'goog.i18n.CompactNumberFormatSymbols_ksf', 'goog.i18n.CompactNumberFormatSymbols_ksf_CM', 'goog.i18n.CompactNumberFormatSymbols_ksh', 'goog.i18n.CompactNumberFormatSymbols_ksh_DE', 'goog.i18n.CompactNumberFormatSymbols_kw', 'goog.i18n.CompactNumberFormatSymbols_kw_GB', 'goog.i18n.CompactNumberFormatSymbols_ky_KG', 'goog.i18n.CompactNumberFormatSymbols_lag', 'goog.i18n.CompactNumberFormatSymbols_lag_TZ', 'goog.i18n.CompactNumberFormatSymbols_lb', 'goog.i18n.CompactNumberFormatSymbols_lb_LU', 'goog.i18n.CompactNumberFormatSymbols_lg', 'goog.i18n.CompactNumberFormatSymbols_lg_UG', 'goog.i18n.CompactNumberFormatSymbols_lkt', 'goog.i18n.CompactNumberFormatSymbols_lkt_US', 'goog.i18n.CompactNumberFormatSymbols_ln_AO', 'goog.i18n.CompactNumberFormatSymbols_ln_CD', 'goog.i18n.CompactNumberFormatSymbols_ln_CF', 'goog.i18n.CompactNumberFormatSymbols_ln_CG', 'goog.i18n.CompactNumberFormatSymbols_lo_LA', 'goog.i18n.CompactNumberFormatSymbols_lrc', 'goog.i18n.CompactNumberFormatSymbols_lrc_IQ', 'goog.i18n.CompactNumberFormatSymbols_lrc_IR', 'goog.i18n.CompactNumberFormatSymbols_lt_LT', 'goog.i18n.CompactNumberFormatSymbols_lu', 'goog.i18n.CompactNumberFormatSymbols_lu_CD', 'goog.i18n.CompactNumberFormatSymbols_luo', 'goog.i18n.CompactNumberFormatSymbols_luo_KE', 'goog.i18n.CompactNumberFormatSymbols_luy', 'goog.i18n.CompactNumberFormatSymbols_luy_KE', 'goog.i18n.CompactNumberFormatSymbols_lv_LV', 'goog.i18n.CompactNumberFormatSymbols_mas', 'goog.i18n.CompactNumberFormatSymbols_mas_KE', 'goog.i18n.CompactNumberFormatSymbols_mas_TZ', 'goog.i18n.CompactNumberFormatSymbols_mer', 'goog.i18n.CompactNumberFormatSymbols_mer_KE', 'goog.i18n.CompactNumberFormatSymbols_mfe', 'goog.i18n.CompactNumberFormatSymbols_mfe_MU', 'goog.i18n.CompactNumberFormatSymbols_mg', 'goog.i18n.CompactNumberFormatSymbols_mg_MG', 'goog.i18n.CompactNumberFormatSymbols_mgh', 'goog.i18n.CompactNumberFormatSymbols_mgh_MZ', 'goog.i18n.CompactNumberFormatSymbols_mgo', 'goog.i18n.CompactNumberFormatSymbols_mgo_CM', 'goog.i18n.CompactNumberFormatSymbols_mk_MK', 'goog.i18n.CompactNumberFormatSymbols_ml_IN', 'goog.i18n.CompactNumberFormatSymbols_mn_MN', 'goog.i18n.CompactNumberFormatSymbols_mr_IN', 'goog.i18n.CompactNumberFormatSymbols_ms_BN', 'goog.i18n.CompactNumberFormatSymbols_ms_MY', 'goog.i18n.CompactNumberFormatSymbols_ms_SG', 'goog.i18n.CompactNumberFormatSymbols_mt_MT', 'goog.i18n.CompactNumberFormatSymbols_mua', 'goog.i18n.CompactNumberFormatSymbols_mua_CM', 'goog.i18n.CompactNumberFormatSymbols_my_MM', 'goog.i18n.CompactNumberFormatSymbols_mzn', 'goog.i18n.CompactNumberFormatSymbols_mzn_IR', 'goog.i18n.CompactNumberFormatSymbols_naq', 'goog.i18n.CompactNumberFormatSymbols_naq_NA', 'goog.i18n.CompactNumberFormatSymbols_nb_NO', 'goog.i18n.CompactNumberFormatSymbols_nb_SJ', 'goog.i18n.CompactNumberFormatSymbols_nd', 'goog.i18n.CompactNumberFormatSymbols_nd_ZW', 'goog.i18n.CompactNumberFormatSymbols_nds', 'goog.i18n.CompactNumberFormatSymbols_nds_DE', 'goog.i18n.CompactNumberFormatSymbols_nds_NL', 'goog.i18n.CompactNumberFormatSymbols_ne_IN', 'goog.i18n.CompactNumberFormatSymbols_ne_NP', 'goog.i18n.CompactNumberFormatSymbols_nl_AW', 'goog.i18n.CompactNumberFormatSymbols_nl_BE', 'goog.i18n.CompactNumberFormatSymbols_nl_BQ', 'goog.i18n.CompactNumberFormatSymbols_nl_CW', 'goog.i18n.CompactNumberFormatSymbols_nl_NL', 'goog.i18n.CompactNumberFormatSymbols_nl_SR', 'goog.i18n.CompactNumberFormatSymbols_nl_SX', 'goog.i18n.CompactNumberFormatSymbols_nmg', 'goog.i18n.CompactNumberFormatSymbols_nmg_CM', 'goog.i18n.CompactNumberFormatSymbols_nn', 'goog.i18n.CompactNumberFormatSymbols_nn_NO', 'goog.i18n.CompactNumberFormatSymbols_nnh', 'goog.i18n.CompactNumberFormatSymbols_nnh_CM', 'goog.i18n.CompactNumberFormatSymbols_nus', 'goog.i18n.CompactNumberFormatSymbols_nus_SS', 'goog.i18n.CompactNumberFormatSymbols_nyn', 'goog.i18n.CompactNumberFormatSymbols_nyn_UG', 'goog.i18n.CompactNumberFormatSymbols_om', 'goog.i18n.CompactNumberFormatSymbols_om_ET', 'goog.i18n.CompactNumberFormatSymbols_om_KE', 'goog.i18n.CompactNumberFormatSymbols_or_IN', 'goog.i18n.CompactNumberFormatSymbols_os', 'goog.i18n.CompactNumberFormatSymbols_os_GE', 'goog.i18n.CompactNumberFormatSymbols_os_RU', 'goog.i18n.CompactNumberFormatSymbols_pa_Arab', 'goog.i18n.CompactNumberFormatSymbols_pa_Arab_PK', 'goog.i18n.CompactNumberFormatSymbols_pa_Guru', 'goog.i18n.CompactNumberFormatSymbols_pa_Guru_IN', 'goog.i18n.CompactNumberFormatSymbols_pl_PL', 'goog.i18n.CompactNumberFormatSymbols_ps', 'goog.i18n.CompactNumberFormatSymbols_ps_AF', 'goog.i18n.CompactNumberFormatSymbols_pt_AO', 'goog.i18n.CompactNumberFormatSymbols_pt_CH', 'goog.i18n.CompactNumberFormatSymbols_pt_CV', 'goog.i18n.CompactNumberFormatSymbols_pt_GQ', 'goog.i18n.CompactNumberFormatSymbols_pt_GW', 'goog.i18n.CompactNumberFormatSymbols_pt_LU', 'goog.i18n.CompactNumberFormatSymbols_pt_MO', 'goog.i18n.CompactNumberFormatSymbols_pt_MZ', 'goog.i18n.CompactNumberFormatSymbols_pt_ST', 'goog.i18n.CompactNumberFormatSymbols_pt_TL', 'goog.i18n.CompactNumberFormatSymbols_qu', 'goog.i18n.CompactNumberFormatSymbols_qu_BO', 'goog.i18n.CompactNumberFormatSymbols_qu_EC', 'goog.i18n.CompactNumberFormatSymbols_qu_PE', 'goog.i18n.CompactNumberFormatSymbols_rm', 'goog.i18n.CompactNumberFormatSymbols_rm_CH', 'goog.i18n.CompactNumberFormatSymbols_rn', 'goog.i18n.CompactNumberFormatSymbols_rn_BI', 'goog.i18n.CompactNumberFormatSymbols_ro_MD', 'goog.i18n.CompactNumberFormatSymbols_ro_RO', 'goog.i18n.CompactNumberFormatSymbols_rof', 'goog.i18n.CompactNumberFormatSymbols_rof_TZ', 'goog.i18n.CompactNumberFormatSymbols_ru_BY', 'goog.i18n.CompactNumberFormatSymbols_ru_KG', 'goog.i18n.CompactNumberFormatSymbols_ru_KZ', 'goog.i18n.CompactNumberFormatSymbols_ru_MD', 'goog.i18n.CompactNumberFormatSymbols_ru_RU', 'goog.i18n.CompactNumberFormatSymbols_ru_UA', 'goog.i18n.CompactNumberFormatSymbols_rw', 'goog.i18n.CompactNumberFormatSymbols_rw_RW', 'goog.i18n.CompactNumberFormatSymbols_rwk', 'goog.i18n.CompactNumberFormatSymbols_rwk_TZ', 'goog.i18n.CompactNumberFormatSymbols_sah', 'goog.i18n.CompactNumberFormatSymbols_sah_RU', 'goog.i18n.CompactNumberFormatSymbols_saq', 'goog.i18n.CompactNumberFormatSymbols_saq_KE', 'goog.i18n.CompactNumberFormatSymbols_sbp', 'goog.i18n.CompactNumberFormatSymbols_sbp_TZ', 'goog.i18n.CompactNumberFormatSymbols_se', 'goog.i18n.CompactNumberFormatSymbols_se_FI', 'goog.i18n.CompactNumberFormatSymbols_se_NO', 'goog.i18n.CompactNumberFormatSymbols_se_SE', 'goog.i18n.CompactNumberFormatSymbols_seh', 'goog.i18n.CompactNumberFormatSymbols_seh_MZ', 'goog.i18n.CompactNumberFormatSymbols_ses', 'goog.i18n.CompactNumberFormatSymbols_ses_ML', 'goog.i18n.CompactNumberFormatSymbols_sg', 'goog.i18n.CompactNumberFormatSymbols_sg_CF', 'goog.i18n.CompactNumberFormatSymbols_shi', 'goog.i18n.CompactNumberFormatSymbols_shi_Latn', 'goog.i18n.CompactNumberFormatSymbols_shi_Latn_MA', 'goog.i18n.CompactNumberFormatSymbols_shi_Tfng', 'goog.i18n.CompactNumberFormatSymbols_shi_Tfng_MA', 'goog.i18n.CompactNumberFormatSymbols_si_LK', 'goog.i18n.CompactNumberFormatSymbols_sk_SK', 'goog.i18n.CompactNumberFormatSymbols_sl_SI', 'goog.i18n.CompactNumberFormatSymbols_smn', 'goog.i18n.CompactNumberFormatSymbols_smn_FI', 'goog.i18n.CompactNumberFormatSymbols_sn', 'goog.i18n.CompactNumberFormatSymbols_sn_ZW', 'goog.i18n.CompactNumberFormatSymbols_so', 'goog.i18n.CompactNumberFormatSymbols_so_DJ', 'goog.i18n.CompactNumberFormatSymbols_so_ET', 'goog.i18n.CompactNumberFormatSymbols_so_KE', 'goog.i18n.CompactNumberFormatSymbols_so_SO', 'goog.i18n.CompactNumberFormatSymbols_sq_AL', 'goog.i18n.CompactNumberFormatSymbols_sq_MK', 'goog.i18n.CompactNumberFormatSymbols_sq_XK', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_BA', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_ME', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_RS', 'goog.i18n.CompactNumberFormatSymbols_sr_Cyrl_XK', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_BA', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_ME', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_RS', 'goog.i18n.CompactNumberFormatSymbols_sr_Latn_XK', 'goog.i18n.CompactNumberFormatSymbols_sv_AX', 'goog.i18n.CompactNumberFormatSymbols_sv_FI', 'goog.i18n.CompactNumberFormatSymbols_sv_SE', 'goog.i18n.CompactNumberFormatSymbols_sw_CD', 'goog.i18n.CompactNumberFormatSymbols_sw_KE', 'goog.i18n.CompactNumberFormatSymbols_sw_TZ', 'goog.i18n.CompactNumberFormatSymbols_sw_UG', 'goog.i18n.CompactNumberFormatSymbols_ta_IN', 'goog.i18n.CompactNumberFormatSymbols_ta_LK', 'goog.i18n.CompactNumberFormatSymbols_ta_MY', 'goog.i18n.CompactNumberFormatSymbols_ta_SG', 'goog.i18n.CompactNumberFormatSymbols_te_IN', 'goog.i18n.CompactNumberFormatSymbols_teo', 'goog.i18n.CompactNumberFormatSymbols_teo_KE', 'goog.i18n.CompactNumberFormatSymbols_teo_UG', 'goog.i18n.CompactNumberFormatSymbols_tg', 'goog.i18n.CompactNumberFormatSymbols_tg_TJ', 'goog.i18n.CompactNumberFormatSymbols_th_TH', 'goog.i18n.CompactNumberFormatSymbols_ti', 'goog.i18n.CompactNumberFormatSymbols_ti_ER', 'goog.i18n.CompactNumberFormatSymbols_ti_ET', 'goog.i18n.CompactNumberFormatSymbols_to', 'goog.i18n.CompactNumberFormatSymbols_to_TO', 'goog.i18n.CompactNumberFormatSymbols_tr_CY', 'goog.i18n.CompactNumberFormatSymbols_tr_TR', 'goog.i18n.CompactNumberFormatSymbols_tt', 'goog.i18n.CompactNumberFormatSymbols_tt_RU', 'goog.i18n.CompactNumberFormatSymbols_twq', 'goog.i18n.CompactNumberFormatSymbols_twq_NE', 'goog.i18n.CompactNumberFormatSymbols_tzm', 'goog.i18n.CompactNumberFormatSymbols_tzm_MA', 'goog.i18n.CompactNumberFormatSymbols_ug', 'goog.i18n.CompactNumberFormatSymbols_ug_CN', 'goog.i18n.CompactNumberFormatSymbols_uk_UA', 'goog.i18n.CompactNumberFormatSymbols_ur_IN', 'goog.i18n.CompactNumberFormatSymbols_ur_PK', 'goog.i18n.CompactNumberFormatSymbols_uz_Arab', 'goog.i18n.CompactNumberFormatSymbols_uz_Arab_AF', 'goog.i18n.CompactNumberFormatSymbols_uz_Cyrl', 'goog.i18n.CompactNumberFormatSymbols_uz_Cyrl_UZ', 'goog.i18n.CompactNumberFormatSymbols_uz_Latn', 'goog.i18n.CompactNumberFormatSymbols_uz_Latn_UZ', 'goog.i18n.CompactNumberFormatSymbols_vai', 'goog.i18n.CompactNumberFormatSymbols_vai_Latn', 'goog.i18n.CompactNumberFormatSymbols_vai_Latn_LR', 'goog.i18n.CompactNumberFormatSymbols_vai_Vaii', 'goog.i18n.CompactNumberFormatSymbols_vai_Vaii_LR', 'goog.i18n.CompactNumberFormatSymbols_vi_VN', 'goog.i18n.CompactNumberFormatSymbols_vun', 'goog.i18n.CompactNumberFormatSymbols_vun_TZ', 'goog.i18n.CompactNumberFormatSymbols_wae', 'goog.i18n.CompactNumberFormatSymbols_wae_CH', 'goog.i18n.CompactNumberFormatSymbols_wo', 'goog.i18n.CompactNumberFormatSymbols_wo_SN', 'goog.i18n.CompactNumberFormatSymbols_xog', 'goog.i18n.CompactNumberFormatSymbols_xog_UG', 'goog.i18n.CompactNumberFormatSymbols_yav', 'goog.i18n.CompactNumberFormatSymbols_yav_CM', 'goog.i18n.CompactNumberFormatSymbols_yi', 'goog.i18n.CompactNumberFormatSymbols_yi_001', 'goog.i18n.CompactNumberFormatSymbols_yo', 'goog.i18n.CompactNumberFormatSymbols_yo_BJ', 'goog.i18n.CompactNumberFormatSymbols_yo_NG', 'goog.i18n.CompactNumberFormatSymbols_yue', 'goog.i18n.CompactNumberFormatSymbols_yue_Hans', 'goog.i18n.CompactNumberFormatSymbols_yue_Hans_CN', 'goog.i18n.CompactNumberFormatSymbols_yue_Hant', 'goog.i18n.CompactNumberFormatSymbols_yue_Hant_HK', 'goog.i18n.CompactNumberFormatSymbols_zgh', 'goog.i18n.CompactNumberFormatSymbols_zgh_MA', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_CN', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_MO', 'goog.i18n.CompactNumberFormatSymbols_zh_Hans_SG', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_HK', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_MO', 'goog.i18n.CompactNumberFormatSymbols_zh_Hant_TW', 'goog.i18n.CompactNumberFormatSymbols_zu_ZA'], ['goog.i18n.CompactNumberFormatSymbols']); +goog.addDependency("i18n/currency.js", ['goog.i18n.currency', 'goog.i18n.currency.CurrencyInfo', 'goog.i18n.currency.CurrencyInfoTier2'], []); +goog.addDependency("i18n/currency_test.js", ['goog.i18n.currencyTest'], ['goog.i18n.NumberFormat', 'goog.i18n.currency', 'goog.i18n.currency.CurrencyInfo', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("i18n/currencycodemap.js", ['goog.i18n.currencyCodeMap', 'goog.i18n.currencyCodeMapTier2'], []); +goog.addDependency("i18n/dateintervalformat.js", [], []); +goog.addDependency("i18n/dateintervalformat_test.js", [], []); +goog.addDependency("i18n/dateintervalpatterns.js", [], []); +goog.addDependency("i18n/dateintervalpatternsext.js", [], []); +goog.addDependency("i18n/dateintervalsymbols.js", [], []); +goog.addDependency("i18n/dateintervalsymbolsext.js", [], []); +goog.addDependency("i18n/datetimeformat.js", ['goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeFormat.Format'], ['goog.asserts', 'goog.date', 'goog.i18n.DateTimeSymbols', 'goog.i18n.TimeZone', 'goog.string']); +goog.addDependency("i18n/datetimeformat_test.js", ['goog.i18n.DateTimeFormatTest'], ['goog.date.Date', 'goog.date.DateTime', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimePatterns', 'goog.i18n.DateTimePatterns_ar', 'goog.i18n.DateTimePatterns_de', 'goog.i18n.DateTimePatterns_en', 'goog.i18n.DateTimePatterns_fa', 'goog.i18n.DateTimePatterns_fr', 'goog.i18n.DateTimePatterns_ja', 'goog.i18n.DateTimePatterns_sv', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_ar', 'goog.i18n.DateTimeSymbols_ar_AE', 'goog.i18n.DateTimeSymbols_ar_SA', 'goog.i18n.DateTimeSymbols_bn_BD', 'goog.i18n.DateTimeSymbols_de', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_en_GB', 'goog.i18n.DateTimeSymbols_en_IE', 'goog.i18n.DateTimeSymbols_en_IN', 'goog.i18n.DateTimeSymbols_en_US', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_fr_DJ', 'goog.i18n.DateTimeSymbols_he_IL', 'goog.i18n.DateTimeSymbols_ja', 'goog.i18n.DateTimeSymbols_ro_RO', 'goog.i18n.DateTimeSymbols_sv', 'goog.i18n.TimeZone', 'goog.testing.jsunit']); +goog.addDependency("i18n/datetimeparse.js", ['goog.i18n.DateTimeParse'], ['goog.asserts', 'goog.date', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeSymbols']); +goog.addDependency("i18n/datetimeparse_test.js", ['goog.i18n.DateTimeParseTest'], ['goog.date.Date', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeParse', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_pl', 'goog.i18n.DateTimeSymbols_zh', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("i18n/datetimepatterns.js", ['goog.i18n.DateTimePatterns', 'goog.i18n.DateTimePatterns_af', 'goog.i18n.DateTimePatterns_am', 'goog.i18n.DateTimePatterns_ar', 'goog.i18n.DateTimePatterns_ar_DZ', 'goog.i18n.DateTimePatterns_az', 'goog.i18n.DateTimePatterns_be', 'goog.i18n.DateTimePatterns_bg', 'goog.i18n.DateTimePatterns_bn', 'goog.i18n.DateTimePatterns_br', 'goog.i18n.DateTimePatterns_bs', 'goog.i18n.DateTimePatterns_ca', 'goog.i18n.DateTimePatterns_chr', 'goog.i18n.DateTimePatterns_cs', 'goog.i18n.DateTimePatterns_cy', 'goog.i18n.DateTimePatterns_da', 'goog.i18n.DateTimePatterns_de', 'goog.i18n.DateTimePatterns_de_AT', 'goog.i18n.DateTimePatterns_de_CH', 'goog.i18n.DateTimePatterns_el', 'goog.i18n.DateTimePatterns_en', 'goog.i18n.DateTimePatterns_en_AU', 'goog.i18n.DateTimePatterns_en_CA', 'goog.i18n.DateTimePatterns_en_GB', 'goog.i18n.DateTimePatterns_en_IE', 'goog.i18n.DateTimePatterns_en_IN', 'goog.i18n.DateTimePatterns_en_SG', 'goog.i18n.DateTimePatterns_en_US', 'goog.i18n.DateTimePatterns_en_ZA', 'goog.i18n.DateTimePatterns_es', 'goog.i18n.DateTimePatterns_es_419', 'goog.i18n.DateTimePatterns_es_ES', 'goog.i18n.DateTimePatterns_es_MX', 'goog.i18n.DateTimePatterns_es_US', 'goog.i18n.DateTimePatterns_et', 'goog.i18n.DateTimePatterns_eu', 'goog.i18n.DateTimePatterns_fa', 'goog.i18n.DateTimePatterns_fi', 'goog.i18n.DateTimePatterns_fil', 'goog.i18n.DateTimePatterns_fr', 'goog.i18n.DateTimePatterns_fr_CA', 'goog.i18n.DateTimePatterns_ga', 'goog.i18n.DateTimePatterns_gl', 'goog.i18n.DateTimePatterns_gsw', 'goog.i18n.DateTimePatterns_gu', 'goog.i18n.DateTimePatterns_haw', 'goog.i18n.DateTimePatterns_he', 'goog.i18n.DateTimePatterns_hi', 'goog.i18n.DateTimePatterns_hr', 'goog.i18n.DateTimePatterns_hu', 'goog.i18n.DateTimePatterns_hy', 'goog.i18n.DateTimePatterns_id', 'goog.i18n.DateTimePatterns_in', 'goog.i18n.DateTimePatterns_is', 'goog.i18n.DateTimePatterns_it', 'goog.i18n.DateTimePatterns_iw', 'goog.i18n.DateTimePatterns_ja', 'goog.i18n.DateTimePatterns_ka', 'goog.i18n.DateTimePatterns_kk', 'goog.i18n.DateTimePatterns_km', 'goog.i18n.DateTimePatterns_kn', 'goog.i18n.DateTimePatterns_ko', 'goog.i18n.DateTimePatterns_ky', 'goog.i18n.DateTimePatterns_ln', 'goog.i18n.DateTimePatterns_lo', 'goog.i18n.DateTimePatterns_lt', 'goog.i18n.DateTimePatterns_lv', 'goog.i18n.DateTimePatterns_mk', 'goog.i18n.DateTimePatterns_ml', 'goog.i18n.DateTimePatterns_mn', 'goog.i18n.DateTimePatterns_mo', 'goog.i18n.DateTimePatterns_mr', 'goog.i18n.DateTimePatterns_ms', 'goog.i18n.DateTimePatterns_mt', 'goog.i18n.DateTimePatterns_my', 'goog.i18n.DateTimePatterns_nb', 'goog.i18n.DateTimePatterns_ne', 'goog.i18n.DateTimePatterns_nl', 'goog.i18n.DateTimePatterns_no', 'goog.i18n.DateTimePatterns_no_NO', 'goog.i18n.DateTimePatterns_or', 'goog.i18n.DateTimePatterns_pa', 'goog.i18n.DateTimePatterns_pl', 'goog.i18n.DateTimePatterns_pt', 'goog.i18n.DateTimePatterns_pt_BR', 'goog.i18n.DateTimePatterns_pt_PT', 'goog.i18n.DateTimePatterns_ro', 'goog.i18n.DateTimePatterns_ru', 'goog.i18n.DateTimePatterns_sh', 'goog.i18n.DateTimePatterns_si', 'goog.i18n.DateTimePatterns_sk', 'goog.i18n.DateTimePatterns_sl', 'goog.i18n.DateTimePatterns_sq', 'goog.i18n.DateTimePatterns_sr', 'goog.i18n.DateTimePatterns_sr_Latn', 'goog.i18n.DateTimePatterns_sv', 'goog.i18n.DateTimePatterns_sw', 'goog.i18n.DateTimePatterns_ta', 'goog.i18n.DateTimePatterns_te', 'goog.i18n.DateTimePatterns_th', 'goog.i18n.DateTimePatterns_tl', 'goog.i18n.DateTimePatterns_tr', 'goog.i18n.DateTimePatterns_uk', 'goog.i18n.DateTimePatterns_ur', 'goog.i18n.DateTimePatterns_uz', 'goog.i18n.DateTimePatterns_vi', 'goog.i18n.DateTimePatterns_zh', 'goog.i18n.DateTimePatterns_zh_CN', 'goog.i18n.DateTimePatterns_zh_HK', 'goog.i18n.DateTimePatterns_zh_TW', 'goog.i18n.DateTimePatterns_zu'], []); +goog.addDependency("i18n/datetimepatternsext.js", ['goog.i18n.DateTimePatternsExt', 'goog.i18n.DateTimePatterns_af_NA', 'goog.i18n.DateTimePatterns_af_ZA', 'goog.i18n.DateTimePatterns_agq', 'goog.i18n.DateTimePatterns_agq_CM', 'goog.i18n.DateTimePatterns_ak', 'goog.i18n.DateTimePatterns_ak_GH', 'goog.i18n.DateTimePatterns_am_ET', 'goog.i18n.DateTimePatterns_ar_001', 'goog.i18n.DateTimePatterns_ar_AE', 'goog.i18n.DateTimePatterns_ar_BH', 'goog.i18n.DateTimePatterns_ar_DJ', 'goog.i18n.DateTimePatterns_ar_EG', 'goog.i18n.DateTimePatterns_ar_EH', 'goog.i18n.DateTimePatterns_ar_ER', 'goog.i18n.DateTimePatterns_ar_IL', 'goog.i18n.DateTimePatterns_ar_IQ', 'goog.i18n.DateTimePatterns_ar_JO', 'goog.i18n.DateTimePatterns_ar_KM', 'goog.i18n.DateTimePatterns_ar_KW', 'goog.i18n.DateTimePatterns_ar_LB', 'goog.i18n.DateTimePatterns_ar_LY', 'goog.i18n.DateTimePatterns_ar_MA', 'goog.i18n.DateTimePatterns_ar_MR', 'goog.i18n.DateTimePatterns_ar_OM', 'goog.i18n.DateTimePatterns_ar_PS', 'goog.i18n.DateTimePatterns_ar_QA', 'goog.i18n.DateTimePatterns_ar_SA', 'goog.i18n.DateTimePatterns_ar_SD', 'goog.i18n.DateTimePatterns_ar_SO', 'goog.i18n.DateTimePatterns_ar_SS', 'goog.i18n.DateTimePatterns_ar_SY', 'goog.i18n.DateTimePatterns_ar_TD', 'goog.i18n.DateTimePatterns_ar_TN', 'goog.i18n.DateTimePatterns_ar_XB', 'goog.i18n.DateTimePatterns_ar_YE', 'goog.i18n.DateTimePatterns_as', 'goog.i18n.DateTimePatterns_as_IN', 'goog.i18n.DateTimePatterns_asa', 'goog.i18n.DateTimePatterns_asa_TZ', 'goog.i18n.DateTimePatterns_ast', 'goog.i18n.DateTimePatterns_ast_ES', 'goog.i18n.DateTimePatterns_az_Cyrl', 'goog.i18n.DateTimePatterns_az_Cyrl_AZ', 'goog.i18n.DateTimePatterns_az_Latn', 'goog.i18n.DateTimePatterns_az_Latn_AZ', 'goog.i18n.DateTimePatterns_bas', 'goog.i18n.DateTimePatterns_bas_CM', 'goog.i18n.DateTimePatterns_be_BY', 'goog.i18n.DateTimePatterns_bem', 'goog.i18n.DateTimePatterns_bem_ZM', 'goog.i18n.DateTimePatterns_bez', 'goog.i18n.DateTimePatterns_bez_TZ', 'goog.i18n.DateTimePatterns_bg_BG', 'goog.i18n.DateTimePatterns_bm', 'goog.i18n.DateTimePatterns_bm_ML', 'goog.i18n.DateTimePatterns_bn_BD', 'goog.i18n.DateTimePatterns_bn_IN', 'goog.i18n.DateTimePatterns_bo', 'goog.i18n.DateTimePatterns_bo_CN', 'goog.i18n.DateTimePatterns_bo_IN', 'goog.i18n.DateTimePatterns_br_FR', 'goog.i18n.DateTimePatterns_brx', 'goog.i18n.DateTimePatterns_brx_IN', 'goog.i18n.DateTimePatterns_bs_Cyrl', 'goog.i18n.DateTimePatterns_bs_Cyrl_BA', 'goog.i18n.DateTimePatterns_bs_Latn', 'goog.i18n.DateTimePatterns_bs_Latn_BA', 'goog.i18n.DateTimePatterns_ca_AD', 'goog.i18n.DateTimePatterns_ca_ES', 'goog.i18n.DateTimePatterns_ca_FR', 'goog.i18n.DateTimePatterns_ca_IT', 'goog.i18n.DateTimePatterns_ccp', 'goog.i18n.DateTimePatterns_ccp_BD', 'goog.i18n.DateTimePatterns_ccp_IN', 'goog.i18n.DateTimePatterns_ce', 'goog.i18n.DateTimePatterns_ce_RU', 'goog.i18n.DateTimePatterns_cgg', 'goog.i18n.DateTimePatterns_cgg_UG', 'goog.i18n.DateTimePatterns_chr_US', 'goog.i18n.DateTimePatterns_ckb', 'goog.i18n.DateTimePatterns_ckb_IQ', 'goog.i18n.DateTimePatterns_ckb_IR', 'goog.i18n.DateTimePatterns_cs_CZ', 'goog.i18n.DateTimePatterns_cy_GB', 'goog.i18n.DateTimePatterns_da_DK', 'goog.i18n.DateTimePatterns_da_GL', 'goog.i18n.DateTimePatterns_dav', 'goog.i18n.DateTimePatterns_dav_KE', 'goog.i18n.DateTimePatterns_de_BE', 'goog.i18n.DateTimePatterns_de_DE', 'goog.i18n.DateTimePatterns_de_IT', 'goog.i18n.DateTimePatterns_de_LI', 'goog.i18n.DateTimePatterns_de_LU', 'goog.i18n.DateTimePatterns_dje', 'goog.i18n.DateTimePatterns_dje_NE', 'goog.i18n.DateTimePatterns_dsb', 'goog.i18n.DateTimePatterns_dsb_DE', 'goog.i18n.DateTimePatterns_dua', 'goog.i18n.DateTimePatterns_dua_CM', 'goog.i18n.DateTimePatterns_dyo', 'goog.i18n.DateTimePatterns_dyo_SN', 'goog.i18n.DateTimePatterns_dz', 'goog.i18n.DateTimePatterns_dz_BT', 'goog.i18n.DateTimePatterns_ebu', 'goog.i18n.DateTimePatterns_ebu_KE', 'goog.i18n.DateTimePatterns_ee', 'goog.i18n.DateTimePatterns_ee_GH', 'goog.i18n.DateTimePatterns_ee_TG', 'goog.i18n.DateTimePatterns_el_CY', 'goog.i18n.DateTimePatterns_el_GR', 'goog.i18n.DateTimePatterns_en_001', 'goog.i18n.DateTimePatterns_en_150', 'goog.i18n.DateTimePatterns_en_AG', 'goog.i18n.DateTimePatterns_en_AI', 'goog.i18n.DateTimePatterns_en_AS', 'goog.i18n.DateTimePatterns_en_AT', 'goog.i18n.DateTimePatterns_en_BB', 'goog.i18n.DateTimePatterns_en_BE', 'goog.i18n.DateTimePatterns_en_BI', 'goog.i18n.DateTimePatterns_en_BM', 'goog.i18n.DateTimePatterns_en_BS', 'goog.i18n.DateTimePatterns_en_BW', 'goog.i18n.DateTimePatterns_en_BZ', 'goog.i18n.DateTimePatterns_en_CC', 'goog.i18n.DateTimePatterns_en_CH', 'goog.i18n.DateTimePatterns_en_CK', 'goog.i18n.DateTimePatterns_en_CM', 'goog.i18n.DateTimePatterns_en_CX', 'goog.i18n.DateTimePatterns_en_CY', 'goog.i18n.DateTimePatterns_en_DE', 'goog.i18n.DateTimePatterns_en_DG', 'goog.i18n.DateTimePatterns_en_DK', 'goog.i18n.DateTimePatterns_en_DM', 'goog.i18n.DateTimePatterns_en_ER', 'goog.i18n.DateTimePatterns_en_FI', 'goog.i18n.DateTimePatterns_en_FJ', 'goog.i18n.DateTimePatterns_en_FK', 'goog.i18n.DateTimePatterns_en_FM', 'goog.i18n.DateTimePatterns_en_GD', 'goog.i18n.DateTimePatterns_en_GG', 'goog.i18n.DateTimePatterns_en_GH', 'goog.i18n.DateTimePatterns_en_GI', 'goog.i18n.DateTimePatterns_en_GM', 'goog.i18n.DateTimePatterns_en_GU', 'goog.i18n.DateTimePatterns_en_GY', 'goog.i18n.DateTimePatterns_en_HK', 'goog.i18n.DateTimePatterns_en_IL', 'goog.i18n.DateTimePatterns_en_IM', 'goog.i18n.DateTimePatterns_en_IO', 'goog.i18n.DateTimePatterns_en_JE', 'goog.i18n.DateTimePatterns_en_JM', 'goog.i18n.DateTimePatterns_en_KE', 'goog.i18n.DateTimePatterns_en_KI', 'goog.i18n.DateTimePatterns_en_KN', 'goog.i18n.DateTimePatterns_en_KY', 'goog.i18n.DateTimePatterns_en_LC', 'goog.i18n.DateTimePatterns_en_LR', 'goog.i18n.DateTimePatterns_en_LS', 'goog.i18n.DateTimePatterns_en_MG', 'goog.i18n.DateTimePatterns_en_MH', 'goog.i18n.DateTimePatterns_en_MO', 'goog.i18n.DateTimePatterns_en_MP', 'goog.i18n.DateTimePatterns_en_MS', 'goog.i18n.DateTimePatterns_en_MT', 'goog.i18n.DateTimePatterns_en_MU', 'goog.i18n.DateTimePatterns_en_MW', 'goog.i18n.DateTimePatterns_en_MY', 'goog.i18n.DateTimePatterns_en_NA', 'goog.i18n.DateTimePatterns_en_NF', 'goog.i18n.DateTimePatterns_en_NG', 'goog.i18n.DateTimePatterns_en_NL', 'goog.i18n.DateTimePatterns_en_NR', 'goog.i18n.DateTimePatterns_en_NU', 'goog.i18n.DateTimePatterns_en_NZ', 'goog.i18n.DateTimePatterns_en_PG', 'goog.i18n.DateTimePatterns_en_PH', 'goog.i18n.DateTimePatterns_en_PK', 'goog.i18n.DateTimePatterns_en_PN', 'goog.i18n.DateTimePatterns_en_PR', 'goog.i18n.DateTimePatterns_en_PW', 'goog.i18n.DateTimePatterns_en_RW', 'goog.i18n.DateTimePatterns_en_SB', 'goog.i18n.DateTimePatterns_en_SC', 'goog.i18n.DateTimePatterns_en_SD', 'goog.i18n.DateTimePatterns_en_SE', 'goog.i18n.DateTimePatterns_en_SH', 'goog.i18n.DateTimePatterns_en_SI', 'goog.i18n.DateTimePatterns_en_SL', 'goog.i18n.DateTimePatterns_en_SS', 'goog.i18n.DateTimePatterns_en_SX', 'goog.i18n.DateTimePatterns_en_SZ', 'goog.i18n.DateTimePatterns_en_TC', 'goog.i18n.DateTimePatterns_en_TK', 'goog.i18n.DateTimePatterns_en_TO', 'goog.i18n.DateTimePatterns_en_TT', 'goog.i18n.DateTimePatterns_en_TV', 'goog.i18n.DateTimePatterns_en_TZ', 'goog.i18n.DateTimePatterns_en_UG', 'goog.i18n.DateTimePatterns_en_UM', 'goog.i18n.DateTimePatterns_en_US_POSIX', 'goog.i18n.DateTimePatterns_en_VC', 'goog.i18n.DateTimePatterns_en_VG', 'goog.i18n.DateTimePatterns_en_VI', 'goog.i18n.DateTimePatterns_en_VU', 'goog.i18n.DateTimePatterns_en_WS', 'goog.i18n.DateTimePatterns_en_XA', 'goog.i18n.DateTimePatterns_en_ZM', 'goog.i18n.DateTimePatterns_en_ZW', 'goog.i18n.DateTimePatterns_eo', 'goog.i18n.DateTimePatterns_es_AR', 'goog.i18n.DateTimePatterns_es_BO', 'goog.i18n.DateTimePatterns_es_BR', 'goog.i18n.DateTimePatterns_es_BZ', 'goog.i18n.DateTimePatterns_es_CL', 'goog.i18n.DateTimePatterns_es_CO', 'goog.i18n.DateTimePatterns_es_CR', 'goog.i18n.DateTimePatterns_es_CU', 'goog.i18n.DateTimePatterns_es_DO', 'goog.i18n.DateTimePatterns_es_EA', 'goog.i18n.DateTimePatterns_es_EC', 'goog.i18n.DateTimePatterns_es_GQ', 'goog.i18n.DateTimePatterns_es_GT', 'goog.i18n.DateTimePatterns_es_HN', 'goog.i18n.DateTimePatterns_es_IC', 'goog.i18n.DateTimePatterns_es_NI', 'goog.i18n.DateTimePatterns_es_PA', 'goog.i18n.DateTimePatterns_es_PE', 'goog.i18n.DateTimePatterns_es_PH', 'goog.i18n.DateTimePatterns_es_PR', 'goog.i18n.DateTimePatterns_es_PY', 'goog.i18n.DateTimePatterns_es_SV', 'goog.i18n.DateTimePatterns_es_UY', 'goog.i18n.DateTimePatterns_es_VE', 'goog.i18n.DateTimePatterns_et_EE', 'goog.i18n.DateTimePatterns_eu_ES', 'goog.i18n.DateTimePatterns_ewo', 'goog.i18n.DateTimePatterns_ewo_CM', 'goog.i18n.DateTimePatterns_fa_AF', 'goog.i18n.DateTimePatterns_fa_IR', 'goog.i18n.DateTimePatterns_ff', 'goog.i18n.DateTimePatterns_ff_CM', 'goog.i18n.DateTimePatterns_ff_GN', 'goog.i18n.DateTimePatterns_ff_MR', 'goog.i18n.DateTimePatterns_ff_SN', 'goog.i18n.DateTimePatterns_fi_FI', 'goog.i18n.DateTimePatterns_fil_PH', 'goog.i18n.DateTimePatterns_fo', 'goog.i18n.DateTimePatterns_fo_DK', 'goog.i18n.DateTimePatterns_fo_FO', 'goog.i18n.DateTimePatterns_fr_BE', 'goog.i18n.DateTimePatterns_fr_BF', 'goog.i18n.DateTimePatterns_fr_BI', 'goog.i18n.DateTimePatterns_fr_BJ', 'goog.i18n.DateTimePatterns_fr_BL', 'goog.i18n.DateTimePatterns_fr_CD', 'goog.i18n.DateTimePatterns_fr_CF', 'goog.i18n.DateTimePatterns_fr_CG', 'goog.i18n.DateTimePatterns_fr_CH', 'goog.i18n.DateTimePatterns_fr_CI', 'goog.i18n.DateTimePatterns_fr_CM', 'goog.i18n.DateTimePatterns_fr_DJ', 'goog.i18n.DateTimePatterns_fr_DZ', 'goog.i18n.DateTimePatterns_fr_FR', 'goog.i18n.DateTimePatterns_fr_GA', 'goog.i18n.DateTimePatterns_fr_GF', 'goog.i18n.DateTimePatterns_fr_GN', 'goog.i18n.DateTimePatterns_fr_GP', 'goog.i18n.DateTimePatterns_fr_GQ', 'goog.i18n.DateTimePatterns_fr_HT', 'goog.i18n.DateTimePatterns_fr_KM', 'goog.i18n.DateTimePatterns_fr_LU', 'goog.i18n.DateTimePatterns_fr_MA', 'goog.i18n.DateTimePatterns_fr_MC', 'goog.i18n.DateTimePatterns_fr_MF', 'goog.i18n.DateTimePatterns_fr_MG', 'goog.i18n.DateTimePatterns_fr_ML', 'goog.i18n.DateTimePatterns_fr_MQ', 'goog.i18n.DateTimePatterns_fr_MR', 'goog.i18n.DateTimePatterns_fr_MU', 'goog.i18n.DateTimePatterns_fr_NC', 'goog.i18n.DateTimePatterns_fr_NE', 'goog.i18n.DateTimePatterns_fr_PF', 'goog.i18n.DateTimePatterns_fr_PM', 'goog.i18n.DateTimePatterns_fr_RE', 'goog.i18n.DateTimePatterns_fr_RW', 'goog.i18n.DateTimePatterns_fr_SC', 'goog.i18n.DateTimePatterns_fr_SN', 'goog.i18n.DateTimePatterns_fr_SY', 'goog.i18n.DateTimePatterns_fr_TD', 'goog.i18n.DateTimePatterns_fr_TG', 'goog.i18n.DateTimePatterns_fr_TN', 'goog.i18n.DateTimePatterns_fr_VU', 'goog.i18n.DateTimePatterns_fr_WF', 'goog.i18n.DateTimePatterns_fr_YT', 'goog.i18n.DateTimePatterns_fur', 'goog.i18n.DateTimePatterns_fur_IT', 'goog.i18n.DateTimePatterns_fy', 'goog.i18n.DateTimePatterns_fy_NL', 'goog.i18n.DateTimePatterns_ga_IE', 'goog.i18n.DateTimePatterns_gd', 'goog.i18n.DateTimePatterns_gd_GB', 'goog.i18n.DateTimePatterns_gl_ES', 'goog.i18n.DateTimePatterns_gsw_CH', 'goog.i18n.DateTimePatterns_gsw_FR', 'goog.i18n.DateTimePatterns_gsw_LI', 'goog.i18n.DateTimePatterns_gu_IN', 'goog.i18n.DateTimePatterns_guz', 'goog.i18n.DateTimePatterns_guz_KE', 'goog.i18n.DateTimePatterns_gv', 'goog.i18n.DateTimePatterns_gv_IM', 'goog.i18n.DateTimePatterns_ha', 'goog.i18n.DateTimePatterns_ha_GH', 'goog.i18n.DateTimePatterns_ha_NE', 'goog.i18n.DateTimePatterns_ha_NG', 'goog.i18n.DateTimePatterns_haw_US', 'goog.i18n.DateTimePatterns_he_IL', 'goog.i18n.DateTimePatterns_hi_IN', 'goog.i18n.DateTimePatterns_hr_BA', 'goog.i18n.DateTimePatterns_hr_HR', 'goog.i18n.DateTimePatterns_hsb', 'goog.i18n.DateTimePatterns_hsb_DE', 'goog.i18n.DateTimePatterns_hu_HU', 'goog.i18n.DateTimePatterns_hy_AM', 'goog.i18n.DateTimePatterns_id_ID', 'goog.i18n.DateTimePatterns_ig', 'goog.i18n.DateTimePatterns_ig_NG', 'goog.i18n.DateTimePatterns_ii', 'goog.i18n.DateTimePatterns_ii_CN', 'goog.i18n.DateTimePatterns_is_IS', 'goog.i18n.DateTimePatterns_it_CH', 'goog.i18n.DateTimePatterns_it_IT', 'goog.i18n.DateTimePatterns_it_SM', 'goog.i18n.DateTimePatterns_it_VA', 'goog.i18n.DateTimePatterns_ja_JP', 'goog.i18n.DateTimePatterns_jgo', 'goog.i18n.DateTimePatterns_jgo_CM', 'goog.i18n.DateTimePatterns_jmc', 'goog.i18n.DateTimePatterns_jmc_TZ', 'goog.i18n.DateTimePatterns_ka_GE', 'goog.i18n.DateTimePatterns_kab', 'goog.i18n.DateTimePatterns_kab_DZ', 'goog.i18n.DateTimePatterns_kam', 'goog.i18n.DateTimePatterns_kam_KE', 'goog.i18n.DateTimePatterns_kde', 'goog.i18n.DateTimePatterns_kde_TZ', 'goog.i18n.DateTimePatterns_kea', 'goog.i18n.DateTimePatterns_kea_CV', 'goog.i18n.DateTimePatterns_khq', 'goog.i18n.DateTimePatterns_khq_ML', 'goog.i18n.DateTimePatterns_ki', 'goog.i18n.DateTimePatterns_ki_KE', 'goog.i18n.DateTimePatterns_kk_KZ', 'goog.i18n.DateTimePatterns_kkj', 'goog.i18n.DateTimePatterns_kkj_CM', 'goog.i18n.DateTimePatterns_kl', 'goog.i18n.DateTimePatterns_kl_GL', 'goog.i18n.DateTimePatterns_kln', 'goog.i18n.DateTimePatterns_kln_KE', 'goog.i18n.DateTimePatterns_km_KH', 'goog.i18n.DateTimePatterns_kn_IN', 'goog.i18n.DateTimePatterns_ko_KP', 'goog.i18n.DateTimePatterns_ko_KR', 'goog.i18n.DateTimePatterns_kok', 'goog.i18n.DateTimePatterns_kok_IN', 'goog.i18n.DateTimePatterns_ks', 'goog.i18n.DateTimePatterns_ks_IN', 'goog.i18n.DateTimePatterns_ksb', 'goog.i18n.DateTimePatterns_ksb_TZ', 'goog.i18n.DateTimePatterns_ksf', 'goog.i18n.DateTimePatterns_ksf_CM', 'goog.i18n.DateTimePatterns_ksh', 'goog.i18n.DateTimePatterns_ksh_DE', 'goog.i18n.DateTimePatterns_kw', 'goog.i18n.DateTimePatterns_kw_GB', 'goog.i18n.DateTimePatterns_ky_KG', 'goog.i18n.DateTimePatterns_lag', 'goog.i18n.DateTimePatterns_lag_TZ', 'goog.i18n.DateTimePatterns_lb', 'goog.i18n.DateTimePatterns_lb_LU', 'goog.i18n.DateTimePatterns_lg', 'goog.i18n.DateTimePatterns_lg_UG', 'goog.i18n.DateTimePatterns_lkt', 'goog.i18n.DateTimePatterns_lkt_US', 'goog.i18n.DateTimePatterns_ln_AO', 'goog.i18n.DateTimePatterns_ln_CD', 'goog.i18n.DateTimePatterns_ln_CF', 'goog.i18n.DateTimePatterns_ln_CG', 'goog.i18n.DateTimePatterns_lo_LA', 'goog.i18n.DateTimePatterns_lrc', 'goog.i18n.DateTimePatterns_lrc_IQ', 'goog.i18n.DateTimePatterns_lrc_IR', 'goog.i18n.DateTimePatterns_lt_LT', 'goog.i18n.DateTimePatterns_lu', 'goog.i18n.DateTimePatterns_lu_CD', 'goog.i18n.DateTimePatterns_luo', 'goog.i18n.DateTimePatterns_luo_KE', 'goog.i18n.DateTimePatterns_luy', 'goog.i18n.DateTimePatterns_luy_KE', 'goog.i18n.DateTimePatterns_lv_LV', 'goog.i18n.DateTimePatterns_mas', 'goog.i18n.DateTimePatterns_mas_KE', 'goog.i18n.DateTimePatterns_mas_TZ', 'goog.i18n.DateTimePatterns_mer', 'goog.i18n.DateTimePatterns_mer_KE', 'goog.i18n.DateTimePatterns_mfe', 'goog.i18n.DateTimePatterns_mfe_MU', 'goog.i18n.DateTimePatterns_mg', 'goog.i18n.DateTimePatterns_mg_MG', 'goog.i18n.DateTimePatterns_mgh', 'goog.i18n.DateTimePatterns_mgh_MZ', 'goog.i18n.DateTimePatterns_mgo', 'goog.i18n.DateTimePatterns_mgo_CM', 'goog.i18n.DateTimePatterns_mk_MK', 'goog.i18n.DateTimePatterns_ml_IN', 'goog.i18n.DateTimePatterns_mn_MN', 'goog.i18n.DateTimePatterns_mr_IN', 'goog.i18n.DateTimePatterns_ms_BN', 'goog.i18n.DateTimePatterns_ms_MY', 'goog.i18n.DateTimePatterns_ms_SG', 'goog.i18n.DateTimePatterns_mt_MT', 'goog.i18n.DateTimePatterns_mua', 'goog.i18n.DateTimePatterns_mua_CM', 'goog.i18n.DateTimePatterns_my_MM', 'goog.i18n.DateTimePatterns_mzn', 'goog.i18n.DateTimePatterns_mzn_IR', 'goog.i18n.DateTimePatterns_naq', 'goog.i18n.DateTimePatterns_naq_NA', 'goog.i18n.DateTimePatterns_nb_NO', 'goog.i18n.DateTimePatterns_nb_SJ', 'goog.i18n.DateTimePatterns_nd', 'goog.i18n.DateTimePatterns_nd_ZW', 'goog.i18n.DateTimePatterns_nds', 'goog.i18n.DateTimePatterns_nds_DE', 'goog.i18n.DateTimePatterns_nds_NL', 'goog.i18n.DateTimePatterns_ne_IN', 'goog.i18n.DateTimePatterns_ne_NP', 'goog.i18n.DateTimePatterns_nl_AW', 'goog.i18n.DateTimePatterns_nl_BE', 'goog.i18n.DateTimePatterns_nl_BQ', 'goog.i18n.DateTimePatterns_nl_CW', 'goog.i18n.DateTimePatterns_nl_NL', 'goog.i18n.DateTimePatterns_nl_SR', 'goog.i18n.DateTimePatterns_nl_SX', 'goog.i18n.DateTimePatterns_nmg', 'goog.i18n.DateTimePatterns_nmg_CM', 'goog.i18n.DateTimePatterns_nn', 'goog.i18n.DateTimePatterns_nn_NO', 'goog.i18n.DateTimePatterns_nnh', 'goog.i18n.DateTimePatterns_nnh_CM', 'goog.i18n.DateTimePatterns_nus', 'goog.i18n.DateTimePatterns_nus_SS', 'goog.i18n.DateTimePatterns_nyn', 'goog.i18n.DateTimePatterns_nyn_UG', 'goog.i18n.DateTimePatterns_om', 'goog.i18n.DateTimePatterns_om_ET', 'goog.i18n.DateTimePatterns_om_KE', 'goog.i18n.DateTimePatterns_or_IN', 'goog.i18n.DateTimePatterns_os', 'goog.i18n.DateTimePatterns_os_GE', 'goog.i18n.DateTimePatterns_os_RU', 'goog.i18n.DateTimePatterns_pa_Arab', 'goog.i18n.DateTimePatterns_pa_Arab_PK', 'goog.i18n.DateTimePatterns_pa_Guru', 'goog.i18n.DateTimePatterns_pa_Guru_IN', 'goog.i18n.DateTimePatterns_pl_PL', 'goog.i18n.DateTimePatterns_ps', 'goog.i18n.DateTimePatterns_ps_AF', 'goog.i18n.DateTimePatterns_pt_AO', 'goog.i18n.DateTimePatterns_pt_CH', 'goog.i18n.DateTimePatterns_pt_CV', 'goog.i18n.DateTimePatterns_pt_GQ', 'goog.i18n.DateTimePatterns_pt_GW', 'goog.i18n.DateTimePatterns_pt_LU', 'goog.i18n.DateTimePatterns_pt_MO', 'goog.i18n.DateTimePatterns_pt_MZ', 'goog.i18n.DateTimePatterns_pt_ST', 'goog.i18n.DateTimePatterns_pt_TL', 'goog.i18n.DateTimePatterns_qu', 'goog.i18n.DateTimePatterns_qu_BO', 'goog.i18n.DateTimePatterns_qu_EC', 'goog.i18n.DateTimePatterns_qu_PE', 'goog.i18n.DateTimePatterns_rm', 'goog.i18n.DateTimePatterns_rm_CH', 'goog.i18n.DateTimePatterns_rn', 'goog.i18n.DateTimePatterns_rn_BI', 'goog.i18n.DateTimePatterns_ro_MD', 'goog.i18n.DateTimePatterns_ro_RO', 'goog.i18n.DateTimePatterns_rof', 'goog.i18n.DateTimePatterns_rof_TZ', 'goog.i18n.DateTimePatterns_ru_BY', 'goog.i18n.DateTimePatterns_ru_KG', 'goog.i18n.DateTimePatterns_ru_KZ', 'goog.i18n.DateTimePatterns_ru_MD', 'goog.i18n.DateTimePatterns_ru_RU', 'goog.i18n.DateTimePatterns_ru_UA', 'goog.i18n.DateTimePatterns_rw', 'goog.i18n.DateTimePatterns_rw_RW', 'goog.i18n.DateTimePatterns_rwk', 'goog.i18n.DateTimePatterns_rwk_TZ', 'goog.i18n.DateTimePatterns_sah', 'goog.i18n.DateTimePatterns_sah_RU', 'goog.i18n.DateTimePatterns_saq', 'goog.i18n.DateTimePatterns_saq_KE', 'goog.i18n.DateTimePatterns_sbp', 'goog.i18n.DateTimePatterns_sbp_TZ', 'goog.i18n.DateTimePatterns_se', 'goog.i18n.DateTimePatterns_se_FI', 'goog.i18n.DateTimePatterns_se_NO', 'goog.i18n.DateTimePatterns_se_SE', 'goog.i18n.DateTimePatterns_seh', 'goog.i18n.DateTimePatterns_seh_MZ', 'goog.i18n.DateTimePatterns_ses', 'goog.i18n.DateTimePatterns_ses_ML', 'goog.i18n.DateTimePatterns_sg', 'goog.i18n.DateTimePatterns_sg_CF', 'goog.i18n.DateTimePatterns_shi', 'goog.i18n.DateTimePatterns_shi_Latn', 'goog.i18n.DateTimePatterns_shi_Latn_MA', 'goog.i18n.DateTimePatterns_shi_Tfng', 'goog.i18n.DateTimePatterns_shi_Tfng_MA', 'goog.i18n.DateTimePatterns_si_LK', 'goog.i18n.DateTimePatterns_sk_SK', 'goog.i18n.DateTimePatterns_sl_SI', 'goog.i18n.DateTimePatterns_smn', 'goog.i18n.DateTimePatterns_smn_FI', 'goog.i18n.DateTimePatterns_sn', 'goog.i18n.DateTimePatterns_sn_ZW', 'goog.i18n.DateTimePatterns_so', 'goog.i18n.DateTimePatterns_so_DJ', 'goog.i18n.DateTimePatterns_so_ET', 'goog.i18n.DateTimePatterns_so_KE', 'goog.i18n.DateTimePatterns_so_SO', 'goog.i18n.DateTimePatterns_sq_AL', 'goog.i18n.DateTimePatterns_sq_MK', 'goog.i18n.DateTimePatterns_sq_XK', 'goog.i18n.DateTimePatterns_sr_Cyrl', 'goog.i18n.DateTimePatterns_sr_Cyrl_BA', 'goog.i18n.DateTimePatterns_sr_Cyrl_ME', 'goog.i18n.DateTimePatterns_sr_Cyrl_RS', 'goog.i18n.DateTimePatterns_sr_Cyrl_XK', 'goog.i18n.DateTimePatterns_sr_Latn_BA', 'goog.i18n.DateTimePatterns_sr_Latn_ME', 'goog.i18n.DateTimePatterns_sr_Latn_RS', 'goog.i18n.DateTimePatterns_sr_Latn_XK', 'goog.i18n.DateTimePatterns_sv_AX', 'goog.i18n.DateTimePatterns_sv_FI', 'goog.i18n.DateTimePatterns_sv_SE', 'goog.i18n.DateTimePatterns_sw_CD', 'goog.i18n.DateTimePatterns_sw_KE', 'goog.i18n.DateTimePatterns_sw_TZ', 'goog.i18n.DateTimePatterns_sw_UG', 'goog.i18n.DateTimePatterns_ta_IN', 'goog.i18n.DateTimePatterns_ta_LK', 'goog.i18n.DateTimePatterns_ta_MY', 'goog.i18n.DateTimePatterns_ta_SG', 'goog.i18n.DateTimePatterns_te_IN', 'goog.i18n.DateTimePatterns_teo', 'goog.i18n.DateTimePatterns_teo_KE', 'goog.i18n.DateTimePatterns_teo_UG', 'goog.i18n.DateTimePatterns_tg', 'goog.i18n.DateTimePatterns_tg_TJ', 'goog.i18n.DateTimePatterns_th_TH', 'goog.i18n.DateTimePatterns_ti', 'goog.i18n.DateTimePatterns_ti_ER', 'goog.i18n.DateTimePatterns_ti_ET', 'goog.i18n.DateTimePatterns_to', 'goog.i18n.DateTimePatterns_to_TO', 'goog.i18n.DateTimePatterns_tr_CY', 'goog.i18n.DateTimePatterns_tr_TR', 'goog.i18n.DateTimePatterns_tt', 'goog.i18n.DateTimePatterns_tt_RU', 'goog.i18n.DateTimePatterns_twq', 'goog.i18n.DateTimePatterns_twq_NE', 'goog.i18n.DateTimePatterns_tzm', 'goog.i18n.DateTimePatterns_tzm_MA', 'goog.i18n.DateTimePatterns_ug', 'goog.i18n.DateTimePatterns_ug_CN', 'goog.i18n.DateTimePatterns_uk_UA', 'goog.i18n.DateTimePatterns_ur_IN', 'goog.i18n.DateTimePatterns_ur_PK', 'goog.i18n.DateTimePatterns_uz_Arab', 'goog.i18n.DateTimePatterns_uz_Arab_AF', 'goog.i18n.DateTimePatterns_uz_Cyrl', 'goog.i18n.DateTimePatterns_uz_Cyrl_UZ', 'goog.i18n.DateTimePatterns_uz_Latn', 'goog.i18n.DateTimePatterns_uz_Latn_UZ', 'goog.i18n.DateTimePatterns_vai', 'goog.i18n.DateTimePatterns_vai_Latn', 'goog.i18n.DateTimePatterns_vai_Latn_LR', 'goog.i18n.DateTimePatterns_vai_Vaii', 'goog.i18n.DateTimePatterns_vai_Vaii_LR', 'goog.i18n.DateTimePatterns_vi_VN', 'goog.i18n.DateTimePatterns_vun', 'goog.i18n.DateTimePatterns_vun_TZ', 'goog.i18n.DateTimePatterns_wae', 'goog.i18n.DateTimePatterns_wae_CH', 'goog.i18n.DateTimePatterns_wo', 'goog.i18n.DateTimePatterns_wo_SN', 'goog.i18n.DateTimePatterns_xog', 'goog.i18n.DateTimePatterns_xog_UG', 'goog.i18n.DateTimePatterns_yav', 'goog.i18n.DateTimePatterns_yav_CM', 'goog.i18n.DateTimePatterns_yi', 'goog.i18n.DateTimePatterns_yi_001', 'goog.i18n.DateTimePatterns_yo', 'goog.i18n.DateTimePatterns_yo_BJ', 'goog.i18n.DateTimePatterns_yo_NG', 'goog.i18n.DateTimePatterns_yue', 'goog.i18n.DateTimePatterns_yue_Hans', 'goog.i18n.DateTimePatterns_yue_Hans_CN', 'goog.i18n.DateTimePatterns_yue_Hant', 'goog.i18n.DateTimePatterns_yue_Hant_HK', 'goog.i18n.DateTimePatterns_zgh', 'goog.i18n.DateTimePatterns_zgh_MA', 'goog.i18n.DateTimePatterns_zh_Hans', 'goog.i18n.DateTimePatterns_zh_Hans_CN', 'goog.i18n.DateTimePatterns_zh_Hans_HK', 'goog.i18n.DateTimePatterns_zh_Hans_MO', 'goog.i18n.DateTimePatterns_zh_Hans_SG', 'goog.i18n.DateTimePatterns_zh_Hant', 'goog.i18n.DateTimePatterns_zh_Hant_HK', 'goog.i18n.DateTimePatterns_zh_Hant_MO', 'goog.i18n.DateTimePatterns_zh_Hant_TW', 'goog.i18n.DateTimePatterns_zu_ZA'], ['goog.i18n.DateTimePatterns']); +goog.addDependency("i18n/datetimesymbols.js", ['goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbolsType', 'goog.i18n.DateTimeSymbols_en_ISO', 'goog.i18n.DateTimeSymbols_af', 'goog.i18n.DateTimeSymbols_am', 'goog.i18n.DateTimeSymbols_ar', 'goog.i18n.DateTimeSymbols_ar_DZ', 'goog.i18n.DateTimeSymbols_az', 'goog.i18n.DateTimeSymbols_be', 'goog.i18n.DateTimeSymbols_bg', 'goog.i18n.DateTimeSymbols_bn', 'goog.i18n.DateTimeSymbols_br', 'goog.i18n.DateTimeSymbols_bs', 'goog.i18n.DateTimeSymbols_ca', 'goog.i18n.DateTimeSymbols_chr', 'goog.i18n.DateTimeSymbols_cs', 'goog.i18n.DateTimeSymbols_cy', 'goog.i18n.DateTimeSymbols_da', 'goog.i18n.DateTimeSymbols_de', 'goog.i18n.DateTimeSymbols_de_AT', 'goog.i18n.DateTimeSymbols_de_CH', 'goog.i18n.DateTimeSymbols_el', 'goog.i18n.DateTimeSymbols_en', 'goog.i18n.DateTimeSymbols_en_AU', 'goog.i18n.DateTimeSymbols_en_CA', 'goog.i18n.DateTimeSymbols_en_GB', 'goog.i18n.DateTimeSymbols_en_IE', 'goog.i18n.DateTimeSymbols_en_IN', 'goog.i18n.DateTimeSymbols_en_SG', 'goog.i18n.DateTimeSymbols_en_US', 'goog.i18n.DateTimeSymbols_en_ZA', 'goog.i18n.DateTimeSymbols_es', 'goog.i18n.DateTimeSymbols_es_419', 'goog.i18n.DateTimeSymbols_es_ES', 'goog.i18n.DateTimeSymbols_es_MX', 'goog.i18n.DateTimeSymbols_es_US', 'goog.i18n.DateTimeSymbols_et', 'goog.i18n.DateTimeSymbols_eu', 'goog.i18n.DateTimeSymbols_fa', 'goog.i18n.DateTimeSymbols_fi', 'goog.i18n.DateTimeSymbols_fil', 'goog.i18n.DateTimeSymbols_fr', 'goog.i18n.DateTimeSymbols_fr_CA', 'goog.i18n.DateTimeSymbols_ga', 'goog.i18n.DateTimeSymbols_gl', 'goog.i18n.DateTimeSymbols_gsw', 'goog.i18n.DateTimeSymbols_gu', 'goog.i18n.DateTimeSymbols_haw', 'goog.i18n.DateTimeSymbols_he', 'goog.i18n.DateTimeSymbols_hi', 'goog.i18n.DateTimeSymbols_hr', 'goog.i18n.DateTimeSymbols_hu', 'goog.i18n.DateTimeSymbols_hy', 'goog.i18n.DateTimeSymbols_id', 'goog.i18n.DateTimeSymbols_in', 'goog.i18n.DateTimeSymbols_is', 'goog.i18n.DateTimeSymbols_it', 'goog.i18n.DateTimeSymbols_iw', 'goog.i18n.DateTimeSymbols_ja', 'goog.i18n.DateTimeSymbols_ka', 'goog.i18n.DateTimeSymbols_kk', 'goog.i18n.DateTimeSymbols_km', 'goog.i18n.DateTimeSymbols_kn', 'goog.i18n.DateTimeSymbols_ko', 'goog.i18n.DateTimeSymbols_ky', 'goog.i18n.DateTimeSymbols_ln', 'goog.i18n.DateTimeSymbols_lo', 'goog.i18n.DateTimeSymbols_lt', 'goog.i18n.DateTimeSymbols_lv', 'goog.i18n.DateTimeSymbols_mk', 'goog.i18n.DateTimeSymbols_ml', 'goog.i18n.DateTimeSymbols_mn', 'goog.i18n.DateTimeSymbols_mo', 'goog.i18n.DateTimeSymbols_mr', 'goog.i18n.DateTimeSymbols_ms', 'goog.i18n.DateTimeSymbols_mt', 'goog.i18n.DateTimeSymbols_my', 'goog.i18n.DateTimeSymbols_nb', 'goog.i18n.DateTimeSymbols_ne', 'goog.i18n.DateTimeSymbols_nl', 'goog.i18n.DateTimeSymbols_no', 'goog.i18n.DateTimeSymbols_no_NO', 'goog.i18n.DateTimeSymbols_or', 'goog.i18n.DateTimeSymbols_pa', 'goog.i18n.DateTimeSymbols_pl', 'goog.i18n.DateTimeSymbols_pt', 'goog.i18n.DateTimeSymbols_pt_BR', 'goog.i18n.DateTimeSymbols_pt_PT', 'goog.i18n.DateTimeSymbols_ro', 'goog.i18n.DateTimeSymbols_ru', 'goog.i18n.DateTimeSymbols_sh', 'goog.i18n.DateTimeSymbols_si', 'goog.i18n.DateTimeSymbols_sk', 'goog.i18n.DateTimeSymbols_sl', 'goog.i18n.DateTimeSymbols_sq', 'goog.i18n.DateTimeSymbols_sr', 'goog.i18n.DateTimeSymbols_sr_Latn', 'goog.i18n.DateTimeSymbols_sv', 'goog.i18n.DateTimeSymbols_sw', 'goog.i18n.DateTimeSymbols_ta', 'goog.i18n.DateTimeSymbols_te', 'goog.i18n.DateTimeSymbols_th', 'goog.i18n.DateTimeSymbols_tl', 'goog.i18n.DateTimeSymbols_tr', 'goog.i18n.DateTimeSymbols_uk', 'goog.i18n.DateTimeSymbols_ur', 'goog.i18n.DateTimeSymbols_uz', 'goog.i18n.DateTimeSymbols_vi', 'goog.i18n.DateTimeSymbols_zh', 'goog.i18n.DateTimeSymbols_zh_CN', 'goog.i18n.DateTimeSymbols_zh_HK', 'goog.i18n.DateTimeSymbols_zh_TW', 'goog.i18n.DateTimeSymbols_zu'], []); +goog.addDependency("i18n/datetimesymbolsext.js", ['goog.i18n.DateTimeSymbolsExt', 'goog.i18n.DateTimeSymbols_af_NA', 'goog.i18n.DateTimeSymbols_af_ZA', 'goog.i18n.DateTimeSymbols_agq', 'goog.i18n.DateTimeSymbols_agq_CM', 'goog.i18n.DateTimeSymbols_ak', 'goog.i18n.DateTimeSymbols_ak_GH', 'goog.i18n.DateTimeSymbols_am_ET', 'goog.i18n.DateTimeSymbols_ar_001', 'goog.i18n.DateTimeSymbols_ar_AE', 'goog.i18n.DateTimeSymbols_ar_BH', 'goog.i18n.DateTimeSymbols_ar_DJ', 'goog.i18n.DateTimeSymbols_ar_EG', 'goog.i18n.DateTimeSymbols_ar_EH', 'goog.i18n.DateTimeSymbols_ar_ER', 'goog.i18n.DateTimeSymbols_ar_IL', 'goog.i18n.DateTimeSymbols_ar_IQ', 'goog.i18n.DateTimeSymbols_ar_JO', 'goog.i18n.DateTimeSymbols_ar_KM', 'goog.i18n.DateTimeSymbols_ar_KW', 'goog.i18n.DateTimeSymbols_ar_LB', 'goog.i18n.DateTimeSymbols_ar_LY', 'goog.i18n.DateTimeSymbols_ar_MA', 'goog.i18n.DateTimeSymbols_ar_MR', 'goog.i18n.DateTimeSymbols_ar_OM', 'goog.i18n.DateTimeSymbols_ar_PS', 'goog.i18n.DateTimeSymbols_ar_QA', 'goog.i18n.DateTimeSymbols_ar_SA', 'goog.i18n.DateTimeSymbols_ar_SD', 'goog.i18n.DateTimeSymbols_ar_SO', 'goog.i18n.DateTimeSymbols_ar_SS', 'goog.i18n.DateTimeSymbols_ar_SY', 'goog.i18n.DateTimeSymbols_ar_TD', 'goog.i18n.DateTimeSymbols_ar_TN', 'goog.i18n.DateTimeSymbols_ar_XB', 'goog.i18n.DateTimeSymbols_ar_YE', 'goog.i18n.DateTimeSymbols_as', 'goog.i18n.DateTimeSymbols_as_IN', 'goog.i18n.DateTimeSymbols_asa', 'goog.i18n.DateTimeSymbols_asa_TZ', 'goog.i18n.DateTimeSymbols_ast', 'goog.i18n.DateTimeSymbols_ast_ES', 'goog.i18n.DateTimeSymbols_az_Cyrl', 'goog.i18n.DateTimeSymbols_az_Cyrl_AZ', 'goog.i18n.DateTimeSymbols_az_Latn', 'goog.i18n.DateTimeSymbols_az_Latn_AZ', 'goog.i18n.DateTimeSymbols_bas', 'goog.i18n.DateTimeSymbols_bas_CM', 'goog.i18n.DateTimeSymbols_be_BY', 'goog.i18n.DateTimeSymbols_bem', 'goog.i18n.DateTimeSymbols_bem_ZM', 'goog.i18n.DateTimeSymbols_bez', 'goog.i18n.DateTimeSymbols_bez_TZ', 'goog.i18n.DateTimeSymbols_bg_BG', 'goog.i18n.DateTimeSymbols_bm', 'goog.i18n.DateTimeSymbols_bm_ML', 'goog.i18n.DateTimeSymbols_bn_BD', 'goog.i18n.DateTimeSymbols_bn_IN', 'goog.i18n.DateTimeSymbols_bo', 'goog.i18n.DateTimeSymbols_bo_CN', 'goog.i18n.DateTimeSymbols_bo_IN', 'goog.i18n.DateTimeSymbols_br_FR', 'goog.i18n.DateTimeSymbols_brx', 'goog.i18n.DateTimeSymbols_brx_IN', 'goog.i18n.DateTimeSymbols_bs_Cyrl', 'goog.i18n.DateTimeSymbols_bs_Cyrl_BA', 'goog.i18n.DateTimeSymbols_bs_Latn', 'goog.i18n.DateTimeSymbols_bs_Latn_BA', 'goog.i18n.DateTimeSymbols_ca_AD', 'goog.i18n.DateTimeSymbols_ca_ES', 'goog.i18n.DateTimeSymbols_ca_FR', 'goog.i18n.DateTimeSymbols_ca_IT', 'goog.i18n.DateTimeSymbols_ccp', 'goog.i18n.DateTimeSymbols_ccp_BD', 'goog.i18n.DateTimeSymbols_ccp_IN', 'goog.i18n.DateTimeSymbols_ce', 'goog.i18n.DateTimeSymbols_ce_RU', 'goog.i18n.DateTimeSymbols_cgg', 'goog.i18n.DateTimeSymbols_cgg_UG', 'goog.i18n.DateTimeSymbols_chr_US', 'goog.i18n.DateTimeSymbols_ckb', 'goog.i18n.DateTimeSymbols_ckb_IQ', 'goog.i18n.DateTimeSymbols_ckb_IR', 'goog.i18n.DateTimeSymbols_cs_CZ', 'goog.i18n.DateTimeSymbols_cy_GB', 'goog.i18n.DateTimeSymbols_da_DK', 'goog.i18n.DateTimeSymbols_da_GL', 'goog.i18n.DateTimeSymbols_dav', 'goog.i18n.DateTimeSymbols_dav_KE', 'goog.i18n.DateTimeSymbols_de_BE', 'goog.i18n.DateTimeSymbols_de_DE', 'goog.i18n.DateTimeSymbols_de_IT', 'goog.i18n.DateTimeSymbols_de_LI', 'goog.i18n.DateTimeSymbols_de_LU', 'goog.i18n.DateTimeSymbols_dje', 'goog.i18n.DateTimeSymbols_dje_NE', 'goog.i18n.DateTimeSymbols_dsb', 'goog.i18n.DateTimeSymbols_dsb_DE', 'goog.i18n.DateTimeSymbols_dua', 'goog.i18n.DateTimeSymbols_dua_CM', 'goog.i18n.DateTimeSymbols_dyo', 'goog.i18n.DateTimeSymbols_dyo_SN', 'goog.i18n.DateTimeSymbols_dz', 'goog.i18n.DateTimeSymbols_dz_BT', 'goog.i18n.DateTimeSymbols_ebu', 'goog.i18n.DateTimeSymbols_ebu_KE', 'goog.i18n.DateTimeSymbols_ee', 'goog.i18n.DateTimeSymbols_ee_GH', 'goog.i18n.DateTimeSymbols_ee_TG', 'goog.i18n.DateTimeSymbols_el_CY', 'goog.i18n.DateTimeSymbols_el_GR', 'goog.i18n.DateTimeSymbols_en_001', 'goog.i18n.DateTimeSymbols_en_150', 'goog.i18n.DateTimeSymbols_en_AG', 'goog.i18n.DateTimeSymbols_en_AI', 'goog.i18n.DateTimeSymbols_en_AS', 'goog.i18n.DateTimeSymbols_en_AT', 'goog.i18n.DateTimeSymbols_en_BB', 'goog.i18n.DateTimeSymbols_en_BE', 'goog.i18n.DateTimeSymbols_en_BI', 'goog.i18n.DateTimeSymbols_en_BM', 'goog.i18n.DateTimeSymbols_en_BS', 'goog.i18n.DateTimeSymbols_en_BW', 'goog.i18n.DateTimeSymbols_en_BZ', 'goog.i18n.DateTimeSymbols_en_CC', 'goog.i18n.DateTimeSymbols_en_CH', 'goog.i18n.DateTimeSymbols_en_CK', 'goog.i18n.DateTimeSymbols_en_CM', 'goog.i18n.DateTimeSymbols_en_CX', 'goog.i18n.DateTimeSymbols_en_CY', 'goog.i18n.DateTimeSymbols_en_DE', 'goog.i18n.DateTimeSymbols_en_DG', 'goog.i18n.DateTimeSymbols_en_DK', 'goog.i18n.DateTimeSymbols_en_DM', 'goog.i18n.DateTimeSymbols_en_ER', 'goog.i18n.DateTimeSymbols_en_FI', 'goog.i18n.DateTimeSymbols_en_FJ', 'goog.i18n.DateTimeSymbols_en_FK', 'goog.i18n.DateTimeSymbols_en_FM', 'goog.i18n.DateTimeSymbols_en_GD', 'goog.i18n.DateTimeSymbols_en_GG', 'goog.i18n.DateTimeSymbols_en_GH', 'goog.i18n.DateTimeSymbols_en_GI', 'goog.i18n.DateTimeSymbols_en_GM', 'goog.i18n.DateTimeSymbols_en_GU', 'goog.i18n.DateTimeSymbols_en_GY', 'goog.i18n.DateTimeSymbols_en_HK', 'goog.i18n.DateTimeSymbols_en_IL', 'goog.i18n.DateTimeSymbols_en_IM', 'goog.i18n.DateTimeSymbols_en_IO', 'goog.i18n.DateTimeSymbols_en_JE', 'goog.i18n.DateTimeSymbols_en_JM', 'goog.i18n.DateTimeSymbols_en_KE', 'goog.i18n.DateTimeSymbols_en_KI', 'goog.i18n.DateTimeSymbols_en_KN', 'goog.i18n.DateTimeSymbols_en_KY', 'goog.i18n.DateTimeSymbols_en_LC', 'goog.i18n.DateTimeSymbols_en_LR', 'goog.i18n.DateTimeSymbols_en_LS', 'goog.i18n.DateTimeSymbols_en_MG', 'goog.i18n.DateTimeSymbols_en_MH', 'goog.i18n.DateTimeSymbols_en_MO', 'goog.i18n.DateTimeSymbols_en_MP', 'goog.i18n.DateTimeSymbols_en_MS', 'goog.i18n.DateTimeSymbols_en_MT', 'goog.i18n.DateTimeSymbols_en_MU', 'goog.i18n.DateTimeSymbols_en_MW', 'goog.i18n.DateTimeSymbols_en_MY', 'goog.i18n.DateTimeSymbols_en_NA', 'goog.i18n.DateTimeSymbols_en_NF', 'goog.i18n.DateTimeSymbols_en_NG', 'goog.i18n.DateTimeSymbols_en_NL', 'goog.i18n.DateTimeSymbols_en_NR', 'goog.i18n.DateTimeSymbols_en_NU', 'goog.i18n.DateTimeSymbols_en_NZ', 'goog.i18n.DateTimeSymbols_en_PG', 'goog.i18n.DateTimeSymbols_en_PH', 'goog.i18n.DateTimeSymbols_en_PK', 'goog.i18n.DateTimeSymbols_en_PN', 'goog.i18n.DateTimeSymbols_en_PR', 'goog.i18n.DateTimeSymbols_en_PW', 'goog.i18n.DateTimeSymbols_en_RW', 'goog.i18n.DateTimeSymbols_en_SB', 'goog.i18n.DateTimeSymbols_en_SC', 'goog.i18n.DateTimeSymbols_en_SD', 'goog.i18n.DateTimeSymbols_en_SE', 'goog.i18n.DateTimeSymbols_en_SH', 'goog.i18n.DateTimeSymbols_en_SI', 'goog.i18n.DateTimeSymbols_en_SL', 'goog.i18n.DateTimeSymbols_en_SS', 'goog.i18n.DateTimeSymbols_en_SX', 'goog.i18n.DateTimeSymbols_en_SZ', 'goog.i18n.DateTimeSymbols_en_TC', 'goog.i18n.DateTimeSymbols_en_TK', 'goog.i18n.DateTimeSymbols_en_TO', 'goog.i18n.DateTimeSymbols_en_TT', 'goog.i18n.DateTimeSymbols_en_TV', 'goog.i18n.DateTimeSymbols_en_TZ', 'goog.i18n.DateTimeSymbols_en_UG', 'goog.i18n.DateTimeSymbols_en_UM', 'goog.i18n.DateTimeSymbols_en_US_POSIX', 'goog.i18n.DateTimeSymbols_en_VC', 'goog.i18n.DateTimeSymbols_en_VG', 'goog.i18n.DateTimeSymbols_en_VI', 'goog.i18n.DateTimeSymbols_en_VU', 'goog.i18n.DateTimeSymbols_en_WS', 'goog.i18n.DateTimeSymbols_en_XA', 'goog.i18n.DateTimeSymbols_en_ZM', 'goog.i18n.DateTimeSymbols_en_ZW', 'goog.i18n.DateTimeSymbols_eo', 'goog.i18n.DateTimeSymbols_es_AR', 'goog.i18n.DateTimeSymbols_es_BO', 'goog.i18n.DateTimeSymbols_es_BR', 'goog.i18n.DateTimeSymbols_es_BZ', 'goog.i18n.DateTimeSymbols_es_CL', 'goog.i18n.DateTimeSymbols_es_CO', 'goog.i18n.DateTimeSymbols_es_CR', 'goog.i18n.DateTimeSymbols_es_CU', 'goog.i18n.DateTimeSymbols_es_DO', 'goog.i18n.DateTimeSymbols_es_EA', 'goog.i18n.DateTimeSymbols_es_EC', 'goog.i18n.DateTimeSymbols_es_GQ', 'goog.i18n.DateTimeSymbols_es_GT', 'goog.i18n.DateTimeSymbols_es_HN', 'goog.i18n.DateTimeSymbols_es_IC', 'goog.i18n.DateTimeSymbols_es_NI', 'goog.i18n.DateTimeSymbols_es_PA', 'goog.i18n.DateTimeSymbols_es_PE', 'goog.i18n.DateTimeSymbols_es_PH', 'goog.i18n.DateTimeSymbols_es_PR', 'goog.i18n.DateTimeSymbols_es_PY', 'goog.i18n.DateTimeSymbols_es_SV', 'goog.i18n.DateTimeSymbols_es_UY', 'goog.i18n.DateTimeSymbols_es_VE', 'goog.i18n.DateTimeSymbols_et_EE', 'goog.i18n.DateTimeSymbols_eu_ES', 'goog.i18n.DateTimeSymbols_ewo', 'goog.i18n.DateTimeSymbols_ewo_CM', 'goog.i18n.DateTimeSymbols_fa_AF', 'goog.i18n.DateTimeSymbols_fa_IR', 'goog.i18n.DateTimeSymbols_ff', 'goog.i18n.DateTimeSymbols_ff_CM', 'goog.i18n.DateTimeSymbols_ff_GN', 'goog.i18n.DateTimeSymbols_ff_MR', 'goog.i18n.DateTimeSymbols_ff_SN', 'goog.i18n.DateTimeSymbols_fi_FI', 'goog.i18n.DateTimeSymbols_fil_PH', 'goog.i18n.DateTimeSymbols_fo', 'goog.i18n.DateTimeSymbols_fo_DK', 'goog.i18n.DateTimeSymbols_fo_FO', 'goog.i18n.DateTimeSymbols_fr_BE', 'goog.i18n.DateTimeSymbols_fr_BF', 'goog.i18n.DateTimeSymbols_fr_BI', 'goog.i18n.DateTimeSymbols_fr_BJ', 'goog.i18n.DateTimeSymbols_fr_BL', 'goog.i18n.DateTimeSymbols_fr_CD', 'goog.i18n.DateTimeSymbols_fr_CF', 'goog.i18n.DateTimeSymbols_fr_CG', 'goog.i18n.DateTimeSymbols_fr_CH', 'goog.i18n.DateTimeSymbols_fr_CI', 'goog.i18n.DateTimeSymbols_fr_CM', 'goog.i18n.DateTimeSymbols_fr_DJ', 'goog.i18n.DateTimeSymbols_fr_DZ', 'goog.i18n.DateTimeSymbols_fr_FR', 'goog.i18n.DateTimeSymbols_fr_GA', 'goog.i18n.DateTimeSymbols_fr_GF', 'goog.i18n.DateTimeSymbols_fr_GN', 'goog.i18n.DateTimeSymbols_fr_GP', 'goog.i18n.DateTimeSymbols_fr_GQ', 'goog.i18n.DateTimeSymbols_fr_HT', 'goog.i18n.DateTimeSymbols_fr_KM', 'goog.i18n.DateTimeSymbols_fr_LU', 'goog.i18n.DateTimeSymbols_fr_MA', 'goog.i18n.DateTimeSymbols_fr_MC', 'goog.i18n.DateTimeSymbols_fr_MF', 'goog.i18n.DateTimeSymbols_fr_MG', 'goog.i18n.DateTimeSymbols_fr_ML', 'goog.i18n.DateTimeSymbols_fr_MQ', 'goog.i18n.DateTimeSymbols_fr_MR', 'goog.i18n.DateTimeSymbols_fr_MU', 'goog.i18n.DateTimeSymbols_fr_NC', 'goog.i18n.DateTimeSymbols_fr_NE', 'goog.i18n.DateTimeSymbols_fr_PF', 'goog.i18n.DateTimeSymbols_fr_PM', 'goog.i18n.DateTimeSymbols_fr_RE', 'goog.i18n.DateTimeSymbols_fr_RW', 'goog.i18n.DateTimeSymbols_fr_SC', 'goog.i18n.DateTimeSymbols_fr_SN', 'goog.i18n.DateTimeSymbols_fr_SY', 'goog.i18n.DateTimeSymbols_fr_TD', 'goog.i18n.DateTimeSymbols_fr_TG', 'goog.i18n.DateTimeSymbols_fr_TN', 'goog.i18n.DateTimeSymbols_fr_VU', 'goog.i18n.DateTimeSymbols_fr_WF', 'goog.i18n.DateTimeSymbols_fr_YT', 'goog.i18n.DateTimeSymbols_fur', 'goog.i18n.DateTimeSymbols_fur_IT', 'goog.i18n.DateTimeSymbols_fy', 'goog.i18n.DateTimeSymbols_fy_NL', 'goog.i18n.DateTimeSymbols_ga_IE', 'goog.i18n.DateTimeSymbols_gd', 'goog.i18n.DateTimeSymbols_gd_GB', 'goog.i18n.DateTimeSymbols_gl_ES', 'goog.i18n.DateTimeSymbols_gsw_CH', 'goog.i18n.DateTimeSymbols_gsw_FR', 'goog.i18n.DateTimeSymbols_gsw_LI', 'goog.i18n.DateTimeSymbols_gu_IN', 'goog.i18n.DateTimeSymbols_guz', 'goog.i18n.DateTimeSymbols_guz_KE', 'goog.i18n.DateTimeSymbols_gv', 'goog.i18n.DateTimeSymbols_gv_IM', 'goog.i18n.DateTimeSymbols_ha', 'goog.i18n.DateTimeSymbols_ha_GH', 'goog.i18n.DateTimeSymbols_ha_NE', 'goog.i18n.DateTimeSymbols_ha_NG', 'goog.i18n.DateTimeSymbols_haw_US', 'goog.i18n.DateTimeSymbols_he_IL', 'goog.i18n.DateTimeSymbols_hi_IN', 'goog.i18n.DateTimeSymbols_hr_BA', 'goog.i18n.DateTimeSymbols_hr_HR', 'goog.i18n.DateTimeSymbols_hsb', 'goog.i18n.DateTimeSymbols_hsb_DE', 'goog.i18n.DateTimeSymbols_hu_HU', 'goog.i18n.DateTimeSymbols_hy_AM', 'goog.i18n.DateTimeSymbols_id_ID', 'goog.i18n.DateTimeSymbols_ig', 'goog.i18n.DateTimeSymbols_ig_NG', 'goog.i18n.DateTimeSymbols_ii', 'goog.i18n.DateTimeSymbols_ii_CN', 'goog.i18n.DateTimeSymbols_is_IS', 'goog.i18n.DateTimeSymbols_it_CH', 'goog.i18n.DateTimeSymbols_it_IT', 'goog.i18n.DateTimeSymbols_it_SM', 'goog.i18n.DateTimeSymbols_it_VA', 'goog.i18n.DateTimeSymbols_ja_JP', 'goog.i18n.DateTimeSymbols_jgo', 'goog.i18n.DateTimeSymbols_jgo_CM', 'goog.i18n.DateTimeSymbols_jmc', 'goog.i18n.DateTimeSymbols_jmc_TZ', 'goog.i18n.DateTimeSymbols_ka_GE', 'goog.i18n.DateTimeSymbols_kab', 'goog.i18n.DateTimeSymbols_kab_DZ', 'goog.i18n.DateTimeSymbols_kam', 'goog.i18n.DateTimeSymbols_kam_KE', 'goog.i18n.DateTimeSymbols_kde', 'goog.i18n.DateTimeSymbols_kde_TZ', 'goog.i18n.DateTimeSymbols_kea', 'goog.i18n.DateTimeSymbols_kea_CV', 'goog.i18n.DateTimeSymbols_khq', 'goog.i18n.DateTimeSymbols_khq_ML', 'goog.i18n.DateTimeSymbols_ki', 'goog.i18n.DateTimeSymbols_ki_KE', 'goog.i18n.DateTimeSymbols_kk_KZ', 'goog.i18n.DateTimeSymbols_kkj', 'goog.i18n.DateTimeSymbols_kkj_CM', 'goog.i18n.DateTimeSymbols_kl', 'goog.i18n.DateTimeSymbols_kl_GL', 'goog.i18n.DateTimeSymbols_kln', 'goog.i18n.DateTimeSymbols_kln_KE', 'goog.i18n.DateTimeSymbols_km_KH', 'goog.i18n.DateTimeSymbols_kn_IN', 'goog.i18n.DateTimeSymbols_ko_KP', 'goog.i18n.DateTimeSymbols_ko_KR', 'goog.i18n.DateTimeSymbols_kok', 'goog.i18n.DateTimeSymbols_kok_IN', 'goog.i18n.DateTimeSymbols_ks', 'goog.i18n.DateTimeSymbols_ks_IN', 'goog.i18n.DateTimeSymbols_ksb', 'goog.i18n.DateTimeSymbols_ksb_TZ', 'goog.i18n.DateTimeSymbols_ksf', 'goog.i18n.DateTimeSymbols_ksf_CM', 'goog.i18n.DateTimeSymbols_ksh', 'goog.i18n.DateTimeSymbols_ksh_DE', 'goog.i18n.DateTimeSymbols_kw', 'goog.i18n.DateTimeSymbols_kw_GB', 'goog.i18n.DateTimeSymbols_ky_KG', 'goog.i18n.DateTimeSymbols_lag', 'goog.i18n.DateTimeSymbols_lag_TZ', 'goog.i18n.DateTimeSymbols_lb', 'goog.i18n.DateTimeSymbols_lb_LU', 'goog.i18n.DateTimeSymbols_lg', 'goog.i18n.DateTimeSymbols_lg_UG', 'goog.i18n.DateTimeSymbols_lkt', 'goog.i18n.DateTimeSymbols_lkt_US', 'goog.i18n.DateTimeSymbols_ln_AO', 'goog.i18n.DateTimeSymbols_ln_CD', 'goog.i18n.DateTimeSymbols_ln_CF', 'goog.i18n.DateTimeSymbols_ln_CG', 'goog.i18n.DateTimeSymbols_lo_LA', 'goog.i18n.DateTimeSymbols_lrc', 'goog.i18n.DateTimeSymbols_lrc_IQ', 'goog.i18n.DateTimeSymbols_lrc_IR', 'goog.i18n.DateTimeSymbols_lt_LT', 'goog.i18n.DateTimeSymbols_lu', 'goog.i18n.DateTimeSymbols_lu_CD', 'goog.i18n.DateTimeSymbols_luo', 'goog.i18n.DateTimeSymbols_luo_KE', 'goog.i18n.DateTimeSymbols_luy', 'goog.i18n.DateTimeSymbols_luy_KE', 'goog.i18n.DateTimeSymbols_lv_LV', 'goog.i18n.DateTimeSymbols_mas', 'goog.i18n.DateTimeSymbols_mas_KE', 'goog.i18n.DateTimeSymbols_mas_TZ', 'goog.i18n.DateTimeSymbols_mer', 'goog.i18n.DateTimeSymbols_mer_KE', 'goog.i18n.DateTimeSymbols_mfe', 'goog.i18n.DateTimeSymbols_mfe_MU', 'goog.i18n.DateTimeSymbols_mg', 'goog.i18n.DateTimeSymbols_mg_MG', 'goog.i18n.DateTimeSymbols_mgh', 'goog.i18n.DateTimeSymbols_mgh_MZ', 'goog.i18n.DateTimeSymbols_mgo', 'goog.i18n.DateTimeSymbols_mgo_CM', 'goog.i18n.DateTimeSymbols_mk_MK', 'goog.i18n.DateTimeSymbols_ml_IN', 'goog.i18n.DateTimeSymbols_mn_MN', 'goog.i18n.DateTimeSymbols_mr_IN', 'goog.i18n.DateTimeSymbols_ms_BN', 'goog.i18n.DateTimeSymbols_ms_MY', 'goog.i18n.DateTimeSymbols_ms_SG', 'goog.i18n.DateTimeSymbols_mt_MT', 'goog.i18n.DateTimeSymbols_mua', 'goog.i18n.DateTimeSymbols_mua_CM', 'goog.i18n.DateTimeSymbols_my_MM', 'goog.i18n.DateTimeSymbols_mzn', 'goog.i18n.DateTimeSymbols_mzn_IR', 'goog.i18n.DateTimeSymbols_naq', 'goog.i18n.DateTimeSymbols_naq_NA', 'goog.i18n.DateTimeSymbols_nb_NO', 'goog.i18n.DateTimeSymbols_nb_SJ', 'goog.i18n.DateTimeSymbols_nd', 'goog.i18n.DateTimeSymbols_nd_ZW', 'goog.i18n.DateTimeSymbols_nds', 'goog.i18n.DateTimeSymbols_nds_DE', 'goog.i18n.DateTimeSymbols_nds_NL', 'goog.i18n.DateTimeSymbols_ne_IN', 'goog.i18n.DateTimeSymbols_ne_NP', 'goog.i18n.DateTimeSymbols_nl_AW', 'goog.i18n.DateTimeSymbols_nl_BE', 'goog.i18n.DateTimeSymbols_nl_BQ', 'goog.i18n.DateTimeSymbols_nl_CW', 'goog.i18n.DateTimeSymbols_nl_NL', 'goog.i18n.DateTimeSymbols_nl_SR', 'goog.i18n.DateTimeSymbols_nl_SX', 'goog.i18n.DateTimeSymbols_nmg', 'goog.i18n.DateTimeSymbols_nmg_CM', 'goog.i18n.DateTimeSymbols_nn', 'goog.i18n.DateTimeSymbols_nn_NO', 'goog.i18n.DateTimeSymbols_nnh', 'goog.i18n.DateTimeSymbols_nnh_CM', 'goog.i18n.DateTimeSymbols_nus', 'goog.i18n.DateTimeSymbols_nus_SS', 'goog.i18n.DateTimeSymbols_nyn', 'goog.i18n.DateTimeSymbols_nyn_UG', 'goog.i18n.DateTimeSymbols_om', 'goog.i18n.DateTimeSymbols_om_ET', 'goog.i18n.DateTimeSymbols_om_KE', 'goog.i18n.DateTimeSymbols_or_IN', 'goog.i18n.DateTimeSymbols_os', 'goog.i18n.DateTimeSymbols_os_GE', 'goog.i18n.DateTimeSymbols_os_RU', 'goog.i18n.DateTimeSymbols_pa_Arab', 'goog.i18n.DateTimeSymbols_pa_Arab_PK', 'goog.i18n.DateTimeSymbols_pa_Guru', 'goog.i18n.DateTimeSymbols_pa_Guru_IN', 'goog.i18n.DateTimeSymbols_pl_PL', 'goog.i18n.DateTimeSymbols_ps', 'goog.i18n.DateTimeSymbols_ps_AF', 'goog.i18n.DateTimeSymbols_pt_AO', 'goog.i18n.DateTimeSymbols_pt_CH', 'goog.i18n.DateTimeSymbols_pt_CV', 'goog.i18n.DateTimeSymbols_pt_GQ', 'goog.i18n.DateTimeSymbols_pt_GW', 'goog.i18n.DateTimeSymbols_pt_LU', 'goog.i18n.DateTimeSymbols_pt_MO', 'goog.i18n.DateTimeSymbols_pt_MZ', 'goog.i18n.DateTimeSymbols_pt_ST', 'goog.i18n.DateTimeSymbols_pt_TL', 'goog.i18n.DateTimeSymbols_qu', 'goog.i18n.DateTimeSymbols_qu_BO', 'goog.i18n.DateTimeSymbols_qu_EC', 'goog.i18n.DateTimeSymbols_qu_PE', 'goog.i18n.DateTimeSymbols_rm', 'goog.i18n.DateTimeSymbols_rm_CH', 'goog.i18n.DateTimeSymbols_rn', 'goog.i18n.DateTimeSymbols_rn_BI', 'goog.i18n.DateTimeSymbols_ro_MD', 'goog.i18n.DateTimeSymbols_ro_RO', 'goog.i18n.DateTimeSymbols_rof', 'goog.i18n.DateTimeSymbols_rof_TZ', 'goog.i18n.DateTimeSymbols_ru_BY', 'goog.i18n.DateTimeSymbols_ru_KG', 'goog.i18n.DateTimeSymbols_ru_KZ', 'goog.i18n.DateTimeSymbols_ru_MD', 'goog.i18n.DateTimeSymbols_ru_RU', 'goog.i18n.DateTimeSymbols_ru_UA', 'goog.i18n.DateTimeSymbols_rw', 'goog.i18n.DateTimeSymbols_rw_RW', 'goog.i18n.DateTimeSymbols_rwk', 'goog.i18n.DateTimeSymbols_rwk_TZ', 'goog.i18n.DateTimeSymbols_sah', 'goog.i18n.DateTimeSymbols_sah_RU', 'goog.i18n.DateTimeSymbols_saq', 'goog.i18n.DateTimeSymbols_saq_KE', 'goog.i18n.DateTimeSymbols_sbp', 'goog.i18n.DateTimeSymbols_sbp_TZ', 'goog.i18n.DateTimeSymbols_se', 'goog.i18n.DateTimeSymbols_se_FI', 'goog.i18n.DateTimeSymbols_se_NO', 'goog.i18n.DateTimeSymbols_se_SE', 'goog.i18n.DateTimeSymbols_seh', 'goog.i18n.DateTimeSymbols_seh_MZ', 'goog.i18n.DateTimeSymbols_ses', 'goog.i18n.DateTimeSymbols_ses_ML', 'goog.i18n.DateTimeSymbols_sg', 'goog.i18n.DateTimeSymbols_sg_CF', 'goog.i18n.DateTimeSymbols_shi', 'goog.i18n.DateTimeSymbols_shi_Latn', 'goog.i18n.DateTimeSymbols_shi_Latn_MA', 'goog.i18n.DateTimeSymbols_shi_Tfng', 'goog.i18n.DateTimeSymbols_shi_Tfng_MA', 'goog.i18n.DateTimeSymbols_si_LK', 'goog.i18n.DateTimeSymbols_sk_SK', 'goog.i18n.DateTimeSymbols_sl_SI', 'goog.i18n.DateTimeSymbols_smn', 'goog.i18n.DateTimeSymbols_smn_FI', 'goog.i18n.DateTimeSymbols_sn', 'goog.i18n.DateTimeSymbols_sn_ZW', 'goog.i18n.DateTimeSymbols_so', 'goog.i18n.DateTimeSymbols_so_DJ', 'goog.i18n.DateTimeSymbols_so_ET', 'goog.i18n.DateTimeSymbols_so_KE', 'goog.i18n.DateTimeSymbols_so_SO', 'goog.i18n.DateTimeSymbols_sq_AL', 'goog.i18n.DateTimeSymbols_sq_MK', 'goog.i18n.DateTimeSymbols_sq_XK', 'goog.i18n.DateTimeSymbols_sr_Cyrl', 'goog.i18n.DateTimeSymbols_sr_Cyrl_BA', 'goog.i18n.DateTimeSymbols_sr_Cyrl_ME', 'goog.i18n.DateTimeSymbols_sr_Cyrl_RS', 'goog.i18n.DateTimeSymbols_sr_Cyrl_XK', 'goog.i18n.DateTimeSymbols_sr_Latn_BA', 'goog.i18n.DateTimeSymbols_sr_Latn_ME', 'goog.i18n.DateTimeSymbols_sr_Latn_RS', 'goog.i18n.DateTimeSymbols_sr_Latn_XK', 'goog.i18n.DateTimeSymbols_sv_AX', 'goog.i18n.DateTimeSymbols_sv_FI', 'goog.i18n.DateTimeSymbols_sv_SE', 'goog.i18n.DateTimeSymbols_sw_CD', 'goog.i18n.DateTimeSymbols_sw_KE', 'goog.i18n.DateTimeSymbols_sw_TZ', 'goog.i18n.DateTimeSymbols_sw_UG', 'goog.i18n.DateTimeSymbols_ta_IN', 'goog.i18n.DateTimeSymbols_ta_LK', 'goog.i18n.DateTimeSymbols_ta_MY', 'goog.i18n.DateTimeSymbols_ta_SG', 'goog.i18n.DateTimeSymbols_te_IN', 'goog.i18n.DateTimeSymbols_teo', 'goog.i18n.DateTimeSymbols_teo_KE', 'goog.i18n.DateTimeSymbols_teo_UG', 'goog.i18n.DateTimeSymbols_tg', 'goog.i18n.DateTimeSymbols_tg_TJ', 'goog.i18n.DateTimeSymbols_th_TH', 'goog.i18n.DateTimeSymbols_ti', 'goog.i18n.DateTimeSymbols_ti_ER', 'goog.i18n.DateTimeSymbols_ti_ET', 'goog.i18n.DateTimeSymbols_to', 'goog.i18n.DateTimeSymbols_to_TO', 'goog.i18n.DateTimeSymbols_tr_CY', 'goog.i18n.DateTimeSymbols_tr_TR', 'goog.i18n.DateTimeSymbols_tt', 'goog.i18n.DateTimeSymbols_tt_RU', 'goog.i18n.DateTimeSymbols_twq', 'goog.i18n.DateTimeSymbols_twq_NE', 'goog.i18n.DateTimeSymbols_tzm', 'goog.i18n.DateTimeSymbols_tzm_MA', 'goog.i18n.DateTimeSymbols_ug', 'goog.i18n.DateTimeSymbols_ug_CN', 'goog.i18n.DateTimeSymbols_uk_UA', 'goog.i18n.DateTimeSymbols_ur_IN', 'goog.i18n.DateTimeSymbols_ur_PK', 'goog.i18n.DateTimeSymbols_uz_Arab', 'goog.i18n.DateTimeSymbols_uz_Arab_AF', 'goog.i18n.DateTimeSymbols_uz_Cyrl', 'goog.i18n.DateTimeSymbols_uz_Cyrl_UZ', 'goog.i18n.DateTimeSymbols_uz_Latn', 'goog.i18n.DateTimeSymbols_uz_Latn_UZ', 'goog.i18n.DateTimeSymbols_vai', 'goog.i18n.DateTimeSymbols_vai_Latn', 'goog.i18n.DateTimeSymbols_vai_Latn_LR', 'goog.i18n.DateTimeSymbols_vai_Vaii', 'goog.i18n.DateTimeSymbols_vai_Vaii_LR', 'goog.i18n.DateTimeSymbols_vi_VN', 'goog.i18n.DateTimeSymbols_vun', 'goog.i18n.DateTimeSymbols_vun_TZ', 'goog.i18n.DateTimeSymbols_wae', 'goog.i18n.DateTimeSymbols_wae_CH', 'goog.i18n.DateTimeSymbols_wo', 'goog.i18n.DateTimeSymbols_wo_SN', 'goog.i18n.DateTimeSymbols_xog', 'goog.i18n.DateTimeSymbols_xog_UG', 'goog.i18n.DateTimeSymbols_yav', 'goog.i18n.DateTimeSymbols_yav_CM', 'goog.i18n.DateTimeSymbols_yi', 'goog.i18n.DateTimeSymbols_yi_001', 'goog.i18n.DateTimeSymbols_yo', 'goog.i18n.DateTimeSymbols_yo_BJ', 'goog.i18n.DateTimeSymbols_yo_NG', 'goog.i18n.DateTimeSymbols_yue', 'goog.i18n.DateTimeSymbols_yue_Hans', 'goog.i18n.DateTimeSymbols_yue_Hans_CN', 'goog.i18n.DateTimeSymbols_yue_Hant', 'goog.i18n.DateTimeSymbols_yue_Hant_HK', 'goog.i18n.DateTimeSymbols_zgh', 'goog.i18n.DateTimeSymbols_zgh_MA', 'goog.i18n.DateTimeSymbols_zh_Hans', 'goog.i18n.DateTimeSymbols_zh_Hans_CN', 'goog.i18n.DateTimeSymbols_zh_Hans_HK', 'goog.i18n.DateTimeSymbols_zh_Hans_MO', 'goog.i18n.DateTimeSymbols_zh_Hans_SG', 'goog.i18n.DateTimeSymbols_zh_Hant', 'goog.i18n.DateTimeSymbols_zh_Hant_HK', 'goog.i18n.DateTimeSymbols_zh_Hant_MO', 'goog.i18n.DateTimeSymbols_zh_Hant_TW', 'goog.i18n.DateTimeSymbols_zu_ZA'], ['goog.i18n.DateTimeSymbols']); +goog.addDependency("i18n/graphemebreak.js", ['goog.i18n.GraphemeBreak'], ['goog.asserts', 'goog.i18n.uChar', 'goog.structs.InversionMap']); +goog.addDependency("i18n/graphemebreak_test.js", ['goog.i18n.GraphemeBreakTest'], ['goog.i18n.GraphemeBreak', 'goog.i18n.uChar', 'goog.testing.jsunit']); +goog.addDependency("i18n/messageformat.js", ['goog.i18n.MessageFormat'], ['goog.array', 'goog.asserts', 'goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.NumberFormat', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.ordinalRules', 'goog.i18n.pluralRules']); +goog.addDependency("i18n/messageformat_test.js", ['goog.i18n.MessageFormatTest'], ['goog.i18n.MessageFormat', 'goog.i18n.NumberFormatSymbols_hr', 'goog.i18n.pluralRules', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("i18n/mime.js", ['goog.i18n.mime', 'goog.i18n.mime.encode'], ['goog.array']); +goog.addDependency("i18n/mime_test.js", ['goog.i18n.mime.encodeTest'], ['goog.i18n.mime.encode', 'goog.testing.jsunit']); +goog.addDependency("i18n/numberformat.js", ['goog.i18n.NumberFormat', 'goog.i18n.NumberFormat.CurrencyStyle', 'goog.i18n.NumberFormat.Format'], ['goog.asserts', 'goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_u_nu_latn', 'goog.i18n.currency', 'goog.math', 'goog.string']); +goog.addDependency("i18n/numberformat_test.js", ['goog.i18n.NumberFormatTest'], ['goog.i18n.CompactNumberFormatSymbols', 'goog.i18n.CompactNumberFormatSymbols_de', 'goog.i18n.CompactNumberFormatSymbols_en', 'goog.i18n.CompactNumberFormatSymbols_fr', 'goog.i18n.NumberFormat', 'goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_ar', 'goog.i18n.NumberFormatSymbols_ar_u_nu_latn', 'goog.i18n.NumberFormatSymbols_de', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_fi', 'goog.i18n.NumberFormatSymbols_fr', 'goog.i18n.NumberFormatSymbols_pl', 'goog.i18n.NumberFormatSymbols_ro', 'goog.i18n.NumberFormatSymbols_u_nu_latn', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("i18n/numberformatsymbols.js", ['goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_u_nu_latn', 'goog.i18n.NumberFormatSymbols_af', 'goog.i18n.NumberFormatSymbols_am', 'goog.i18n.NumberFormatSymbols_ar', 'goog.i18n.NumberFormatSymbols_ar_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_DZ', 'goog.i18n.NumberFormatSymbols_az', 'goog.i18n.NumberFormatSymbols_be', 'goog.i18n.NumberFormatSymbols_bg', 'goog.i18n.NumberFormatSymbols_bn', 'goog.i18n.NumberFormatSymbols_bn_u_nu_latn', 'goog.i18n.NumberFormatSymbols_br', 'goog.i18n.NumberFormatSymbols_bs', 'goog.i18n.NumberFormatSymbols_ca', 'goog.i18n.NumberFormatSymbols_chr', 'goog.i18n.NumberFormatSymbols_cs', 'goog.i18n.NumberFormatSymbols_cy', 'goog.i18n.NumberFormatSymbols_da', 'goog.i18n.NumberFormatSymbols_de', 'goog.i18n.NumberFormatSymbols_de_AT', 'goog.i18n.NumberFormatSymbols_de_CH', 'goog.i18n.NumberFormatSymbols_el', 'goog.i18n.NumberFormatSymbols_en', 'goog.i18n.NumberFormatSymbols_en_AU', 'goog.i18n.NumberFormatSymbols_en_CA', 'goog.i18n.NumberFormatSymbols_en_GB', 'goog.i18n.NumberFormatSymbols_en_IE', 'goog.i18n.NumberFormatSymbols_en_IN', 'goog.i18n.NumberFormatSymbols_en_SG', 'goog.i18n.NumberFormatSymbols_en_US', 'goog.i18n.NumberFormatSymbols_en_ZA', 'goog.i18n.NumberFormatSymbols_es', 'goog.i18n.NumberFormatSymbols_es_419', 'goog.i18n.NumberFormatSymbols_es_ES', 'goog.i18n.NumberFormatSymbols_es_MX', 'goog.i18n.NumberFormatSymbols_es_US', 'goog.i18n.NumberFormatSymbols_et', 'goog.i18n.NumberFormatSymbols_eu', 'goog.i18n.NumberFormatSymbols_fa', 'goog.i18n.NumberFormatSymbols_fa_u_nu_latn', 'goog.i18n.NumberFormatSymbols_fi', 'goog.i18n.NumberFormatSymbols_fil', 'goog.i18n.NumberFormatSymbols_fr', 'goog.i18n.NumberFormatSymbols_fr_CA', 'goog.i18n.NumberFormatSymbols_ga', 'goog.i18n.NumberFormatSymbols_gl', 'goog.i18n.NumberFormatSymbols_gsw', 'goog.i18n.NumberFormatSymbols_gu', 'goog.i18n.NumberFormatSymbols_haw', 'goog.i18n.NumberFormatSymbols_he', 'goog.i18n.NumberFormatSymbols_hi', 'goog.i18n.NumberFormatSymbols_hr', 'goog.i18n.NumberFormatSymbols_hu', 'goog.i18n.NumberFormatSymbols_hy', 'goog.i18n.NumberFormatSymbols_id', 'goog.i18n.NumberFormatSymbols_in', 'goog.i18n.NumberFormatSymbols_is', 'goog.i18n.NumberFormatSymbols_it', 'goog.i18n.NumberFormatSymbols_iw', 'goog.i18n.NumberFormatSymbols_ja', 'goog.i18n.NumberFormatSymbols_ka', 'goog.i18n.NumberFormatSymbols_kk', 'goog.i18n.NumberFormatSymbols_km', 'goog.i18n.NumberFormatSymbols_kn', 'goog.i18n.NumberFormatSymbols_ko', 'goog.i18n.NumberFormatSymbols_ky', 'goog.i18n.NumberFormatSymbols_ln', 'goog.i18n.NumberFormatSymbols_lo', 'goog.i18n.NumberFormatSymbols_lt', 'goog.i18n.NumberFormatSymbols_lv', 'goog.i18n.NumberFormatSymbols_mk', 'goog.i18n.NumberFormatSymbols_ml', 'goog.i18n.NumberFormatSymbols_mn', 'goog.i18n.NumberFormatSymbols_mo', 'goog.i18n.NumberFormatSymbols_mr', 'goog.i18n.NumberFormatSymbols_mr_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ms', 'goog.i18n.NumberFormatSymbols_mt', 'goog.i18n.NumberFormatSymbols_my', 'goog.i18n.NumberFormatSymbols_my_u_nu_latn', 'goog.i18n.NumberFormatSymbols_nb', 'goog.i18n.NumberFormatSymbols_ne', 'goog.i18n.NumberFormatSymbols_ne_u_nu_latn', 'goog.i18n.NumberFormatSymbols_nl', 'goog.i18n.NumberFormatSymbols_no', 'goog.i18n.NumberFormatSymbols_no_NO', 'goog.i18n.NumberFormatSymbols_or', 'goog.i18n.NumberFormatSymbols_pa', 'goog.i18n.NumberFormatSymbols_pl', 'goog.i18n.NumberFormatSymbols_pt', 'goog.i18n.NumberFormatSymbols_pt_BR', 'goog.i18n.NumberFormatSymbols_pt_PT', 'goog.i18n.NumberFormatSymbols_ro', 'goog.i18n.NumberFormatSymbols_ru', 'goog.i18n.NumberFormatSymbols_sh', 'goog.i18n.NumberFormatSymbols_si', 'goog.i18n.NumberFormatSymbols_sk', 'goog.i18n.NumberFormatSymbols_sl', 'goog.i18n.NumberFormatSymbols_sq', 'goog.i18n.NumberFormatSymbols_sr', 'goog.i18n.NumberFormatSymbols_sr_Latn', 'goog.i18n.NumberFormatSymbols_sv', 'goog.i18n.NumberFormatSymbols_sw', 'goog.i18n.NumberFormatSymbols_ta', 'goog.i18n.NumberFormatSymbols_te', 'goog.i18n.NumberFormatSymbols_th', 'goog.i18n.NumberFormatSymbols_tl', 'goog.i18n.NumberFormatSymbols_tr', 'goog.i18n.NumberFormatSymbols_uk', 'goog.i18n.NumberFormatSymbols_ur', 'goog.i18n.NumberFormatSymbols_uz', 'goog.i18n.NumberFormatSymbols_vi', 'goog.i18n.NumberFormatSymbols_zh', 'goog.i18n.NumberFormatSymbols_zh_CN', 'goog.i18n.NumberFormatSymbols_zh_HK', 'goog.i18n.NumberFormatSymbols_zh_TW', 'goog.i18n.NumberFormatSymbols_zu'], []); +goog.addDependency("i18n/numberformatsymbolsext.js", ['goog.i18n.NumberFormatSymbolsExt', 'goog.i18n.NumberFormatSymbols_af_NA', 'goog.i18n.NumberFormatSymbols_af_ZA', 'goog.i18n.NumberFormatSymbols_agq', 'goog.i18n.NumberFormatSymbols_agq_CM', 'goog.i18n.NumberFormatSymbols_ak', 'goog.i18n.NumberFormatSymbols_ak_GH', 'goog.i18n.NumberFormatSymbols_am_ET', 'goog.i18n.NumberFormatSymbols_ar_001', 'goog.i18n.NumberFormatSymbols_ar_001_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_AE', 'goog.i18n.NumberFormatSymbols_ar_AE_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_BH', 'goog.i18n.NumberFormatSymbols_ar_BH_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_DJ', 'goog.i18n.NumberFormatSymbols_ar_DJ_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_EG', 'goog.i18n.NumberFormatSymbols_ar_EG_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_EH', 'goog.i18n.NumberFormatSymbols_ar_ER', 'goog.i18n.NumberFormatSymbols_ar_ER_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_IL', 'goog.i18n.NumberFormatSymbols_ar_IL_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_IQ', 'goog.i18n.NumberFormatSymbols_ar_IQ_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_JO', 'goog.i18n.NumberFormatSymbols_ar_JO_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_KM', 'goog.i18n.NumberFormatSymbols_ar_KM_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_KW', 'goog.i18n.NumberFormatSymbols_ar_KW_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_LB', 'goog.i18n.NumberFormatSymbols_ar_LB_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_LY', 'goog.i18n.NumberFormatSymbols_ar_MA', 'goog.i18n.NumberFormatSymbols_ar_MR', 'goog.i18n.NumberFormatSymbols_ar_MR_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_OM', 'goog.i18n.NumberFormatSymbols_ar_OM_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_PS', 'goog.i18n.NumberFormatSymbols_ar_PS_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_QA', 'goog.i18n.NumberFormatSymbols_ar_QA_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_SA', 'goog.i18n.NumberFormatSymbols_ar_SA_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_SD', 'goog.i18n.NumberFormatSymbols_ar_SD_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_SO', 'goog.i18n.NumberFormatSymbols_ar_SO_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_SS', 'goog.i18n.NumberFormatSymbols_ar_SS_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_SY', 'goog.i18n.NumberFormatSymbols_ar_SY_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_TD', 'goog.i18n.NumberFormatSymbols_ar_TD_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ar_TN', 'goog.i18n.NumberFormatSymbols_ar_XB', 'goog.i18n.NumberFormatSymbols_ar_YE', 'goog.i18n.NumberFormatSymbols_ar_YE_u_nu_latn', 'goog.i18n.NumberFormatSymbols_as', 'goog.i18n.NumberFormatSymbols_as_u_nu_latn', 'goog.i18n.NumberFormatSymbols_as_IN', 'goog.i18n.NumberFormatSymbols_as_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_asa', 'goog.i18n.NumberFormatSymbols_asa_TZ', 'goog.i18n.NumberFormatSymbols_ast', 'goog.i18n.NumberFormatSymbols_ast_ES', 'goog.i18n.NumberFormatSymbols_az_Cyrl', 'goog.i18n.NumberFormatSymbols_az_Cyrl_AZ', 'goog.i18n.NumberFormatSymbols_az_Latn', 'goog.i18n.NumberFormatSymbols_az_Latn_AZ', 'goog.i18n.NumberFormatSymbols_bas', 'goog.i18n.NumberFormatSymbols_bas_CM', 'goog.i18n.NumberFormatSymbols_be_BY', 'goog.i18n.NumberFormatSymbols_bem', 'goog.i18n.NumberFormatSymbols_bem_ZM', 'goog.i18n.NumberFormatSymbols_bez', 'goog.i18n.NumberFormatSymbols_bez_TZ', 'goog.i18n.NumberFormatSymbols_bg_BG', 'goog.i18n.NumberFormatSymbols_bm', 'goog.i18n.NumberFormatSymbols_bm_ML', 'goog.i18n.NumberFormatSymbols_bn_BD', 'goog.i18n.NumberFormatSymbols_bn_BD_u_nu_latn', 'goog.i18n.NumberFormatSymbols_bn_IN', 'goog.i18n.NumberFormatSymbols_bn_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_bo', 'goog.i18n.NumberFormatSymbols_bo_CN', 'goog.i18n.NumberFormatSymbols_bo_IN', 'goog.i18n.NumberFormatSymbols_br_FR', 'goog.i18n.NumberFormatSymbols_brx', 'goog.i18n.NumberFormatSymbols_brx_IN', 'goog.i18n.NumberFormatSymbols_bs_Cyrl', 'goog.i18n.NumberFormatSymbols_bs_Cyrl_BA', 'goog.i18n.NumberFormatSymbols_bs_Latn', 'goog.i18n.NumberFormatSymbols_bs_Latn_BA', 'goog.i18n.NumberFormatSymbols_ca_AD', 'goog.i18n.NumberFormatSymbols_ca_ES', 'goog.i18n.NumberFormatSymbols_ca_FR', 'goog.i18n.NumberFormatSymbols_ca_IT', 'goog.i18n.NumberFormatSymbols_ccp', 'goog.i18n.NumberFormatSymbols_ccp_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ccp_BD', 'goog.i18n.NumberFormatSymbols_ccp_BD_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ccp_IN', 'goog.i18n.NumberFormatSymbols_ccp_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ce', 'goog.i18n.NumberFormatSymbols_ce_RU', 'goog.i18n.NumberFormatSymbols_cgg', 'goog.i18n.NumberFormatSymbols_cgg_UG', 'goog.i18n.NumberFormatSymbols_chr_US', 'goog.i18n.NumberFormatSymbols_ckb', 'goog.i18n.NumberFormatSymbols_ckb_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ckb_IQ', 'goog.i18n.NumberFormatSymbols_ckb_IQ_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ckb_IR', 'goog.i18n.NumberFormatSymbols_ckb_IR_u_nu_latn', 'goog.i18n.NumberFormatSymbols_cs_CZ', 'goog.i18n.NumberFormatSymbols_cy_GB', 'goog.i18n.NumberFormatSymbols_da_DK', 'goog.i18n.NumberFormatSymbols_da_GL', 'goog.i18n.NumberFormatSymbols_dav', 'goog.i18n.NumberFormatSymbols_dav_KE', 'goog.i18n.NumberFormatSymbols_de_BE', 'goog.i18n.NumberFormatSymbols_de_DE', 'goog.i18n.NumberFormatSymbols_de_IT', 'goog.i18n.NumberFormatSymbols_de_LI', 'goog.i18n.NumberFormatSymbols_de_LU', 'goog.i18n.NumberFormatSymbols_dje', 'goog.i18n.NumberFormatSymbols_dje_NE', 'goog.i18n.NumberFormatSymbols_dsb', 'goog.i18n.NumberFormatSymbols_dsb_DE', 'goog.i18n.NumberFormatSymbols_dua', 'goog.i18n.NumberFormatSymbols_dua_CM', 'goog.i18n.NumberFormatSymbols_dyo', 'goog.i18n.NumberFormatSymbols_dyo_SN', 'goog.i18n.NumberFormatSymbols_dz', 'goog.i18n.NumberFormatSymbols_dz_u_nu_latn', 'goog.i18n.NumberFormatSymbols_dz_BT', 'goog.i18n.NumberFormatSymbols_dz_BT_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ebu', 'goog.i18n.NumberFormatSymbols_ebu_KE', 'goog.i18n.NumberFormatSymbols_ee', 'goog.i18n.NumberFormatSymbols_ee_GH', 'goog.i18n.NumberFormatSymbols_ee_TG', 'goog.i18n.NumberFormatSymbols_el_CY', 'goog.i18n.NumberFormatSymbols_el_GR', 'goog.i18n.NumberFormatSymbols_en_001', 'goog.i18n.NumberFormatSymbols_en_150', 'goog.i18n.NumberFormatSymbols_en_AG', 'goog.i18n.NumberFormatSymbols_en_AI', 'goog.i18n.NumberFormatSymbols_en_AS', 'goog.i18n.NumberFormatSymbols_en_AT', 'goog.i18n.NumberFormatSymbols_en_BB', 'goog.i18n.NumberFormatSymbols_en_BE', 'goog.i18n.NumberFormatSymbols_en_BI', 'goog.i18n.NumberFormatSymbols_en_BM', 'goog.i18n.NumberFormatSymbols_en_BS', 'goog.i18n.NumberFormatSymbols_en_BW', 'goog.i18n.NumberFormatSymbols_en_BZ', 'goog.i18n.NumberFormatSymbols_en_CC', 'goog.i18n.NumberFormatSymbols_en_CH', 'goog.i18n.NumberFormatSymbols_en_CK', 'goog.i18n.NumberFormatSymbols_en_CM', 'goog.i18n.NumberFormatSymbols_en_CX', 'goog.i18n.NumberFormatSymbols_en_CY', 'goog.i18n.NumberFormatSymbols_en_DE', 'goog.i18n.NumberFormatSymbols_en_DG', 'goog.i18n.NumberFormatSymbols_en_DK', 'goog.i18n.NumberFormatSymbols_en_DM', 'goog.i18n.NumberFormatSymbols_en_ER', 'goog.i18n.NumberFormatSymbols_en_FI', 'goog.i18n.NumberFormatSymbols_en_FJ', 'goog.i18n.NumberFormatSymbols_en_FK', 'goog.i18n.NumberFormatSymbols_en_FM', 'goog.i18n.NumberFormatSymbols_en_GD', 'goog.i18n.NumberFormatSymbols_en_GG', 'goog.i18n.NumberFormatSymbols_en_GH', 'goog.i18n.NumberFormatSymbols_en_GI', 'goog.i18n.NumberFormatSymbols_en_GM', 'goog.i18n.NumberFormatSymbols_en_GU', 'goog.i18n.NumberFormatSymbols_en_GY', 'goog.i18n.NumberFormatSymbols_en_HK', 'goog.i18n.NumberFormatSymbols_en_IL', 'goog.i18n.NumberFormatSymbols_en_IM', 'goog.i18n.NumberFormatSymbols_en_IO', 'goog.i18n.NumberFormatSymbols_en_JE', 'goog.i18n.NumberFormatSymbols_en_JM', 'goog.i18n.NumberFormatSymbols_en_KE', 'goog.i18n.NumberFormatSymbols_en_KI', 'goog.i18n.NumberFormatSymbols_en_KN', 'goog.i18n.NumberFormatSymbols_en_KY', 'goog.i18n.NumberFormatSymbols_en_LC', 'goog.i18n.NumberFormatSymbols_en_LR', 'goog.i18n.NumberFormatSymbols_en_LS', 'goog.i18n.NumberFormatSymbols_en_MG', 'goog.i18n.NumberFormatSymbols_en_MH', 'goog.i18n.NumberFormatSymbols_en_MO', 'goog.i18n.NumberFormatSymbols_en_MP', 'goog.i18n.NumberFormatSymbols_en_MS', 'goog.i18n.NumberFormatSymbols_en_MT', 'goog.i18n.NumberFormatSymbols_en_MU', 'goog.i18n.NumberFormatSymbols_en_MW', 'goog.i18n.NumberFormatSymbols_en_MY', 'goog.i18n.NumberFormatSymbols_en_NA', 'goog.i18n.NumberFormatSymbols_en_NF', 'goog.i18n.NumberFormatSymbols_en_NG', 'goog.i18n.NumberFormatSymbols_en_NL', 'goog.i18n.NumberFormatSymbols_en_NR', 'goog.i18n.NumberFormatSymbols_en_NU', 'goog.i18n.NumberFormatSymbols_en_NZ', 'goog.i18n.NumberFormatSymbols_en_PG', 'goog.i18n.NumberFormatSymbols_en_PH', 'goog.i18n.NumberFormatSymbols_en_PK', 'goog.i18n.NumberFormatSymbols_en_PN', 'goog.i18n.NumberFormatSymbols_en_PR', 'goog.i18n.NumberFormatSymbols_en_PW', 'goog.i18n.NumberFormatSymbols_en_RW', 'goog.i18n.NumberFormatSymbols_en_SB', 'goog.i18n.NumberFormatSymbols_en_SC', 'goog.i18n.NumberFormatSymbols_en_SD', 'goog.i18n.NumberFormatSymbols_en_SE', 'goog.i18n.NumberFormatSymbols_en_SH', 'goog.i18n.NumberFormatSymbols_en_SI', 'goog.i18n.NumberFormatSymbols_en_SL', 'goog.i18n.NumberFormatSymbols_en_SS', 'goog.i18n.NumberFormatSymbols_en_SX', 'goog.i18n.NumberFormatSymbols_en_SZ', 'goog.i18n.NumberFormatSymbols_en_TC', 'goog.i18n.NumberFormatSymbols_en_TK', 'goog.i18n.NumberFormatSymbols_en_TO', 'goog.i18n.NumberFormatSymbols_en_TT', 'goog.i18n.NumberFormatSymbols_en_TV', 'goog.i18n.NumberFormatSymbols_en_TZ', 'goog.i18n.NumberFormatSymbols_en_UG', 'goog.i18n.NumberFormatSymbols_en_UM', 'goog.i18n.NumberFormatSymbols_en_US_POSIX', 'goog.i18n.NumberFormatSymbols_en_VC', 'goog.i18n.NumberFormatSymbols_en_VG', 'goog.i18n.NumberFormatSymbols_en_VI', 'goog.i18n.NumberFormatSymbols_en_VU', 'goog.i18n.NumberFormatSymbols_en_WS', 'goog.i18n.NumberFormatSymbols_en_XA', 'goog.i18n.NumberFormatSymbols_en_ZM', 'goog.i18n.NumberFormatSymbols_en_ZW', 'goog.i18n.NumberFormatSymbols_eo', 'goog.i18n.NumberFormatSymbols_es_AR', 'goog.i18n.NumberFormatSymbols_es_BO', 'goog.i18n.NumberFormatSymbols_es_BR', 'goog.i18n.NumberFormatSymbols_es_BZ', 'goog.i18n.NumberFormatSymbols_es_CL', 'goog.i18n.NumberFormatSymbols_es_CO', 'goog.i18n.NumberFormatSymbols_es_CR', 'goog.i18n.NumberFormatSymbols_es_CU', 'goog.i18n.NumberFormatSymbols_es_DO', 'goog.i18n.NumberFormatSymbols_es_EA', 'goog.i18n.NumberFormatSymbols_es_EC', 'goog.i18n.NumberFormatSymbols_es_GQ', 'goog.i18n.NumberFormatSymbols_es_GT', 'goog.i18n.NumberFormatSymbols_es_HN', 'goog.i18n.NumberFormatSymbols_es_IC', 'goog.i18n.NumberFormatSymbols_es_NI', 'goog.i18n.NumberFormatSymbols_es_PA', 'goog.i18n.NumberFormatSymbols_es_PE', 'goog.i18n.NumberFormatSymbols_es_PH', 'goog.i18n.NumberFormatSymbols_es_PR', 'goog.i18n.NumberFormatSymbols_es_PY', 'goog.i18n.NumberFormatSymbols_es_SV', 'goog.i18n.NumberFormatSymbols_es_UY', 'goog.i18n.NumberFormatSymbols_es_VE', 'goog.i18n.NumberFormatSymbols_et_EE', 'goog.i18n.NumberFormatSymbols_eu_ES', 'goog.i18n.NumberFormatSymbols_ewo', 'goog.i18n.NumberFormatSymbols_ewo_CM', 'goog.i18n.NumberFormatSymbols_fa_AF', 'goog.i18n.NumberFormatSymbols_fa_AF_u_nu_latn', 'goog.i18n.NumberFormatSymbols_fa_IR', 'goog.i18n.NumberFormatSymbols_fa_IR_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ff', 'goog.i18n.NumberFormatSymbols_ff_CM', 'goog.i18n.NumberFormatSymbols_ff_GN', 'goog.i18n.NumberFormatSymbols_ff_MR', 'goog.i18n.NumberFormatSymbols_ff_SN', 'goog.i18n.NumberFormatSymbols_fi_FI', 'goog.i18n.NumberFormatSymbols_fil_PH', 'goog.i18n.NumberFormatSymbols_fo', 'goog.i18n.NumberFormatSymbols_fo_DK', 'goog.i18n.NumberFormatSymbols_fo_FO', 'goog.i18n.NumberFormatSymbols_fr_BE', 'goog.i18n.NumberFormatSymbols_fr_BF', 'goog.i18n.NumberFormatSymbols_fr_BI', 'goog.i18n.NumberFormatSymbols_fr_BJ', 'goog.i18n.NumberFormatSymbols_fr_BL', 'goog.i18n.NumberFormatSymbols_fr_CD', 'goog.i18n.NumberFormatSymbols_fr_CF', 'goog.i18n.NumberFormatSymbols_fr_CG', 'goog.i18n.NumberFormatSymbols_fr_CH', 'goog.i18n.NumberFormatSymbols_fr_CI', 'goog.i18n.NumberFormatSymbols_fr_CM', 'goog.i18n.NumberFormatSymbols_fr_DJ', 'goog.i18n.NumberFormatSymbols_fr_DZ', 'goog.i18n.NumberFormatSymbols_fr_FR', 'goog.i18n.NumberFormatSymbols_fr_GA', 'goog.i18n.NumberFormatSymbols_fr_GF', 'goog.i18n.NumberFormatSymbols_fr_GN', 'goog.i18n.NumberFormatSymbols_fr_GP', 'goog.i18n.NumberFormatSymbols_fr_GQ', 'goog.i18n.NumberFormatSymbols_fr_HT', 'goog.i18n.NumberFormatSymbols_fr_KM', 'goog.i18n.NumberFormatSymbols_fr_LU', 'goog.i18n.NumberFormatSymbols_fr_MA', 'goog.i18n.NumberFormatSymbols_fr_MC', 'goog.i18n.NumberFormatSymbols_fr_MF', 'goog.i18n.NumberFormatSymbols_fr_MG', 'goog.i18n.NumberFormatSymbols_fr_ML', 'goog.i18n.NumberFormatSymbols_fr_MQ', 'goog.i18n.NumberFormatSymbols_fr_MR', 'goog.i18n.NumberFormatSymbols_fr_MU', 'goog.i18n.NumberFormatSymbols_fr_NC', 'goog.i18n.NumberFormatSymbols_fr_NE', 'goog.i18n.NumberFormatSymbols_fr_PF', 'goog.i18n.NumberFormatSymbols_fr_PM', 'goog.i18n.NumberFormatSymbols_fr_RE', 'goog.i18n.NumberFormatSymbols_fr_RW', 'goog.i18n.NumberFormatSymbols_fr_SC', 'goog.i18n.NumberFormatSymbols_fr_SN', 'goog.i18n.NumberFormatSymbols_fr_SY', 'goog.i18n.NumberFormatSymbols_fr_TD', 'goog.i18n.NumberFormatSymbols_fr_TG', 'goog.i18n.NumberFormatSymbols_fr_TN', 'goog.i18n.NumberFormatSymbols_fr_VU', 'goog.i18n.NumberFormatSymbols_fr_WF', 'goog.i18n.NumberFormatSymbols_fr_YT', 'goog.i18n.NumberFormatSymbols_fur', 'goog.i18n.NumberFormatSymbols_fur_IT', 'goog.i18n.NumberFormatSymbols_fy', 'goog.i18n.NumberFormatSymbols_fy_NL', 'goog.i18n.NumberFormatSymbols_ga_IE', 'goog.i18n.NumberFormatSymbols_gd', 'goog.i18n.NumberFormatSymbols_gd_GB', 'goog.i18n.NumberFormatSymbols_gl_ES', 'goog.i18n.NumberFormatSymbols_gsw_CH', 'goog.i18n.NumberFormatSymbols_gsw_FR', 'goog.i18n.NumberFormatSymbols_gsw_LI', 'goog.i18n.NumberFormatSymbols_gu_IN', 'goog.i18n.NumberFormatSymbols_guz', 'goog.i18n.NumberFormatSymbols_guz_KE', 'goog.i18n.NumberFormatSymbols_gv', 'goog.i18n.NumberFormatSymbols_gv_IM', 'goog.i18n.NumberFormatSymbols_ha', 'goog.i18n.NumberFormatSymbols_ha_GH', 'goog.i18n.NumberFormatSymbols_ha_NE', 'goog.i18n.NumberFormatSymbols_ha_NG', 'goog.i18n.NumberFormatSymbols_haw_US', 'goog.i18n.NumberFormatSymbols_he_IL', 'goog.i18n.NumberFormatSymbols_hi_IN', 'goog.i18n.NumberFormatSymbols_hr_BA', 'goog.i18n.NumberFormatSymbols_hr_HR', 'goog.i18n.NumberFormatSymbols_hsb', 'goog.i18n.NumberFormatSymbols_hsb_DE', 'goog.i18n.NumberFormatSymbols_hu_HU', 'goog.i18n.NumberFormatSymbols_hy_AM', 'goog.i18n.NumberFormatSymbols_id_ID', 'goog.i18n.NumberFormatSymbols_ig', 'goog.i18n.NumberFormatSymbols_ig_NG', 'goog.i18n.NumberFormatSymbols_ii', 'goog.i18n.NumberFormatSymbols_ii_CN', 'goog.i18n.NumberFormatSymbols_is_IS', 'goog.i18n.NumberFormatSymbols_it_CH', 'goog.i18n.NumberFormatSymbols_it_IT', 'goog.i18n.NumberFormatSymbols_it_SM', 'goog.i18n.NumberFormatSymbols_it_VA', 'goog.i18n.NumberFormatSymbols_ja_JP', 'goog.i18n.NumberFormatSymbols_jgo', 'goog.i18n.NumberFormatSymbols_jgo_CM', 'goog.i18n.NumberFormatSymbols_jmc', 'goog.i18n.NumberFormatSymbols_jmc_TZ', 'goog.i18n.NumberFormatSymbols_ka_GE', 'goog.i18n.NumberFormatSymbols_kab', 'goog.i18n.NumberFormatSymbols_kab_DZ', 'goog.i18n.NumberFormatSymbols_kam', 'goog.i18n.NumberFormatSymbols_kam_KE', 'goog.i18n.NumberFormatSymbols_kde', 'goog.i18n.NumberFormatSymbols_kde_TZ', 'goog.i18n.NumberFormatSymbols_kea', 'goog.i18n.NumberFormatSymbols_kea_CV', 'goog.i18n.NumberFormatSymbols_khq', 'goog.i18n.NumberFormatSymbols_khq_ML', 'goog.i18n.NumberFormatSymbols_ki', 'goog.i18n.NumberFormatSymbols_ki_KE', 'goog.i18n.NumberFormatSymbols_kk_KZ', 'goog.i18n.NumberFormatSymbols_kkj', 'goog.i18n.NumberFormatSymbols_kkj_CM', 'goog.i18n.NumberFormatSymbols_kl', 'goog.i18n.NumberFormatSymbols_kl_GL', 'goog.i18n.NumberFormatSymbols_kln', 'goog.i18n.NumberFormatSymbols_kln_KE', 'goog.i18n.NumberFormatSymbols_km_KH', 'goog.i18n.NumberFormatSymbols_kn_IN', 'goog.i18n.NumberFormatSymbols_ko_KP', 'goog.i18n.NumberFormatSymbols_ko_KR', 'goog.i18n.NumberFormatSymbols_kok', 'goog.i18n.NumberFormatSymbols_kok_IN', 'goog.i18n.NumberFormatSymbols_ks', 'goog.i18n.NumberFormatSymbols_ks_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ks_IN', 'goog.i18n.NumberFormatSymbols_ks_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ksb', 'goog.i18n.NumberFormatSymbols_ksb_TZ', 'goog.i18n.NumberFormatSymbols_ksf', 'goog.i18n.NumberFormatSymbols_ksf_CM', 'goog.i18n.NumberFormatSymbols_ksh', 'goog.i18n.NumberFormatSymbols_ksh_DE', 'goog.i18n.NumberFormatSymbols_kw', 'goog.i18n.NumberFormatSymbols_kw_GB', 'goog.i18n.NumberFormatSymbols_ky_KG', 'goog.i18n.NumberFormatSymbols_lag', 'goog.i18n.NumberFormatSymbols_lag_TZ', 'goog.i18n.NumberFormatSymbols_lb', 'goog.i18n.NumberFormatSymbols_lb_LU', 'goog.i18n.NumberFormatSymbols_lg', 'goog.i18n.NumberFormatSymbols_lg_UG', 'goog.i18n.NumberFormatSymbols_lkt', 'goog.i18n.NumberFormatSymbols_lkt_US', 'goog.i18n.NumberFormatSymbols_ln_AO', 'goog.i18n.NumberFormatSymbols_ln_CD', 'goog.i18n.NumberFormatSymbols_ln_CF', 'goog.i18n.NumberFormatSymbols_ln_CG', 'goog.i18n.NumberFormatSymbols_lo_LA', 'goog.i18n.NumberFormatSymbols_lrc', 'goog.i18n.NumberFormatSymbols_lrc_u_nu_latn', 'goog.i18n.NumberFormatSymbols_lrc_IQ', 'goog.i18n.NumberFormatSymbols_lrc_IQ_u_nu_latn', 'goog.i18n.NumberFormatSymbols_lrc_IR', 'goog.i18n.NumberFormatSymbols_lrc_IR_u_nu_latn', 'goog.i18n.NumberFormatSymbols_lt_LT', 'goog.i18n.NumberFormatSymbols_lu', 'goog.i18n.NumberFormatSymbols_lu_CD', 'goog.i18n.NumberFormatSymbols_luo', 'goog.i18n.NumberFormatSymbols_luo_KE', 'goog.i18n.NumberFormatSymbols_luy', 'goog.i18n.NumberFormatSymbols_luy_KE', 'goog.i18n.NumberFormatSymbols_lv_LV', 'goog.i18n.NumberFormatSymbols_mas', 'goog.i18n.NumberFormatSymbols_mas_KE', 'goog.i18n.NumberFormatSymbols_mas_TZ', 'goog.i18n.NumberFormatSymbols_mer', 'goog.i18n.NumberFormatSymbols_mer_KE', 'goog.i18n.NumberFormatSymbols_mfe', 'goog.i18n.NumberFormatSymbols_mfe_MU', 'goog.i18n.NumberFormatSymbols_mg', 'goog.i18n.NumberFormatSymbols_mg_MG', 'goog.i18n.NumberFormatSymbols_mgh', 'goog.i18n.NumberFormatSymbols_mgh_MZ', 'goog.i18n.NumberFormatSymbols_mgo', 'goog.i18n.NumberFormatSymbols_mgo_CM', 'goog.i18n.NumberFormatSymbols_mk_MK', 'goog.i18n.NumberFormatSymbols_ml_IN', 'goog.i18n.NumberFormatSymbols_mn_MN', 'goog.i18n.NumberFormatSymbols_mr_IN', 'goog.i18n.NumberFormatSymbols_mr_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ms_BN', 'goog.i18n.NumberFormatSymbols_ms_MY', 'goog.i18n.NumberFormatSymbols_ms_SG', 'goog.i18n.NumberFormatSymbols_mt_MT', 'goog.i18n.NumberFormatSymbols_mua', 'goog.i18n.NumberFormatSymbols_mua_CM', 'goog.i18n.NumberFormatSymbols_my_MM', 'goog.i18n.NumberFormatSymbols_my_MM_u_nu_latn', 'goog.i18n.NumberFormatSymbols_mzn', 'goog.i18n.NumberFormatSymbols_mzn_u_nu_latn', 'goog.i18n.NumberFormatSymbols_mzn_IR', 'goog.i18n.NumberFormatSymbols_mzn_IR_u_nu_latn', 'goog.i18n.NumberFormatSymbols_naq', 'goog.i18n.NumberFormatSymbols_naq_NA', 'goog.i18n.NumberFormatSymbols_nb_NO', 'goog.i18n.NumberFormatSymbols_nb_SJ', 'goog.i18n.NumberFormatSymbols_nd', 'goog.i18n.NumberFormatSymbols_nd_ZW', 'goog.i18n.NumberFormatSymbols_nds', 'goog.i18n.NumberFormatSymbols_nds_DE', 'goog.i18n.NumberFormatSymbols_nds_NL', 'goog.i18n.NumberFormatSymbols_ne_IN', 'goog.i18n.NumberFormatSymbols_ne_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ne_NP', 'goog.i18n.NumberFormatSymbols_ne_NP_u_nu_latn', 'goog.i18n.NumberFormatSymbols_nl_AW', 'goog.i18n.NumberFormatSymbols_nl_BE', 'goog.i18n.NumberFormatSymbols_nl_BQ', 'goog.i18n.NumberFormatSymbols_nl_CW', 'goog.i18n.NumberFormatSymbols_nl_NL', 'goog.i18n.NumberFormatSymbols_nl_SR', 'goog.i18n.NumberFormatSymbols_nl_SX', 'goog.i18n.NumberFormatSymbols_nmg', 'goog.i18n.NumberFormatSymbols_nmg_CM', 'goog.i18n.NumberFormatSymbols_nn', 'goog.i18n.NumberFormatSymbols_nn_NO', 'goog.i18n.NumberFormatSymbols_nnh', 'goog.i18n.NumberFormatSymbols_nnh_CM', 'goog.i18n.NumberFormatSymbols_nus', 'goog.i18n.NumberFormatSymbols_nus_SS', 'goog.i18n.NumberFormatSymbols_nyn', 'goog.i18n.NumberFormatSymbols_nyn_UG', 'goog.i18n.NumberFormatSymbols_om', 'goog.i18n.NumberFormatSymbols_om_ET', 'goog.i18n.NumberFormatSymbols_om_KE', 'goog.i18n.NumberFormatSymbols_or_IN', 'goog.i18n.NumberFormatSymbols_os', 'goog.i18n.NumberFormatSymbols_os_GE', 'goog.i18n.NumberFormatSymbols_os_RU', 'goog.i18n.NumberFormatSymbols_pa_Arab', 'goog.i18n.NumberFormatSymbols_pa_Arab_u_nu_latn', 'goog.i18n.NumberFormatSymbols_pa_Arab_PK', 'goog.i18n.NumberFormatSymbols_pa_Arab_PK_u_nu_latn', 'goog.i18n.NumberFormatSymbols_pa_Guru', 'goog.i18n.NumberFormatSymbols_pa_Guru_IN', 'goog.i18n.NumberFormatSymbols_pl_PL', 'goog.i18n.NumberFormatSymbols_ps', 'goog.i18n.NumberFormatSymbols_ps_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ps_AF', 'goog.i18n.NumberFormatSymbols_ps_AF_u_nu_latn', 'goog.i18n.NumberFormatSymbols_pt_AO', 'goog.i18n.NumberFormatSymbols_pt_CH', 'goog.i18n.NumberFormatSymbols_pt_CV', 'goog.i18n.NumberFormatSymbols_pt_GQ', 'goog.i18n.NumberFormatSymbols_pt_GW', 'goog.i18n.NumberFormatSymbols_pt_LU', 'goog.i18n.NumberFormatSymbols_pt_MO', 'goog.i18n.NumberFormatSymbols_pt_MZ', 'goog.i18n.NumberFormatSymbols_pt_ST', 'goog.i18n.NumberFormatSymbols_pt_TL', 'goog.i18n.NumberFormatSymbols_qu', 'goog.i18n.NumberFormatSymbols_qu_BO', 'goog.i18n.NumberFormatSymbols_qu_EC', 'goog.i18n.NumberFormatSymbols_qu_PE', 'goog.i18n.NumberFormatSymbols_rm', 'goog.i18n.NumberFormatSymbols_rm_CH', 'goog.i18n.NumberFormatSymbols_rn', 'goog.i18n.NumberFormatSymbols_rn_BI', 'goog.i18n.NumberFormatSymbols_ro_MD', 'goog.i18n.NumberFormatSymbols_ro_RO', 'goog.i18n.NumberFormatSymbols_rof', 'goog.i18n.NumberFormatSymbols_rof_TZ', 'goog.i18n.NumberFormatSymbols_ru_BY', 'goog.i18n.NumberFormatSymbols_ru_KG', 'goog.i18n.NumberFormatSymbols_ru_KZ', 'goog.i18n.NumberFormatSymbols_ru_MD', 'goog.i18n.NumberFormatSymbols_ru_RU', 'goog.i18n.NumberFormatSymbols_ru_UA', 'goog.i18n.NumberFormatSymbols_rw', 'goog.i18n.NumberFormatSymbols_rw_RW', 'goog.i18n.NumberFormatSymbols_rwk', 'goog.i18n.NumberFormatSymbols_rwk_TZ', 'goog.i18n.NumberFormatSymbols_sah', 'goog.i18n.NumberFormatSymbols_sah_RU', 'goog.i18n.NumberFormatSymbols_saq', 'goog.i18n.NumberFormatSymbols_saq_KE', 'goog.i18n.NumberFormatSymbols_sbp', 'goog.i18n.NumberFormatSymbols_sbp_TZ', 'goog.i18n.NumberFormatSymbols_se', 'goog.i18n.NumberFormatSymbols_se_FI', 'goog.i18n.NumberFormatSymbols_se_NO', 'goog.i18n.NumberFormatSymbols_se_SE', 'goog.i18n.NumberFormatSymbols_seh', 'goog.i18n.NumberFormatSymbols_seh_MZ', 'goog.i18n.NumberFormatSymbols_ses', 'goog.i18n.NumberFormatSymbols_ses_ML', 'goog.i18n.NumberFormatSymbols_sg', 'goog.i18n.NumberFormatSymbols_sg_CF', 'goog.i18n.NumberFormatSymbols_shi', 'goog.i18n.NumberFormatSymbols_shi_Latn', 'goog.i18n.NumberFormatSymbols_shi_Latn_MA', 'goog.i18n.NumberFormatSymbols_shi_Tfng', 'goog.i18n.NumberFormatSymbols_shi_Tfng_MA', 'goog.i18n.NumberFormatSymbols_si_LK', 'goog.i18n.NumberFormatSymbols_sk_SK', 'goog.i18n.NumberFormatSymbols_sl_SI', 'goog.i18n.NumberFormatSymbols_smn', 'goog.i18n.NumberFormatSymbols_smn_FI', 'goog.i18n.NumberFormatSymbols_sn', 'goog.i18n.NumberFormatSymbols_sn_ZW', 'goog.i18n.NumberFormatSymbols_so', 'goog.i18n.NumberFormatSymbols_so_DJ', 'goog.i18n.NumberFormatSymbols_so_ET', 'goog.i18n.NumberFormatSymbols_so_KE', 'goog.i18n.NumberFormatSymbols_so_SO', 'goog.i18n.NumberFormatSymbols_sq_AL', 'goog.i18n.NumberFormatSymbols_sq_MK', 'goog.i18n.NumberFormatSymbols_sq_XK', 'goog.i18n.NumberFormatSymbols_sr_Cyrl', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_BA', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_ME', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_RS', 'goog.i18n.NumberFormatSymbols_sr_Cyrl_XK', 'goog.i18n.NumberFormatSymbols_sr_Latn_BA', 'goog.i18n.NumberFormatSymbols_sr_Latn_ME', 'goog.i18n.NumberFormatSymbols_sr_Latn_RS', 'goog.i18n.NumberFormatSymbols_sr_Latn_XK', 'goog.i18n.NumberFormatSymbols_sv_AX', 'goog.i18n.NumberFormatSymbols_sv_FI', 'goog.i18n.NumberFormatSymbols_sv_SE', 'goog.i18n.NumberFormatSymbols_sw_CD', 'goog.i18n.NumberFormatSymbols_sw_KE', 'goog.i18n.NumberFormatSymbols_sw_TZ', 'goog.i18n.NumberFormatSymbols_sw_UG', 'goog.i18n.NumberFormatSymbols_ta_IN', 'goog.i18n.NumberFormatSymbols_ta_LK', 'goog.i18n.NumberFormatSymbols_ta_MY', 'goog.i18n.NumberFormatSymbols_ta_SG', 'goog.i18n.NumberFormatSymbols_te_IN', 'goog.i18n.NumberFormatSymbols_teo', 'goog.i18n.NumberFormatSymbols_teo_KE', 'goog.i18n.NumberFormatSymbols_teo_UG', 'goog.i18n.NumberFormatSymbols_tg', 'goog.i18n.NumberFormatSymbols_tg_TJ', 'goog.i18n.NumberFormatSymbols_th_TH', 'goog.i18n.NumberFormatSymbols_ti', 'goog.i18n.NumberFormatSymbols_ti_ER', 'goog.i18n.NumberFormatSymbols_ti_ET', 'goog.i18n.NumberFormatSymbols_to', 'goog.i18n.NumberFormatSymbols_to_TO', 'goog.i18n.NumberFormatSymbols_tr_CY', 'goog.i18n.NumberFormatSymbols_tr_TR', 'goog.i18n.NumberFormatSymbols_tt', 'goog.i18n.NumberFormatSymbols_tt_RU', 'goog.i18n.NumberFormatSymbols_twq', 'goog.i18n.NumberFormatSymbols_twq_NE', 'goog.i18n.NumberFormatSymbols_tzm', 'goog.i18n.NumberFormatSymbols_tzm_MA', 'goog.i18n.NumberFormatSymbols_ug', 'goog.i18n.NumberFormatSymbols_ug_CN', 'goog.i18n.NumberFormatSymbols_uk_UA', 'goog.i18n.NumberFormatSymbols_ur_IN', 'goog.i18n.NumberFormatSymbols_ur_IN_u_nu_latn', 'goog.i18n.NumberFormatSymbols_ur_PK', 'goog.i18n.NumberFormatSymbols_uz_Arab', 'goog.i18n.NumberFormatSymbols_uz_Arab_u_nu_latn', 'goog.i18n.NumberFormatSymbols_uz_Arab_AF', 'goog.i18n.NumberFormatSymbols_uz_Arab_AF_u_nu_latn', 'goog.i18n.NumberFormatSymbols_uz_Cyrl', 'goog.i18n.NumberFormatSymbols_uz_Cyrl_UZ', 'goog.i18n.NumberFormatSymbols_uz_Latn', 'goog.i18n.NumberFormatSymbols_uz_Latn_UZ', 'goog.i18n.NumberFormatSymbols_vai', 'goog.i18n.NumberFormatSymbols_vai_Latn', 'goog.i18n.NumberFormatSymbols_vai_Latn_LR', 'goog.i18n.NumberFormatSymbols_vai_Vaii', 'goog.i18n.NumberFormatSymbols_vai_Vaii_LR', 'goog.i18n.NumberFormatSymbols_vi_VN', 'goog.i18n.NumberFormatSymbols_vun', 'goog.i18n.NumberFormatSymbols_vun_TZ', 'goog.i18n.NumberFormatSymbols_wae', 'goog.i18n.NumberFormatSymbols_wae_CH', 'goog.i18n.NumberFormatSymbols_wo', 'goog.i18n.NumberFormatSymbols_wo_SN', 'goog.i18n.NumberFormatSymbols_xog', 'goog.i18n.NumberFormatSymbols_xog_UG', 'goog.i18n.NumberFormatSymbols_yav', 'goog.i18n.NumberFormatSymbols_yav_CM', 'goog.i18n.NumberFormatSymbols_yi', 'goog.i18n.NumberFormatSymbols_yi_001', 'goog.i18n.NumberFormatSymbols_yo', 'goog.i18n.NumberFormatSymbols_yo_BJ', 'goog.i18n.NumberFormatSymbols_yo_NG', 'goog.i18n.NumberFormatSymbols_yue', 'goog.i18n.NumberFormatSymbols_yue_Hans', 'goog.i18n.NumberFormatSymbols_yue_Hans_CN', 'goog.i18n.NumberFormatSymbols_yue_Hant', 'goog.i18n.NumberFormatSymbols_yue_Hant_HK', 'goog.i18n.NumberFormatSymbols_zgh', 'goog.i18n.NumberFormatSymbols_zgh_MA', 'goog.i18n.NumberFormatSymbols_zh_Hans', 'goog.i18n.NumberFormatSymbols_zh_Hans_CN', 'goog.i18n.NumberFormatSymbols_zh_Hans_HK', 'goog.i18n.NumberFormatSymbols_zh_Hans_MO', 'goog.i18n.NumberFormatSymbols_zh_Hans_SG', 'goog.i18n.NumberFormatSymbols_zh_Hant', 'goog.i18n.NumberFormatSymbols_zh_Hant_HK', 'goog.i18n.NumberFormatSymbols_zh_Hant_MO', 'goog.i18n.NumberFormatSymbols_zh_Hant_TW', 'goog.i18n.NumberFormatSymbols_zu_ZA'], ['goog.i18n.NumberFormatSymbols', 'goog.i18n.NumberFormatSymbols_u_nu_latn']); +goog.addDependency("i18n/ordinalrules.js", ['goog.i18n.ordinalRules'], []); +goog.addDependency("i18n/pluralrules.js", ['goog.i18n.pluralRules'], []); +goog.addDependency("i18n/pluralrules_test.js", ['goog.i18n.pluralRulesTest'], ['goog.i18n.pluralRules', 'goog.testing.jsunit']); +goog.addDependency("i18n/timezone.js", ['goog.i18n.TimeZone'], ['goog.array', 'goog.date.DateLike', 'goog.object', 'goog.string']); +goog.addDependency("i18n/timezone_test.js", ['goog.i18n.TimeZoneTest'], ['goog.i18n.TimeZone', 'goog.testing.jsunit']); +goog.addDependency("i18n/uchar.js", ['goog.i18n.uChar'], []); +goog.addDependency("i18n/uchar/localnamefetcher.js", ['goog.i18n.uChar.LocalNameFetcher'], ['goog.i18n.uChar.NameFetcher', 'goog.i18n.uCharNames', 'goog.log']); +goog.addDependency("i18n/uchar/localnamefetcher_test.js", ['goog.i18n.uChar.LocalNameFetcherTest'], ['goog.i18n.uChar.LocalNameFetcher', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("i18n/uchar/namefetcher.js", ['goog.i18n.uChar.NameFetcher'], []); +goog.addDependency("i18n/uchar/remotenamefetcher.js", ['goog.i18n.uChar.RemoteNameFetcher'], ['goog.Disposable', 'goog.Uri', 'goog.events', 'goog.i18n.uChar', 'goog.i18n.uChar.NameFetcher', 'goog.log', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.structs.Map']); +goog.addDependency("i18n/uchar/remotenamefetcher_test.js", ['goog.i18n.uChar.RemoteNameFetcherTest'], ['goog.i18n.uChar.RemoteNameFetcher', 'goog.net.XhrIo', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']); +goog.addDependency("i18n/uchar_test.js", ['goog.i18n.uCharTest'], ['goog.i18n.uChar', 'goog.testing.jsunit']); +goog.addDependency("i18n/ucharnames.js", ['goog.i18n.uCharNames'], ['goog.i18n.uChar']); +goog.addDependency("i18n/ucharnames_test.js", ['goog.i18n.uCharNamesTest'], ['goog.i18n.uCharNames', 'goog.testing.jsunit']); +goog.addDependency("iter/es6.js", [], []); +goog.addDependency("iter/es6_test.js", [], ['goog.testing.jsunit']); +goog.addDependency("iter/iter.js", ['goog.iter', 'goog.iter.Iterable', 'goog.iter.Iterator', 'goog.iter.StopIteration'], ['goog.array', 'goog.asserts', 'goog.functions', 'goog.math']); +goog.addDependency("iter/iter_test.js", ['goog.iterTest'], ['goog.iter', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.testing.jsunit']); +goog.addDependency("json/evaljsonprocessor.js", ['goog.json.EvalJsonProcessor'], ['goog.json', 'goog.json.Processor', 'goog.json.Serializer']); +goog.addDependency("json/hybrid.js", ['goog.json.hybrid'], ['goog.asserts', 'goog.json']); +goog.addDependency("json/hybrid_test.js", ['goog.json.hybridTest'], ['goog.json', 'goog.json.hybrid', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("json/json.js", ['goog.json', 'goog.json.Replacer', 'goog.json.Reviver', 'goog.json.Serializer'], []); +goog.addDependency("json/json_perf.js", ['goog.jsonPerf'], ['goog.dom', 'goog.json', 'goog.math', 'goog.string', 'goog.testing.PerformanceTable', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("json/json_test.js", ['goog.jsonTest'], ['goog.functions', 'goog.json', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("json/nativejsonprocessor.js", ['goog.json.NativeJsonProcessor'], ['goog.asserts', 'goog.json.Processor']); +goog.addDependency("json/processor.js", ['goog.json.Processor'], ['goog.string.Parser', 'goog.string.Stringifier']); +goog.addDependency("json/processor_test.js", ['goog.json.processorTest'], ['goog.json.EvalJsonProcessor', 'goog.json.NativeJsonProcessor', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("labs/dom/pagevisibilitymonitor.js", ['goog.labs.dom.PageVisibilityEvent', 'goog.labs.dom.PageVisibilityMonitor', 'goog.labs.dom.PageVisibilityState'], ['goog.dom', 'goog.dom.vendor', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.memoize']); +goog.addDependency("labs/dom/pagevisibilitymonitor_test.js", ['goog.labs.dom.PageVisibilityMonitorTest'], ['goog.events', 'goog.functions', 'goog.labs.dom.PageVisibilityMonitor', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("labs/events/nondisposableeventtarget.js", ['goog.labs.events.NonDisposableEventTarget'], ['goog.array', 'goog.asserts', 'goog.events.Event', 'goog.events.Listenable', 'goog.events.ListenerMap', 'goog.object']); +goog.addDependency("labs/events/nondisposableeventtarget_test.js", ['goog.labs.events.NonDisposableEventTargetTest'], ['goog.events.Listenable', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.labs.events.NonDisposableEventTarget', 'goog.testing.jsunit']); +goog.addDependency("labs/events/nondisposableeventtarget_via_googevents_test.js", ['goog.labs.events.NonDisposableEventTargetGoogEventsTest'], ['goog.events', 'goog.events.eventTargetTester', 'goog.events.eventTargetTester.KeyType', 'goog.events.eventTargetTester.UnlistenReturnType', 'goog.labs.events.NonDisposableEventTarget', 'goog.testing', 'goog.testing.jsunit']); +goog.addDependency("labs/events/touch.js", ['goog.labs.events.touch', 'goog.labs.events.touch.TouchData'], ['goog.array', 'goog.asserts', 'goog.events.EventType', 'goog.string']); +goog.addDependency("labs/events/touch_test.js", ['goog.labs.events.touchTest'], ['goog.labs.events.touch', 'goog.testing.jsunit']); +goog.addDependency("labs/format/csv.js", ['goog.labs.format.csv', 'goog.labs.format.csv.ParseError', 'goog.labs.format.csv.Token'], ['goog.array', 'goog.asserts', 'goog.debug.Error', 'goog.object', 'goog.string', 'goog.string.newlines']); +goog.addDependency("labs/format/csv_test.js", ['goog.labs.format.csvTest'], ['goog.labs.format.csv', 'goog.labs.format.csv.ParseError', 'goog.object', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("labs/i18n/listformat.js", ['goog.labs.i18n.GenderInfo', 'goog.labs.i18n.GenderInfo.Gender', 'goog.labs.i18n.ListFormat'], ['goog.asserts', 'goog.labs.i18n.ListFormatSymbols']); +goog.addDependency("labs/i18n/listformat_test.js", ['goog.labs.i18n.ListFormatTest'], ['goog.labs.i18n.GenderInfo', 'goog.labs.i18n.ListFormat', 'goog.labs.i18n.ListFormatSymbols', 'goog.labs.i18n.ListFormatSymbols_el', 'goog.labs.i18n.ListFormatSymbols_en', 'goog.labs.i18n.ListFormatSymbols_fr', 'goog.labs.i18n.ListFormatSymbols_ml', 'goog.labs.i18n.ListFormatSymbols_zu', 'goog.testing.jsunit']); +goog.addDependency("labs/i18n/listsymbols.js", ['goog.labs.i18n.ListFormatSymbols', 'goog.labs.i18n.ListFormatSymbols_af', 'goog.labs.i18n.ListFormatSymbols_am', 'goog.labs.i18n.ListFormatSymbols_ar', 'goog.labs.i18n.ListFormatSymbols_ar_DZ', 'goog.labs.i18n.ListFormatSymbols_az', 'goog.labs.i18n.ListFormatSymbols_be', 'goog.labs.i18n.ListFormatSymbols_bg', 'goog.labs.i18n.ListFormatSymbols_bn', 'goog.labs.i18n.ListFormatSymbols_br', 'goog.labs.i18n.ListFormatSymbols_bs', 'goog.labs.i18n.ListFormatSymbols_ca', 'goog.labs.i18n.ListFormatSymbols_chr', 'goog.labs.i18n.ListFormatSymbols_cs', 'goog.labs.i18n.ListFormatSymbols_cy', 'goog.labs.i18n.ListFormatSymbols_da', 'goog.labs.i18n.ListFormatSymbols_de', 'goog.labs.i18n.ListFormatSymbols_de_AT', 'goog.labs.i18n.ListFormatSymbols_de_CH', 'goog.labs.i18n.ListFormatSymbols_el', 'goog.labs.i18n.ListFormatSymbols_en', 'goog.labs.i18n.ListFormatSymbols_en_AU', 'goog.labs.i18n.ListFormatSymbols_en_CA', 'goog.labs.i18n.ListFormatSymbols_en_GB', 'goog.labs.i18n.ListFormatSymbols_en_IE', 'goog.labs.i18n.ListFormatSymbols_en_IN', 'goog.labs.i18n.ListFormatSymbols_en_SG', 'goog.labs.i18n.ListFormatSymbols_en_US', 'goog.labs.i18n.ListFormatSymbols_en_ZA', 'goog.labs.i18n.ListFormatSymbols_es', 'goog.labs.i18n.ListFormatSymbols_es_419', 'goog.labs.i18n.ListFormatSymbols_es_ES', 'goog.labs.i18n.ListFormatSymbols_es_MX', 'goog.labs.i18n.ListFormatSymbols_es_US', 'goog.labs.i18n.ListFormatSymbols_et', 'goog.labs.i18n.ListFormatSymbols_eu', 'goog.labs.i18n.ListFormatSymbols_fa', 'goog.labs.i18n.ListFormatSymbols_fi', 'goog.labs.i18n.ListFormatSymbols_fil', 'goog.labs.i18n.ListFormatSymbols_fr', 'goog.labs.i18n.ListFormatSymbols_fr_CA', 'goog.labs.i18n.ListFormatSymbols_ga', 'goog.labs.i18n.ListFormatSymbols_gl', 'goog.labs.i18n.ListFormatSymbols_gsw', 'goog.labs.i18n.ListFormatSymbols_gu', 'goog.labs.i18n.ListFormatSymbols_haw', 'goog.labs.i18n.ListFormatSymbols_he', 'goog.labs.i18n.ListFormatSymbols_hi', 'goog.labs.i18n.ListFormatSymbols_hr', 'goog.labs.i18n.ListFormatSymbols_hu', 'goog.labs.i18n.ListFormatSymbols_hy', 'goog.labs.i18n.ListFormatSymbols_id', 'goog.labs.i18n.ListFormatSymbols_in', 'goog.labs.i18n.ListFormatSymbols_is', 'goog.labs.i18n.ListFormatSymbols_it', 'goog.labs.i18n.ListFormatSymbols_iw', 'goog.labs.i18n.ListFormatSymbols_ja', 'goog.labs.i18n.ListFormatSymbols_ka', 'goog.labs.i18n.ListFormatSymbols_kk', 'goog.labs.i18n.ListFormatSymbols_km', 'goog.labs.i18n.ListFormatSymbols_kn', 'goog.labs.i18n.ListFormatSymbols_ko', 'goog.labs.i18n.ListFormatSymbols_ky', 'goog.labs.i18n.ListFormatSymbols_ln', 'goog.labs.i18n.ListFormatSymbols_lo', 'goog.labs.i18n.ListFormatSymbols_lt', 'goog.labs.i18n.ListFormatSymbols_lv', 'goog.labs.i18n.ListFormatSymbols_mk', 'goog.labs.i18n.ListFormatSymbols_ml', 'goog.labs.i18n.ListFormatSymbols_mn', 'goog.labs.i18n.ListFormatSymbols_mo', 'goog.labs.i18n.ListFormatSymbols_mr', 'goog.labs.i18n.ListFormatSymbols_ms', 'goog.labs.i18n.ListFormatSymbols_mt', 'goog.labs.i18n.ListFormatSymbols_my', 'goog.labs.i18n.ListFormatSymbols_nb', 'goog.labs.i18n.ListFormatSymbols_ne', 'goog.labs.i18n.ListFormatSymbols_nl', 'goog.labs.i18n.ListFormatSymbols_no', 'goog.labs.i18n.ListFormatSymbols_no_NO', 'goog.labs.i18n.ListFormatSymbols_or', 'goog.labs.i18n.ListFormatSymbols_pa', 'goog.labs.i18n.ListFormatSymbols_pl', 'goog.labs.i18n.ListFormatSymbols_pt', 'goog.labs.i18n.ListFormatSymbols_pt_BR', 'goog.labs.i18n.ListFormatSymbols_pt_PT', 'goog.labs.i18n.ListFormatSymbols_ro', 'goog.labs.i18n.ListFormatSymbols_ru', 'goog.labs.i18n.ListFormatSymbols_sh', 'goog.labs.i18n.ListFormatSymbols_si', 'goog.labs.i18n.ListFormatSymbols_sk', 'goog.labs.i18n.ListFormatSymbols_sl', 'goog.labs.i18n.ListFormatSymbols_sq', 'goog.labs.i18n.ListFormatSymbols_sr', 'goog.labs.i18n.ListFormatSymbols_sr_Latn', 'goog.labs.i18n.ListFormatSymbols_sv', 'goog.labs.i18n.ListFormatSymbols_sw', 'goog.labs.i18n.ListFormatSymbols_ta', 'goog.labs.i18n.ListFormatSymbols_te', 'goog.labs.i18n.ListFormatSymbols_th', 'goog.labs.i18n.ListFormatSymbols_tl', 'goog.labs.i18n.ListFormatSymbols_tr', 'goog.labs.i18n.ListFormatSymbols_uk', 'goog.labs.i18n.ListFormatSymbols_ur', 'goog.labs.i18n.ListFormatSymbols_uz', 'goog.labs.i18n.ListFormatSymbols_vi', 'goog.labs.i18n.ListFormatSymbols_zh', 'goog.labs.i18n.ListFormatSymbols_zh_CN', 'goog.labs.i18n.ListFormatSymbols_zh_HK', 'goog.labs.i18n.ListFormatSymbols_zh_TW', 'goog.labs.i18n.ListFormatSymbols_zu'], []); +goog.addDependency("labs/i18n/listsymbolsext.js", ['goog.labs.i18n.ListFormatSymbolsExt', 'goog.labs.i18n.ListFormatSymbols_af_NA', 'goog.labs.i18n.ListFormatSymbols_af_ZA', 'goog.labs.i18n.ListFormatSymbols_agq', 'goog.labs.i18n.ListFormatSymbols_agq_CM', 'goog.labs.i18n.ListFormatSymbols_ak', 'goog.labs.i18n.ListFormatSymbols_ak_GH', 'goog.labs.i18n.ListFormatSymbols_am_ET', 'goog.labs.i18n.ListFormatSymbols_ar_001', 'goog.labs.i18n.ListFormatSymbols_ar_AE', 'goog.labs.i18n.ListFormatSymbols_ar_BH', 'goog.labs.i18n.ListFormatSymbols_ar_DJ', 'goog.labs.i18n.ListFormatSymbols_ar_EG', 'goog.labs.i18n.ListFormatSymbols_ar_EH', 'goog.labs.i18n.ListFormatSymbols_ar_ER', 'goog.labs.i18n.ListFormatSymbols_ar_IL', 'goog.labs.i18n.ListFormatSymbols_ar_IQ', 'goog.labs.i18n.ListFormatSymbols_ar_JO', 'goog.labs.i18n.ListFormatSymbols_ar_KM', 'goog.labs.i18n.ListFormatSymbols_ar_KW', 'goog.labs.i18n.ListFormatSymbols_ar_LB', 'goog.labs.i18n.ListFormatSymbols_ar_LY', 'goog.labs.i18n.ListFormatSymbols_ar_MA', 'goog.labs.i18n.ListFormatSymbols_ar_MR', 'goog.labs.i18n.ListFormatSymbols_ar_OM', 'goog.labs.i18n.ListFormatSymbols_ar_PS', 'goog.labs.i18n.ListFormatSymbols_ar_QA', 'goog.labs.i18n.ListFormatSymbols_ar_SA', 'goog.labs.i18n.ListFormatSymbols_ar_SD', 'goog.labs.i18n.ListFormatSymbols_ar_SO', 'goog.labs.i18n.ListFormatSymbols_ar_SS', 'goog.labs.i18n.ListFormatSymbols_ar_SY', 'goog.labs.i18n.ListFormatSymbols_ar_TD', 'goog.labs.i18n.ListFormatSymbols_ar_TN', 'goog.labs.i18n.ListFormatSymbols_ar_XB', 'goog.labs.i18n.ListFormatSymbols_ar_YE', 'goog.labs.i18n.ListFormatSymbols_as', 'goog.labs.i18n.ListFormatSymbols_as_IN', 'goog.labs.i18n.ListFormatSymbols_asa', 'goog.labs.i18n.ListFormatSymbols_asa_TZ', 'goog.labs.i18n.ListFormatSymbols_ast', 'goog.labs.i18n.ListFormatSymbols_ast_ES', 'goog.labs.i18n.ListFormatSymbols_az_Cyrl', 'goog.labs.i18n.ListFormatSymbols_az_Cyrl_AZ', 'goog.labs.i18n.ListFormatSymbols_az_Latn', 'goog.labs.i18n.ListFormatSymbols_az_Latn_AZ', 'goog.labs.i18n.ListFormatSymbols_bas', 'goog.labs.i18n.ListFormatSymbols_bas_CM', 'goog.labs.i18n.ListFormatSymbols_be_BY', 'goog.labs.i18n.ListFormatSymbols_bem', 'goog.labs.i18n.ListFormatSymbols_bem_ZM', 'goog.labs.i18n.ListFormatSymbols_bez', 'goog.labs.i18n.ListFormatSymbols_bez_TZ', 'goog.labs.i18n.ListFormatSymbols_bg_BG', 'goog.labs.i18n.ListFormatSymbols_bm', 'goog.labs.i18n.ListFormatSymbols_bm_ML', 'goog.labs.i18n.ListFormatSymbols_bn_BD', 'goog.labs.i18n.ListFormatSymbols_bn_IN', 'goog.labs.i18n.ListFormatSymbols_bo', 'goog.labs.i18n.ListFormatSymbols_bo_CN', 'goog.labs.i18n.ListFormatSymbols_bo_IN', 'goog.labs.i18n.ListFormatSymbols_br_FR', 'goog.labs.i18n.ListFormatSymbols_brx', 'goog.labs.i18n.ListFormatSymbols_brx_IN', 'goog.labs.i18n.ListFormatSymbols_bs_Cyrl', 'goog.labs.i18n.ListFormatSymbols_bs_Cyrl_BA', 'goog.labs.i18n.ListFormatSymbols_bs_Latn', 'goog.labs.i18n.ListFormatSymbols_bs_Latn_BA', 'goog.labs.i18n.ListFormatSymbols_ca_AD', 'goog.labs.i18n.ListFormatSymbols_ca_ES', 'goog.labs.i18n.ListFormatSymbols_ca_FR', 'goog.labs.i18n.ListFormatSymbols_ca_IT', 'goog.labs.i18n.ListFormatSymbols_ccp', 'goog.labs.i18n.ListFormatSymbols_ccp_BD', 'goog.labs.i18n.ListFormatSymbols_ccp_IN', 'goog.labs.i18n.ListFormatSymbols_ce', 'goog.labs.i18n.ListFormatSymbols_ce_RU', 'goog.labs.i18n.ListFormatSymbols_cgg', 'goog.labs.i18n.ListFormatSymbols_cgg_UG', 'goog.labs.i18n.ListFormatSymbols_chr_US', 'goog.labs.i18n.ListFormatSymbols_ckb', 'goog.labs.i18n.ListFormatSymbols_ckb_IQ', 'goog.labs.i18n.ListFormatSymbols_ckb_IR', 'goog.labs.i18n.ListFormatSymbols_cs_CZ', 'goog.labs.i18n.ListFormatSymbols_cy_GB', 'goog.labs.i18n.ListFormatSymbols_da_DK', 'goog.labs.i18n.ListFormatSymbols_da_GL', 'goog.labs.i18n.ListFormatSymbols_dav', 'goog.labs.i18n.ListFormatSymbols_dav_KE', 'goog.labs.i18n.ListFormatSymbols_de_BE', 'goog.labs.i18n.ListFormatSymbols_de_DE', 'goog.labs.i18n.ListFormatSymbols_de_IT', 'goog.labs.i18n.ListFormatSymbols_de_LI', 'goog.labs.i18n.ListFormatSymbols_de_LU', 'goog.labs.i18n.ListFormatSymbols_dje', 'goog.labs.i18n.ListFormatSymbols_dje_NE', 'goog.labs.i18n.ListFormatSymbols_dsb', 'goog.labs.i18n.ListFormatSymbols_dsb_DE', 'goog.labs.i18n.ListFormatSymbols_dua', 'goog.labs.i18n.ListFormatSymbols_dua_CM', 'goog.labs.i18n.ListFormatSymbols_dyo', 'goog.labs.i18n.ListFormatSymbols_dyo_SN', 'goog.labs.i18n.ListFormatSymbols_dz', 'goog.labs.i18n.ListFormatSymbols_dz_BT', 'goog.labs.i18n.ListFormatSymbols_ebu', 'goog.labs.i18n.ListFormatSymbols_ebu_KE', 'goog.labs.i18n.ListFormatSymbols_ee', 'goog.labs.i18n.ListFormatSymbols_ee_GH', 'goog.labs.i18n.ListFormatSymbols_ee_TG', 'goog.labs.i18n.ListFormatSymbols_el_CY', 'goog.labs.i18n.ListFormatSymbols_el_GR', 'goog.labs.i18n.ListFormatSymbols_en_001', 'goog.labs.i18n.ListFormatSymbols_en_150', 'goog.labs.i18n.ListFormatSymbols_en_AG', 'goog.labs.i18n.ListFormatSymbols_en_AI', 'goog.labs.i18n.ListFormatSymbols_en_AS', 'goog.labs.i18n.ListFormatSymbols_en_AT', 'goog.labs.i18n.ListFormatSymbols_en_BB', 'goog.labs.i18n.ListFormatSymbols_en_BE', 'goog.labs.i18n.ListFormatSymbols_en_BI', 'goog.labs.i18n.ListFormatSymbols_en_BM', 'goog.labs.i18n.ListFormatSymbols_en_BS', 'goog.labs.i18n.ListFormatSymbols_en_BW', 'goog.labs.i18n.ListFormatSymbols_en_BZ', 'goog.labs.i18n.ListFormatSymbols_en_CC', 'goog.labs.i18n.ListFormatSymbols_en_CH', 'goog.labs.i18n.ListFormatSymbols_en_CK', 'goog.labs.i18n.ListFormatSymbols_en_CM', 'goog.labs.i18n.ListFormatSymbols_en_CX', 'goog.labs.i18n.ListFormatSymbols_en_CY', 'goog.labs.i18n.ListFormatSymbols_en_DE', 'goog.labs.i18n.ListFormatSymbols_en_DG', 'goog.labs.i18n.ListFormatSymbols_en_DK', 'goog.labs.i18n.ListFormatSymbols_en_DM', 'goog.labs.i18n.ListFormatSymbols_en_ER', 'goog.labs.i18n.ListFormatSymbols_en_FI', 'goog.labs.i18n.ListFormatSymbols_en_FJ', 'goog.labs.i18n.ListFormatSymbols_en_FK', 'goog.labs.i18n.ListFormatSymbols_en_FM', 'goog.labs.i18n.ListFormatSymbols_en_GD', 'goog.labs.i18n.ListFormatSymbols_en_GG', 'goog.labs.i18n.ListFormatSymbols_en_GH', 'goog.labs.i18n.ListFormatSymbols_en_GI', 'goog.labs.i18n.ListFormatSymbols_en_GM', 'goog.labs.i18n.ListFormatSymbols_en_GU', 'goog.labs.i18n.ListFormatSymbols_en_GY', 'goog.labs.i18n.ListFormatSymbols_en_HK', 'goog.labs.i18n.ListFormatSymbols_en_IL', 'goog.labs.i18n.ListFormatSymbols_en_IM', 'goog.labs.i18n.ListFormatSymbols_en_IO', 'goog.labs.i18n.ListFormatSymbols_en_JE', 'goog.labs.i18n.ListFormatSymbols_en_JM', 'goog.labs.i18n.ListFormatSymbols_en_KE', 'goog.labs.i18n.ListFormatSymbols_en_KI', 'goog.labs.i18n.ListFormatSymbols_en_KN', 'goog.labs.i18n.ListFormatSymbols_en_KY', 'goog.labs.i18n.ListFormatSymbols_en_LC', 'goog.labs.i18n.ListFormatSymbols_en_LR', 'goog.labs.i18n.ListFormatSymbols_en_LS', 'goog.labs.i18n.ListFormatSymbols_en_MG', 'goog.labs.i18n.ListFormatSymbols_en_MH', 'goog.labs.i18n.ListFormatSymbols_en_MO', 'goog.labs.i18n.ListFormatSymbols_en_MP', 'goog.labs.i18n.ListFormatSymbols_en_MS', 'goog.labs.i18n.ListFormatSymbols_en_MT', 'goog.labs.i18n.ListFormatSymbols_en_MU', 'goog.labs.i18n.ListFormatSymbols_en_MW', 'goog.labs.i18n.ListFormatSymbols_en_MY', 'goog.labs.i18n.ListFormatSymbols_en_NA', 'goog.labs.i18n.ListFormatSymbols_en_NF', 'goog.labs.i18n.ListFormatSymbols_en_NG', 'goog.labs.i18n.ListFormatSymbols_en_NL', 'goog.labs.i18n.ListFormatSymbols_en_NR', 'goog.labs.i18n.ListFormatSymbols_en_NU', 'goog.labs.i18n.ListFormatSymbols_en_NZ', 'goog.labs.i18n.ListFormatSymbols_en_PG', 'goog.labs.i18n.ListFormatSymbols_en_PH', 'goog.labs.i18n.ListFormatSymbols_en_PK', 'goog.labs.i18n.ListFormatSymbols_en_PN', 'goog.labs.i18n.ListFormatSymbols_en_PR', 'goog.labs.i18n.ListFormatSymbols_en_PW', 'goog.labs.i18n.ListFormatSymbols_en_RW', 'goog.labs.i18n.ListFormatSymbols_en_SB', 'goog.labs.i18n.ListFormatSymbols_en_SC', 'goog.labs.i18n.ListFormatSymbols_en_SD', 'goog.labs.i18n.ListFormatSymbols_en_SE', 'goog.labs.i18n.ListFormatSymbols_en_SH', 'goog.labs.i18n.ListFormatSymbols_en_SI', 'goog.labs.i18n.ListFormatSymbols_en_SL', 'goog.labs.i18n.ListFormatSymbols_en_SS', 'goog.labs.i18n.ListFormatSymbols_en_SX', 'goog.labs.i18n.ListFormatSymbols_en_SZ', 'goog.labs.i18n.ListFormatSymbols_en_TC', 'goog.labs.i18n.ListFormatSymbols_en_TK', 'goog.labs.i18n.ListFormatSymbols_en_TO', 'goog.labs.i18n.ListFormatSymbols_en_TT', 'goog.labs.i18n.ListFormatSymbols_en_TV', 'goog.labs.i18n.ListFormatSymbols_en_TZ', 'goog.labs.i18n.ListFormatSymbols_en_UG', 'goog.labs.i18n.ListFormatSymbols_en_UM', 'goog.labs.i18n.ListFormatSymbols_en_US_POSIX', 'goog.labs.i18n.ListFormatSymbols_en_VC', 'goog.labs.i18n.ListFormatSymbols_en_VG', 'goog.labs.i18n.ListFormatSymbols_en_VI', 'goog.labs.i18n.ListFormatSymbols_en_VU', 'goog.labs.i18n.ListFormatSymbols_en_WS', 'goog.labs.i18n.ListFormatSymbols_en_XA', 'goog.labs.i18n.ListFormatSymbols_en_ZM', 'goog.labs.i18n.ListFormatSymbols_en_ZW', 'goog.labs.i18n.ListFormatSymbols_eo', 'goog.labs.i18n.ListFormatSymbols_es_AR', 'goog.labs.i18n.ListFormatSymbols_es_BO', 'goog.labs.i18n.ListFormatSymbols_es_BR', 'goog.labs.i18n.ListFormatSymbols_es_BZ', 'goog.labs.i18n.ListFormatSymbols_es_CL', 'goog.labs.i18n.ListFormatSymbols_es_CO', 'goog.labs.i18n.ListFormatSymbols_es_CR', 'goog.labs.i18n.ListFormatSymbols_es_CU', 'goog.labs.i18n.ListFormatSymbols_es_DO', 'goog.labs.i18n.ListFormatSymbols_es_EA', 'goog.labs.i18n.ListFormatSymbols_es_EC', 'goog.labs.i18n.ListFormatSymbols_es_GQ', 'goog.labs.i18n.ListFormatSymbols_es_GT', 'goog.labs.i18n.ListFormatSymbols_es_HN', 'goog.labs.i18n.ListFormatSymbols_es_IC', 'goog.labs.i18n.ListFormatSymbols_es_NI', 'goog.labs.i18n.ListFormatSymbols_es_PA', 'goog.labs.i18n.ListFormatSymbols_es_PE', 'goog.labs.i18n.ListFormatSymbols_es_PH', 'goog.labs.i18n.ListFormatSymbols_es_PR', 'goog.labs.i18n.ListFormatSymbols_es_PY', 'goog.labs.i18n.ListFormatSymbols_es_SV', 'goog.labs.i18n.ListFormatSymbols_es_UY', 'goog.labs.i18n.ListFormatSymbols_es_VE', 'goog.labs.i18n.ListFormatSymbols_et_EE', 'goog.labs.i18n.ListFormatSymbols_eu_ES', 'goog.labs.i18n.ListFormatSymbols_ewo', 'goog.labs.i18n.ListFormatSymbols_ewo_CM', 'goog.labs.i18n.ListFormatSymbols_fa_AF', 'goog.labs.i18n.ListFormatSymbols_fa_IR', 'goog.labs.i18n.ListFormatSymbols_ff', 'goog.labs.i18n.ListFormatSymbols_ff_CM', 'goog.labs.i18n.ListFormatSymbols_ff_GN', 'goog.labs.i18n.ListFormatSymbols_ff_MR', 'goog.labs.i18n.ListFormatSymbols_ff_SN', 'goog.labs.i18n.ListFormatSymbols_fi_FI', 'goog.labs.i18n.ListFormatSymbols_fil_PH', 'goog.labs.i18n.ListFormatSymbols_fo', 'goog.labs.i18n.ListFormatSymbols_fo_DK', 'goog.labs.i18n.ListFormatSymbols_fo_FO', 'goog.labs.i18n.ListFormatSymbols_fr_BE', 'goog.labs.i18n.ListFormatSymbols_fr_BF', 'goog.labs.i18n.ListFormatSymbols_fr_BI', 'goog.labs.i18n.ListFormatSymbols_fr_BJ', 'goog.labs.i18n.ListFormatSymbols_fr_BL', 'goog.labs.i18n.ListFormatSymbols_fr_CD', 'goog.labs.i18n.ListFormatSymbols_fr_CF', 'goog.labs.i18n.ListFormatSymbols_fr_CG', 'goog.labs.i18n.ListFormatSymbols_fr_CH', 'goog.labs.i18n.ListFormatSymbols_fr_CI', 'goog.labs.i18n.ListFormatSymbols_fr_CM', 'goog.labs.i18n.ListFormatSymbols_fr_DJ', 'goog.labs.i18n.ListFormatSymbols_fr_DZ', 'goog.labs.i18n.ListFormatSymbols_fr_FR', 'goog.labs.i18n.ListFormatSymbols_fr_GA', 'goog.labs.i18n.ListFormatSymbols_fr_GF', 'goog.labs.i18n.ListFormatSymbols_fr_GN', 'goog.labs.i18n.ListFormatSymbols_fr_GP', 'goog.labs.i18n.ListFormatSymbols_fr_GQ', 'goog.labs.i18n.ListFormatSymbols_fr_HT', 'goog.labs.i18n.ListFormatSymbols_fr_KM', 'goog.labs.i18n.ListFormatSymbols_fr_LU', 'goog.labs.i18n.ListFormatSymbols_fr_MA', 'goog.labs.i18n.ListFormatSymbols_fr_MC', 'goog.labs.i18n.ListFormatSymbols_fr_MF', 'goog.labs.i18n.ListFormatSymbols_fr_MG', 'goog.labs.i18n.ListFormatSymbols_fr_ML', 'goog.labs.i18n.ListFormatSymbols_fr_MQ', 'goog.labs.i18n.ListFormatSymbols_fr_MR', 'goog.labs.i18n.ListFormatSymbols_fr_MU', 'goog.labs.i18n.ListFormatSymbols_fr_NC', 'goog.labs.i18n.ListFormatSymbols_fr_NE', 'goog.labs.i18n.ListFormatSymbols_fr_PF', 'goog.labs.i18n.ListFormatSymbols_fr_PM', 'goog.labs.i18n.ListFormatSymbols_fr_RE', 'goog.labs.i18n.ListFormatSymbols_fr_RW', 'goog.labs.i18n.ListFormatSymbols_fr_SC', 'goog.labs.i18n.ListFormatSymbols_fr_SN', 'goog.labs.i18n.ListFormatSymbols_fr_SY', 'goog.labs.i18n.ListFormatSymbols_fr_TD', 'goog.labs.i18n.ListFormatSymbols_fr_TG', 'goog.labs.i18n.ListFormatSymbols_fr_TN', 'goog.labs.i18n.ListFormatSymbols_fr_VU', 'goog.labs.i18n.ListFormatSymbols_fr_WF', 'goog.labs.i18n.ListFormatSymbols_fr_YT', 'goog.labs.i18n.ListFormatSymbols_fur', 'goog.labs.i18n.ListFormatSymbols_fur_IT', 'goog.labs.i18n.ListFormatSymbols_fy', 'goog.labs.i18n.ListFormatSymbols_fy_NL', 'goog.labs.i18n.ListFormatSymbols_ga_IE', 'goog.labs.i18n.ListFormatSymbols_gd', 'goog.labs.i18n.ListFormatSymbols_gd_GB', 'goog.labs.i18n.ListFormatSymbols_gl_ES', 'goog.labs.i18n.ListFormatSymbols_gsw_CH', 'goog.labs.i18n.ListFormatSymbols_gsw_FR', 'goog.labs.i18n.ListFormatSymbols_gsw_LI', 'goog.labs.i18n.ListFormatSymbols_gu_IN', 'goog.labs.i18n.ListFormatSymbols_guz', 'goog.labs.i18n.ListFormatSymbols_guz_KE', 'goog.labs.i18n.ListFormatSymbols_gv', 'goog.labs.i18n.ListFormatSymbols_gv_IM', 'goog.labs.i18n.ListFormatSymbols_ha', 'goog.labs.i18n.ListFormatSymbols_ha_GH', 'goog.labs.i18n.ListFormatSymbols_ha_NE', 'goog.labs.i18n.ListFormatSymbols_ha_NG', 'goog.labs.i18n.ListFormatSymbols_haw_US', 'goog.labs.i18n.ListFormatSymbols_he_IL', 'goog.labs.i18n.ListFormatSymbols_hi_IN', 'goog.labs.i18n.ListFormatSymbols_hr_BA', 'goog.labs.i18n.ListFormatSymbols_hr_HR', 'goog.labs.i18n.ListFormatSymbols_hsb', 'goog.labs.i18n.ListFormatSymbols_hsb_DE', 'goog.labs.i18n.ListFormatSymbols_hu_HU', 'goog.labs.i18n.ListFormatSymbols_hy_AM', 'goog.labs.i18n.ListFormatSymbols_id_ID', 'goog.labs.i18n.ListFormatSymbols_ig', 'goog.labs.i18n.ListFormatSymbols_ig_NG', 'goog.labs.i18n.ListFormatSymbols_ii', 'goog.labs.i18n.ListFormatSymbols_ii_CN', 'goog.labs.i18n.ListFormatSymbols_is_IS', 'goog.labs.i18n.ListFormatSymbols_it_CH', 'goog.labs.i18n.ListFormatSymbols_it_IT', 'goog.labs.i18n.ListFormatSymbols_it_SM', 'goog.labs.i18n.ListFormatSymbols_it_VA', 'goog.labs.i18n.ListFormatSymbols_ja_JP', 'goog.labs.i18n.ListFormatSymbols_jgo', 'goog.labs.i18n.ListFormatSymbols_jgo_CM', 'goog.labs.i18n.ListFormatSymbols_jmc', 'goog.labs.i18n.ListFormatSymbols_jmc_TZ', 'goog.labs.i18n.ListFormatSymbols_ka_GE', 'goog.labs.i18n.ListFormatSymbols_kab', 'goog.labs.i18n.ListFormatSymbols_kab_DZ', 'goog.labs.i18n.ListFormatSymbols_kam', 'goog.labs.i18n.ListFormatSymbols_kam_KE', 'goog.labs.i18n.ListFormatSymbols_kde', 'goog.labs.i18n.ListFormatSymbols_kde_TZ', 'goog.labs.i18n.ListFormatSymbols_kea', 'goog.labs.i18n.ListFormatSymbols_kea_CV', 'goog.labs.i18n.ListFormatSymbols_khq', 'goog.labs.i18n.ListFormatSymbols_khq_ML', 'goog.labs.i18n.ListFormatSymbols_ki', 'goog.labs.i18n.ListFormatSymbols_ki_KE', 'goog.labs.i18n.ListFormatSymbols_kk_KZ', 'goog.labs.i18n.ListFormatSymbols_kkj', 'goog.labs.i18n.ListFormatSymbols_kkj_CM', 'goog.labs.i18n.ListFormatSymbols_kl', 'goog.labs.i18n.ListFormatSymbols_kl_GL', 'goog.labs.i18n.ListFormatSymbols_kln', 'goog.labs.i18n.ListFormatSymbols_kln_KE', 'goog.labs.i18n.ListFormatSymbols_km_KH', 'goog.labs.i18n.ListFormatSymbols_kn_IN', 'goog.labs.i18n.ListFormatSymbols_ko_KP', 'goog.labs.i18n.ListFormatSymbols_ko_KR', 'goog.labs.i18n.ListFormatSymbols_kok', 'goog.labs.i18n.ListFormatSymbols_kok_IN', 'goog.labs.i18n.ListFormatSymbols_ks', 'goog.labs.i18n.ListFormatSymbols_ks_IN', 'goog.labs.i18n.ListFormatSymbols_ksb', 'goog.labs.i18n.ListFormatSymbols_ksb_TZ', 'goog.labs.i18n.ListFormatSymbols_ksf', 'goog.labs.i18n.ListFormatSymbols_ksf_CM', 'goog.labs.i18n.ListFormatSymbols_ksh', 'goog.labs.i18n.ListFormatSymbols_ksh_DE', 'goog.labs.i18n.ListFormatSymbols_kw', 'goog.labs.i18n.ListFormatSymbols_kw_GB', 'goog.labs.i18n.ListFormatSymbols_ky_KG', 'goog.labs.i18n.ListFormatSymbols_lag', 'goog.labs.i18n.ListFormatSymbols_lag_TZ', 'goog.labs.i18n.ListFormatSymbols_lb', 'goog.labs.i18n.ListFormatSymbols_lb_LU', 'goog.labs.i18n.ListFormatSymbols_lg', 'goog.labs.i18n.ListFormatSymbols_lg_UG', 'goog.labs.i18n.ListFormatSymbols_lkt', 'goog.labs.i18n.ListFormatSymbols_lkt_US', 'goog.labs.i18n.ListFormatSymbols_ln_AO', 'goog.labs.i18n.ListFormatSymbols_ln_CD', 'goog.labs.i18n.ListFormatSymbols_ln_CF', 'goog.labs.i18n.ListFormatSymbols_ln_CG', 'goog.labs.i18n.ListFormatSymbols_lo_LA', 'goog.labs.i18n.ListFormatSymbols_lrc', 'goog.labs.i18n.ListFormatSymbols_lrc_IQ', 'goog.labs.i18n.ListFormatSymbols_lrc_IR', 'goog.labs.i18n.ListFormatSymbols_lt_LT', 'goog.labs.i18n.ListFormatSymbols_lu', 'goog.labs.i18n.ListFormatSymbols_lu_CD', 'goog.labs.i18n.ListFormatSymbols_luo', 'goog.labs.i18n.ListFormatSymbols_luo_KE', 'goog.labs.i18n.ListFormatSymbols_luy', 'goog.labs.i18n.ListFormatSymbols_luy_KE', 'goog.labs.i18n.ListFormatSymbols_lv_LV', 'goog.labs.i18n.ListFormatSymbols_mas', 'goog.labs.i18n.ListFormatSymbols_mas_KE', 'goog.labs.i18n.ListFormatSymbols_mas_TZ', 'goog.labs.i18n.ListFormatSymbols_mer', 'goog.labs.i18n.ListFormatSymbols_mer_KE', 'goog.labs.i18n.ListFormatSymbols_mfe', 'goog.labs.i18n.ListFormatSymbols_mfe_MU', 'goog.labs.i18n.ListFormatSymbols_mg', 'goog.labs.i18n.ListFormatSymbols_mg_MG', 'goog.labs.i18n.ListFormatSymbols_mgh', 'goog.labs.i18n.ListFormatSymbols_mgh_MZ', 'goog.labs.i18n.ListFormatSymbols_mgo', 'goog.labs.i18n.ListFormatSymbols_mgo_CM', 'goog.labs.i18n.ListFormatSymbols_mk_MK', 'goog.labs.i18n.ListFormatSymbols_ml_IN', 'goog.labs.i18n.ListFormatSymbols_mn_MN', 'goog.labs.i18n.ListFormatSymbols_mr_IN', 'goog.labs.i18n.ListFormatSymbols_ms_BN', 'goog.labs.i18n.ListFormatSymbols_ms_MY', 'goog.labs.i18n.ListFormatSymbols_ms_SG', 'goog.labs.i18n.ListFormatSymbols_mt_MT', 'goog.labs.i18n.ListFormatSymbols_mua', 'goog.labs.i18n.ListFormatSymbols_mua_CM', 'goog.labs.i18n.ListFormatSymbols_my_MM', 'goog.labs.i18n.ListFormatSymbols_mzn', 'goog.labs.i18n.ListFormatSymbols_mzn_IR', 'goog.labs.i18n.ListFormatSymbols_naq', 'goog.labs.i18n.ListFormatSymbols_naq_NA', 'goog.labs.i18n.ListFormatSymbols_nb_NO', 'goog.labs.i18n.ListFormatSymbols_nb_SJ', 'goog.labs.i18n.ListFormatSymbols_nd', 'goog.labs.i18n.ListFormatSymbols_nd_ZW', 'goog.labs.i18n.ListFormatSymbols_nds', 'goog.labs.i18n.ListFormatSymbols_nds_DE', 'goog.labs.i18n.ListFormatSymbols_nds_NL', 'goog.labs.i18n.ListFormatSymbols_ne_IN', 'goog.labs.i18n.ListFormatSymbols_ne_NP', 'goog.labs.i18n.ListFormatSymbols_nl_AW', 'goog.labs.i18n.ListFormatSymbols_nl_BE', 'goog.labs.i18n.ListFormatSymbols_nl_BQ', 'goog.labs.i18n.ListFormatSymbols_nl_CW', 'goog.labs.i18n.ListFormatSymbols_nl_NL', 'goog.labs.i18n.ListFormatSymbols_nl_SR', 'goog.labs.i18n.ListFormatSymbols_nl_SX', 'goog.labs.i18n.ListFormatSymbols_nmg', 'goog.labs.i18n.ListFormatSymbols_nmg_CM', 'goog.labs.i18n.ListFormatSymbols_nn', 'goog.labs.i18n.ListFormatSymbols_nn_NO', 'goog.labs.i18n.ListFormatSymbols_nnh', 'goog.labs.i18n.ListFormatSymbols_nnh_CM', 'goog.labs.i18n.ListFormatSymbols_nus', 'goog.labs.i18n.ListFormatSymbols_nus_SS', 'goog.labs.i18n.ListFormatSymbols_nyn', 'goog.labs.i18n.ListFormatSymbols_nyn_UG', 'goog.labs.i18n.ListFormatSymbols_om', 'goog.labs.i18n.ListFormatSymbols_om_ET', 'goog.labs.i18n.ListFormatSymbols_om_KE', 'goog.labs.i18n.ListFormatSymbols_or_IN', 'goog.labs.i18n.ListFormatSymbols_os', 'goog.labs.i18n.ListFormatSymbols_os_GE', 'goog.labs.i18n.ListFormatSymbols_os_RU', 'goog.labs.i18n.ListFormatSymbols_pa_Arab', 'goog.labs.i18n.ListFormatSymbols_pa_Arab_PK', 'goog.labs.i18n.ListFormatSymbols_pa_Guru', 'goog.labs.i18n.ListFormatSymbols_pa_Guru_IN', 'goog.labs.i18n.ListFormatSymbols_pl_PL', 'goog.labs.i18n.ListFormatSymbols_ps', 'goog.labs.i18n.ListFormatSymbols_ps_AF', 'goog.labs.i18n.ListFormatSymbols_pt_AO', 'goog.labs.i18n.ListFormatSymbols_pt_CH', 'goog.labs.i18n.ListFormatSymbols_pt_CV', 'goog.labs.i18n.ListFormatSymbols_pt_GQ', 'goog.labs.i18n.ListFormatSymbols_pt_GW', 'goog.labs.i18n.ListFormatSymbols_pt_LU', 'goog.labs.i18n.ListFormatSymbols_pt_MO', 'goog.labs.i18n.ListFormatSymbols_pt_MZ', 'goog.labs.i18n.ListFormatSymbols_pt_ST', 'goog.labs.i18n.ListFormatSymbols_pt_TL', 'goog.labs.i18n.ListFormatSymbols_qu', 'goog.labs.i18n.ListFormatSymbols_qu_BO', 'goog.labs.i18n.ListFormatSymbols_qu_EC', 'goog.labs.i18n.ListFormatSymbols_qu_PE', 'goog.labs.i18n.ListFormatSymbols_rm', 'goog.labs.i18n.ListFormatSymbols_rm_CH', 'goog.labs.i18n.ListFormatSymbols_rn', 'goog.labs.i18n.ListFormatSymbols_rn_BI', 'goog.labs.i18n.ListFormatSymbols_ro_MD', 'goog.labs.i18n.ListFormatSymbols_ro_RO', 'goog.labs.i18n.ListFormatSymbols_rof', 'goog.labs.i18n.ListFormatSymbols_rof_TZ', 'goog.labs.i18n.ListFormatSymbols_ru_BY', 'goog.labs.i18n.ListFormatSymbols_ru_KG', 'goog.labs.i18n.ListFormatSymbols_ru_KZ', 'goog.labs.i18n.ListFormatSymbols_ru_MD', 'goog.labs.i18n.ListFormatSymbols_ru_RU', 'goog.labs.i18n.ListFormatSymbols_ru_UA', 'goog.labs.i18n.ListFormatSymbols_rw', 'goog.labs.i18n.ListFormatSymbols_rw_RW', 'goog.labs.i18n.ListFormatSymbols_rwk', 'goog.labs.i18n.ListFormatSymbols_rwk_TZ', 'goog.labs.i18n.ListFormatSymbols_sah', 'goog.labs.i18n.ListFormatSymbols_sah_RU', 'goog.labs.i18n.ListFormatSymbols_saq', 'goog.labs.i18n.ListFormatSymbols_saq_KE', 'goog.labs.i18n.ListFormatSymbols_sbp', 'goog.labs.i18n.ListFormatSymbols_sbp_TZ', 'goog.labs.i18n.ListFormatSymbols_se', 'goog.labs.i18n.ListFormatSymbols_se_FI', 'goog.labs.i18n.ListFormatSymbols_se_NO', 'goog.labs.i18n.ListFormatSymbols_se_SE', 'goog.labs.i18n.ListFormatSymbols_seh', 'goog.labs.i18n.ListFormatSymbols_seh_MZ', 'goog.labs.i18n.ListFormatSymbols_ses', 'goog.labs.i18n.ListFormatSymbols_ses_ML', 'goog.labs.i18n.ListFormatSymbols_sg', 'goog.labs.i18n.ListFormatSymbols_sg_CF', 'goog.labs.i18n.ListFormatSymbols_shi', 'goog.labs.i18n.ListFormatSymbols_shi_Latn', 'goog.labs.i18n.ListFormatSymbols_shi_Latn_MA', 'goog.labs.i18n.ListFormatSymbols_shi_Tfng', 'goog.labs.i18n.ListFormatSymbols_shi_Tfng_MA', 'goog.labs.i18n.ListFormatSymbols_si_LK', 'goog.labs.i18n.ListFormatSymbols_sk_SK', 'goog.labs.i18n.ListFormatSymbols_sl_SI', 'goog.labs.i18n.ListFormatSymbols_smn', 'goog.labs.i18n.ListFormatSymbols_smn_FI', 'goog.labs.i18n.ListFormatSymbols_sn', 'goog.labs.i18n.ListFormatSymbols_sn_ZW', 'goog.labs.i18n.ListFormatSymbols_so', 'goog.labs.i18n.ListFormatSymbols_so_DJ', 'goog.labs.i18n.ListFormatSymbols_so_ET', 'goog.labs.i18n.ListFormatSymbols_so_KE', 'goog.labs.i18n.ListFormatSymbols_so_SO', 'goog.labs.i18n.ListFormatSymbols_sq_AL', 'goog.labs.i18n.ListFormatSymbols_sq_MK', 'goog.labs.i18n.ListFormatSymbols_sq_XK', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_BA', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_ME', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_RS', 'goog.labs.i18n.ListFormatSymbols_sr_Cyrl_XK', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_BA', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_ME', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_RS', 'goog.labs.i18n.ListFormatSymbols_sr_Latn_XK', 'goog.labs.i18n.ListFormatSymbols_sv_AX', 'goog.labs.i18n.ListFormatSymbols_sv_FI', 'goog.labs.i18n.ListFormatSymbols_sv_SE', 'goog.labs.i18n.ListFormatSymbols_sw_CD', 'goog.labs.i18n.ListFormatSymbols_sw_KE', 'goog.labs.i18n.ListFormatSymbols_sw_TZ', 'goog.labs.i18n.ListFormatSymbols_sw_UG', 'goog.labs.i18n.ListFormatSymbols_ta_IN', 'goog.labs.i18n.ListFormatSymbols_ta_LK', 'goog.labs.i18n.ListFormatSymbols_ta_MY', 'goog.labs.i18n.ListFormatSymbols_ta_SG', 'goog.labs.i18n.ListFormatSymbols_te_IN', 'goog.labs.i18n.ListFormatSymbols_teo', 'goog.labs.i18n.ListFormatSymbols_teo_KE', 'goog.labs.i18n.ListFormatSymbols_teo_UG', 'goog.labs.i18n.ListFormatSymbols_tg', 'goog.labs.i18n.ListFormatSymbols_tg_TJ', 'goog.labs.i18n.ListFormatSymbols_th_TH', 'goog.labs.i18n.ListFormatSymbols_ti', 'goog.labs.i18n.ListFormatSymbols_ti_ER', 'goog.labs.i18n.ListFormatSymbols_ti_ET', 'goog.labs.i18n.ListFormatSymbols_to', 'goog.labs.i18n.ListFormatSymbols_to_TO', 'goog.labs.i18n.ListFormatSymbols_tr_CY', 'goog.labs.i18n.ListFormatSymbols_tr_TR', 'goog.labs.i18n.ListFormatSymbols_tt', 'goog.labs.i18n.ListFormatSymbols_tt_RU', 'goog.labs.i18n.ListFormatSymbols_twq', 'goog.labs.i18n.ListFormatSymbols_twq_NE', 'goog.labs.i18n.ListFormatSymbols_tzm', 'goog.labs.i18n.ListFormatSymbols_tzm_MA', 'goog.labs.i18n.ListFormatSymbols_ug', 'goog.labs.i18n.ListFormatSymbols_ug_CN', 'goog.labs.i18n.ListFormatSymbols_uk_UA', 'goog.labs.i18n.ListFormatSymbols_ur_IN', 'goog.labs.i18n.ListFormatSymbols_ur_PK', 'goog.labs.i18n.ListFormatSymbols_uz_Arab', 'goog.labs.i18n.ListFormatSymbols_uz_Arab_AF', 'goog.labs.i18n.ListFormatSymbols_uz_Cyrl', 'goog.labs.i18n.ListFormatSymbols_uz_Cyrl_UZ', 'goog.labs.i18n.ListFormatSymbols_uz_Latn', 'goog.labs.i18n.ListFormatSymbols_uz_Latn_UZ', 'goog.labs.i18n.ListFormatSymbols_vai', 'goog.labs.i18n.ListFormatSymbols_vai_Latn', 'goog.labs.i18n.ListFormatSymbols_vai_Latn_LR', 'goog.labs.i18n.ListFormatSymbols_vai_Vaii', 'goog.labs.i18n.ListFormatSymbols_vai_Vaii_LR', 'goog.labs.i18n.ListFormatSymbols_vi_VN', 'goog.labs.i18n.ListFormatSymbols_vun', 'goog.labs.i18n.ListFormatSymbols_vun_TZ', 'goog.labs.i18n.ListFormatSymbols_wae', 'goog.labs.i18n.ListFormatSymbols_wae_CH', 'goog.labs.i18n.ListFormatSymbols_wo', 'goog.labs.i18n.ListFormatSymbols_wo_SN', 'goog.labs.i18n.ListFormatSymbols_xog', 'goog.labs.i18n.ListFormatSymbols_xog_UG', 'goog.labs.i18n.ListFormatSymbols_yav', 'goog.labs.i18n.ListFormatSymbols_yav_CM', 'goog.labs.i18n.ListFormatSymbols_yi', 'goog.labs.i18n.ListFormatSymbols_yi_001', 'goog.labs.i18n.ListFormatSymbols_yo', 'goog.labs.i18n.ListFormatSymbols_yo_BJ', 'goog.labs.i18n.ListFormatSymbols_yo_NG', 'goog.labs.i18n.ListFormatSymbols_yue', 'goog.labs.i18n.ListFormatSymbols_yue_Hans', 'goog.labs.i18n.ListFormatSymbols_yue_Hans_CN', 'goog.labs.i18n.ListFormatSymbols_yue_Hant', 'goog.labs.i18n.ListFormatSymbols_yue_Hant_HK', 'goog.labs.i18n.ListFormatSymbols_zgh', 'goog.labs.i18n.ListFormatSymbols_zgh_MA', 'goog.labs.i18n.ListFormatSymbols_zh_Hans', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_CN', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_HK', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_MO', 'goog.labs.i18n.ListFormatSymbols_zh_Hans_SG', 'goog.labs.i18n.ListFormatSymbols_zh_Hant', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_HK', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_MO', 'goog.labs.i18n.ListFormatSymbols_zh_Hant_TW', 'goog.labs.i18n.ListFormatSymbols_zu_ZA'], ['goog.labs.i18n.ListFormatSymbols']); +goog.addDependency("labs/iterable/iterable.js", [], []); +goog.addDependency("labs/iterable/iterable_test.js", [], ['goog.testing.jsunit']); +goog.addDependency("labs/mock/mock.js", ['goog.labs.mock', 'goog.labs.mock.VerificationError'], ['goog.array', 'goog.asserts', 'goog.debug', 'goog.debug.Error', 'goog.functions', 'goog.labs.mock.verification', 'goog.labs.mock.verification.VerificationMode', 'goog.object']); +goog.addDependency("labs/mock/mock_test.js", ['goog.labs.mockTest'], ['goog.array', 'goog.labs.mock', 'goog.labs.mock.VerificationError', 'goog.labs.testing.AnythingMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.string', 'goog.testing.jsunit']); +goog.addDependency("labs/mock/verificationmode.js", ['goog.labs.mock.verification', 'goog.labs.mock.verification.VerificationMode'], []); +goog.addDependency("labs/mock/verificationmode_test.js", [], []); +goog.addDependency("labs/net/image.js", ['goog.labs.net.image'], ['goog.Promise', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.net.EventType', 'goog.userAgent']); +goog.addDependency("labs/net/image_test.js", ['goog.labs.net.imageTest'], ['goog.labs.net.image', 'goog.string', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("labs/net/webchannel.js", ['goog.net.WebChannel'], ['goog.events', 'goog.events.Event']); +goog.addDependency("labs/net/webchannel/basetestchannel.js", ['goog.labs.net.webChannel.BaseTestChannel'], ['goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.requestStats', 'goog.net.WebChannel']); +goog.addDependency("labs/net/webchannel/channel.js", ['goog.labs.net.webChannel.Channel'], []); +goog.addDependency("labs/net/webchannel/channelrequest.js", ['goog.labs.net.webChannel.ChannelRequest'], ['goog.Timer', 'goog.async.Throttle', 'goog.events.EventHandler', 'goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.environment', 'goog.labs.net.webChannel.requestStats', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.userAgent']); +goog.addDependency("labs/net/webchannel/channelrequest_test.js", ['goog.labs.net.webChannel.channelRequestTest'], ['goog.Uri', 'goog.functions', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']); +goog.addDependency("labs/net/webchannel/connectionstate.js", ['goog.labs.net.webChannel.ConnectionState'], []); +goog.addDependency("labs/net/webchannel/environment.js", [], []); +goog.addDependency("labs/net/webchannel/environment_test.js", [], []); +goog.addDependency("labs/net/webchannel/forwardchannelrequestpool.js", [], []); +goog.addDependency("labs/net/webchannel/forwardchannelrequestpool_test.js", [], []); +goog.addDependency("labs/net/webchannel/netutils.js", ['goog.labs.net.webChannel.netUtils'], ['goog.Uri', 'goog.labs.net.webChannel.WebChannelDebug']); +goog.addDependency("labs/net/webchannel/requeststats.js", ['goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Event', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.labs.net.webChannel.requestStats.ServerReachabilityEvent', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.labs.net.webChannel.requestStats.StatEvent', 'goog.labs.net.webChannel.requestStats.TimingEvent'], ['goog.events.Event', 'goog.events.EventTarget']); +goog.addDependency("labs/net/webchannel/webchannelbase.js", ['goog.labs.net.webChannel.WebChannelBase'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.async.run', 'goog.debug.TextFormatter', 'goog.json', 'goog.labs.net.webChannel.BaseTestChannel', 'goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ConnectionState', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.WireV8', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.log', 'goog.net.WebChannel', 'goog.net.XhrIo', 'goog.net.rpc.HttpCors', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.CircularBuffer']); +goog.addDependency("labs/net/webchannel/webchannelbase_test.js", ['goog.labs.net.webChannel.webChannelBaseTest'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("labs/net/webchannel/webchannelbasetransport.js", ['goog.labs.net.webChannel.WebChannelBaseTransport'], ['goog.asserts', 'goog.events.EventTarget', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.log', 'goog.net.WebChannel', 'goog.net.WebChannelTransport', 'goog.object', 'goog.string', 'goog.string.path']); +goog.addDependency("labs/net/webchannel/webchannelbasetransport_test.js", ['goog.labs.net.webChannel.webChannelBaseTransportTest'], ['goog.events', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.net.WebChannel', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("labs/net/webchannel/webchanneldebug.js", ['goog.labs.net.webChannel.WebChannelDebug'], ['goog.json', 'goog.log']); +goog.addDependency("labs/net/webchannel/wire.js", ['goog.labs.net.webChannel.Wire'], []); +goog.addDependency("labs/net/webchannel/wirev8.js", ['goog.labs.net.webChannel.WireV8'], ['goog.asserts', 'goog.json', 'goog.json.NativeJsonProcessor', 'goog.labs.net.webChannel.Wire', 'goog.structs']); +goog.addDependency("labs/net/webchannel/wirev8_test.js", ['goog.labs.net.webChannel.WireV8Test'], ['goog.labs.net.webChannel.WireV8', 'goog.testing.jsunit']); +goog.addDependency("labs/net/webchanneltransport.js", ['goog.net.WebChannelTransport'], []); +goog.addDependency("labs/net/webchanneltransportfactory.js", ['goog.net.createWebChannelTransport'], ['goog.functions', 'goog.labs.net.webChannel.WebChannelBaseTransport']); +goog.addDependency("labs/net/xhr.js", ['goog.labs.net.xhr', 'goog.labs.net.xhr.Error', 'goog.labs.net.xhr.HttpError', 'goog.labs.net.xhr.Options', 'goog.labs.net.xhr.PostData', 'goog.labs.net.xhr.ResponseType', 'goog.labs.net.xhr.TimeoutError'], ['goog.Promise', 'goog.asserts', 'goog.debug.Error', 'goog.net.HttpStatus', 'goog.net.XmlHttp', 'goog.object', 'goog.string', 'goog.uri.utils', 'goog.userAgent']); +goog.addDependency("labs/net/xhr_test.js", ['goog.labs.net.xhrTest'], ['goog.Promise', 'goog.events', 'goog.events.EventType', 'goog.labs.net.xhr', 'goog.net.WrapperXmlHttpFactory', 'goog.net.XhrLike', 'goog.net.XmlHttp', 'goog.testing.MockClock', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("labs/pubsub/broadcastpubsub.js", ['goog.labs.pubsub.BroadcastPubSub'], ['goog.Disposable', 'goog.Timer', 'goog.array', 'goog.async.run', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.log', 'goog.math', 'goog.pubsub.PubSub', 'goog.storage.Storage', 'goog.storage.mechanism.HTML5LocalStorage', 'goog.string', 'goog.userAgent']); +goog.addDependency("labs/pubsub/broadcastpubsub_test.js", ['goog.labs.pubsub.BroadcastPubSubTest'], ['goog.array', 'goog.debug.Logger', 'goog.json', 'goog.labs.pubsub.BroadcastPubSub', 'goog.storage.Storage', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("labs/storage/boundedcollectablestorage.js", ['goog.labs.storage.BoundedCollectableStorage'], ['goog.array', 'goog.asserts', 'goog.iter', 'goog.storage.CollectableStorage', 'goog.storage.ErrorCode', 'goog.storage.ExpiringStorage']); +goog.addDependency("labs/storage/boundedcollectablestorage_test.js", ['goog.labs.storage.BoundedCollectableStorageTest'], ['goog.labs.storage.BoundedCollectableStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storageTester', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("labs/structs/multimap.js", ['goog.labs.structs.Multimap'], ['goog.array', 'goog.object']); +goog.addDependency("labs/structs/multimap_test.js", ['goog.labs.structs.MultimapTest'], ['goog.labs.structs.Multimap', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("labs/style/pixeldensitymonitor.js", ['goog.labs.style.PixelDensityMonitor', 'goog.labs.style.PixelDensityMonitor.Density', 'goog.labs.style.PixelDensityMonitor.EventType'], ['goog.events', 'goog.events.EventTarget']); +goog.addDependency("labs/style/pixeldensitymonitor_test.js", ['goog.labs.style.PixelDensityMonitorTest'], ['goog.array', 'goog.dom.DomHelper', 'goog.events', 'goog.labs.style.PixelDensityMonitor', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("labs/testing/assertthat.js", ['goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat'], ['goog.debug.Error']); +goog.addDependency("labs/testing/assertthat_test.js", ['goog.labs.testing.assertThatTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("labs/testing/decoratormatcher.js", ['goog.labs.testing.AnythingMatcher'], ['goog.labs.testing.Matcher']); +goog.addDependency("labs/testing/decoratormatcher_test.js", ['goog.labs.testing.decoratorMatcherTest'], ['goog.labs.testing.AnythingMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/dictionarymatcher.js", ['goog.labs.testing.HasEntriesMatcher', 'goog.labs.testing.HasEntryMatcher', 'goog.labs.testing.HasKeyMatcher', 'goog.labs.testing.HasValueMatcher'], ['goog.asserts', 'goog.labs.testing.Matcher', 'goog.object']); +goog.addDependency("labs/testing/dictionarymatcher_test.js", ['goog.labs.testing.dictionaryMatcherTest'], ['goog.labs.testing.HasEntryMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/environment.js", ['goog.labs.testing.Environment'], ['goog.Thenable', 'goog.array', 'goog.asserts', 'goog.debug.Console', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/environment_test.js", ['goog.labs.testing.environmentTest'], ['goog.Promise', 'goog.labs.testing.Environment', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.testSuite']); +goog.addDependency("labs/testing/environment_usage_test.js", ['goog.labs.testing.environmentUsageTest'], ['goog.labs.testing.Environment']); +goog.addDependency("labs/testing/json_fuzzing.js", ['goog.labs.testing.JsonFuzzing'], ['goog.string', 'goog.testing.PseudoRandom']); +goog.addDependency("labs/testing/json_fuzzing_test.js", ['goog.labs.testing.JsonFuzzingTest'], ['goog.json', 'goog.labs.testing.JsonFuzzing', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/logicmatcher.js", ['goog.labs.testing.AllOfMatcher', 'goog.labs.testing.AnyOfMatcher', 'goog.labs.testing.IsNotMatcher'], ['goog.array', 'goog.labs.testing.Matcher']); +goog.addDependency("labs/testing/logicmatcher_test.js", ['goog.labs.testing.logicMatcherTest'], ['goog.labs.testing.AllOfMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/matcher.js", ['goog.labs.testing.Matcher'], []); +goog.addDependency("labs/testing/numbermatcher.js", ['goog.labs.testing.AnyNumberMatcher', 'goog.labs.testing.CloseToMatcher', 'goog.labs.testing.EqualToMatcher', 'goog.labs.testing.GreaterThanEqualToMatcher', 'goog.labs.testing.GreaterThanMatcher', 'goog.labs.testing.LessThanEqualToMatcher', 'goog.labs.testing.LessThanMatcher'], ['goog.asserts', 'goog.labs.testing.Matcher']); +goog.addDependency("labs/testing/numbermatcher_test.js", ['goog.labs.testing.numberMatcherTest'], ['goog.labs.testing.LessThanMatcher', 'goog.labs.testing.MatcherError', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/objectmatcher.js", ['goog.labs.testing.AnyObjectMatcher', 'goog.labs.testing.HasPropertyMatcher', 'goog.labs.testing.InstanceOfMatcher', 'goog.labs.testing.IsNullMatcher', 'goog.labs.testing.IsNullOrUndefinedMatcher', 'goog.labs.testing.IsUndefinedMatcher', 'goog.labs.testing.ObjectEqualsMatcher'], ['goog.labs.testing.Matcher']); +goog.addDependency("labs/testing/objectmatcher_test.js", ['goog.labs.testing.objectMatcherTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.ObjectEqualsMatcher', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/testing/stringmatcher.js", ['goog.labs.testing.AnyStringMatcher', 'goog.labs.testing.ContainsStringMatcher', 'goog.labs.testing.EndsWithMatcher', 'goog.labs.testing.EqualToIgnoringWhitespaceMatcher', 'goog.labs.testing.EqualsMatcher', 'goog.labs.testing.RegexMatcher', 'goog.labs.testing.StartsWithMatcher', 'goog.labs.testing.StringContainsInOrderMatcher'], ['goog.asserts', 'goog.labs.testing.Matcher', 'goog.string']); +goog.addDependency("labs/testing/stringmatcher_test.js", ['goog.labs.testing.stringMatcherTest'], ['goog.labs.testing.MatcherError', 'goog.labs.testing.StringContainsInOrderMatcher', 'goog.labs.testing.assertThat', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/browser.js", ['goog.labs.userAgent.browser'], ['goog.array', 'goog.labs.userAgent.util', 'goog.object', 'goog.string']); +goog.addDependency("labs/useragent/browser_test.js", ['goog.labs.userAgent.browserTest'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.object', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/device.js", ['goog.labs.userAgent.device'], ['goog.labs.userAgent.util']); +goog.addDependency("labs/useragent/device_test.js", ['goog.labs.userAgent.deviceTest'], ['goog.labs.userAgent.device', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/engine.js", ['goog.labs.userAgent.engine'], ['goog.array', 'goog.labs.userAgent.util', 'goog.string']); +goog.addDependency("labs/useragent/engine_test.js", ['goog.labs.userAgent.engineTest'], ['goog.labs.userAgent.engine', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/platform.js", ['goog.labs.userAgent.platform'], ['goog.labs.userAgent.util', 'goog.string']); +goog.addDependency("labs/useragent/platform_test.js", ['goog.labs.userAgent.platformTest'], ['goog.labs.userAgent.platform', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/test_agents.js", ['goog.labs.userAgent.testAgents'], []); +goog.addDependency("labs/useragent/util.js", ['goog.labs.userAgent.util'], ['goog.string']); +goog.addDependency("labs/useragent/util_test.js", ['goog.labs.userAgent.utilTest'], ['goog.functions', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("labs/useragent/verifier.js", ['goog.labs.useragent.verifier'], []); +goog.addDependency("labs/useragent/verifier_test.js", [], []); +goog.addDependency("loader/abstractmodulemanager.js", ['goog.loader.AbstractModuleManager', 'goog.loader.AbstractModuleManager.CallbackType', 'goog.loader.AbstractModuleManager.FailureType'], ['goog.Disposable', 'goog.module.AbstractModuleLoader', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback']); +goog.addDependency("loader/activemodulemanager.js", [], []); +goog.addDependency("locale/countries.js", ['goog.locale.countries'], []); +goog.addDependency("locale/countrylanguagenames_test.js", ['goog.locale.countryLanguageNamesTest'], ['goog.locale', 'goog.testing.jsunit']); +goog.addDependency("locale/defaultlocalenameconstants.js", ['goog.locale.defaultLocaleNameConstants'], []); +goog.addDependency("locale/genericfontnames.js", ['goog.locale.genericFontNames'], []); +goog.addDependency("locale/genericfontnames_test.js", ['goog.locale.genericFontNamesTest'], ['goog.locale.genericFontNames', 'goog.testing.jsunit']); +goog.addDependency("locale/genericfontnamesdata.js", ['goog.locale.genericFontNamesData'], []); +goog.addDependency("locale/locale.js", ['goog.locale'], ['goog.locale.nativeNameConstants']); +goog.addDependency("locale/nativenameconstants.js", ['goog.locale.nativeNameConstants'], []); +goog.addDependency("locale/scriptToLanguages.js", ['goog.locale.scriptToLanguages'], ['goog.locale']); +goog.addDependency("locale/timezonedetection.js", ['goog.locale.timeZoneDetection'], ['goog.locale.TimeZoneFingerprint']); +goog.addDependency("locale/timezonedetection_test.js", ['goog.locale.timeZoneDetectionTest'], ['goog.locale.timeZoneDetection', 'goog.testing.jsunit']); +goog.addDependency("locale/timezonefingerprint.js", ['goog.locale.TimeZoneFingerprint'], []); +goog.addDependency("locale/timezonelist.js", ['goog.locale.TimeZoneList', 'goog.locale.getTimeZoneAllLongNames', 'goog.locale.getTimeZoneSelectedLongNames', 'goog.locale.getTimeZoneSelectedShortNames'], ['goog.locale']); +goog.addDependency("locale/timezonelist_test.js", ['goog.locale.TimeZoneListTest'], ['goog.locale', 'goog.locale.TimeZoneList', 'goog.testing.jsunit']); +goog.addDependency("log/log.js", ['goog.log', 'goog.log.Level', 'goog.log.LogRecord', 'goog.log.Logger'], ['goog.debug', 'goog.debug.LogManager', 'goog.debug.LogRecord', 'goog.debug.Logger']); +goog.addDependency("log/log_test.js", ['goog.logTest'], ['goog.debug.LogManager', 'goog.log', 'goog.log.Level', 'goog.testing.jsunit']); +goog.addDependency("math/affinetransform.js", ['goog.math.AffineTransform'], []); +goog.addDependency("math/affinetransform_test.js", ['goog.math.AffineTransformTest'], ['goog.array', 'goog.math', 'goog.math.AffineTransform', 'goog.testing.jsunit']); +goog.addDependency("math/bezier.js", ['goog.math.Bezier'], ['goog.math', 'goog.math.Coordinate']); +goog.addDependency("math/bezier_test.js", ['goog.math.BezierTest'], ['goog.math', 'goog.math.Bezier', 'goog.math.Coordinate', 'goog.testing.jsunit']); +goog.addDependency("math/box.js", ['goog.math.Box'], ['goog.asserts', 'goog.math.Coordinate']); +goog.addDependency("math/box_test.js", ['goog.math.BoxTest'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.testing.jsunit']); +goog.addDependency("math/coordinate.js", ['goog.math.Coordinate'], ['goog.math']); +goog.addDependency("math/coordinate3.js", ['goog.math.Coordinate3'], []); +goog.addDependency("math/coordinate3_test.js", ['goog.math.Coordinate3Test'], ['goog.math.Coordinate3', 'goog.testing.jsunit']); +goog.addDependency("math/coordinate_test.js", ['goog.math.CoordinateTest'], ['goog.math.Coordinate', 'goog.testing.jsunit']); +goog.addDependency("math/exponentialbackoff.js", ['goog.math.ExponentialBackoff'], ['goog.asserts']); +goog.addDependency("math/exponentialbackoff_test.js", ['goog.math.ExponentialBackoffTest'], ['goog.math.ExponentialBackoff', 'goog.testing.jsunit']); +goog.addDependency("math/integer.js", ['goog.math.Integer'], []); +goog.addDependency("math/integer_test.js", ['goog.math.IntegerTest'], ['goog.math.Integer', 'goog.testing.jsunit']); +goog.addDependency("math/interpolator/interpolator1.js", ['goog.math.interpolator.Interpolator1'], []); +goog.addDependency("math/interpolator/linear1.js", ['goog.math.interpolator.Linear1'], ['goog.array', 'goog.asserts', 'goog.math', 'goog.math.interpolator.Interpolator1']); +goog.addDependency("math/interpolator/linear1_test.js", ['goog.math.interpolator.Linear1Test'], ['goog.math.interpolator.Linear1', 'goog.testing.jsunit']); +goog.addDependency("math/interpolator/pchip1.js", ['goog.math.interpolator.Pchip1'], ['goog.math', 'goog.math.interpolator.Spline1']); +goog.addDependency("math/interpolator/pchip1_test.js", ['goog.math.interpolator.Pchip1Test'], ['goog.math.interpolator.Pchip1', 'goog.testing.jsunit']); +goog.addDependency("math/interpolator/spline1.js", ['goog.math.interpolator.Spline1'], ['goog.array', 'goog.asserts', 'goog.math', 'goog.math.interpolator.Interpolator1', 'goog.math.tdma']); +goog.addDependency("math/interpolator/spline1_test.js", ['goog.math.interpolator.Spline1Test'], ['goog.math.interpolator.Spline1', 'goog.testing.jsunit']); +goog.addDependency("math/irect.js", ['goog.math.IRect'], []); +goog.addDependency("math/line.js", ['goog.math.Line'], ['goog.math', 'goog.math.Coordinate']); +goog.addDependency("math/line_test.js", ['goog.math.LineTest'], ['goog.math.Coordinate', 'goog.math.Line', 'goog.testing.jsunit']); +goog.addDependency("math/long.js", ['goog.math.Long'], ['goog.asserts', 'goog.reflect']); +goog.addDependency("math/long_test.js", ['goog.math.LongTest'], ['goog.asserts', 'goog.math.Long', 'goog.testing.jsunit']); +goog.addDependency("math/math.js", ['goog.math'], ['goog.array', 'goog.asserts']); +goog.addDependency("math/math_test.js", ['goog.mathTest'], ['goog.math', 'goog.testing.jsunit']); +goog.addDependency("math/matrix.js", ['goog.math.Matrix'], ['goog.array', 'goog.asserts', 'goog.math', 'goog.math.Size', 'goog.string']); +goog.addDependency("math/matrix_test.js", ['goog.math.MatrixTest'], ['goog.math.Matrix', 'goog.testing.jsunit']); +goog.addDependency("math/path.js", ['goog.math.Path', 'goog.math.Path.Segment'], ['goog.array', 'goog.math', 'goog.math.AffineTransform']); +goog.addDependency("math/path_test.js", ['goog.math.PathTest'], ['goog.array', 'goog.math.AffineTransform', 'goog.math.Path', 'goog.testing.jsunit']); +goog.addDependency("math/paths.js", ['goog.math.paths'], ['goog.math.Coordinate', 'goog.math.Path']); +goog.addDependency("math/paths_test.js", ['goog.math.pathsTest'], ['goog.math.Coordinate', 'goog.math.paths', 'goog.testing.jsunit']); +goog.addDependency("math/range.js", ['goog.math.Range'], ['goog.asserts']); +goog.addDependency("math/range_test.js", ['goog.math.RangeTest'], ['goog.math.Range', 'goog.testing.jsunit']); +goog.addDependency("math/rangeset.js", ['goog.math.RangeSet'], ['goog.array', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.math.Range']); +goog.addDependency("math/rangeset_test.js", ['goog.math.RangeSetTest'], ['goog.iter', 'goog.math.Range', 'goog.math.RangeSet', 'goog.testing.jsunit']); +goog.addDependency("math/rect.js", ['goog.math.Rect'], ['goog.asserts', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.IRect', 'goog.math.Size']); +goog.addDependency("math/rect_test.js", ['goog.math.RectTest'], ['goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.testing.jsunit']); +goog.addDependency("math/size.js", ['goog.math.Size'], []); +goog.addDependency("math/size_test.js", ['goog.math.SizeTest'], ['goog.math.Size', 'goog.testing.jsunit']); +goog.addDependency("math/tdma.js", ['goog.math.tdma'], []); +goog.addDependency("math/tdma_test.js", ['goog.math.tdmaTest'], ['goog.math.tdma', 'goog.testing.jsunit']); +goog.addDependency("math/vec2.js", ['goog.math.Vec2'], ['goog.math', 'goog.math.Coordinate']); +goog.addDependency("math/vec2_test.js", ['goog.math.Vec2Test'], ['goog.math.Vec2', 'goog.testing.jsunit']); +goog.addDependency("math/vec3.js", ['goog.math.Vec3'], ['goog.math', 'goog.math.Coordinate3']); +goog.addDependency("math/vec3_test.js", ['goog.math.Vec3Test'], ['goog.math.Coordinate3', 'goog.math.Vec3', 'goog.testing.jsunit']); +goog.addDependency("memoize/memoize.js", ['goog.memoize'], []); +goog.addDependency("memoize/memoize_test.js", ['goog.memoizeTest'], ['goog.memoize', 'goog.testing.jsunit']); +goog.addDependency("messaging/abstractchannel.js", ['goog.messaging.AbstractChannel'], ['goog.Disposable', 'goog.json', 'goog.log', 'goog.messaging.MessageChannel']); +goog.addDependency("messaging/abstractchannel_test.js", ['goog.messaging.AbstractChannelTest'], ['goog.messaging.AbstractChannel', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit']); +goog.addDependency("messaging/bufferedchannel.js", ['goog.messaging.BufferedChannel'], ['goog.Disposable', 'goog.Timer', 'goog.events', 'goog.log', 'goog.messaging.MessageChannel', 'goog.messaging.MultiChannel']); +goog.addDependency("messaging/bufferedchannel_test.js", ['goog.messaging.BufferedChannelTest'], ['goog.debug.Console', 'goog.dom', 'goog.dom.TagName', 'goog.log', 'goog.log.Level', 'goog.messaging.BufferedChannel', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/deferredchannel.js", ['goog.messaging.DeferredChannel'], ['goog.Disposable', 'goog.messaging.MessageChannel']); +goog.addDependency("messaging/deferredchannel_test.js", ['goog.messaging.DeferredChannelTest'], ['goog.async.Deferred', 'goog.messaging.DeferredChannel', 'goog.testing.MockControl', 'goog.testing.async.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/loggerclient.js", ['goog.messaging.LoggerClient'], ['goog.Disposable', 'goog.debug', 'goog.debug.LogManager', 'goog.debug.Logger']); +goog.addDependency("messaging/loggerclient_test.js", ['goog.messaging.LoggerClientTest'], ['goog.debug', 'goog.debug.Logger', 'goog.messaging.LoggerClient', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/loggerserver.js", ['goog.messaging.LoggerServer'], ['goog.Disposable', 'goog.log', 'goog.log.Level']); +goog.addDependency("messaging/loggerserver_test.js", ['goog.messaging.LoggerServerTest'], ['goog.debug.LogManager', 'goog.debug.Logger', 'goog.log', 'goog.log.Level', 'goog.messaging.LoggerServer', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/messagechannel.js", ['goog.messaging.MessageChannel'], []); +goog.addDependency("messaging/messaging.js", ['goog.messaging'], []); +goog.addDependency("messaging/messaging_test.js", ['goog.testing.messaging.MockMessageChannelTest'], ['goog.messaging', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/multichannel.js", ['goog.messaging.MultiChannel', 'goog.messaging.MultiChannel.VirtualChannel'], ['goog.Disposable', 'goog.log', 'goog.messaging.MessageChannel', 'goog.object']); +goog.addDependency("messaging/multichannel_test.js", ['goog.messaging.MultiChannelTest'], ['goog.messaging.MultiChannel', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel', 'goog.testing.mockmatchers.IgnoreArgument']); +goog.addDependency("messaging/portcaller.js", ['goog.messaging.PortCaller'], ['goog.Disposable', 'goog.async.Deferred', 'goog.messaging.DeferredChannel', 'goog.messaging.PortChannel', 'goog.messaging.PortNetwork', 'goog.object']); +goog.addDependency("messaging/portcaller_test.js", ['goog.messaging.PortCallerTest'], ['goog.events.EventTarget', 'goog.messaging.PortCaller', 'goog.messaging.PortNetwork', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/portchannel.js", ['goog.messaging.PortChannel'], ['goog.Timer', 'goog.array', 'goog.async.Deferred', 'goog.debug', 'goog.events', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.messaging.AbstractChannel', 'goog.messaging.DeferredChannel', 'goog.object', 'goog.string', 'goog.userAgent']); +goog.addDependency("messaging/portchannel_test.js", ['goog.messaging.PortChannelTest'], ['goog.Promise', 'goog.Timer', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.json', 'goog.messaging.PortChannel', 'goog.testing.MockControl', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageEvent']); +goog.addDependency("messaging/portnetwork.js", ['goog.messaging.PortNetwork'], []); +goog.addDependency("messaging/portnetwork_test.js", ['goog.messaging.PortNetworkTest'], ['goog.Promise', 'goog.Timer', 'goog.labs.userAgent.browser', 'goog.messaging.PortChannel', 'goog.messaging.PortOperator', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("messaging/portoperator.js", ['goog.messaging.PortOperator'], ['goog.Disposable', 'goog.asserts', 'goog.log', 'goog.messaging.PortChannel', 'goog.messaging.PortNetwork', 'goog.object']); +goog.addDependency("messaging/portoperator_test.js", ['goog.messaging.PortOperatorTest'], ['goog.messaging.PortNetwork', 'goog.messaging.PortOperator', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel', 'goog.testing.messaging.MockMessagePort']); +goog.addDependency("messaging/respondingchannel.js", ['goog.messaging.RespondingChannel'], ['goog.Disposable', 'goog.log', 'goog.messaging.MultiChannel']); +goog.addDependency("messaging/respondingchannel_test.js", ['goog.messaging.RespondingChannelTest'], ['goog.messaging.RespondingChannel', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("messaging/testdata/portchannel_worker.js", ['goog.messaging.testdata.portchannel_worker'], ['goog.messaging.PortChannel']); +goog.addDependency("messaging/testdata/portnetwork_worker1.js", ['goog.messaging.testdata.portnetwork_worker1'], ['goog.messaging.PortCaller', 'goog.messaging.PortChannel']); +goog.addDependency("messaging/testdata/portnetwork_worker2.js", ['goog.messaging.testdata.portnetwork_worker2'], ['goog.messaging.PortCaller', 'goog.messaging.PortChannel']); +goog.addDependency("module/abstractmoduleloader.js", ['goog.module.AbstractModuleLoader'], ['goog.module', 'goog.module.ModuleInfo']); +goog.addDependency("module/basemodule.js", ['goog.module.BaseModule'], ['goog.Disposable', 'goog.module']); +goog.addDependency("module/loader.js", ['goog.module.Loader'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.module', 'goog.object']); +goog.addDependency("module/module.js", ['goog.module'], []); +goog.addDependency("module/moduleinfo.js", ['goog.module.ModuleInfo'], ['goog.Disposable', 'goog.async.throwException', 'goog.functions', 'goog.html.TrustedResourceUrl', 'goog.module', 'goog.module.BaseModule', 'goog.module.ModuleLoadCallback']); +goog.addDependency("module/moduleinfo_test.js", ['goog.module.ModuleInfoTest'], ['goog.module.BaseModule', 'goog.module.ModuleInfo', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("module/moduleloadcallback.js", ['goog.module.ModuleLoadCallback'], ['goog.debug.entryPointRegistry', 'goog.module']); +goog.addDependency("module/moduleloadcallback_test.js", ['goog.module.ModuleLoadCallbackTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.functions', 'goog.module.ModuleLoadCallback', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("module/moduleloader.js", ['goog.module.ModuleLoader'], ['goog.Timer', 'goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.html.TrustedResourceUrl', 'goog.labs.userAgent.browser', 'goog.log', 'goog.module.AbstractModuleLoader', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.net.jsloader', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("module/moduleloader_test.js", ['goog.module.ModuleLoaderTest'], ['goog.Promise', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.functions', 'goog.html.TrustedResourceUrl', 'goog.loader.activeModuleManager', 'goog.module.ModuleLoader', 'goog.module.ModuleManager', 'goog.net.BulkLoader', 'goog.net.XmlHttp', 'goog.object', 'goog.string.Const', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.events.EventObserver', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("module/modulemanager.js", ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.disposeAll', 'goog.loader.AbstractModuleManager', 'goog.loader.activeModuleManager', 'goog.log', 'goog.module', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']); +goog.addDependency("module/modulemanager_test.js", ['goog.module.ModuleManagerTest'], ['goog.array', 'goog.functions', 'goog.module.BaseModule', 'goog.module.ModuleManager', 'goog.testing', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("module/testdata/modA_1.js", ['goog.module.testdata.modA_1'], []); +goog.addDependency("module/testdata/modA_2.js", ['goog.module.testdata.modA_2'], ['goog.module.ModuleManager']); +goog.addDependency("module/testdata/modB_1.js", ['goog.module.testdata.modB_1'], ['goog.module.ModuleManager']); +goog.addDependency("net/browserchannel.js", ['goog.net.BrowserChannel', 'goog.net.BrowserChannel.Error', 'goog.net.BrowserChannel.Event', 'goog.net.BrowserChannel.Handler', 'goog.net.BrowserChannel.LogSaver', 'goog.net.BrowserChannel.QueuedMap', 'goog.net.BrowserChannel.ServerReachability', 'goog.net.BrowserChannel.ServerReachabilityEvent', 'goog.net.BrowserChannel.Stat', 'goog.net.BrowserChannel.StatEvent', 'goog.net.BrowserChannel.State', 'goog.net.BrowserChannel.TimingEvent'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.debug.TextFormatter', 'goog.events.Event', 'goog.events.EventTarget', 'goog.json', 'goog.json.NativeJsonProcessor', 'goog.log', 'goog.net.BrowserTestChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.net.XhrIo', 'goog.net.tmpnetwork', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.CircularBuffer']); +goog.addDependency("net/browserchannel_test.js", ['goog.net.BrowserChannelTest'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.functions', 'goog.json', 'goog.net.BrowserChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.net.tmpnetwork', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("net/browsertestchannel.js", ['goog.net.BrowserTestChannel'], ['goog.json.NativeJsonProcessor', 'goog.net.ChannelRequest', 'goog.net.ChannelRequest.Error', 'goog.net.tmpnetwork', 'goog.string.Parser']); +goog.addDependency("net/bulkloader.js", ['goog.net.BulkLoader'], ['goog.events.EventHandler', 'goog.events.EventTarget', 'goog.log', 'goog.net.BulkLoaderHelper', 'goog.net.EventType', 'goog.net.XhrIo']); +goog.addDependency("net/bulkloader_test.js", ['goog.net.BulkLoaderTest'], ['goog.events.Event', 'goog.events.EventHandler', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("net/bulkloaderhelper.js", ['goog.net.BulkLoaderHelper'], ['goog.Disposable']); +goog.addDependency("net/channeldebug.js", ['goog.net.ChannelDebug'], ['goog.json', 'goog.log']); +goog.addDependency("net/channelrequest.js", ['goog.net.ChannelRequest', 'goog.net.ChannelRequest.Error'], ['goog.Timer', 'goog.async.Throttle', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events.EventHandler', 'goog.html.SafeUrl', 'goog.html.uncheckedconversions', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("net/channelrequest_test.js", ['goog.net.ChannelRequestTest'], ['goog.Uri', 'goog.functions', 'goog.net.BrowserChannel', 'goog.net.ChannelDebug', 'goog.net.ChannelRequest', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction']); +goog.addDependency("net/cookies.js", ['goog.net.Cookies', 'goog.net.cookies'], ['goog.string']); +goog.addDependency("net/cookies_test.js", ['goog.net.cookiesTest'], ['goog.array', 'goog.net.cookies', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("net/corsxmlhttpfactory.js", ['goog.net.CorsXmlHttpFactory', 'goog.net.IeCorsXhrAdapter'], ['goog.net.HttpStatus', 'goog.net.XhrLike', 'goog.net.XmlHttp', 'goog.net.XmlHttpFactory']); +goog.addDependency("net/corsxmlhttpfactory_test.js", ['goog.net.CorsXmlHttpFactoryTest'], ['goog.net.CorsXmlHttpFactory', 'goog.net.IeCorsXhrAdapter', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("net/crossdomainrpc.js", ['goog.net.CrossDomainRpc'], ['goog.Uri', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.html.SafeHtml', 'goog.log', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.string', 'goog.userAgent']); +goog.addDependency("net/crossdomainrpc_test.js", ['goog.net.CrossDomainRpcTest'], ['goog.Promise', 'goog.log', 'goog.net.CrossDomainRpc', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("net/errorcode.js", ['goog.net.ErrorCode'], []); +goog.addDependency("net/eventtype.js", ['goog.net.EventType'], []); +goog.addDependency("net/fetchxmlhttpfactory.js", ['goog.net.FetchXmlHttp', 'goog.net.FetchXmlHttpFactory'], ['goog.asserts', 'goog.events.EventTarget', 'goog.functions', 'goog.log', 'goog.net.XhrLike', 'goog.net.XmlHttpFactory']); +goog.addDependency("net/fetchxmlhttpfactory_test.js", ['goog.net.FetchXmlHttpFactoryTest'], ['goog.net.FetchXmlHttp', 'goog.net.FetchXmlHttpFactory', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("net/filedownloader.js", ['goog.net.FileDownloader', 'goog.net.FileDownloader.Error'], ['goog.Disposable', 'goog.asserts', 'goog.async.Deferred', 'goog.crypt.hash32', 'goog.debug.Error', 'goog.events', 'goog.events.EventHandler', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrIoPool', 'goog.object']); +goog.addDependency("net/filedownloader_test.js", ['goog.net.FileDownloaderTest'], ['goog.fs.Error', 'goog.net.ErrorCode', 'goog.net.FileDownloader', 'goog.net.XhrIo', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.fs', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit', 'goog.testing.net.XhrIoPool']); +goog.addDependency("net/httpstatus.js", ['goog.net.HttpStatus'], []); +goog.addDependency("net/httpstatusname.js", ['goog.net.HttpStatusName'], []); +goog.addDependency("net/iframeio.js", ['goog.net.IframeIo', 'goog.net.IframeIo.IncrementalDataEvent'], ['goog.Timer', 'goog.Uri', 'goog.array', 'goog.asserts', 'goog.debug.HtmlFormatter', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.html.uncheckedconversions', 'goog.json', 'goog.log', 'goog.log.Level', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.reflect', 'goog.string', 'goog.string.Const', 'goog.structs', 'goog.userAgent']); +goog.addDependency("net/iframeio_different_base_test.js", ['goog.net.iframeIoDifferentBaseTest'], ['goog.Promise', 'goog.events', 'goog.net.EventType', 'goog.net.IframeIo', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("net/iframeio_test.js", ['goog.net.IframeIoTest'], ['goog.debug', 'goog.debug.DivConsole', 'goog.debug.LogManager', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.log', 'goog.log.Level', 'goog.net.IframeIo', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("net/iframeloadmonitor.js", ['goog.net.IframeLoadMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']); +goog.addDependency("net/iframeloadmonitor_test.js", [], []); +goog.addDependency("net/imageloader.js", ['goog.net.ImageLoader'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.net.EventType', 'goog.object', 'goog.userAgent']); +goog.addDependency("net/imageloader_test.js", ['goog.net.ImageLoaderTest'], ['goog.Promise', 'goog.Timer', 'goog.array', 'goog.dispose', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.net.EventType', 'goog.net.ImageLoader', 'goog.object', 'goog.string', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("net/ipaddress.js", ['goog.net.IpAddress', 'goog.net.Ipv4Address', 'goog.net.Ipv6Address'], ['goog.array', 'goog.math.Integer', 'goog.object', 'goog.string']); +goog.addDependency("net/ipaddress_test.js", ['goog.net.IpAddressTest'], ['goog.array', 'goog.math.Integer', 'goog.net.IpAddress', 'goog.net.Ipv4Address', 'goog.net.Ipv6Address', 'goog.testing.jsunit']); +goog.addDependency("net/jsloader.js", ['goog.net.jsloader', 'goog.net.jsloader.Error', 'goog.net.jsloader.ErrorCode', 'goog.net.jsloader.Options'], ['goog.array', 'goog.async.Deferred', 'goog.debug.Error', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.TrustedResourceUrl', 'goog.object']); +goog.addDependency("net/jsloader_test.js", ['goog.net.jsloaderTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.html.TrustedResourceUrl', 'goog.net.jsloader', 'goog.net.jsloader.ErrorCode', 'goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("net/jsonp.js", ['goog.net.Jsonp'], ['goog.html.TrustedResourceUrl', 'goog.net.jsloader', 'goog.object']); +goog.addDependency("net/jsonp_test.js", ['goog.net.JsonpTest'], ['goog.html.TrustedResourceUrl', 'goog.net.Jsonp', 'goog.string.Const', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("net/mockiframeio.js", ['goog.net.MockIFrameIo'], ['goog.events.EventTarget', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.IframeIo']); +goog.addDependency("net/multiiframeloadmonitor.js", ['goog.net.MultiIframeLoadMonitor'], ['goog.events', 'goog.net.IframeLoadMonitor']); +goog.addDependency("net/multiiframeloadmonitor_test.js", [], []); +goog.addDependency("net/networkstatusmonitor.js", ['goog.net.NetworkStatusMonitor'], ['goog.events.Listenable']); +goog.addDependency("net/networktester.js", ['goog.net.NetworkTester'], ['goog.Timer', 'goog.Uri', 'goog.log']); +goog.addDependency("net/networktester_test.js", ['goog.net.NetworkTesterTest'], ['goog.Uri', 'goog.net.NetworkTester', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("net/rpc/httpcors.js", [], []); +goog.addDependency("net/rpc/httpcors_test.js", [], []); +goog.addDependency("net/streams/base64pbstreamparser.js", [], []); +goog.addDependency("net/streams/base64pbstreamparser_test.js", [], []); +goog.addDependency("net/streams/base64streamdecoder.js", ['goog.net.streams.Base64StreamDecoder'], ['goog.asserts', 'goog.crypt.base64']); +goog.addDependency("net/streams/base64streamdecoder_test.js", ['goog.net.streams.Base64StreamDecoderTest'], ['goog.net.streams.Base64StreamDecoder', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("net/streams/jsonstreamparser.js", ['goog.net.streams.JsonStreamParser', 'goog.net.streams.JsonStreamParser.Options'], ['goog.asserts', 'goog.net.streams.StreamParser', 'goog.net.streams.utils']); +goog.addDependency("net/streams/jsonstreamparser_test.js", ['goog.net.streams.JsonStreamParserTest'], ['goog.array', 'goog.json', 'goog.labs.testing.JsonFuzzing', 'goog.net.streams.JsonStreamParser', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.uri.utils']); +goog.addDependency("net/streams/nodereadablestream.js", ['goog.net.streams.NodeReadableStream'], []); +goog.addDependency("net/streams/pbjsonstreamparser.js", [], []); +goog.addDependency("net/streams/pbjsonstreamparser_test.js", [], []); +goog.addDependency("net/streams/pbstreamparser.js", ['goog.net.streams.PbStreamParser'], ['goog.asserts', 'goog.net.streams.StreamParser']); +goog.addDependency("net/streams/pbstreamparser_test.js", ['goog.net.streams.PbStreamParserTest'], ['goog.net.streams.PbStreamParser', 'goog.object', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("net/streams/streamfactory.js", ['goog.net.streams.createXhrNodeReadableStream'], ['goog.asserts', 'goog.net.streams.XhrNodeReadableStream', 'goog.net.streams.XhrStreamReader']); +goog.addDependency("net/streams/streamparser.js", ['goog.net.streams.StreamParser'], []); +goog.addDependency("net/streams/utils.js", [], []); +goog.addDependency("net/streams/xhrnodereadablestream.js", ['goog.net.streams.XhrNodeReadableStream'], ['goog.array', 'goog.log', 'goog.net.streams.NodeReadableStream', 'goog.net.streams.XhrStreamReader']); +goog.addDependency("net/streams/xhrnodereadablestream_test.js", ['goog.net.streams.XhrNodeReadableStreamTest'], ['goog.net.streams.NodeReadableStream', 'goog.net.streams.XhrNodeReadableStream', 'goog.net.streams.XhrStreamReader', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("net/streams/xhrstreamreader.js", ['goog.net.streams.XhrStreamReader'], ['goog.events.EventHandler', 'goog.log', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.net.XhrIo', 'goog.net.XmlHttp', 'goog.net.streams.Base64PbStreamParser', 'goog.net.streams.JsonStreamParser', 'goog.net.streams.PbJsonStreamParser', 'goog.net.streams.PbStreamParser', 'goog.string', 'goog.userAgent']); +goog.addDependency("net/streams/xhrstreamreader_test.js", ['goog.net.streams.XhrStreamReaderTest'], ['goog.net.ErrorCode', 'goog.net.XmlHttp', 'goog.net.streams.Base64PbStreamParser', 'goog.net.streams.JsonStreamParser', 'goog.net.streams.PbJsonStreamParser', 'goog.net.streams.PbStreamParser', 'goog.net.streams.XhrStreamReader', 'goog.object', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.net.XhrIo']); +goog.addDependency("net/testdata/jsloader_test1.js", ['goog.net.testdata.jsloader_test1'], []); +goog.addDependency("net/testdata/jsloader_test2.js", ['goog.net.testdata.jsloader_test2'], []); +goog.addDependency("net/testdata/jsloader_test3.js", ['goog.net.testdata.jsloader_test3'], []); +goog.addDependency("net/testdata/jsloader_test4.js", ['goog.net.testdata.jsloader_test4'], []); +goog.addDependency("net/tmpnetwork.js", ['goog.net.tmpnetwork'], ['goog.Uri', 'goog.net.ChannelDebug']); +goog.addDependency("net/websocket.js", ['goog.net.WebSocket', 'goog.net.WebSocket.ErrorEvent', 'goog.net.WebSocket.EventType', 'goog.net.WebSocket.MessageEvent'], ['goog.Timer', 'goog.asserts', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.log']); +goog.addDependency("net/websocket_test.js", ['goog.net.WebSocketTest'], ['goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.functions', 'goog.net.WebSocket', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("net/wrapperxmlhttpfactory.js", ['goog.net.WrapperXmlHttpFactory'], ['goog.net.XhrLike', 'goog.net.XmlHttpFactory']); +goog.addDependency("net/xhrio.js", ['goog.net.XhrIo', 'goog.net.XhrIo.ResponseType'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.debug.entryPointRegistry', 'goog.events.EventTarget', 'goog.json.hybrid', 'goog.log', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.net.XmlHttp', 'goog.string', 'goog.structs', 'goog.structs.Map', 'goog.uri.utils', 'goog.userAgent']); +goog.addDependency("net/xhrio_test.js", ['goog.net.XhrIoTest'], ['goog.Uri', 'goog.debug.EntryPointMonitor', 'goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.events', 'goog.functions', 'goog.net.EventType', 'goog.net.WrapperXmlHttpFactory', 'goog.net.XhrIo', 'goog.net.XmlHttp', 'goog.object', 'goog.string', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.testing.recordFunction', 'goog.userAgent.product']); +goog.addDependency("net/xhriopool.js", ['goog.net.XhrIoPool'], ['goog.net.XhrIo', 'goog.structs.PriorityPool']); +goog.addDependency("net/xhriopool_test.js", ['goog.net.XhrIoPoolTest'], ['goog.net.XhrIoPool', 'goog.structs.Map', 'goog.testing.jsunit']); +goog.addDependency("net/xhrlike.js", ['goog.net.XhrLike'], []); +goog.addDependency("net/xhrmanager.js", ['goog.net.XhrManager', 'goog.net.XhrManager.Event', 'goog.net.XhrManager.Request'], ['goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrIoPool', 'goog.structs.Map']); +goog.addDependency("net/xhrmanager_test.js", ['goog.net.XhrManagerTest'], ['goog.events', 'goog.net.EventType', 'goog.net.XhrIo', 'goog.net.XhrManager', 'goog.testing.jsunit', 'goog.testing.net.XhrIoPool', 'goog.testing.recordFunction']); +goog.addDependency("net/xmlhttp.js", ['goog.net.DefaultXmlHttpFactory', 'goog.net.XmlHttp', 'goog.net.XmlHttp.OptionType', 'goog.net.XmlHttp.ReadyState', 'goog.net.XmlHttpDefines'], ['goog.asserts', 'goog.net.WrapperXmlHttpFactory', 'goog.net.XmlHttpFactory']); +goog.addDependency("net/xmlhttpfactory.js", ['goog.net.XmlHttpFactory'], ['goog.net.XhrLike']); +goog.addDependency("net/xpc/crosspagechannel.js", ['goog.net.xpc.CrossPageChannel'], ['goog.Uri', 'goog.async.Deferred', 'goog.async.Delay', 'goog.dispose', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.json', 'goog.log', 'goog.messaging.AbstractChannel', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.ChannelStates', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.DirectTransport', 'goog.net.xpc.FrameElementMethodTransport', 'goog.net.xpc.IframePollingTransport', 'goog.net.xpc.IframeRelayTransport', 'goog.net.xpc.NativeMessagingTransport', 'goog.net.xpc.NixTransport', 'goog.net.xpc.TransportTypes', 'goog.net.xpc.UriCfgFields', 'goog.string', 'goog.uri.utils', 'goog.userAgent']); +goog.addDependency("net/xpc/crosspagechannel_test.js", ['goog.net.xpc.CrossPageChannelTest'], ['goog.Disposable', 'goog.Promise', 'goog.Timer', 'goog.Uri', 'goog.dom', 'goog.dom.TagName', 'goog.labs.userAgent.browser', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("net/xpc/crosspagechannelrole.js", ['goog.net.xpc.CrossPageChannelRole'], []); +goog.addDependency("net/xpc/directtransport.js", ['goog.net.xpc.DirectTransport'], ['goog.Timer', 'goog.async.Deferred', 'goog.events.EventHandler', 'goog.log', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.object']); +goog.addDependency("net/xpc/directtransport_test.js", ['goog.net.xpc.DirectTransportTest'], ['goog.Promise', 'goog.dom', 'goog.dom.TagName', 'goog.labs.userAgent.browser', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("net/xpc/frameelementmethodtransport.js", ['goog.net.xpc.FrameElementMethodTransport'], ['goog.log', 'goog.net.xpc', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes']); +goog.addDependency("net/xpc/iframepollingtransport.js", ['goog.net.xpc.IframePollingTransport', 'goog.net.xpc.IframePollingTransport.Receiver', 'goog.net.xpc.IframePollingTransport.Sender'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.userAgent']); +goog.addDependency("net/xpc/iframepollingtransport_test.js", ['goog.net.xpc.IframePollingTransportTest'], ['goog.Timer', 'goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.TransportTypes', 'goog.object', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("net/xpc/iframerelaytransport.js", ['goog.net.xpc.IframeRelayTransport'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events', 'goog.html.SafeHtml', 'goog.log', 'goog.log.Level', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("net/xpc/nativemessagingtransport.js", ['goog.net.xpc.NativeMessagingTransport'], ['goog.Timer', 'goog.asserts', 'goog.async.Deferred', 'goog.events', 'goog.events.EventHandler', 'goog.log', 'goog.net.xpc', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes']); +goog.addDependency("net/xpc/nativemessagingtransport_test.js", ['goog.net.xpc.NativeMessagingTransportTest'], ['goog.dom', 'goog.events', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannel', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.NativeMessagingTransport', 'goog.testing.jsunit']); +goog.addDependency("net/xpc/nixtransport.js", ['goog.net.xpc.NixTransport'], ['goog.log', 'goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.CrossPageChannelRole', 'goog.net.xpc.Transport', 'goog.net.xpc.TransportTypes', 'goog.reflect']); +goog.addDependency("net/xpc/relay.js", ['goog.net.xpc.relay'], []); +goog.addDependency("net/xpc/transport.js", ['goog.net.xpc.Transport'], ['goog.Disposable', 'goog.dom', 'goog.net.xpc.TransportNames']); +goog.addDependency("net/xpc/xpc.js", ['goog.net.xpc', 'goog.net.xpc.CfgFields', 'goog.net.xpc.ChannelStates', 'goog.net.xpc.TransportNames', 'goog.net.xpc.TransportTypes', 'goog.net.xpc.UriCfgFields'], ['goog.log']); +goog.addDependency("object/object.js", ['goog.object'], []); +goog.addDependency("object/object_test.js", ['goog.objectTest'], ['goog.array', 'goog.functions', 'goog.object', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("positioning/absoluteposition.js", ['goog.positioning.AbsolutePosition'], ['goog.math.Coordinate', 'goog.positioning', 'goog.positioning.AbstractPosition']); +goog.addDependency("positioning/abstractposition.js", ['goog.positioning.AbstractPosition'], []); +goog.addDependency("positioning/anchoredposition.js", ['goog.positioning.AnchoredPosition'], ['goog.positioning', 'goog.positioning.AbstractPosition']); +goog.addDependency("positioning/anchoredposition_test.js", ['goog.positioning.AnchoredPositionTest'], ['goog.dom', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.style', 'goog.testing.jsunit']); +goog.addDependency("positioning/anchoredviewportposition.js", ['goog.positioning.AnchoredViewportPosition'], ['goog.positioning', 'goog.positioning.AnchoredPosition', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus']); +goog.addDependency("positioning/anchoredviewportposition_test.js", ['goog.positioning.AnchoredViewportPositionTest'], ['goog.dom', 'goog.math.Box', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.positioning.OverflowStatus', 'goog.style', 'goog.testing.jsunit']); +goog.addDependency("positioning/clientposition.js", ['goog.positioning.ClientPosition'], ['goog.asserts', 'goog.dom', 'goog.math.Coordinate', 'goog.positioning', 'goog.positioning.AbstractPosition', 'goog.style']); +goog.addDependency("positioning/clientposition_test.js", ['goog.positioning.clientPositionTest'], ['goog.dom', 'goog.dom.TagName', 'goog.positioning.ClientPosition', 'goog.positioning.Corner', 'goog.style', 'goog.testing.jsunit']); +goog.addDependency("positioning/menuanchoredposition.js", ['goog.positioning.MenuAnchoredPosition'], ['goog.positioning.AnchoredViewportPosition', 'goog.positioning.Overflow']); +goog.addDependency("positioning/menuanchoredposition_test.js", ['goog.positioning.MenuAnchoredPositionTest'], ['goog.dom', 'goog.dom.TagName', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.testing.jsunit']); +goog.addDependency("positioning/positioning.js", ['goog.positioning', 'goog.positioning.Corner', 'goog.positioning.CornerBit', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.style', 'goog.style.bidi']); +goog.addDependency("positioning/positioning_test.js", ['goog.positioningTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.labs.userAgent.browser', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Size', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("positioning/viewportclientposition.js", ['goog.positioning.ViewportClientPosition'], ['goog.dom', 'goog.math.Coordinate', 'goog.positioning', 'goog.positioning.ClientPosition', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.style']); +goog.addDependency("positioning/viewportclientposition_test.js", ['goog.positioning.ViewportClientPositionTest'], ['goog.dom', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.ViewportClientPosition', 'goog.style', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("positioning/viewportposition.js", ['goog.positioning.ViewportPosition'], ['goog.math.Coordinate', 'goog.positioning', 'goog.positioning.AbstractPosition', 'goog.positioning.Corner', 'goog.style']); +goog.addDependency("promise/promise.js", ['goog.Promise'], ['goog.Thenable', 'goog.asserts', 'goog.async.FreeList', 'goog.async.run', 'goog.async.throwException', 'goog.debug.Error', 'goog.promise.Resolver']); +goog.addDependency("promise/promise_test.js", ['goog.PromiseTest'], ['goog.Promise', 'goog.Thenable', 'goog.Timer', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("promise/resolver.js", ['goog.promise.Resolver'], []); +goog.addDependency("promise/testsuiteadapter.js", ['goog.promise.testSuiteAdapter'], ['goog.Promise']); +goog.addDependency("promise/thenable.js", ['goog.Thenable'], []); +goog.addDependency("proto/proto.js", ['goog.proto'], ['goog.proto.Serializer']); +goog.addDependency("proto/serializer.js", ['goog.proto.Serializer'], ['goog.json.Serializer', 'goog.string']); +goog.addDependency("proto/serializer_test.js", ['goog.protoTest'], ['goog.proto', 'goog.testing.jsunit']); +goog.addDependency("proto2/descriptor.js", ['goog.proto2.Descriptor', 'goog.proto2.Metadata'], ['goog.array', 'goog.asserts', 'goog.object', 'goog.string']); +goog.addDependency("proto2/descriptor_test.js", ['goog.proto2.DescriptorTest'], ['goog.proto2.Descriptor', 'goog.proto2.Message', 'goog.testing.jsunit']); +goog.addDependency("proto2/fielddescriptor.js", ['goog.proto2.FieldDescriptor'], ['goog.asserts', 'goog.string']); +goog.addDependency("proto2/fielddescriptor_test.js", ['goog.proto2.FieldDescriptorTest'], ['goog.proto2.FieldDescriptor', 'goog.proto2.Message', 'goog.testing.jsunit']); +goog.addDependency("proto2/lazydeserializer.js", ['goog.proto2.LazyDeserializer'], ['goog.asserts', 'goog.proto2.Message', 'goog.proto2.Serializer']); +goog.addDependency("proto2/message.js", ['goog.proto2.Message'], ['goog.asserts', 'goog.proto2.Descriptor', 'goog.proto2.FieldDescriptor']); +goog.addDependency("proto2/message_test.js", ['goog.proto2.MessageTest'], ['goog.testing.TestCase', 'goog.testing.jsunit', 'proto2.TestAllTypes', 'proto2.TestAllTypes.NestedEnum', 'proto2.TestAllTypes.NestedMessage', 'proto2.TestAllTypes.OptionalGroup', 'proto2.TestAllTypes.RepeatedGroup']); +goog.addDependency("proto2/objectserializer.js", ['goog.proto2.ObjectSerializer'], ['goog.asserts', 'goog.proto2.FieldDescriptor', 'goog.proto2.Serializer', 'goog.string']); +goog.addDependency("proto2/objectserializer_test.js", ['goog.proto2.ObjectSerializerTest'], ['goog.proto2.ObjectSerializer', 'goog.proto2.Serializer', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'proto2.TestAllTypes']); +goog.addDependency("proto2/package_test.pb.js", ['someprotopackage.TestPackageTypes'], ['goog.proto2.Message', 'proto2.TestAllTypes']); +goog.addDependency("proto2/pbliteserializer.js", ['goog.proto2.PbLiteSerializer'], ['goog.asserts', 'goog.proto2.FieldDescriptor', 'goog.proto2.LazyDeserializer', 'goog.proto2.Serializer']); +goog.addDependency("proto2/pbliteserializer_test.js", ['goog.proto2.PbLiteSerializerTest'], ['goog.proto2.PbLiteSerializer', 'goog.testing.jsunit', 'proto2.TestAllTypes']); +goog.addDependency("proto2/proto_test.js", ['goog.proto2.messageTest'], ['goog.proto2.FieldDescriptor', 'goog.testing.jsunit', 'proto2.TestAllTypes', 'proto2.TestDefaultParent', 'someprotopackage.TestPackageTypes']); +goog.addDependency("proto2/serializer.js", ['goog.proto2.Serializer'], ['goog.asserts', 'goog.proto2.FieldDescriptor', 'goog.proto2.Message']); +goog.addDependency("proto2/test.pb.js", ['proto2.TestAllTypes', 'proto2.TestAllTypes.NestedEnum', 'proto2.TestAllTypes.NestedMessage', 'proto2.TestAllTypes.OptionalGroup', 'proto2.TestAllTypes.RepeatedGroup', 'proto2.TestDefaultChild', 'proto2.TestDefaultParent'], ['goog.proto2.Message']); +goog.addDependency("proto2/textformatserializer.js", ['goog.proto2.TextFormatSerializer'], ['goog.array', 'goog.asserts', 'goog.math', 'goog.object', 'goog.proto2.FieldDescriptor', 'goog.proto2.Message', 'goog.proto2.Serializer', 'goog.string']); +goog.addDependency("proto2/textformatserializer_test.js", ['goog.proto2.TextFormatSerializerTest'], ['goog.proto2.ObjectSerializer', 'goog.proto2.TextFormatSerializer', 'goog.testing.jsunit', 'proto2.TestAllTypes']); +goog.addDependency("proto2/util.js", ['goog.proto2.Util'], ['goog.asserts']); +goog.addDependency("pubsub/pubsub.js", ['goog.pubsub.PubSub'], ['goog.Disposable', 'goog.array', 'goog.async.run']); +goog.addDependency("pubsub/pubsub_test.js", ['goog.pubsub.PubSubTest'], ['goog.array', 'goog.pubsub.PubSub', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("pubsub/topicid.js", ['goog.pubsub.TopicId'], []); +goog.addDependency("pubsub/typedpubsub.js", ['goog.pubsub.TypedPubSub'], ['goog.Disposable', 'goog.pubsub.PubSub']); +goog.addDependency("pubsub/typedpubsub_test.js", ['goog.pubsub.TypedPubSubTest'], ['goog.array', 'goog.pubsub.TopicId', 'goog.pubsub.TypedPubSub', 'goog.testing.jsunit']); +goog.addDependency("reflect/reflect.js", ['goog.reflect'], []); +goog.addDependency("reflect/reflect_test.js", ['goog.reflectTest'], ['goog.object', 'goog.reflect', 'goog.testing.jsunit']); +goog.addDependency("result/chain_test.js", ['goog.result.chainTest'], ['goog.Timer', 'goog.result', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("result/combine_test.js", ['goog.result.combineTest'], ['goog.Timer', 'goog.array', 'goog.result', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("result/deferredadaptor.js", ['goog.result.DeferredAdaptor'], ['goog.async.Deferred', 'goog.result', 'goog.result.Result']); +goog.addDependency("result/deferredadaptor_test.js", ['goog.result.DeferredAdaptorTest'], ['goog.result', 'goog.result.DeferredAdaptor', 'goog.result.SimpleResult', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("result/dependentresult.js", ['goog.result.DependentResult'], ['goog.result.Result']); +goog.addDependency("result/result_interface.js", ['goog.result.Result'], ['goog.Thenable']); +goog.addDependency("result/resultutil.js", ['goog.result'], ['goog.array', 'goog.result.DependentResult', 'goog.result.Result', 'goog.result.SimpleResult']); +goog.addDependency("result/resultutil_test.js", ['goog.resultTest'], ['goog.result', 'goog.testing.jsunit']); +goog.addDependency("result/simpleresult.js", ['goog.result.SimpleResult', 'goog.result.SimpleResult.StateError'], ['goog.Promise', 'goog.Thenable', 'goog.debug.Error', 'goog.result.Result']); +goog.addDependency("result/simpleresult_test.js", ['goog.result.SimpleResultTest'], ['goog.Timer', 'goog.Promise', 'goog.Thenable', 'goog.result', 'goog.testing.MockClock', 'goog.testing.recordFunction', 'goog.testing.jsunit']); +goog.addDependency("result/transform_test.js", ['goog.result.transformTest'], ['goog.Timer', 'goog.result.SimpleResult', 'goog.result', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("result/wait_test.js", ['goog.result.waitTest'], ['goog.Timer', 'goog.result.SimpleResult', 'goog.result', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("soy/data.js", ['goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind', 'goog.soy.data.SanitizedCss', 'goog.soy.data.SanitizedHtml', 'goog.soy.data.SanitizedHtmlAttribute', 'goog.soy.data.SanitizedJs', 'goog.soy.data.SanitizedStyle', 'goog.soy.data.SanitizedTrustedResourceUri', 'goog.soy.data.SanitizedUri', 'goog.soy.data.UnsanitizedText'], ['goog.Uri', 'goog.asserts', 'goog.html.SafeHtml', 'goog.html.SafeScript', 'goog.html.SafeStyle', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.html.uncheckedconversions', 'goog.i18n.bidi.Dir', 'goog.string.Const']); +goog.addDependency("soy/data_test.js", ['goog.soy.dataTest'], ['goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.html.SafeUrl', 'goog.html.TrustedResourceUrl', 'goog.soy.testHelper', 'goog.testing.jsunit']); +goog.addDependency("soy/renderer.js", ['goog.soy.InjectedDataSupplier', 'goog.soy.Renderer'], ['goog.asserts', 'goog.dom', 'goog.soy', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind']); +goog.addDependency("soy/renderer_test.js", ['goog.soy.RendererTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.html.SafeHtml', 'goog.i18n.bidi.Dir', 'goog.soy.Renderer', 'goog.soy.data.SanitizedContentKind', 'goog.soy.testHelper', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("soy/soy.js", ['goog.soy'], ['goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.html.legacyconversions', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind', 'goog.string']); +goog.addDependency("soy/soy_test.js", ['goog.soyTest'], ['goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.soy', 'goog.soy.testHelper', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("soy/soy_testhelper.js", ['goog.soy.testHelper'], ['goog.dom', 'goog.dom.TagName', 'goog.i18n.bidi.Dir', 'goog.soy.data.SanitizedContent', 'goog.soy.data.SanitizedContentKind', 'goog.soy.data.SanitizedCss', 'goog.soy.data.SanitizedTrustedResourceUri', 'goog.string', 'goog.userAgent']); +goog.addDependency("spell/spellcheck.js", ['goog.spell.SpellCheck', 'goog.spell.SpellCheck.WordChangedEvent'], ['goog.Timer', 'goog.events.Event', 'goog.events.EventTarget', 'goog.structs.Set']); +goog.addDependency("spell/spellcheck_test.js", ['goog.spell.SpellCheckTest'], ['goog.spell.SpellCheck', 'goog.testing.jsunit']); +goog.addDependency("stats/basicstat.js", ['goog.stats.BasicStat'], ['goog.asserts', 'goog.log', 'goog.string.format', 'goog.structs.CircularBuffer']); +goog.addDependency("stats/basicstat_test.js", ['goog.stats.BasicStatTest'], ['goog.array', 'goog.stats.BasicStat', 'goog.string.format', 'goog.testing.PseudoRandom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("storage/collectablestorage.js", ['goog.storage.CollectableStorage'], ['goog.array', 'goog.iter', 'goog.storage.ErrorCode', 'goog.storage.ExpiringStorage', 'goog.storage.RichStorage']); +goog.addDependency("storage/collectablestorage_test.js", ['goog.storage.CollectableStorageTest'], ['goog.storage.CollectableStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storageTester', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("storage/collectablestoragetester.js", ['goog.storage.collectableStorageTester'], ['goog.testing.asserts']); +goog.addDependency("storage/encryptedstorage.js", ['goog.storage.EncryptedStorage'], ['goog.crypt', 'goog.crypt.Arc4', 'goog.crypt.Sha1', 'goog.crypt.base64', 'goog.json', 'goog.json.Serializer', 'goog.storage.CollectableStorage', 'goog.storage.ErrorCode', 'goog.storage.RichStorage']); +goog.addDependency("storage/encryptedstorage_test.js", ['goog.storage.EncryptedStorageTest'], ['goog.json', 'goog.storage.EncryptedStorage', 'goog.storage.ErrorCode', 'goog.storage.RichStorage', 'goog.storage.collectableStorageTester', 'goog.storage.storageTester', 'goog.testing.MockClock', 'goog.testing.PseudoRandom', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("storage/errorcode.js", ['goog.storage.ErrorCode'], []); +goog.addDependency("storage/expiringstorage.js", ['goog.storage.ExpiringStorage'], ['goog.storage.RichStorage']); +goog.addDependency("storage/expiringstorage_test.js", ['goog.storage.ExpiringStorageTest'], ['goog.storage.ExpiringStorage', 'goog.storage.storageTester', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("storage/mechanism/errorcode.js", ['goog.storage.mechanism.ErrorCode'], []); +goog.addDependency("storage/mechanism/errorhandlingmechanism.js", ['goog.storage.mechanism.ErrorHandlingMechanism'], ['goog.storage.mechanism.Mechanism']); +goog.addDependency("storage/mechanism/errorhandlingmechanism_test.js", ['goog.storage.mechanism.ErrorHandlingMechanismTest'], ['goog.storage.mechanism.ErrorHandlingMechanism', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("storage/mechanism/html5localstorage.js", ['goog.storage.mechanism.HTML5LocalStorage'], ['goog.storage.mechanism.HTML5WebStorage']); +goog.addDependency("storage/mechanism/html5localstorage_test.js", ['goog.storage.mechanism.HTML5LocalStorageTest'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.storage.mechanism.mechanismTestDefinition', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("storage/mechanism/html5sessionstorage.js", ['goog.storage.mechanism.HTML5SessionStorage'], ['goog.storage.mechanism.HTML5WebStorage']); +goog.addDependency("storage/mechanism/html5sessionstorage_test.js", ['goog.storage.mechanism.HTML5SessionStorageTest'], ['goog.storage.mechanism.HTML5SessionStorage', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.storage.mechanism.mechanismTestDefinition', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("storage/mechanism/html5webstorage.js", ['goog.storage.mechanism.HTML5WebStorage'], ['goog.asserts', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.IterableMechanism']); +goog.addDependency("storage/mechanism/html5webstorage_test.js", ['goog.storage.mechanism.HTML5MockStorage', 'goog.storage.mechanism.HTML5WebStorageTest', 'goog.storage.mechanism.MockThrowableStorage'], ['goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.HTML5WebStorage', 'goog.testing.jsunit']); +goog.addDependency("storage/mechanism/ieuserdata.js", ['goog.storage.mechanism.IEUserData'], ['goog.asserts', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.storage.mechanism.ErrorCode', 'goog.storage.mechanism.IterableMechanism', 'goog.structs.Map', 'goog.userAgent']); +goog.addDependency("storage/mechanism/ieuserdata_test.js", ['goog.storage.mechanism.IEUserDataTest'], ['goog.storage.mechanism.IEUserData', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.storage.mechanism.mechanismTestDefinition', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("storage/mechanism/iterablemechanism.js", ['goog.storage.mechanism.IterableMechanism'], ['goog.array', 'goog.asserts', 'goog.iter', 'goog.storage.mechanism.Mechanism']); +goog.addDependency("storage/mechanism/iterablemechanismtester.js", ['goog.storage.mechanism.iterableMechanismTester'], ['goog.iter', 'goog.iter.StopIteration', 'goog.testing.asserts']); +goog.addDependency("storage/mechanism/mechanism.js", ['goog.storage.mechanism.Mechanism'], []); +goog.addDependency("storage/mechanism/mechanismfactory.js", ['goog.storage.mechanism.mechanismfactory'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.HTML5SessionStorage', 'goog.storage.mechanism.IEUserData', 'goog.storage.mechanism.PrefixedMechanism']); +goog.addDependency("storage/mechanism/mechanismfactory_test.js", ['goog.storage.mechanism.mechanismfactoryTest'], ['goog.storage.mechanism.mechanismfactory', 'goog.testing.jsunit']); +goog.addDependency("storage/mechanism/mechanismseparationtester.js", ['goog.storage.mechanism.mechanismSeparationTester'], ['goog.iter.StopIteration', 'goog.storage.mechanism.mechanismTestDefinition', 'goog.testing.asserts']); +goog.addDependency("storage/mechanism/mechanismsharingtester.js", ['goog.storage.mechanism.mechanismSharingTester'], ['goog.iter.StopIteration', 'goog.storage.mechanism.mechanismTestDefinition', 'goog.testing.asserts']); +goog.addDependency("storage/mechanism/mechanismtestdefinition.js", ['goog.storage.mechanism.mechanismTestDefinition'], []); +goog.addDependency("storage/mechanism/mechanismtester.js", ['goog.storage.mechanism.mechanismTester'], ['goog.storage.mechanism.ErrorCode', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("storage/mechanism/prefixedmechanism.js", ['goog.storage.mechanism.PrefixedMechanism'], ['goog.iter.Iterator', 'goog.storage.mechanism.IterableMechanism']); +goog.addDependency("storage/mechanism/prefixedmechanism_test.js", ['goog.storage.mechanism.PrefixedMechanismTest'], ['goog.storage.mechanism.HTML5LocalStorage', 'goog.storage.mechanism.PrefixedMechanism', 'goog.storage.mechanism.mechanismSeparationTester', 'goog.storage.mechanism.mechanismSharingTester', 'goog.testing.jsunit']); +goog.addDependency("storage/richstorage.js", ['goog.storage.RichStorage', 'goog.storage.RichStorage.Wrapper'], ['goog.storage.ErrorCode', 'goog.storage.Storage']); +goog.addDependency("storage/richstorage_test.js", ['goog.storage.RichStorageTest'], ['goog.storage.ErrorCode', 'goog.storage.RichStorage', 'goog.storage.storageTester', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("storage/storage.js", ['goog.storage.Storage'], ['goog.json', 'goog.storage.ErrorCode']); +goog.addDependency("storage/storage_test.js", ['goog.storage.storage_test'], ['goog.functions', 'goog.storage.ErrorCode', 'goog.storage.Storage', 'goog.storage.mechanism.mechanismfactory', 'goog.storage.storageTester', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.storage.FakeMechanism']); +goog.addDependency("storage/storagetester.js", ['goog.storage.storageTester'], ['goog.storage.Storage', 'goog.structs.Map', 'goog.testing.asserts']); +goog.addDependency("string/const.js", ['goog.string.Const'], ['goog.asserts', 'goog.string.TypedString']); +goog.addDependency("string/const_test.js", ['goog.string.constTest'], ['goog.string.Const', 'goog.testing.jsunit']); +goog.addDependency("string/linkify.js", ['goog.string.linkify'], ['goog.html.SafeHtml', 'goog.string']); +goog.addDependency("string/linkify_test.js", ['goog.string.linkifyTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.string', 'goog.string.linkify', 'goog.testing.dom', 'goog.testing.jsunit']); +goog.addDependency("string/newlines.js", ['goog.string.newlines', 'goog.string.newlines.Line'], ['goog.array']); +goog.addDependency("string/newlines_test.js", ['goog.string.newlinesTest'], ['goog.string.newlines', 'goog.testing.jsunit']); +goog.addDependency("string/parser.js", ['goog.string.Parser'], []); +goog.addDependency("string/path.js", ['goog.string.path'], ['goog.array', 'goog.string']); +goog.addDependency("string/path_test.js", ['goog.string.pathTest'], ['goog.string.path', 'goog.testing.jsunit']); +goog.addDependency("string/string.js", ['goog.string', 'goog.string.Unicode'], []); +goog.addDependency("string/string_test.js", ['goog.stringTest'], ['goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit']); +goog.addDependency("string/stringbuffer.js", ['goog.string.StringBuffer'], []); +goog.addDependency("string/stringbuffer_test.js", ['goog.string.StringBufferTest'], ['goog.string.StringBuffer', 'goog.testing.jsunit']); +goog.addDependency("string/stringformat.js", ['goog.string.format'], ['goog.string']); +goog.addDependency("string/stringformat_test.js", ['goog.string.formatTest'], ['goog.string.format', 'goog.testing.jsunit']); +goog.addDependency("string/stringifier.js", ['goog.string.Stringifier'], []); +goog.addDependency("string/typedstring.js", ['goog.string.TypedString'], []); +goog.addDependency("structs/avltree.js", ['goog.structs.AvlTree', 'goog.structs.AvlTree.Node'], ['goog.structs.Collection']); +goog.addDependency("structs/avltree_test.js", ['goog.structs.AvlTreeTest'], ['goog.array', 'goog.structs.AvlTree', 'goog.testing.jsunit']); +goog.addDependency("structs/circularbuffer.js", ['goog.structs.CircularBuffer'], []); +goog.addDependency("structs/circularbuffer_test.js", ['goog.structs.CircularBufferTest'], ['goog.structs.CircularBuffer', 'goog.testing.jsunit']); +goog.addDependency("structs/collection.js", ['goog.structs.Collection'], []); +goog.addDependency("structs/collection_test.js", ['goog.structs.CollectionTest'], ['goog.structs.AvlTree', 'goog.structs.Set', 'goog.testing.jsunit']); +goog.addDependency("structs/heap.js", ['goog.structs.Heap'], ['goog.array', 'goog.object', 'goog.structs.Node']); +goog.addDependency("structs/heap_test.js", ['goog.structs.HeapTest'], ['goog.structs', 'goog.structs.Heap', 'goog.testing.jsunit']); +goog.addDependency("structs/inversionmap.js", ['goog.structs.InversionMap'], ['goog.array', 'goog.asserts']); +goog.addDependency("structs/inversionmap_test.js", ['goog.structs.InversionMapTest'], ['goog.structs.InversionMap', 'goog.testing.jsunit']); +goog.addDependency("structs/linkedmap.js", ['goog.structs.LinkedMap'], ['goog.structs.Map']); +goog.addDependency("structs/linkedmap_test.js", ['goog.structs.LinkedMapTest'], ['goog.structs.LinkedMap', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("structs/map.js", ['goog.structs.Map'], ['goog.iter.Iterator', 'goog.iter.StopIteration']); +goog.addDependency("structs/map_test.js", ['goog.structs.MapTest'], ['goog.iter', 'goog.structs', 'goog.structs.Map', 'goog.testing.jsunit']); +goog.addDependency("structs/node.js", ['goog.structs.Node'], []); +goog.addDependency("structs/pool.js", ['goog.structs.Pool'], ['goog.Disposable', 'goog.structs.Queue', 'goog.structs.Set']); +goog.addDependency("structs/pool_test.js", ['goog.structs.PoolTest'], ['goog.structs.Pool', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("structs/prioritypool.js", ['goog.structs.PriorityPool'], ['goog.structs.Pool', 'goog.structs.PriorityQueue']); +goog.addDependency("structs/prioritypool_test.js", ['goog.structs.PriorityPoolTest'], ['goog.structs.PriorityPool', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("structs/priorityqueue.js", ['goog.structs.PriorityQueue'], ['goog.structs.Heap']); +goog.addDependency("structs/priorityqueue_test.js", ['goog.structs.PriorityQueueTest'], ['goog.structs', 'goog.structs.PriorityQueue', 'goog.testing.jsunit']); +goog.addDependency("structs/quadtree.js", ['goog.structs.QuadTree', 'goog.structs.QuadTree.Node', 'goog.structs.QuadTree.Point'], ['goog.math.Coordinate']); +goog.addDependency("structs/quadtree_test.js", ['goog.structs.QuadTreeTest'], ['goog.structs', 'goog.structs.QuadTree', 'goog.testing.jsunit']); +goog.addDependency("structs/queue.js", ['goog.structs.Queue'], ['goog.array']); +goog.addDependency("structs/queue_test.js", ['goog.structs.QueueTest'], ['goog.structs.Queue', 'goog.testing.jsunit']); +goog.addDependency("structs/set.js", ['goog.structs.Set'], ['goog.structs', 'goog.structs.Collection', 'goog.structs.Map']); +goog.addDependency("structs/set_test.js", ['goog.structs.SetTest'], ['goog.iter', 'goog.structs', 'goog.structs.Set', 'goog.testing.jsunit']); +goog.addDependency("structs/simplepool.js", ['goog.structs.SimplePool'], ['goog.Disposable']); +goog.addDependency("structs/stringset.js", ['goog.structs.StringSet'], ['goog.asserts', 'goog.iter']); +goog.addDependency("structs/stringset_test.js", ['goog.structs.StringSetTest'], ['goog.array', 'goog.iter', 'goog.structs.StringSet', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("structs/structs.js", ['goog.structs'], ['goog.array', 'goog.object']); +goog.addDependency("structs/structs_test.js", ['goog.structsTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.structs', 'goog.structs.Map', 'goog.structs.Set', 'goog.testing.jsunit']); +goog.addDependency("structs/treenode.js", ['goog.structs.TreeNode'], ['goog.array', 'goog.asserts', 'goog.structs.Node']); +goog.addDependency("structs/treenode_test.js", ['goog.structs.TreeNodeTest'], ['goog.structs.TreeNode', 'goog.testing.jsunit']); +goog.addDependency("structs/trie.js", ['goog.structs.Trie'], ['goog.object', 'goog.structs']); +goog.addDependency("structs/trie_test.js", ['goog.structs.TrieTest'], ['goog.object', 'goog.structs', 'goog.structs.Trie', 'goog.testing.jsunit']); +goog.addDependency("style/bidi.js", ['goog.style.bidi'], ['goog.dom', 'goog.style', 'goog.userAgent', 'goog.userAgent.platform', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("style/bidi_test.js", ['goog.style.bidiTest'], ['goog.dom', 'goog.style', 'goog.style.bidi', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("style/cursor.js", ['goog.style.cursor'], ['goog.userAgent']); +goog.addDependency("style/cursor_test.js", ['goog.style.cursorTest'], ['goog.style.cursor', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("style/style.js", ['goog.style'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.vendor', 'goog.html.SafeStyleSheet', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.object', 'goog.reflect', 'goog.string', 'goog.userAgent']); +goog.addDependency("style/style_document_scroll_test.js", ['goog.style.style_document_scroll_test'], ['goog.dom', 'goog.style', 'goog.testing.jsunit']); +goog.addDependency("style/style_test.js", ['goog.style_test'], ['goog.array', 'goog.color', 'goog.dom', 'goog.dom.TagName', 'goog.events.BrowserEvent', 'goog.html.testing', 'goog.labs.userAgent.util', 'goog.math.Box', 'goog.math.Coordinate', 'goog.math.Rect', 'goog.math.Size', 'goog.object', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.MockUserAgent', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil', 'goog.userAgentTestUtil.UserAgents']); +goog.addDependency("style/style_webkit_scrollbars_test.js", ['goog.style.webkitScrollbarsTest'], ['goog.asserts', 'goog.style', 'goog.styleScrollbarTester', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("style/stylescrollbartester.js", ['goog.styleScrollbarTester'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.testing.asserts']); +goog.addDependency("style/transform.js", ['goog.style.transform'], ['goog.functions', 'goog.math.Coordinate', 'goog.math.Coordinate3', 'goog.style', 'goog.userAgent', 'goog.userAgent.product.isVersion']); +goog.addDependency("style/transform_test.js", ['goog.style.transformTest'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.style.transform', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product.isVersion']); +goog.addDependency("style/transition.js", ['goog.style.transition', 'goog.style.transition.Css3Property'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.dom.vendor', 'goog.functions', 'goog.html.SafeHtml', 'goog.style', 'goog.userAgent']); +goog.addDependency("style/transition_test.js", ['goog.style.transitionTest'], ['goog.style', 'goog.style.transition', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("test_module.js", [], []); +goog.addDependency("test_module_dep.js", [], []); +goog.addDependency("testing/assertionfailure.js", [], []); +goog.addDependency("testing/asserts.js", ['goog.testing.asserts'], ['goog.testing.JsUnitException']); +goog.addDependency("testing/asserts_test.js", ['goog.testing.assertsTest'], ['goog.array', 'goog.dom', 'goog.iter.Iterator', 'goog.iter.StopIteration', 'goog.labs.userAgent.browser', 'goog.string', 'goog.structs.Map', 'goog.structs.Set', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("testing/async/mockcontrol.js", ['goog.testing.async.MockControl'], ['goog.asserts', 'goog.async.Deferred', 'goog.debug', 'goog.testing.asserts', 'goog.testing.mockmatchers.IgnoreArgument']); +goog.addDependency("testing/async/mockcontrol_test.js", ['goog.testing.async.MockControlTest'], ['goog.async.Deferred', 'goog.testing.MockControl', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.async.MockControl', 'goog.testing.jsunit']); +goog.addDependency("testing/asynctestcase.js", ['goog.testing.AsyncTestCase', 'goog.testing.AsyncTestCase.ControlBreakingException'], ['goog.testing.TestCase', 'goog.testing.asserts']); +goog.addDependency("testing/asynctestcase_async_test.js", ['goog.testing.AsyncTestCaseAsyncTest'], ['goog.testing.AsyncTestCase', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/asynctestcase_noasync_test.js", ['goog.testing.AsyncTestCaseSyncTest'], ['goog.testing.AsyncTestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/asynctestcase_test.js", ['goog.testing.AsyncTestCaseTest'], ['goog.debug.Error', 'goog.testing.AsyncTestCase', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("testing/benchmark.js", ['goog.testing.benchmark'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.PerformanceTable', 'goog.testing.PerformanceTimer', 'goog.testing.TestCase']); +goog.addDependency("testing/continuationtestcase.js", ['goog.testing.ContinuationTestCase', 'goog.testing.ContinuationTestCase.ContinuationTest', 'goog.testing.ContinuationTestCase.Step'], ['goog.array', 'goog.events.EventHandler', 'goog.testing.TestCase', 'goog.testing.asserts']); +goog.addDependency("testing/continuationtestcase_test.js", ['goog.testing.ContinuationTestCaseTest'], ['goog.events', 'goog.events.EventTarget', 'goog.testing.ContinuationTestCase', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/deferredtestcase.js", ['goog.testing.DeferredTestCase'], ['goog.testing.AsyncTestCase', 'goog.testing.TestCase']); +goog.addDependency("testing/deferredtestcase_test.js", ['goog.testing.DeferredTestCaseTest'], ['goog.async.Deferred', 'goog.testing.DeferredTestCase', 'goog.testing.TestCase', 'goog.testing.TestRunner', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("testing/dom.js", ['goog.testing.dom'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.NodeIterator', 'goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.iter', 'goog.object', 'goog.string', 'goog.style', 'goog.testing.asserts', 'goog.userAgent']); +goog.addDependency("testing/dom_test.js", ['goog.testing.domTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.TestCase', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("testing/editor/dom.js", ['goog.testing.editor.dom'], ['goog.dom.NodeType', 'goog.dom.TagIterator', 'goog.dom.TagWalkType', 'goog.iter', 'goog.string', 'goog.testing.asserts']); +goog.addDependency("testing/editor/dom_test.js", ['goog.testing.editor.domTest'], ['goog.dom', 'goog.dom.TagName', 'goog.functions', 'goog.testing.TestCase', 'goog.testing.editor.dom', 'goog.testing.jsunit']); +goog.addDependency("testing/editor/fieldmock.js", ['goog.testing.editor.FieldMock'], ['goog.dom', 'goog.dom.Range', 'goog.editor.Field', 'goog.testing.LooseMock', 'goog.testing.mockmatchers']); +goog.addDependency("testing/editor/testhelper.js", ['goog.testing.editor.TestHelper'], ['goog.Disposable', 'goog.dom', 'goog.dom.Range', 'goog.editor.BrowserFeature', 'goog.editor.node', 'goog.editor.plugins.AbstractBubblePlugin', 'goog.testing.dom']); +goog.addDependency("testing/editor/testhelper_test.js", ['goog.testing.editor.TestHelperTest'], ['goog.dom', 'goog.dom.TagName', 'goog.editor.node', 'goog.testing.TestCase', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("testing/events/eventobserver.js", ['goog.testing.events.EventObserver'], ['goog.array']); +goog.addDependency("testing/events/eventobserver_test.js", ['goog.testing.events.EventObserverTest'], ['goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.testing.events.EventObserver', 'goog.testing.jsunit']); +goog.addDependency("testing/events/events.js", ['goog.testing.events', 'goog.testing.events.Event'], ['goog.Disposable', 'goog.asserts', 'goog.dom.NodeType', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.BrowserFeature', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.object', 'goog.style', 'goog.userAgent']); +goog.addDependency("testing/events/events_test.js", ['goog.testing.eventsTest'], ['goog.array', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.userAgent']); +goog.addDependency("testing/events/matchers.js", ['goog.testing.events.EventMatcher'], ['goog.events.Event', 'goog.testing.mockmatchers.ArgumentMatcher']); +goog.addDependency("testing/events/matchers_test.js", ['goog.testing.events.EventMatcherTest'], ['goog.events.Event', 'goog.testing.events.EventMatcher', 'goog.testing.jsunit']); +goog.addDependency("testing/events/onlinehandler.js", ['goog.testing.events.OnlineHandler'], ['goog.events.EventTarget', 'goog.net.NetworkStatusMonitor']); +goog.addDependency("testing/events/onlinehandler_test.js", ['goog.testing.events.OnlineHandlerTest'], ['goog.events', 'goog.net.NetworkStatusMonitor', 'goog.testing.events.EventObserver', 'goog.testing.events.OnlineHandler', 'goog.testing.jsunit']); +goog.addDependency("testing/expectedfailures.js", ['goog.testing.ExpectedFailures'], ['goog.asserts', 'goog.debug.DivConsole', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.log', 'goog.style', 'goog.testing.JsUnitException', 'goog.testing.TestCase', 'goog.testing.asserts']); +goog.addDependency("testing/expectedfailures_test.js", ['goog.testing.ExpectedFailuresTest'], ['goog.debug.Logger', 'goog.testing.ExpectedFailures', 'goog.testing.JsUnitException', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/blob.js", ['goog.testing.fs.Blob'], ['goog.crypt', 'goog.crypt.base64']); +goog.addDependency("testing/fs/blob_test.js", ['goog.testing.fs.BlobTest'], ['goog.dom', 'goog.testing.fs.Blob', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/directoryentry_test.js", ['goog.testing.fs.DirectoryEntryTest'], ['goog.array', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.testing.MockClock', 'goog.testing.TestCase', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/entry.js", ['goog.testing.fs.DirectoryEntry', 'goog.testing.fs.Entry', 'goog.testing.fs.FileEntry'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.fs.DirectoryEntry', 'goog.fs.DirectoryEntryImpl', 'goog.fs.Entry', 'goog.fs.Error', 'goog.fs.FileEntry', 'goog.functions', 'goog.object', 'goog.string', 'goog.testing.fs.File', 'goog.testing.fs.FileWriter']); +goog.addDependency("testing/fs/entry_test.js", ['goog.testing.fs.EntryTest'], ['goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.testing.MockClock', 'goog.testing.TestCase', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/file.js", ['goog.testing.fs.File'], ['goog.testing.fs.Blob']); +goog.addDependency("testing/fs/fileentry_test.js", ['goog.testing.fs.FileEntryTest'], ['goog.testing.MockClock', 'goog.testing.fs.FileEntry', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/filereader.js", ['goog.testing.fs.FileReader'], ['goog.Timer', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.testing.fs.ProgressEvent']); +goog.addDependency("testing/fs/filereader_test.js", ['goog.testing.fs.FileReaderTest'], ['goog.Promise', 'goog.array', 'goog.events', 'goog.fs.Error', 'goog.fs.FileReader', 'goog.object', 'goog.testing.events.EventObserver', 'goog.testing.fs.FileReader', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/filesystem.js", ['goog.testing.fs.FileSystem'], ['goog.fs.FileSystem', 'goog.testing.fs.DirectoryEntry']); +goog.addDependency("testing/fs/filewriter.js", ['goog.testing.fs.FileWriter'], ['goog.Timer', 'goog.events.EventTarget', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.string', 'goog.testing.fs.ProgressEvent']); +goog.addDependency("testing/fs/filewriter_test.js", ['goog.testing.fs.FileWriterTest'], ['goog.Promise', 'goog.array', 'goog.events', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.object', 'goog.testing.MockClock', 'goog.testing.events.EventObserver', 'goog.testing.fs.Blob', 'goog.testing.fs.FileSystem', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/fs.js", ['goog.testing.fs'], ['goog.Timer', 'goog.array', 'goog.async.Deferred', 'goog.fs', 'goog.testing.fs.Blob', 'goog.testing.fs.FileSystem']); +goog.addDependency("testing/fs/fs_test.js", ['goog.testing.fsTest'], ['goog.testing.fs', 'goog.testing.fs.Blob', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/integration_test.js", ['goog.testing.fs.integrationTest'], ['goog.Promise', 'goog.events', 'goog.fs', 'goog.fs.DirectoryEntry', 'goog.fs.Error', 'goog.fs.FileSaver', 'goog.testing.PropertyReplacer', 'goog.testing.fs', 'goog.testing.jsunit']); +goog.addDependency("testing/fs/progressevent.js", ['goog.testing.fs.ProgressEvent'], ['goog.events.Event']); +goog.addDependency("testing/functionmock.js", ['goog.testing', 'goog.testing.FunctionMock', 'goog.testing.GlobalFunctionMock', 'goog.testing.MethodMock'], ['goog.object', 'goog.testing.LooseMock', 'goog.testing.Mock', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock']); +goog.addDependency("testing/functionmock_test.js", ['goog.testing.FunctionMockTest'], ['goog.array', 'goog.string', 'goog.testing', 'goog.testing.FunctionMock', 'goog.testing.Mock', 'goog.testing.ObjectPropertyString', 'goog.testing.StrictMock', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.mockmatchers']); +goog.addDependency("testing/graphics.js", ['goog.testing.graphics'], ['goog.graphics.Path', 'goog.testing.asserts']); +goog.addDependency("testing/i18n/asserts.js", ['goog.testing.i18n.asserts'], ['goog.testing.jsunit']); +goog.addDependency("testing/i18n/asserts_test.js", ['goog.testing.i18n.assertsTest'], ['goog.testing.ExpectedFailures', 'goog.testing.i18n.asserts']); +goog.addDependency("testing/jstdasyncwrapper.js", ['goog.testing.JsTdAsyncWrapper'], ['goog.Promise']); +goog.addDependency("testing/jstdtestcaseadapter.js", ['goog.testing.JsTdTestCaseAdapter'], ['goog.async.run', 'goog.functions', 'goog.testing.JsTdAsyncWrapper', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/jsunit.js", ['goog.testing.jsunit'], ['goog.dom.TagName', 'goog.testing.TestCase', 'goog.testing.TestRunner', 'goog.userAgent']); +goog.addDependency("testing/jsunitexception.js", ['goog.testing.JsUnitException'], ['goog.testing.stacktrace']); +goog.addDependency("testing/loosemock.js", ['goog.testing.LooseExpectationCollection', 'goog.testing.LooseMock'], ['goog.array', 'goog.structs.Map', 'goog.testing.Mock']); +goog.addDependency("testing/loosemock_test.js", ['goog.testing.LooseMockTest'], ['goog.testing.LooseMock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.mockmatchers']); +goog.addDependency("testing/messaging/mockmessagechannel.js", ['goog.testing.messaging.MockMessageChannel'], ['goog.messaging.AbstractChannel', 'goog.testing.asserts']); +goog.addDependency("testing/messaging/mockmessageevent.js", ['goog.testing.messaging.MockMessageEvent'], ['goog.events.BrowserEvent', 'goog.events.EventType', 'goog.testing.events.Event']); +goog.addDependency("testing/messaging/mockmessageport.js", ['goog.testing.messaging.MockMessagePort'], ['goog.events.EventTarget']); +goog.addDependency("testing/messaging/mockportnetwork.js", ['goog.testing.messaging.MockPortNetwork'], ['goog.messaging.PortNetwork', 'goog.testing.messaging.MockMessageChannel']); +goog.addDependency("testing/mock.js", ['goog.testing.Mock', 'goog.testing.MockExpectation'], ['goog.array', 'goog.object', 'goog.testing.JsUnitException', 'goog.testing.MockInterface', 'goog.testing.mockmatchers']); +goog.addDependency("testing/mock_test.js", ['goog.testing.MockTest'], ['goog.array', 'goog.testing', 'goog.testing.Mock', 'goog.testing.MockControl', 'goog.testing.MockExpectation', 'goog.testing.jsunit']); +goog.addDependency("testing/mockclassfactory.js", ['goog.testing.MockClassFactory', 'goog.testing.MockClassRecord'], ['goog.array', 'goog.object', 'goog.testing.LooseMock', 'goog.testing.StrictMock', 'goog.testing.TestCase', 'goog.testing.mockmatchers']); +goog.addDependency("testing/mockclassfactory_test.js", ['fake.BaseClass', 'fake.ChildClass', 'goog.testing.MockClassFactoryTest'], ['goog.testing', 'goog.testing.MockClassFactory', 'goog.testing.jsunit']); +goog.addDependency("testing/mockclock.js", ['goog.testing.MockClock'], ['goog.Disposable', 'goog.Promise', 'goog.Thenable', 'goog.async.run', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.events.Event']); +goog.addDependency("testing/mockclock_test.js", ['goog.testing.MockClockTest'], ['goog.Promise', 'goog.Timer', 'goog.events', 'goog.functions', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.recordFunction']); +goog.addDependency("testing/mockcontrol.js", ['goog.testing.MockControl'], ['goog.array', 'goog.testing', 'goog.testing.LooseMock', 'goog.testing.StrictMock']); +goog.addDependency("testing/mockcontrol_test.js", ['goog.testing.MockControlTest'], ['goog.testing.Mock', 'goog.testing.MockControl', 'goog.testing.jsunit']); +goog.addDependency("testing/mockinterface.js", ['goog.testing.MockInterface'], []); +goog.addDependency("testing/mockmatchers.js", ['goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.testing.mockmatchers.IgnoreArgument', 'goog.testing.mockmatchers.InstanceOf', 'goog.testing.mockmatchers.ObjectEquals', 'goog.testing.mockmatchers.RegexpMatch', 'goog.testing.mockmatchers.SaveArgument', 'goog.testing.mockmatchers.TypeOf'], ['goog.array', 'goog.dom', 'goog.testing.asserts']); +goog.addDependency("testing/mockmatchers_test.js", ['goog.testing.mockmatchersTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher']); +goog.addDependency("testing/mockrandom.js", ['goog.testing.MockRandom'], ['goog.Disposable']); +goog.addDependency("testing/mockrandom_test.js", ['goog.testing.MockRandomTest'], ['goog.testing.MockRandom', 'goog.testing.jsunit']); +goog.addDependency("testing/mockrange.js", ['goog.testing.MockRange'], ['goog.dom.AbstractRange', 'goog.testing.LooseMock']); +goog.addDependency("testing/mockrange_test.js", ['goog.testing.MockRangeTest'], ['goog.testing.MockRange', 'goog.testing.jsunit']); +goog.addDependency("testing/mockstorage.js", ['goog.testing.MockStorage'], ['goog.structs.Map']); +goog.addDependency("testing/mockstorage_test.js", ['goog.testing.MockStorageTest'], ['goog.testing.MockStorage', 'goog.testing.jsunit']); +goog.addDependency("testing/mockuseragent.js", ['goog.testing.MockUserAgent'], ['goog.Disposable', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.userAgent']); +goog.addDependency("testing/mockuseragent_test.js", ['goog.testing.MockUserAgentTest'], ['goog.dispose', 'goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("testing/multitestrunner.js", ['goog.testing.MultiTestRunner', 'goog.testing.MultiTestRunner.TestFrame'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.functions', 'goog.object', 'goog.string', 'goog.testing.TestCase', 'goog.ui.Component', 'goog.ui.ServerChart', 'goog.ui.TableSorter']); +goog.addDependency("testing/multitestrunner_test.js", [], []); +goog.addDependency("testing/net/mockiframeio.js", ['goog.testing.net.MockIFrameIo'], ['goog.events.EventTarget', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.IframeIo', 'goog.testing.TestQueue']); +goog.addDependency("testing/net/xhrio.js", ['goog.testing.net.XhrIo'], ['goog.array', 'goog.dom.xml', 'goog.events', 'goog.events.EventTarget', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.HttpStatus', 'goog.net.XhrIo', 'goog.net.XmlHttp', 'goog.object', 'goog.structs', 'goog.structs.Map', 'goog.testing.TestQueue', 'goog.uri.utils']); +goog.addDependency("testing/net/xhrio_test.js", ['goog.testing.net.XhrIoTest'], ['goog.dom.xml', 'goog.events', 'goog.events.Event', 'goog.net.ErrorCode', 'goog.net.EventType', 'goog.net.XmlHttp', 'goog.object', 'goog.testing.MockControl', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.mockmatchers.InstanceOf', 'goog.testing.net.XhrIo']); +goog.addDependency("testing/net/xhriopool.js", ['goog.testing.net.XhrIoPool'], ['goog.net.XhrIoPool', 'goog.testing.net.XhrIo']); +goog.addDependency("testing/objectpropertystring.js", ['goog.testing.ObjectPropertyString'], []); +goog.addDependency("testing/parallel_closure_test_suite.js", [], []); +goog.addDependency("testing/parallel_closure_test_suite_test.js", [], []); +goog.addDependency("testing/performancetable.js", ['goog.testing.PerformanceTable'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.testing.PerformanceTimer']); +goog.addDependency("testing/performancetimer.js", ['goog.testing.PerformanceTimer', 'goog.testing.PerformanceTimer.Task'], ['goog.array', 'goog.async.Deferred', 'goog.math']); +goog.addDependency("testing/performancetimer_test.js", ['goog.testing.PerformanceTimerTest'], ['goog.async.Deferred', 'goog.dom', 'goog.math', 'goog.testing.MockClock', 'goog.testing.PerformanceTimer', 'goog.testing.jsunit']); +goog.addDependency("testing/propertyreplacer.js", ['goog.testing.PropertyReplacer'], ['goog.asserts', 'goog.testing.ObjectPropertyString', 'goog.userAgent']); +goog.addDependency("testing/propertyreplacer_test.js", ['goog.testing.PropertyReplacerTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("testing/proto2/proto2.js", ['goog.testing.proto2'], ['goog.proto2.Message', 'goog.proto2.ObjectSerializer', 'goog.testing.asserts']); +goog.addDependency("testing/proto2/proto2_test.js", ['goog.testing.proto2Test'], ['goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.proto2', 'proto2.TestAllTypes']); +goog.addDependency("testing/pseudorandom.js", ['goog.testing.PseudoRandom'], ['goog.Disposable']); +goog.addDependency("testing/pseudorandom_test.js", ['goog.testing.PseudoRandomTest'], ['goog.testing.PseudoRandom', 'goog.testing.jsunit']); +goog.addDependency("testing/recordfunction.js", ['goog.testing.FunctionCall', 'goog.testing.recordConstructor', 'goog.testing.recordFunction'], ['goog.testing.asserts']); +goog.addDependency("testing/recordfunction_test.js", ['goog.testing.recordFunctionTest'], ['goog.functions', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.recordConstructor', 'goog.testing.recordFunction']); +goog.addDependency("testing/shardingtestcase.js", ['goog.testing.ShardingTestCase'], ['goog.asserts', 'goog.testing.TestCase']); +goog.addDependency("testing/shardingtestcase_test.js", ['goog.testing.ShardingTestCaseTest'], ['goog.testing.ShardingTestCase', 'goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("testing/singleton.js", ['goog.testing.singleton'], []); +goog.addDependency("testing/singleton_test.js", ['goog.testing.singletonTest'], ['goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.singleton']); +goog.addDependency("testing/stacktrace.js", ['goog.testing.stacktrace', 'goog.testing.stacktrace.Frame'], []); +goog.addDependency("testing/stacktrace_test.js", ['goog.testing.stacktraceTest'], ['goog.functions', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.stacktrace', 'goog.testing.stacktrace.Frame', 'goog.userAgent']); +goog.addDependency("testing/storage/fakemechanism.js", ['goog.testing.storage.FakeMechanism'], ['goog.storage.mechanism.IterableMechanism', 'goog.structs.Map']); +goog.addDependency("testing/strictmock.js", ['goog.testing.StrictMock'], ['goog.array', 'goog.testing.Mock']); +goog.addDependency("testing/strictmock_test.js", ['goog.testing.StrictMockTest'], ['goog.testing.StrictMock', 'goog.testing.jsunit']); +goog.addDependency("testing/style/layoutasserts.js", ['goog.testing.style.layoutasserts'], ['goog.style', 'goog.testing.asserts', 'goog.testing.style']); +goog.addDependency("testing/style/layoutasserts_test.js", ['goog.testing.style.layoutassertsTest'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.style.layoutasserts']); +goog.addDependency("testing/style/style.js", ['goog.testing.style'], ['goog.dom', 'goog.math.Rect', 'goog.style']); +goog.addDependency("testing/style/style_test.js", ['goog.testing.styleTest'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.testing.jsunit', 'goog.testing.style']); +goog.addDependency("testing/testcase.js", ['goog.testing.TestCase', 'goog.testing.TestCase.Error', 'goog.testing.TestCase.Order', 'goog.testing.TestCase.Result', 'goog.testing.TestCase.Test'], ['goog.Promise', 'goog.Thenable', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.json', 'goog.object', 'goog.testing.JsUnitException', 'goog.testing.asserts']); +goog.addDependency("testing/testcase_test.js", ['goog.testing.TestCaseTest'], ['goog.Promise', 'goog.Timer', 'goog.functions', 'goog.string', 'goog.testing.ExpectedFailures', 'goog.testing.JsUnitException', 'goog.testing.MethodMock', 'goog.testing.MockRandom', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit']); +goog.addDependency("testing/testqueue.js", ['goog.testing.TestQueue'], []); +goog.addDependency("testing/testrunner.js", ['goog.testing.TestRunner'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.testing.TestCase', 'goog.userAgent']); +goog.addDependency("testing/testrunner_test.js", ['goog.testing.TestRunnerTest'], ['goog.testing.TestCase', 'goog.testing.TestRunner', 'goog.testing.asserts', 'goog.testing.jsunit']); +goog.addDependency("testing/testsuite.js", ['goog.testing.testSuite'], ['goog.labs.testing.Environment', 'goog.testing.TestCase']); +goog.addDependency("testing/ui/rendererasserts.js", ['goog.testing.ui.rendererasserts'], ['goog.testing.asserts', 'goog.ui.ControlRenderer']); +goog.addDependency("testing/ui/rendererasserts_test.js", ['goog.testing.ui.rendererassertsTest'], ['goog.testing.TestCase', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.ControlRenderer']); +goog.addDependency("testing/ui/rendererharness.js", ['goog.testing.ui.RendererHarness'], ['goog.Disposable', 'goog.dom.NodeType', 'goog.testing.asserts', 'goog.testing.dom']); +goog.addDependency("testing/ui/style.js", ['goog.testing.ui.style'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.testing.asserts']); +goog.addDependency("testing/ui/style_test.js", ['goog.testing.ui.styleTest'], ['goog.dom', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.testing.ui.style']); +goog.addDependency("timer/timer.js", ['goog.Timer'], ['goog.Promise', 'goog.events.EventTarget']); +goog.addDependency("timer/timer_test.js", ['goog.TimerTest'], ['goog.Promise', 'goog.Timer', 'goog.events', 'goog.testing.MockClock', 'goog.testing.jsunit']); +goog.addDependency("transitionalforwarddeclarations.js", [], []); +goog.addDependency("transpile.js", [], []); +goog.addDependency("tweak/entries.js", ['goog.tweak.BaseEntry', 'goog.tweak.BasePrimitiveSetting', 'goog.tweak.BaseSetting', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.StringSetting'], ['goog.array', 'goog.asserts', 'goog.log', 'goog.object']); +goog.addDependency("tweak/entries_test.js", ['goog.tweak.BaseEntryTest'], ['goog.testing.MockControl', 'goog.testing.jsunit', 'goog.tweak.testhelpers']); +goog.addDependency("tweak/registry.js", ['goog.tweak.Registry'], ['goog.array', 'goog.asserts', 'goog.log', 'goog.string', 'goog.tweak.BasePrimitiveSetting', 'goog.tweak.BaseSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.NumericSetting', 'goog.tweak.StringSetting', 'goog.uri.utils']); +goog.addDependency("tweak/registry_test.js", ['goog.tweak.RegistryTest'], ['goog.asserts.AssertionError', 'goog.testing.jsunit', 'goog.tweak', 'goog.tweak.testhelpers']); +goog.addDependency("tweak/testhelpers.js", ['goog.tweak.testhelpers'], ['goog.tweak', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.Registry', 'goog.tweak.StringSetting']); +goog.addDependency("tweak/tweak.js", ['goog.tweak', 'goog.tweak.ConfigParams'], ['goog.asserts', 'goog.tweak.BaseSetting', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.Registry', 'goog.tweak.StringSetting']); +goog.addDependency("tweak/tweakui.js", ['goog.tweak.EntriesPanel', 'goog.tweak.TweakUi'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.SafeStyleSheet', 'goog.object', 'goog.string.Const', 'goog.style', 'goog.tweak', 'goog.tweak.BaseEntry', 'goog.tweak.BooleanGroup', 'goog.tweak.BooleanInGroupSetting', 'goog.tweak.BooleanSetting', 'goog.tweak.ButtonAction', 'goog.tweak.NumericSetting', 'goog.tweak.StringSetting', 'goog.ui.Zippy', 'goog.userAgent']); +goog.addDependency("tweak/tweakui_test.js", ['goog.tweak.TweakUiTest'], ['goog.dom', 'goog.dom.TagName', 'goog.string', 'goog.testing.jsunit', 'goog.tweak', 'goog.tweak.TweakUi', 'goog.tweak.testhelpers']); +goog.addDependency("ui/abstractspellchecker.js", ['goog.ui.AbstractSpellChecker', 'goog.ui.AbstractSpellChecker.AsyncResult'], ['goog.a11y.aria', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.selection', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.spell.SpellCheck', 'goog.structs.Set', 'goog.style', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.MenuSeparator', 'goog.ui.PopupMenu']); +goog.addDependency("ui/ac/ac.js", ['goog.ui.ac'], ['goog.ui.ac.ArrayMatcher', 'goog.ui.ac.AutoComplete', 'goog.ui.ac.InputHandler', 'goog.ui.ac.Renderer']); +goog.addDependency("ui/ac/ac_test.js", ['goog.ui.acTest'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.classlist', 'goog.dom.selection', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.style', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.ui.ac', 'goog.userAgent']); +goog.addDependency("ui/ac/arraymatcher.js", ['goog.ui.ac.ArrayMatcher'], ['goog.string']); +goog.addDependency("ui/ac/arraymatcher_test.js", ['goog.ui.ac.ArrayMatcherTest'], ['goog.testing.jsunit', 'goog.ui.ac.ArrayMatcher']); +goog.addDependency("ui/ac/autocomplete.js", ['goog.ui.ac.AutoComplete', 'goog.ui.ac.AutoComplete.EventType'], ['goog.array', 'goog.asserts', 'goog.events', 'goog.events.EventTarget', 'goog.object', 'goog.ui.ac.RenderOptions']); +goog.addDependency("ui/ac/autocomplete_test.js", ['goog.ui.ac.AutoCompleteTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.string', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.ui.ac.AutoComplete', 'goog.ui.ac.InputHandler', 'goog.ui.ac.RenderOptions', 'goog.ui.ac.Renderer']); +goog.addDependency("ui/ac/cachingmatcher.js", ['goog.ui.ac.CachingMatcher'], ['goog.array', 'goog.async.Throttle', 'goog.ui.ac.ArrayMatcher', 'goog.ui.ac.RenderOptions']); +goog.addDependency("ui/ac/cachingmatcher_test.js", ['goog.ui.ac.CachingMatcherTest'], ['goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.ui.ac.CachingMatcher']); +goog.addDependency("ui/ac/inputhandler.js", ['goog.ui.ac.InputHandler'], ['goog.Disposable', 'goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.selection', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.string', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("ui/ac/inputhandler_test.js", ['goog.ui.ac.InputHandlerTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.selection', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.KeyCodes', 'goog.functions', 'goog.object', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.ui.ac.InputHandler', 'goog.userAgent']); +goog.addDependency("ui/ac/remote.js", ['goog.ui.ac.Remote'], ['goog.ui.ac.AutoComplete', 'goog.ui.ac.InputHandler', 'goog.ui.ac.RemoteArrayMatcher', 'goog.ui.ac.Renderer']); +goog.addDependency("ui/ac/remotearraymatcher.js", ['goog.ui.ac.RemoteArrayMatcher'], ['goog.Disposable', 'goog.Uri', 'goog.events', 'goog.net.EventType', 'goog.net.XhrIo']); +goog.addDependency("ui/ac/remotearraymatcher_test.js", ['goog.ui.ac.RemoteArrayMatcherTest'], ['goog.net.XhrIo', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.ui.ac.RemoteArrayMatcher']); +goog.addDependency("ui/ac/renderer.js", ['goog.ui.ac.Renderer', 'goog.ui.ac.Renderer.CustomRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dispose', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.dom.FadeInAndShow', 'goog.fx.dom.FadeOutAndHide', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.string', 'goog.style', 'goog.ui.IdGenerator', 'goog.ui.ac.AutoComplete']); +goog.addDependency("ui/ac/renderer_test.js", ['goog.ui.ac.RendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.fx.dom.FadeInAndShow', 'goog.fx.dom.FadeOutAndHide', 'goog.string', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.ac.AutoComplete', 'goog.ui.ac.Renderer']); +goog.addDependency("ui/ac/renderoptions.js", ['goog.ui.ac.RenderOptions'], []); +goog.addDependency("ui/ac/richinputhandler.js", ['goog.ui.ac.RichInputHandler'], ['goog.ui.ac.InputHandler']); +goog.addDependency("ui/ac/richremote.js", ['goog.ui.ac.RichRemote'], ['goog.ui.ac.AutoComplete', 'goog.ui.ac.Remote', 'goog.ui.ac.Renderer', 'goog.ui.ac.RichInputHandler', 'goog.ui.ac.RichRemoteArrayMatcher']); +goog.addDependency("ui/ac/richremotearraymatcher.js", ['goog.ui.ac.RichRemoteArrayMatcher'], ['goog.dom', 'goog.ui.ac.RemoteArrayMatcher']); +goog.addDependency("ui/ac/richremotearraymatcher_test.js", ['goog.ui.ac.RichRemoteArrayMatcherTest'], ['goog.net.XhrIo', 'goog.testing.MockControl', 'goog.testing.jsunit', 'goog.testing.net.XhrIo', 'goog.ui.ac.RichRemoteArrayMatcher']); +goog.addDependency("ui/activitymonitor.js", ['goog.ui.ActivityMonitor'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType']); +goog.addDependency("ui/activitymonitor_test.js", ['goog.ui.ActivityMonitorTest'], ['goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.ActivityMonitor']); +goog.addDependency("ui/advancedtooltip.js", ['goog.ui.AdvancedTooltip'], ['goog.events', 'goog.events.EventType', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style', 'goog.ui.Tooltip', 'goog.userAgent']); +goog.addDependency("ui/advancedtooltip_test.js", ['goog.ui.AdvancedTooltipTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events.Event', 'goog.events.EventType', 'goog.math.Box', 'goog.math.Coordinate', 'goog.style', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.AdvancedTooltip', 'goog.ui.Tooltip', 'goog.userAgent']); +goog.addDependency("ui/animatedzippy.js", ['goog.ui.AnimatedZippy'], ['goog.a11y.aria.Role', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.fx.easing', 'goog.ui.Zippy', 'goog.ui.ZippyEvent']); +goog.addDependency("ui/animatedzippy_test.js", ['goog.ui.AnimatedZippyTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.events', 'goog.functions', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.ui.AnimatedZippy', 'goog.ui.Zippy']); +goog.addDependency("ui/attachablemenu.js", ['goog.ui.AttachableMenu'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.string', 'goog.style', 'goog.ui.ItemEvent', 'goog.ui.MenuBase', 'goog.ui.PopupBase', 'goog.userAgent']); +goog.addDependency("ui/bidiinput.js", ['goog.ui.BidiInput'], ['goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events', 'goog.events.InputHandler', 'goog.i18n.bidi', 'goog.ui.Component']); +goog.addDependency("ui/bidiinput_test.js", ['goog.ui.BidiInputTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.BidiInput']); +goog.addDependency("ui/bubble.js", ['goog.ui.Bubble'], ['goog.Timer', 'goog.dom.safe', 'goog.events', 'goog.events.EventType', 'goog.html.SafeHtml', 'goog.math.Box', 'goog.positioning', 'goog.positioning.AbsolutePosition', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.CornerBit', 'goog.string.Const', 'goog.style', 'goog.ui.Component', 'goog.ui.Popup']); +goog.addDependency("ui/button.js", ['goog.ui.Button', 'goog.ui.Button.Side'], ['goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.ui.ButtonRenderer', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.NativeButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/button_test.js", ['goog.ui.ButtonTest'], ['goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.NativeButtonRenderer']); +goog.addDependency("ui/buttonrenderer.js", ['goog.ui.ButtonRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/buttonrenderer_test.js", ['goog.ui.ButtonRendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.ButtonSide', 'goog.ui.Component', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/buttonside.js", ['goog.ui.ButtonSide'], []); +goog.addDependency("ui/charcounter.js", ['goog.ui.CharCounter', 'goog.ui.CharCounter.Display'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.InputHandler']); +goog.addDependency("ui/charcounter_test.js", ['goog.ui.CharCounterTest'], ['goog.dom', 'goog.testing.asserts', 'goog.testing.jsunit', 'goog.ui.CharCounter', 'goog.userAgent']); +goog.addDependency("ui/charpicker.js", ['goog.ui.CharPicker'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.i18n.CharListDecompressor', 'goog.i18n.CharPickerData', 'goog.i18n.uChar', 'goog.i18n.uChar.NameFetcher', 'goog.structs.Set', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.ContainerScroller', 'goog.ui.FlatButtonRenderer', 'goog.ui.HoverCard', 'goog.ui.LabelInput', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.Tooltip']); +goog.addDependency("ui/charpicker_test.js", ['goog.ui.CharPickerTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dispose', 'goog.dom', 'goog.events.Event', 'goog.events.EventType', 'goog.i18n.CharPickerData', 'goog.i18n.uChar.NameFetcher', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.ui.CharPicker', 'goog.ui.FlatButtonRenderer']); +goog.addDependency("ui/checkbox.js", ['goog.ui.Checkbox', 'goog.ui.Checkbox.State'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.string', 'goog.ui.CheckboxRenderer', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.registry']); +goog.addDependency("ui/checkbox_test.js", ['goog.ui.CheckboxTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.KeyCodes', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Checkbox', 'goog.ui.CheckboxRenderer', 'goog.ui.Component', 'goog.ui.ControlRenderer', 'goog.ui.decorate']); +goog.addDependency("ui/checkboxmenuitem.js", ['goog.ui.CheckBoxMenuItem'], ['goog.ui.MenuItem', 'goog.ui.registry']); +goog.addDependency("ui/checkboxrenderer.js", ['goog.ui.CheckboxRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.object', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/colormenubutton.js", ['goog.ui.ColorMenuButton'], ['goog.array', 'goog.object', 'goog.ui.ColorMenuButtonRenderer', 'goog.ui.ColorPalette', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.registry']); +goog.addDependency("ui/colormenubuttonrenderer.js", ['goog.ui.ColorMenuButtonRenderer'], ['goog.asserts', 'goog.color', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.MenuButtonRenderer', 'goog.userAgent']); +goog.addDependency("ui/colormenubuttonrenderer_test.js", ['goog.ui.ColorMenuButtonTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.testing.ui.RendererHarness', 'goog.testing.ui.rendererasserts', 'goog.ui.ColorMenuButton', 'goog.ui.ColorMenuButtonRenderer', 'goog.userAgent']); +goog.addDependency("ui/colorpalette.js", ['goog.ui.ColorPalette'], ['goog.array', 'goog.color', 'goog.dom.TagName', 'goog.style', 'goog.ui.Palette', 'goog.ui.PaletteRenderer']); +goog.addDependency("ui/colorpalette_test.js", ['goog.ui.ColorPaletteTest'], ['goog.color', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.ui.ColorPalette']); +goog.addDependency("ui/colorpicker.js", ['goog.ui.ColorPicker', 'goog.ui.ColorPicker.EventType'], ['goog.ui.ColorPalette', 'goog.ui.Component']); +goog.addDependency("ui/combobox.js", ['goog.ui.ComboBox', 'goog.ui.ComboBoxItem'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.log', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.ItemEvent', 'goog.ui.LabelInput', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.MenuSeparator', 'goog.ui.registry', 'goog.userAgent']); +goog.addDependency("ui/combobox_test.js", ['goog.ui.ComboBoxTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.KeyCodes', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.ComboBox', 'goog.ui.ComboBoxItem', 'goog.ui.Component', 'goog.ui.ControlRenderer', 'goog.ui.LabelInput', 'goog.ui.Menu', 'goog.ui.MenuItem']); +goog.addDependency("ui/component.js", ['goog.ui.Component', 'goog.ui.Component.Error', 'goog.ui.Component.EventType', 'goog.ui.Component.State'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.object', 'goog.style', 'goog.ui.IdGenerator']); +goog.addDependency("ui/component_test.js", ['goog.ui.ComponentTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.events.EventTarget', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.Component']); +goog.addDependency("ui/container.js", ['goog.ui.Container', 'goog.ui.Container.EventType', 'goog.ui.Container.Orientation'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.object', 'goog.style', 'goog.ui.Component', 'goog.ui.ContainerRenderer', 'goog.ui.Control']); +goog.addDependency("ui/container_test.js", ['goog.ui.ContainerTest'], ['goog.a11y.aria', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.events.KeyEvent', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Container', 'goog.ui.Control']); +goog.addDependency("ui/containerrenderer.js", ['goog.ui.ContainerRenderer'], ['goog.a11y.aria', 'goog.array', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.string', 'goog.style', 'goog.ui.registry', 'goog.userAgent']); +goog.addDependency("ui/containerrenderer_test.js", ['goog.ui.ContainerRendererTest'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Container', 'goog.ui.ContainerRenderer', 'goog.userAgent']); +goog.addDependency("ui/containerscroller.js", ['goog.ui.ContainerScroller'], ['goog.Disposable', 'goog.Timer', 'goog.events.EventHandler', 'goog.style', 'goog.ui.Component', 'goog.ui.Container']); +goog.addDependency("ui/containerscroller_test.js", ['goog.ui.ContainerScrollerTest'], ['goog.dom', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Container', 'goog.ui.ContainerScroller']); +goog.addDependency("ui/control.js", ['goog.ui.Control'], ['goog.Disposable', 'goog.array', 'goog.dom', 'goog.events.BrowserEvent', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.string', 'goog.ui.Component', 'goog.ui.ControlContent', 'goog.ui.ControlRenderer', 'goog.ui.registry', 'goog.userAgent']); +goog.addDependency("ui/control_test.js", ['goog.ui.ControlTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.html.testing', 'goog.object', 'goog.string', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.ControlRenderer', 'goog.ui.registry', 'goog.userAgent']); +goog.addDependency("ui/controlcontent.js", ['goog.ui.ControlContent'], []); +goog.addDependency("ui/controlrenderer.js", ['goog.ui.ControlRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.object', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.ControlContent', 'goog.userAgent']); +goog.addDependency("ui/controlrenderer_test.js", ['goog.ui.ControlRendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.object', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.ControlRenderer', 'goog.userAgent']); +goog.addDependency("ui/cookieeditor.js", ['goog.ui.CookieEditor'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.events.EventType', 'goog.net.cookies', 'goog.string', 'goog.style', 'goog.ui.Component']); +goog.addDependency("ui/cookieeditor_test.js", ['goog.ui.CookieEditorTest'], ['goog.dom', 'goog.events.Event', 'goog.events.EventType', 'goog.net.cookies', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.CookieEditor']); +goog.addDependency("ui/css3buttonrenderer.js", ['goog.ui.Css3ButtonRenderer'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.Component', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']); +goog.addDependency("ui/css3menubuttonrenderer.js", ['goog.ui.Css3MenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuButton', 'goog.ui.MenuButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/cssnames.js", ['goog.ui.INLINE_BLOCK_CLASSNAME'], []); +goog.addDependency("ui/custombutton.js", ['goog.ui.CustomButton'], ['goog.ui.Button', 'goog.ui.CustomButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/custombuttonrenderer.js", ['goog.ui.CustomButtonRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.string', 'goog.ui.ButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME']); +goog.addDependency("ui/customcolorpalette.js", ['goog.ui.CustomColorPalette'], ['goog.color', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.ColorPalette', 'goog.ui.Component']); +goog.addDependency("ui/customcolorpalette_test.js", ['goog.ui.CustomColorPaletteTest'], ['goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.jsunit', 'goog.ui.CustomColorPalette']); +goog.addDependency("ui/datepicker.js", ['goog.ui.DatePicker', 'goog.ui.DatePicker.Events', 'goog.ui.DatePickerEvent'], ['goog.a11y.aria', 'goog.asserts', 'goog.date.Date', 'goog.date.DateRange', 'goog.date.Interval', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyHandler', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimePatterns', 'goog.i18n.DateTimeSymbols', 'goog.style', 'goog.ui.Component', 'goog.ui.DefaultDatePickerRenderer', 'goog.ui.IdGenerator']); +goog.addDependency("ui/datepicker_test.js", ['goog.ui.DatePickerTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.date.Date', 'goog.date.DateRange', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.KeyCodes', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_en_US', 'goog.i18n.DateTimeSymbols_zh_HK', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.DatePicker']); +goog.addDependency("ui/datepickerrenderer.js", ['goog.ui.DatePickerRenderer'], []); +goog.addDependency("ui/decorate.js", ['goog.ui.decorate'], ['goog.ui.registry']); +goog.addDependency("ui/decorate_test.js", ['goog.ui.decorateTest'], ['goog.testing.jsunit', 'goog.ui.decorate', 'goog.ui.registry']); +goog.addDependency("ui/defaultdatepickerrenderer.js", ['goog.ui.DefaultDatePickerRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.ui.DatePickerRenderer']); +goog.addDependency("ui/dialog.js", ['goog.ui.Dialog', 'goog.ui.Dialog.ButtonSet', 'goog.ui.Dialog.ButtonSet.DefaultButtons', 'goog.ui.Dialog.DefaultButtonCaptions', 'goog.ui.Dialog.DefaultButtonKeys', 'goog.ui.Dialog.Event', 'goog.ui.Dialog.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.safe', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Dragger', 'goog.html.SafeHtml', 'goog.math.Rect', 'goog.string', 'goog.structs.Map', 'goog.style', 'goog.ui.ModalPopup']); +goog.addDependency("ui/dialog_test.js", ['goog.ui.DialogTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.css3', 'goog.html.SafeHtml', 'goog.html.testing', 'goog.style', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Dialog', 'goog.userAgent']); +goog.addDependency("ui/dimensionpicker.js", ['goog.ui.DimensionPicker'], ['goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Size', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.DimensionPickerRenderer', 'goog.ui.registry']); +goog.addDependency("ui/dimensionpicker_test.js", ['goog.ui.DimensionPickerTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events.KeyCodes', 'goog.math.Size', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.DimensionPicker', 'goog.ui.DimensionPickerRenderer']); +goog.addDependency("ui/dimensionpickerrenderer.js", ['goog.ui.DimensionPickerRenderer'], ['goog.a11y.aria.Announcer', 'goog.a11y.aria.LivePriority', 'goog.dom', 'goog.dom.TagName', 'goog.i18n.bidi', 'goog.style', 'goog.ui.ControlRenderer', 'goog.userAgent']); +goog.addDependency("ui/dimensionpickerrenderer_test.js", ['goog.ui.DimensionPickerRendererTest'], ['goog.a11y.aria.LivePriority', 'goog.array', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.DimensionPicker', 'goog.ui.DimensionPickerRenderer']); +goog.addDependency("ui/dragdropdetector.js", ['goog.ui.DragDropDetector', 'goog.ui.DragDropDetector.EventType', 'goog.ui.DragDropDetector.ImageDropEvent', 'goog.ui.DragDropDetector.LinkDropEvent'], ['goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.userAgent']); +goog.addDependency("ui/drilldownrow.js", ['goog.ui.DrilldownRow'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.string.Unicode', 'goog.ui.Component']); +goog.addDependency("ui/drilldownrow_test.js", ['goog.ui.DrilldownRowTest'], ['goog.dom', 'goog.dom.TagName', 'goog.html.SafeHtml', 'goog.testing.jsunit', 'goog.ui.DrilldownRow']); +goog.addDependency("ui/editor/abstractdialog.js", ['goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder', 'goog.ui.editor.AbstractDialog.EventType'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventTarget', 'goog.string', 'goog.ui.Dialog', 'goog.ui.PopupBase']); +goog.addDependency("ui/editor/abstractdialog_test.js", ['goog.ui.editor.AbstractDialogTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.KeyCodes', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.AbstractDialog', 'goog.userAgent']); +goog.addDependency("ui/editor/bubble.js", ['goog.ui.editor.Bubble'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.ViewportSizeMonitor', 'goog.dom.classlist', 'goog.editor.style', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.functions', 'goog.log', 'goog.math.Box', 'goog.object', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.PopupBase', 'goog.userAgent']); +goog.addDependency("ui/editor/bubble_test.js", ['goog.ui.editor.BubbleTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.positioning.Corner', 'goog.positioning.OverflowStatus', 'goog.string', 'goog.style', 'goog.testing.editor.TestHelper', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.editor.Bubble', 'goog.userAgent.product']); +goog.addDependency("ui/editor/defaulttoolbar.js", ['goog.ui.editor.ButtonDescriptor', 'goog.ui.editor.DefaultToolbar'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.editor.Command', 'goog.style', 'goog.ui.editor.ToolbarFactory', 'goog.ui.editor.messages', 'goog.userAgent']); +goog.addDependency("ui/editor/linkdialog.js", ['goog.ui.editor.LinkDialog', 'goog.ui.editor.LinkDialog.BeforeTestLinkEvent', 'goog.ui.editor.LinkDialog.EventType', 'goog.ui.editor.LinkDialog.OkEvent'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.editor.BrowserFeature', 'goog.editor.Link', 'goog.editor.focus', 'goog.editor.node', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.InputHandler', 'goog.html.SafeHtml', 'goog.html.SafeHtmlFormatter', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.LinkButtonRenderer', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.TabPane', 'goog.ui.editor.messages', 'goog.userAgent', 'goog.window']); +goog.addDependency("ui/editor/linkdialog_test.js", ['goog.ui.editor.LinkDialogTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.editor.BrowserFeature', 'goog.editor.Link', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.style', 'goog.testing.MockControl', 'goog.testing.PropertyReplacer', 'goog.testing.dom', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.mockmatchers.ArgumentMatcher', 'goog.ui.editor.AbstractDialog', 'goog.ui.editor.LinkDialog', 'goog.ui.editor.messages', 'goog.userAgent']); +goog.addDependency("ui/editor/messages.js", ['goog.ui.editor.messages'], ['goog.html.SafeHtmlFormatter']); +goog.addDependency("ui/editor/tabpane.js", ['goog.ui.editor.TabPane'], ['goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.Tab', 'goog.ui.TabBar']); +goog.addDependency("ui/editor/toolbarcontroller.js", ['goog.ui.editor.ToolbarController'], ['goog.editor.Field', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.ui.Component']); +goog.addDependency("ui/editor/toolbarfactory.js", ['goog.ui.editor.ToolbarFactory'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.string', 'goog.string.Unicode', 'goog.style', 'goog.ui.Component', 'goog.ui.Container', 'goog.ui.Option', 'goog.ui.Toolbar', 'goog.ui.ToolbarButton', 'goog.ui.ToolbarColorMenuButton', 'goog.ui.ToolbarMenuButton', 'goog.ui.ToolbarRenderer', 'goog.ui.ToolbarSelect', 'goog.userAgent']); +goog.addDependency("ui/editor/toolbarfactory_test.js", ['goog.ui.editor.ToolbarFactoryTest'], ['goog.dom', 'goog.testing.ExpectedFailures', 'goog.testing.editor.TestHelper', 'goog.testing.jsunit', 'goog.ui.editor.ToolbarFactory', 'goog.userAgent']); +goog.addDependency("ui/emoji/emoji.js", ['goog.ui.emoji.Emoji'], []); +goog.addDependency("ui/emoji/emojipalette.js", ['goog.ui.emoji.EmojiPalette'], ['goog.events.EventType', 'goog.net.ImageLoader', 'goog.ui.Palette', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPaletteRenderer']); +goog.addDependency("ui/emoji/emojipaletterenderer.js", ['goog.ui.emoji.EmojiPaletteRenderer'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.style', 'goog.ui.PaletteRenderer', 'goog.ui.emoji.Emoji']); +goog.addDependency("ui/emoji/emojipicker.js", ['goog.ui.emoji.EmojiPicker'], ['goog.dom.TagName', 'goog.style', 'goog.ui.Component', 'goog.ui.TabPane', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPalette', 'goog.ui.emoji.EmojiPaletteRenderer', 'goog.ui.emoji.ProgressiveEmojiPaletteRenderer']); +goog.addDependency("ui/emoji/emojipicker_test.js", ['goog.ui.emoji.EmojiPickerTest'], ['goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPicker', 'goog.ui.emoji.SpriteInfo']); +goog.addDependency("ui/emoji/fast_nonprogressive_emojipicker_test.js", ['goog.ui.emoji.FastNonProgressiveEmojiPickerTest'], ['goog.Promise', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.net.EventType', 'goog.style', 'goog.testing.jsunit', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPicker', 'goog.ui.emoji.SpriteInfo']); +goog.addDependency("ui/emoji/fast_progressive_emojipicker_test.js", ['goog.ui.emoji.FastProgressiveEmojiPickerTest'], ['goog.Promise', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.net.EventType', 'goog.style', 'goog.testing.jsunit', 'goog.ui.emoji.Emoji', 'goog.ui.emoji.EmojiPicker', 'goog.ui.emoji.SpriteInfo']); +goog.addDependency("ui/emoji/popupemojipicker.js", ['goog.ui.emoji.PopupEmojiPicker'], ['goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.ui.Component', 'goog.ui.Popup', 'goog.ui.emoji.EmojiPicker']); +goog.addDependency("ui/emoji/popupemojipicker_test.js", ['goog.ui.emoji.PopupEmojiPickerTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.emoji.PopupEmojiPicker']); +goog.addDependency("ui/emoji/progressiveemojipaletterenderer.js", ['goog.ui.emoji.ProgressiveEmojiPaletteRenderer'], ['goog.dom.TagName', 'goog.style', 'goog.ui.emoji.EmojiPaletteRenderer']); +goog.addDependency("ui/emoji/spriteinfo.js", ['goog.ui.emoji.SpriteInfo'], []); +goog.addDependency("ui/emoji/spriteinfo_test.js", ['goog.ui.emoji.SpriteInfoTest'], ['goog.testing.jsunit', 'goog.ui.emoji.SpriteInfo']); +goog.addDependency("ui/filteredmenu.js", ['goog.ui.FilteredMenu'], ['goog.a11y.aria', 'goog.a11y.aria.AutoCompleteValues', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.FilterObservingMenuItem', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']); +goog.addDependency("ui/filteredmenu_test.js", ['goog.ui.FilteredMenuTest'], ['goog.a11y.aria', 'goog.a11y.aria.AutoCompleteValues', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Rect', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.FilteredMenu', 'goog.ui.MenuItem']); +goog.addDependency("ui/filterobservingmenuitem.js", ['goog.ui.FilterObservingMenuItem'], ['goog.ui.FilterObservingMenuItemRenderer', 'goog.ui.MenuItem', 'goog.ui.registry']); +goog.addDependency("ui/filterobservingmenuitemrenderer.js", ['goog.ui.FilterObservingMenuItemRenderer'], ['goog.ui.MenuItemRenderer']); +goog.addDependency("ui/flatbuttonrenderer.js", ['goog.ui.FlatButtonRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.ButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']); +goog.addDependency("ui/flatmenubuttonrenderer.js", ['goog.ui.FlatMenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.ui.FlatButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.MenuRenderer', 'goog.ui.registry']); +goog.addDependency("ui/formpost.js", ['goog.ui.FormPost'], ['goog.array', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.ui.Component']); +goog.addDependency("ui/formpost_test.js", ['goog.ui.FormPostTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.object', 'goog.testing.jsunit', 'goog.ui.FormPost', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("ui/gauge.js", ['goog.ui.Gauge', 'goog.ui.GaugeColoredRange'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom.TagName', 'goog.events', 'goog.fx.Animation', 'goog.fx.Transition', 'goog.fx.easing', 'goog.graphics', 'goog.graphics.Font', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.math', 'goog.ui.Component', 'goog.ui.GaugeTheme']); +goog.addDependency("ui/gaugetheme.js", ['goog.ui.GaugeTheme'], ['goog.graphics.LinearGradient', 'goog.graphics.SolidFill', 'goog.graphics.Stroke']); +goog.addDependency("ui/hovercard.js", ['goog.ui.HoverCard', 'goog.ui.HoverCard.EventType', 'goog.ui.HoverCard.TriggerEvent'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.ui.AdvancedTooltip', 'goog.ui.PopupBase', 'goog.ui.Tooltip']); +goog.addDependency("ui/hovercard_test.js", ['goog.ui.HoverCardTest'], ['goog.dom', 'goog.events', 'goog.math.Coordinate', 'goog.style', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.HoverCard']); +goog.addDependency("ui/hsvapalette.js", ['goog.ui.HsvaPalette'], ['goog.array', 'goog.color.alpha', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.HsvPalette']); +goog.addDependency("ui/hsvapalette_test.js", ['goog.ui.HsvaPaletteTest'], ['goog.color.alpha', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.Event', 'goog.math.Coordinate', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.HsvaPalette', 'goog.userAgent']); +goog.addDependency("ui/hsvpalette.js", ['goog.ui.HsvPalette'], ['goog.color', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.events.InputHandler', 'goog.style', 'goog.style.bidi', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/hsvpalette_test.js", ['goog.ui.HsvPaletteTest'], ['goog.color', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.math.Coordinate', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.HsvPalette', 'goog.userAgent']); +goog.addDependency("ui/idgenerator.js", ['goog.ui.IdGenerator'], []); +goog.addDependency("ui/idletimer.js", ['goog.ui.IdleTimer'], ['goog.Timer', 'goog.events', 'goog.events.EventTarget', 'goog.structs.Set', 'goog.ui.ActivityMonitor']); +goog.addDependency("ui/idletimer_test.js", ['goog.ui.IdleTimerTest'], ['goog.events', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.ui.IdleTimer', 'goog.ui.MockActivityMonitor']); +goog.addDependency("ui/iframemask.js", ['goog.ui.IframeMask'], ['goog.Disposable', 'goog.Timer', 'goog.dom', 'goog.dom.iframe', 'goog.events.EventHandler', 'goog.structs.Pool', 'goog.style']); +goog.addDependency("ui/iframemask_test.js", ['goog.ui.IframeMaskTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.iframe', 'goog.structs.Pool', 'goog.style', 'goog.testing.MockClock', 'goog.testing.StrictMock', 'goog.testing.jsunit', 'goog.ui.IframeMask', 'goog.ui.Popup', 'goog.ui.PopupBase', 'goog.userAgent']); +goog.addDependency("ui/imagelessbuttonrenderer.js", ['goog.ui.ImagelessButtonRenderer'], ['goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']); +goog.addDependency("ui/imagelessmenubuttonrenderer.js", ['goog.ui.ImagelessMenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuButton', 'goog.ui.MenuButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/inputdatepicker.js", ['goog.ui.InputDatePicker'], ['goog.date.DateTime', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.i18n.DateTimeParse', 'goog.string', 'goog.ui.Component', 'goog.ui.DatePicker', 'goog.ui.LabelInput', 'goog.ui.PopupBase', 'goog.ui.PopupDatePicker']); +goog.addDependency("ui/inputdatepicker_test.js", ['goog.ui.InputDatePickerTest'], ['goog.dom', 'goog.i18n.DateTimeFormat', 'goog.i18n.DateTimeParse', 'goog.testing.jsunit', 'goog.ui.InputDatePicker']); +goog.addDependency("ui/itemevent.js", ['goog.ui.ItemEvent'], ['goog.events.Event']); +goog.addDependency("ui/keyboardshortcuthandler.js", ['goog.ui.KeyboardShortcutEvent', 'goog.ui.KeyboardShortcutHandler', 'goog.ui.KeyboardShortcutHandler.EventType', 'goog.ui.KeyboardShortcutHandler.Modifiers'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyNames', 'goog.events.Keys', 'goog.object', 'goog.userAgent']); +goog.addDependency("ui/keyboardshortcuthandler_test.js", ['goog.ui.KeyboardShortcutHandlerTest'], ['goog.dom', 'goog.events', 'goog.events.BrowserEvent', 'goog.events.KeyCodes', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.StrictMock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.KeyboardShortcutHandler', 'goog.userAgent']); +goog.addDependency("ui/labelinput.js", ['goog.ui.LabelInput'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/labelinput_test.js", ['goog.ui.LabelInputTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.LabelInput', 'goog.userAgent']); +goog.addDependency("ui/linkbuttonrenderer.js", ['goog.ui.LinkButtonRenderer'], ['goog.ui.Button', 'goog.ui.FlatButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/media/flashobject.js", ['goog.ui.media.FlashObject', 'goog.ui.media.FlashObject.ScriptAccessLevel', 'goog.ui.media.FlashObject.Wmodes'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.html.TrustedResourceUrl', 'goog.html.flash', 'goog.log', 'goog.object', 'goog.string', 'goog.structs.Map', 'goog.style', 'goog.ui.Component', 'goog.userAgent', 'goog.userAgent.flash']); +goog.addDependency("ui/media/flashobject_test.js", ['goog.ui.media.FlashObjectTest'], ['goog.dom', 'goog.dom.DomHelper', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.html.testing', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.userAgent']); +goog.addDependency("ui/media/flickr.js", ['goog.ui.media.FlickrSet', 'goog.ui.media.FlickrSetModel'], ['goog.html.TrustedResourceUrl', 'goog.string.Const', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/flickr_test.js", ['goog.ui.media.FlickrSetTest'], ['goog.dom', 'goog.dom.TagName', 'goog.html.testing', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.FlickrSet', 'goog.ui.media.FlickrSetModel', 'goog.ui.media.Media']); +goog.addDependency("ui/media/googlevideo.js", ['goog.ui.media.GoogleVideo', 'goog.ui.media.GoogleVideoModel'], ['goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/googlevideo_test.js", ['goog.ui.media.GoogleVideoTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.GoogleVideo', 'goog.ui.media.GoogleVideoModel', 'goog.ui.media.Media']); +goog.addDependency("ui/media/media.js", ['goog.ui.media.Media', 'goog.ui.media.MediaRenderer'], ['goog.asserts', 'goog.dom.TagName', 'goog.style', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/media/media_test.js", ['goog.ui.media.MediaTest'], ['goog.dom', 'goog.dom.TagName', 'goog.html.testing', 'goog.math.Size', 'goog.testing.jsunit', 'goog.ui.ControlRenderer', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/mediamodel.js", ['goog.ui.media.MediaModel', 'goog.ui.media.MediaModel.Category', 'goog.ui.media.MediaModel.Credit', 'goog.ui.media.MediaModel.Credit.Role', 'goog.ui.media.MediaModel.Credit.Scheme', 'goog.ui.media.MediaModel.Medium', 'goog.ui.media.MediaModel.MimeType', 'goog.ui.media.MediaModel.Player', 'goog.ui.media.MediaModel.SubTitle', 'goog.ui.media.MediaModel.Thumbnail'], ['goog.array', 'goog.html.TrustedResourceUrl']); +goog.addDependency("ui/media/mediamodel_test.js", ['goog.ui.media.MediaModelTest'], ['goog.testing.jsunit', 'goog.ui.media.MediaModel']); +goog.addDependency("ui/media/mp3.js", ['goog.ui.media.Mp3'], ['goog.string', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/mp3_test.js", ['goog.ui.media.Mp3Test'], ['goog.dom', 'goog.dom.TagName', 'goog.html.testing', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.Mp3']); +goog.addDependency("ui/media/photo.js", ['goog.ui.media.Photo'], ['goog.dom.TagName', 'goog.ui.media.Media', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/photo_test.js", ['goog.ui.media.PhotoTest'], ['goog.dom', 'goog.dom.TagName', 'goog.html.testing', 'goog.testing.jsunit', 'goog.ui.media.MediaModel', 'goog.ui.media.Photo']); +goog.addDependency("ui/media/picasa.js", ['goog.ui.media.PicasaAlbum', 'goog.ui.media.PicasaAlbumModel'], ['goog.html.TrustedResourceUrl', 'goog.string.Const', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/picasa_test.js", ['goog.ui.media.PicasaTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.PicasaAlbum', 'goog.ui.media.PicasaAlbumModel']); +goog.addDependency("ui/media/vimeo.js", ['goog.ui.media.Vimeo', 'goog.ui.media.VimeoModel'], ['goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/vimeo_test.js", ['goog.ui.media.VimeoTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.Vimeo', 'goog.ui.media.VimeoModel']); +goog.addDependency("ui/media/youtube.js", ['goog.ui.media.Youtube', 'goog.ui.media.YoutubeModel'], ['goog.dom.TagName', 'goog.html.TrustedResourceUrl', 'goog.string', 'goog.string.Const', 'goog.ui.Component', 'goog.ui.media.FlashObject', 'goog.ui.media.Media', 'goog.ui.media.MediaModel', 'goog.ui.media.MediaRenderer']); +goog.addDependency("ui/media/youtube_test.js", ['goog.ui.media.YoutubeTest'], ['goog.dom', 'goog.dom.TagName', 'goog.testing.jsunit', 'goog.ui.media.FlashObject', 'goog.ui.media.Youtube', 'goog.ui.media.YoutubeModel']); +goog.addDependency("ui/menu.js", ['goog.ui.Menu', 'goog.ui.Menu.EventType'], ['goog.dom.TagName', 'goog.math.Coordinate', 'goog.string', 'goog.style', 'goog.ui.Component.EventType', 'goog.ui.Component.State', 'goog.ui.Container', 'goog.ui.Container.Orientation', 'goog.ui.MenuHeader', 'goog.ui.MenuItem', 'goog.ui.MenuRenderer', 'goog.ui.MenuSeparator']); +goog.addDependency("ui/menu_test.js", ['goog.ui.MenuTest'], ['goog.dom', 'goog.events', 'goog.math.Coordinate', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Menu']); +goog.addDependency("ui/menubar.js", ['goog.ui.menuBar'], ['goog.ui.Container', 'goog.ui.MenuBarRenderer']); +goog.addDependency("ui/menubardecorator.js", ['goog.ui.menuBarDecorator'], ['goog.ui.MenuBarRenderer', 'goog.ui.menuBar', 'goog.ui.registry']); +goog.addDependency("ui/menubarrenderer.js", ['goog.ui.MenuBarRenderer'], ['goog.a11y.aria.Role', 'goog.ui.Container', 'goog.ui.ContainerRenderer']); +goog.addDependency("ui/menubase.js", ['goog.ui.MenuBase'], ['goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyHandler', 'goog.ui.Popup']); +goog.addDependency("ui/menubutton.js", ['goog.ui.MenuButton'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.math.Box', 'goog.math.Rect', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.positioning.Overflow', 'goog.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.IdGenerator', 'goog.ui.Menu', 'goog.ui.MenuButtonRenderer', 'goog.ui.MenuItem', 'goog.ui.MenuRenderer', 'goog.ui.registry', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("ui/menubutton_test.js", ['goog.ui.MenuButtonTest'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.positioning', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.positioning.Overflow', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.SubMenu', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("ui/menubuttonrenderer.js", ['goog.ui.MenuButtonRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.Menu', 'goog.ui.MenuRenderer']); +goog.addDependency("ui/menubuttonrenderer_test.js", ['goog.ui.MenuButtonRendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.MenuButton', 'goog.ui.MenuButtonRenderer', 'goog.userAgent']); +goog.addDependency("ui/menuheader.js", ['goog.ui.MenuHeader'], ['goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuHeaderRenderer', 'goog.ui.registry']); +goog.addDependency("ui/menuheaderrenderer.js", ['goog.ui.MenuHeaderRenderer'], ['goog.ui.ControlRenderer']); +goog.addDependency("ui/menuitem.js", ['goog.ui.MenuItem'], ['goog.a11y.aria.Role', 'goog.array', 'goog.dom', 'goog.dom.classlist', 'goog.math.Coordinate', 'goog.string', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuItemRenderer', 'goog.ui.registry']); +goog.addDependency("ui/menuitem_test.js", ['goog.ui.MenuItemTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.KeyCodes', 'goog.html.testing', 'goog.math.Coordinate', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.MenuItemRenderer']); +goog.addDependency("ui/menuitemrenderer.js", ['goog.ui.MenuItemRenderer'], ['goog.a11y.aria.Role', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Component', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/menuitemrenderer_test.js", ['goog.ui.MenuItemRendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.classlist', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.MenuItemRenderer']); +goog.addDependency("ui/menurenderer.js", ['goog.ui.MenuRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.ui.ContainerRenderer', 'goog.ui.Separator']); +goog.addDependency("ui/menuseparator.js", ['goog.ui.MenuSeparator'], ['goog.ui.MenuSeparatorRenderer', 'goog.ui.Separator', 'goog.ui.registry']); +goog.addDependency("ui/menuseparatorrenderer.js", ['goog.ui.MenuSeparatorRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/menuseparatorrenderer_test.js", ['goog.ui.MenuSeparatorRendererTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.MenuSeparator', 'goog.ui.MenuSeparatorRenderer']); +goog.addDependency("ui/mockactivitymonitor.js", ['goog.ui.MockActivityMonitor'], ['goog.events.EventType', 'goog.ui.ActivityMonitor']); +goog.addDependency("ui/mockactivitymonitor_test.js", ['goog.ui.MockActivityMonitorTest'], ['goog.events', 'goog.functions', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.ActivityMonitor', 'goog.ui.MockActivityMonitor']); +goog.addDependency("ui/modalariavisibilityhelper.js", ['goog.ui.ModalAriaVisibilityHelper'], ['goog.a11y.aria', 'goog.a11y.aria.State']); +goog.addDependency("ui/modalariavisibilityhelper_test.js", ['goog.ui.ModalAriaVisibilityHelperTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.string', 'goog.testing.jsunit', 'goog.ui.ModalAriaVisibilityHelper']); +goog.addDependency("ui/modalpopup.js", ['goog.ui.ModalPopup'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.animationFrame', 'goog.dom.classlist', 'goog.dom.iframe', 'goog.events', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.fx.Transition', 'goog.string', 'goog.style', 'goog.ui.Component', 'goog.ui.ModalAriaVisibilityHelper', 'goog.ui.PopupBase', 'goog.userAgent']); +goog.addDependency("ui/modalpopup_test.js", ['goog.ui.ModalPopupTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dispose', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.fx.Transition', 'goog.fx.css3', 'goog.string', 'goog.style', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.ModalPopup', 'goog.ui.PopupBase']); +goog.addDependency("ui/nativebuttonrenderer.js", ['goog.ui.NativeButtonRenderer'], ['goog.asserts', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.ui.ButtonRenderer', 'goog.ui.Component']); +goog.addDependency("ui/nativebuttonrenderer_test.js", ['goog.ui.NativeButtonRendererTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.testing.ExpectedFailures', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.NativeButtonRenderer', 'goog.userAgent']); +goog.addDependency("ui/option.js", ['goog.ui.Option'], ['goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.registry']); +goog.addDependency("ui/palette.js", ['goog.ui.Palette'], ['goog.array', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Size', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.PaletteRenderer', 'goog.ui.SelectionModel']); +goog.addDependency("ui/palette_test.js", ['goog.ui.PaletteTest'], ['goog.a11y.aria', 'goog.dom', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyEvent', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.Container', 'goog.ui.Palette']); +goog.addDependency("ui/paletterenderer.js", ['goog.ui.PaletteRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.NodeIterator', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.iter', 'goog.style', 'goog.ui.ControlRenderer', 'goog.userAgent']); +goog.addDependency("ui/paletterenderer_test.js", ['goog.ui.PaletteRendererTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.html.testing', 'goog.testing.jsunit', 'goog.ui.Palette', 'goog.ui.PaletteRenderer']); +goog.addDependency("ui/plaintextspellchecker.js", ['goog.ui.PlainTextSpellChecker'], ['goog.Timer', 'goog.a11y.aria', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.spell.SpellCheck', 'goog.style', 'goog.ui.AbstractSpellChecker', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/plaintextspellchecker_test.js", ['goog.ui.PlainTextSpellCheckerTest'], ['goog.Timer', 'goog.dom', 'goog.events.KeyCodes', 'goog.spell.SpellCheck', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.PlainTextSpellChecker']); +goog.addDependency("ui/popup.js", ['goog.ui.Popup'], ['goog.math.Box', 'goog.positioning.AbstractPosition', 'goog.positioning.Corner', 'goog.style', 'goog.ui.PopupBase']); +goog.addDependency("ui/popup_test.js", ['goog.ui.PopupTest'], ['goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.style', 'goog.testing.jsunit', 'goog.ui.Popup', 'goog.userAgent']); +goog.addDependency("ui/popupbase.js", ['goog.ui.PopupBase', 'goog.ui.PopupBase.EventType', 'goog.ui.PopupBase.Type'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Transition', 'goog.style', 'goog.userAgent']); +goog.addDependency("ui/popupbase_test.js", ['goog.ui.PopupBaseTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Transition', 'goog.fx.css3', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.PopupBase']); +goog.addDependency("ui/popupcolorpicker.js", ['goog.ui.PopupColorPicker'], ['goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.ui.ColorPicker', 'goog.ui.Component', 'goog.ui.Popup']); +goog.addDependency("ui/popupcolorpicker_test.js", ['goog.ui.PopupColorPickerTest'], ['goog.dom', 'goog.events', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.ColorPicker', 'goog.ui.PopupColorPicker']); +goog.addDependency("ui/popupdatepicker.js", ['goog.ui.PopupDatePicker'], ['goog.events.EventType', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.style', 'goog.ui.Component', 'goog.ui.DatePicker', 'goog.ui.Popup', 'goog.ui.PopupBase']); +goog.addDependency("ui/popupdatepicker_test.js", ['goog.ui.PopupDatePickerTest'], ['goog.date.Date', 'goog.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.PopupBase', 'goog.ui.PopupDatePicker']); +goog.addDependency("ui/popupmenu.js", ['goog.ui.PopupMenu'], ['goog.events', 'goog.events.BrowserEvent', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.positioning.MenuAnchoredPosition', 'goog.positioning.Overflow', 'goog.positioning.ViewportClientPosition', 'goog.structs.Map', 'goog.style', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.PopupBase']); +goog.addDependency("ui/popupmenu_test.js", ['goog.ui.PopupMenuTest'], ['goog.dom', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.math.Box', 'goog.math.Coordinate', 'goog.positioning.Corner', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.PopupMenu']); +goog.addDependency("ui/progressbar.js", ['goog.ui.ProgressBar', 'goog.ui.ProgressBar.Orientation'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.ui.Component', 'goog.ui.RangeModel', 'goog.userAgent']); +goog.addDependency("ui/prompt.js", ['goog.ui.Prompt'], ['goog.Timer', 'goog.dom', 'goog.dom.InputType', 'goog.dom.TagName', 'goog.events', 'goog.events.EventType', 'goog.functions', 'goog.html.SafeHtml', 'goog.ui.Component', 'goog.ui.Dialog', 'goog.userAgent']); +goog.addDependency("ui/prompt_test.js", ['goog.ui.PromptTest'], ['goog.dom.selection', 'goog.events.InputHandler', 'goog.events.KeyCodes', 'goog.functions', 'goog.string', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.BidiInput', 'goog.ui.Dialog', 'goog.ui.Prompt', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("ui/rangemodel.js", ['goog.ui.RangeModel'], ['goog.events.EventTarget', 'goog.ui.Component']); +goog.addDependency("ui/rangemodel_test.js", ['goog.ui.RangeModelTest'], ['goog.testing.jsunit', 'goog.ui.RangeModel']); +goog.addDependency("ui/ratings.js", ['goog.ui.Ratings', 'goog.ui.Ratings.EventType'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.ui.Component']); +goog.addDependency("ui/registry.js", ['goog.ui.registry'], ['goog.asserts', 'goog.dom.classlist']); +goog.addDependency("ui/registry_test.js", ['goog.ui.registryTest'], ['goog.object', 'goog.testing.jsunit', 'goog.ui.registry']); +goog.addDependency("ui/richtextspellchecker.js", ['goog.ui.RichTextSpellChecker'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.Range', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.math.Coordinate', 'goog.spell.SpellCheck', 'goog.string.StringBuffer', 'goog.style', 'goog.ui.AbstractSpellChecker', 'goog.ui.Component', 'goog.ui.PopupMenu']); +goog.addDependency("ui/richtextspellchecker_test.js", ['goog.ui.RichTextSpellCheckerTest'], ['goog.dom.Range', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.KeyCodes', 'goog.object', 'goog.spell.SpellCheck', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.RichTextSpellChecker']); +goog.addDependency("ui/roundedpanel.js", ['goog.ui.BaseRoundedPanel', 'goog.ui.CssRoundedPanel', 'goog.ui.GraphicsRoundedPanel', 'goog.ui.RoundedPanel', 'goog.ui.RoundedPanel.Corner'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.graphics', 'goog.graphics.Path', 'goog.graphics.SolidFill', 'goog.graphics.Stroke', 'goog.math', 'goog.math.Coordinate', 'goog.style', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/roundedpanel_test.js", ['goog.ui.RoundedPanelTest'], ['goog.testing.jsunit', 'goog.ui.CssRoundedPanel', 'goog.ui.GraphicsRoundedPanel', 'goog.ui.RoundedPanel', 'goog.userAgent']); +goog.addDependency("ui/roundedtabrenderer.js", ['goog.ui.RoundedTabRenderer'], ['goog.dom', 'goog.dom.TagName', 'goog.ui.Tab', 'goog.ui.TabBar', 'goog.ui.TabRenderer', 'goog.ui.registry']); +goog.addDependency("ui/scrollfloater.js", ['goog.ui.ScrollFloater', 'goog.ui.ScrollFloater.EventType'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/scrollfloater_test.js", ['goog.ui.ScrollFloaterTest'], ['goog.dom', 'goog.events', 'goog.style', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.ui.ScrollFloater']); +goog.addDependency("ui/select.js", ['goog.ui.Select'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.events.EventType', 'goog.ui.Component', 'goog.ui.IdGenerator', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.MenuRenderer', 'goog.ui.SelectionModel', 'goog.ui.registry']); +goog.addDependency("ui/select_test.js", ['goog.ui.SelectTest'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.CustomButtonRenderer', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.Select', 'goog.ui.Separator']); +goog.addDependency("ui/selectionmenubutton.js", ['goog.ui.SelectionMenuButton', 'goog.ui.SelectionMenuButton.SelectionState'], ['goog.dom.InputType', 'goog.dom.TagName', 'goog.events.EventType', 'goog.style', 'goog.ui.Component', 'goog.ui.MenuButton', 'goog.ui.MenuItem', 'goog.ui.registry']); +goog.addDependency("ui/selectionmenubutton_test.js", ['goog.ui.SelectionMenuButtonTest'], ['goog.dom', 'goog.events', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.SelectionMenuButton']); +goog.addDependency("ui/selectionmodel.js", ['goog.ui.SelectionModel'], ['goog.array', 'goog.events.EventTarget', 'goog.events.EventType']); +goog.addDependency("ui/selectionmodel_test.js", ['goog.ui.SelectionModelTest'], ['goog.array', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.SelectionModel']); +goog.addDependency("ui/separator.js", ['goog.ui.Separator'], ['goog.a11y.aria', 'goog.asserts', 'goog.ui.Component', 'goog.ui.Control', 'goog.ui.MenuSeparatorRenderer', 'goog.ui.registry']); +goog.addDependency("ui/serverchart.js", ['goog.ui.ServerChart', 'goog.ui.ServerChart.AxisDisplayType', 'goog.ui.ServerChart.ChartType', 'goog.ui.ServerChart.EncodingType', 'goog.ui.ServerChart.Event', 'goog.ui.ServerChart.LegendPosition', 'goog.ui.ServerChart.MaximumValue', 'goog.ui.ServerChart.MultiAxisAlignment', 'goog.ui.ServerChart.MultiAxisType', 'goog.ui.ServerChart.UriParam', 'goog.ui.ServerChart.UriTooLongEvent'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events.Event', 'goog.string', 'goog.ui.Component']); +goog.addDependency("ui/serverchart_test.js", ['goog.ui.ServerChartTest'], ['goog.Uri', 'goog.events', 'goog.testing.jsunit', 'goog.ui.ServerChart']); +goog.addDependency("ui/slider.js", ['goog.ui.Slider', 'goog.ui.Slider.Orientation'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.dom.TagName', 'goog.ui.SliderBase']); +goog.addDependency("ui/sliderbase.js", ['goog.ui.SliderBase', 'goog.ui.SliderBase.AnimationFactory', 'goog.ui.SliderBase.Orientation'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.events.MouseWheelHandler', 'goog.functions', 'goog.fx.AnimationParallelQueue', 'goog.fx.Dragger', 'goog.fx.Transition', 'goog.fx.dom.ResizeHeight', 'goog.fx.dom.ResizeWidth', 'goog.fx.dom.Slide', 'goog.math', 'goog.math.Coordinate', 'goog.style', 'goog.style.bidi', 'goog.ui.Component', 'goog.ui.RangeModel']); +goog.addDependency("ui/sliderbase_test.js", ['goog.ui.SliderBaseTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.fx.Animation', 'goog.math.Coordinate', 'goog.style', 'goog.style.bidi', 'goog.testing.MockClock', 'goog.testing.MockControl', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.mockmatchers', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.SliderBase', 'goog.userAgent']); +goog.addDependency("ui/splitpane.js", ['goog.ui.SplitPane', 'goog.ui.SplitPane.Orientation'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.fx.Dragger', 'goog.math.Rect', 'goog.math.Size', 'goog.style', 'goog.ui.Component', 'goog.userAgent']); +goog.addDependency("ui/splitpane_test.js", ['goog.ui.SplitPaneTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.math.Size', 'goog.style', 'goog.testing.events', 'goog.testing.jsunit', 'goog.testing.recordFunction', 'goog.ui.Component', 'goog.ui.SplitPane']); +goog.addDependency("ui/style/app/buttonrenderer.js", ['goog.ui.style.app.ButtonRenderer'], ['goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.Button', 'goog.ui.CustomButtonRenderer', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.registry']); +goog.addDependency("ui/style/app/buttonrenderer_test.js", ['goog.ui.style.app.ButtonRendererTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.ui.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.style.app.ButtonRenderer', 'goog.userAgent']); +goog.addDependency("ui/style/app/menubuttonrenderer.js", ['goog.ui.style.app.MenuButtonRenderer'], ['goog.a11y.aria.Role', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuRenderer', 'goog.ui.style.app.ButtonRenderer']); +goog.addDependency("ui/style/app/menubuttonrenderer_test.js", ['goog.ui.style.app.MenuButtonRendererTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.ui.style', 'goog.ui.Component', 'goog.ui.MenuButton', 'goog.ui.style.app.MenuButtonRenderer']); +goog.addDependency("ui/style/app/primaryactionbuttonrenderer.js", ['goog.ui.style.app.PrimaryActionButtonRenderer'], ['goog.ui.Button', 'goog.ui.registry', 'goog.ui.style.app.ButtonRenderer']); +goog.addDependency("ui/style/app/primaryactionbuttonrenderer_test.js", ['goog.ui.style.app.PrimaryActionButtonRendererTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.ui.style', 'goog.ui.Button', 'goog.ui.Component', 'goog.ui.style.app.PrimaryActionButtonRenderer']); +goog.addDependency("ui/submenu.js", ['goog.ui.SubMenu'], ['goog.Timer', 'goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.KeyCodes', 'goog.positioning.AnchoredViewportPosition', 'goog.positioning.Corner', 'goog.style', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.SubMenuRenderer', 'goog.ui.registry']); +goog.addDependency("ui/submenu_test.js", ['goog.ui.SubMenuTest'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.functions', 'goog.positioning', 'goog.positioning.Overflow', 'goog.style', 'goog.testing.MockClock', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.ui.SubMenu', 'goog.ui.SubMenuRenderer']); +goog.addDependency("ui/submenurenderer.js", ['goog.ui.SubMenuRenderer'], ['goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItemRenderer']); +goog.addDependency("ui/tab.js", ['goog.ui.Tab'], ['goog.ui.Component', 'goog.ui.Control', 'goog.ui.TabRenderer', 'goog.ui.registry']); +goog.addDependency("ui/tab_test.js", ['goog.ui.TabTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Tab', 'goog.ui.TabRenderer']); +goog.addDependency("ui/tabbar.js", ['goog.ui.TabBar', 'goog.ui.TabBar.Location'], ['goog.ui.Component.EventType', 'goog.ui.Container', 'goog.ui.Container.Orientation', 'goog.ui.Tab', 'goog.ui.TabBarRenderer', 'goog.ui.registry']); +goog.addDependency("ui/tabbar_test.js", ['goog.ui.TabBarTest'], ['goog.dom', 'goog.events', 'goog.events.Event', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.Container', 'goog.ui.Tab', 'goog.ui.TabBar', 'goog.ui.TabBarRenderer']); +goog.addDependency("ui/tabbarrenderer.js", ['goog.ui.TabBarRenderer'], ['goog.a11y.aria.Role', 'goog.object', 'goog.ui.ContainerRenderer']); +goog.addDependency("ui/tabbarrenderer_test.js", ['goog.ui.TabBarRendererTest'], ['goog.a11y.aria.Role', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Container', 'goog.ui.TabBar', 'goog.ui.TabBarRenderer']); +goog.addDependency("ui/tablesorter.js", ['goog.ui.TableSorter', 'goog.ui.TableSorter.EventType'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events.EventType', 'goog.functions', 'goog.ui.Component']); +goog.addDependency("ui/tablesorter_test.js", ['goog.ui.TableSorterTest'], ['goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.TableSorter']); +goog.addDependency("ui/tabpane.js", ['goog.ui.TabPane', 'goog.ui.TabPane.Events', 'goog.ui.TabPane.TabLocation', 'goog.ui.TabPane.TabPage', 'goog.ui.TabPaneEvent'], ['goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.Event', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.html.SafeStyleSheet', 'goog.style']); +goog.addDependency("ui/tabpane_test.js", ['goog.ui.TabPaneTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.TabPane']); +goog.addDependency("ui/tabrenderer.js", ['goog.ui.TabRenderer'], ['goog.a11y.aria.Role', 'goog.ui.Component', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/tabrenderer_test.js", ['goog.ui.TabRendererTest'], ['goog.a11y.aria.Role', 'goog.dom', 'goog.dom.classlist', 'goog.testing.dom', 'goog.testing.jsunit', 'goog.testing.ui.rendererasserts', 'goog.ui.Tab', 'goog.ui.TabRenderer']); +goog.addDependency("ui/textarea.js", ['goog.ui.Textarea', 'goog.ui.Textarea.EventType'], ['goog.asserts', 'goog.dom', 'goog.dom.classlist', 'goog.events.EventType', 'goog.style', 'goog.ui.Control', 'goog.ui.TextareaRenderer', 'goog.userAgent']); +goog.addDependency("ui/textarea_test.js", ['goog.ui.TextareaTest'], ['goog.dom', 'goog.dom.classlist', 'goog.events', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.events.EventObserver', 'goog.testing.jsunit', 'goog.ui.Textarea', 'goog.ui.TextareaRenderer', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("ui/textarearenderer.js", ['goog.ui.TextareaRenderer'], ['goog.dom.TagName', 'goog.ui.Component', 'goog.ui.ControlRenderer']); +goog.addDependency("ui/togglebutton.js", ['goog.ui.ToggleButton'], ['goog.ui.Button', 'goog.ui.Component', 'goog.ui.CustomButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbar.js", ['goog.ui.Toolbar'], ['goog.ui.Container', 'goog.ui.ToolbarRenderer']); +goog.addDependency("ui/toolbar_test.js", ['goog.ui.ToolbarTest'], ['goog.a11y.aria', 'goog.dom', 'goog.events.EventType', 'goog.testing.events', 'goog.testing.events.Event', 'goog.testing.jsunit', 'goog.ui.Toolbar', 'goog.ui.ToolbarMenuButton']); +goog.addDependency("ui/toolbarbutton.js", ['goog.ui.ToolbarButton'], ['goog.ui.Button', 'goog.ui.ToolbarButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbarbuttonrenderer.js", ['goog.ui.ToolbarButtonRenderer'], ['goog.ui.CustomButtonRenderer']); +goog.addDependency("ui/toolbarcolormenubutton.js", ['goog.ui.ToolbarColorMenuButton'], ['goog.ui.ColorMenuButton', 'goog.ui.ToolbarColorMenuButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbarcolormenubuttonrenderer.js", ['goog.ui.ToolbarColorMenuButtonRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.ui.ColorMenuButtonRenderer', 'goog.ui.MenuButtonRenderer', 'goog.ui.ToolbarMenuButtonRenderer']); +goog.addDependency("ui/toolbarcolormenubuttonrenderer_test.js", ['goog.ui.ToolbarColorMenuButtonRendererTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.testing.ui.RendererHarness', 'goog.testing.ui.rendererasserts', 'goog.ui.ToolbarColorMenuButton', 'goog.ui.ToolbarColorMenuButtonRenderer']); +goog.addDependency("ui/toolbarmenubutton.js", ['goog.ui.ToolbarMenuButton'], ['goog.ui.MenuButton', 'goog.ui.ToolbarMenuButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbarmenubuttonrenderer.js", ['goog.ui.ToolbarMenuButtonRenderer'], ['goog.ui.MenuButtonRenderer']); +goog.addDependency("ui/toolbarrenderer.js", ['goog.ui.ToolbarRenderer'], ['goog.a11y.aria.Role', 'goog.dom.TagName', 'goog.ui.Container', 'goog.ui.ContainerRenderer', 'goog.ui.Separator', 'goog.ui.ToolbarSeparatorRenderer']); +goog.addDependency("ui/toolbarselect.js", ['goog.ui.ToolbarSelect'], ['goog.ui.Select', 'goog.ui.ToolbarMenuButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbarseparator.js", ['goog.ui.ToolbarSeparator'], ['goog.ui.Separator', 'goog.ui.ToolbarSeparatorRenderer', 'goog.ui.registry']); +goog.addDependency("ui/toolbarseparatorrenderer.js", ['goog.ui.ToolbarSeparatorRenderer'], ['goog.asserts', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.MenuSeparatorRenderer']); +goog.addDependency("ui/toolbarseparatorrenderer_test.js", ['goog.ui.ToolbarSeparatorRendererTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.INLINE_BLOCK_CLASSNAME', 'goog.ui.ToolbarSeparator', 'goog.ui.ToolbarSeparatorRenderer']); +goog.addDependency("ui/toolbartogglebutton.js", ['goog.ui.ToolbarToggleButton'], ['goog.ui.ToggleButton', 'goog.ui.ToolbarButtonRenderer', 'goog.ui.registry']); +goog.addDependency("ui/tooltip.js", ['goog.ui.Tooltip', 'goog.ui.Tooltip.CursorTooltipPosition', 'goog.ui.Tooltip.ElementTooltipPosition', 'goog.ui.Tooltip.State'], ['goog.Timer', 'goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.TagName', 'goog.dom.safe', 'goog.events', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.math.Box', 'goog.math.Coordinate', 'goog.positioning', 'goog.positioning.AnchoredPosition', 'goog.positioning.Corner', 'goog.positioning.Overflow', 'goog.positioning.OverflowStatus', 'goog.positioning.ViewportPosition', 'goog.structs.Set', 'goog.style', 'goog.ui.Popup', 'goog.ui.PopupBase']); +goog.addDependency("ui/tooltip_test.js", ['goog.ui.TooltipTest'], ['goog.dom', 'goog.dom.TagName', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.html.testing', 'goog.math.Coordinate', 'goog.positioning.AbsolutePosition', 'goog.style', 'goog.testing.MockClock', 'goog.testing.TestQueue', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.PopupBase', 'goog.ui.Tooltip', 'goog.userAgent']); +goog.addDependency("ui/tree/basenode.js", ['goog.ui.tree.BaseNode', 'goog.ui.tree.BaseNode.EventType'], ['goog.Timer', 'goog.a11y.aria', 'goog.a11y.aria.State', 'goog.asserts', 'goog.dom.safe', 'goog.events.Event', 'goog.events.KeyCodes', 'goog.html.SafeHtml', 'goog.html.SafeStyle', 'goog.string', 'goog.string.StringBuffer', 'goog.style', 'goog.ui.Component']); +goog.addDependency("ui/tree/basenode_test.js", ['goog.ui.tree.BaseNodeTest'], ['goog.a11y.aria', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.html.testing', 'goog.testing.jsunit', 'goog.ui.Component', 'goog.ui.tree.BaseNode', 'goog.ui.tree.TreeControl', 'goog.ui.tree.TreeNode']); +goog.addDependency("ui/tree/treecontrol.js", ['goog.ui.tree.TreeControl'], ['goog.a11y.aria', 'goog.asserts', 'goog.dom.classlist', 'goog.events.EventType', 'goog.events.FocusHandler', 'goog.events.KeyHandler', 'goog.html.SafeHtml', 'goog.log', 'goog.ui.tree.BaseNode', 'goog.ui.tree.TreeNode', 'goog.ui.tree.TypeAhead', 'goog.userAgent']); +goog.addDependency("ui/tree/treecontrol_test.js", ['goog.ui.tree.TreeControlTest'], ['goog.dom', 'goog.testing.jsunit', 'goog.ui.tree.TreeControl']); +goog.addDependency("ui/tree/treenode.js", ['goog.ui.tree.TreeNode'], ['goog.ui.tree.BaseNode']); +goog.addDependency("ui/tree/typeahead.js", ['goog.ui.tree.TypeAhead', 'goog.ui.tree.TypeAhead.Offset'], ['goog.array', 'goog.events.KeyCodes', 'goog.string', 'goog.structs.Trie']); +goog.addDependency("ui/tree/typeahead_test.js", ['goog.ui.tree.TypeAheadTest'], ['goog.dom', 'goog.events.KeyCodes', 'goog.testing.jsunit', 'goog.ui.tree.TreeControl', 'goog.ui.tree.TypeAhead']); +goog.addDependency("ui/tristatemenuitem.js", ['goog.ui.TriStateMenuItem', 'goog.ui.TriStateMenuItem.State'], ['goog.dom.classlist', 'goog.ui.Component', 'goog.ui.MenuItem', 'goog.ui.TriStateMenuItemRenderer', 'goog.ui.registry']); +goog.addDependency("ui/tristatemenuitemrenderer.js", ['goog.ui.TriStateMenuItemRenderer'], ['goog.asserts', 'goog.dom.classlist', 'goog.ui.MenuItemRenderer']); +goog.addDependency("ui/twothumbslider.js", ['goog.ui.TwoThumbSlider'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.dom', 'goog.dom.TagName', 'goog.ui.SliderBase']); +goog.addDependency("ui/twothumbslider_test.js", ['goog.ui.TwoThumbSliderTest'], ['goog.testing.jsunit', 'goog.ui.SliderBase', 'goog.ui.TwoThumbSlider']); +goog.addDependency("ui/zippy.js", ['goog.ui.Zippy', 'goog.ui.Zippy.Events', 'goog.ui.ZippyEvent'], ['goog.a11y.aria', 'goog.a11y.aria.Role', 'goog.a11y.aria.State', 'goog.dom', 'goog.dom.classlist', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.events.KeyCodes', 'goog.events.KeyHandler', 'goog.style']); +goog.addDependency("ui/zippy_test.js", ['goog.ui.ZippyTest'], ['goog.a11y.aria', 'goog.dom', 'goog.dom.TagName', 'goog.dom.classlist', 'goog.events', 'goog.events.KeyCodes', 'goog.object', 'goog.testing.events', 'goog.testing.jsunit', 'goog.ui.Zippy']); +goog.addDependency("uri/uri.js", ['goog.Uri', 'goog.Uri.QueryData'], ['goog.array', 'goog.asserts', 'goog.string', 'goog.structs', 'goog.structs.Map', 'goog.uri.utils', 'goog.uri.utils.ComponentIndex', 'goog.uri.utils.StandardQueryParam']); +goog.addDependency("uri/uri_test.js", ['goog.UriTest'], ['goog.Uri', 'goog.testing.jsunit']); +goog.addDependency("uri/utils.js", ['goog.uri.utils', 'goog.uri.utils.ComponentIndex', 'goog.uri.utils.QueryArray', 'goog.uri.utils.QueryValue', 'goog.uri.utils.StandardQueryParam'], ['goog.array', 'goog.asserts', 'goog.string']); +goog.addDependency("uri/utils_test.js", ['goog.uri.utilsTest'], ['goog.functions', 'goog.string', 'goog.testing.jsunit', 'goog.uri.utils']); +goog.addDependency("useragent/adobereader.js", ['goog.userAgent.adobeReader'], ['goog.string', 'goog.userAgent']); +goog.addDependency("useragent/adobereader_test.js", ['goog.userAgent.adobeReaderTest'], ['goog.testing.jsunit', 'goog.userAgent.adobeReader']); +goog.addDependency("useragent/flash.js", ['goog.userAgent.flash'], ['goog.string']); +goog.addDependency("useragent/flash_test.js", ['goog.userAgent.flashTest'], ['goog.testing.jsunit', 'goog.userAgent.flash']); +goog.addDependency("useragent/iphoto.js", ['goog.userAgent.iphoto'], ['goog.string', 'goog.userAgent']); +goog.addDependency("useragent/jscript.js", ['goog.userAgent.jscript'], ['goog.string']); +goog.addDependency("useragent/jscript_test.js", ['goog.userAgent.jscriptTest'], ['goog.testing.jsunit', 'goog.userAgent.jscript']); +goog.addDependency("useragent/keyboard.js", ['goog.userAgent.keyboard'], ['goog.labs.userAgent.platform']); +goog.addDependency("useragent/keyboard_test.js", ['goog.userAgent.keyboardTest'], ['goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent.keyboard', 'goog.userAgentTestUtil']); +goog.addDependency("useragent/platform.js", ['goog.userAgent.platform'], ['goog.string', 'goog.userAgent']); +goog.addDependency("useragent/platform_test.js", ['goog.userAgent.platformTest'], ['goog.testing.MockUserAgent', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.platform', 'goog.userAgentTestUtil']); +goog.addDependency("useragent/product.js", ['goog.userAgent.product'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.platform', 'goog.userAgent']); +goog.addDependency("useragent/product_isversion.js", ['goog.userAgent.product.isVersion'], ['goog.labs.userAgent.platform', 'goog.string', 'goog.userAgent', 'goog.userAgent.product']); +goog.addDependency("useragent/product_test.js", ['goog.userAgent.productTest'], ['goog.array', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion', 'goog.userAgentTestUtil']); +goog.addDependency("useragent/useragent.js", ['goog.userAgent'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.engine', 'goog.labs.userAgent.platform', 'goog.labs.userAgent.util', 'goog.reflect', 'goog.string']); +goog.addDependency("useragent/useragent_quirks_test.js", ['goog.userAgentQuirksTest'], ['goog.testing.jsunit', 'goog.userAgent']); +goog.addDependency("useragent/useragent_test.js", ['goog.userAgentTest'], ['goog.array', 'goog.labs.userAgent.platform', 'goog.labs.userAgent.testAgents', 'goog.labs.userAgent.util', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']); +goog.addDependency("useragent/useragenttestutil.js", ['goog.userAgentTestUtil', 'goog.userAgentTestUtil.UserAgents'], ['goog.labs.userAgent.browser', 'goog.labs.userAgent.engine', 'goog.labs.userAgent.platform', 'goog.object', 'goog.userAgent', 'goog.userAgent.keyboard', 'goog.userAgent.platform', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']); +goog.addDependency("vec/float32array.js", ['goog.vec.Float32Array'], []); +goog.addDependency("vec/float32array_test.js", ['goog.vec.Float32ArrayTest'], ['goog.vec.Float32Array', 'goog.testing.jsunit']); +goog.addDependency("vec/float64array.js", ['goog.vec.Float64Array'], []); +goog.addDependency("vec/float64array_test.js", ['goog.vec.Float64ArrayTest'], ['goog.vec.Float64Array', 'goog.testing.jsunit']); +goog.addDependency("vec/mat3.js", ['goog.vec.Mat3'], ['goog.vec']); +goog.addDependency("vec/mat3_test.js", ['goog.vec.Mat3Test'], ['goog.vec.Mat3', 'goog.testing.jsunit']); +goog.addDependency("vec/mat3d.js", ['goog.vec.mat3d', 'goog.vec.mat3d.Type'], ['goog.vec', 'goog.vec.vec3d.Type']); +goog.addDependency("vec/mat3d_test.js", ['goog.vec.mat3dTest'], ['goog.vec.mat3d', 'goog.testing.jsunit']); +goog.addDependency("vec/mat3f.js", ['goog.vec.mat3f', 'goog.vec.mat3f.Type'], ['goog.vec', 'goog.vec.vec3f.Type']); +goog.addDependency("vec/mat3f_test.js", ['goog.vec.mat3fTest'], ['goog.vec.mat3f', 'goog.testing.jsunit']); +goog.addDependency("vec/mat4.js", ['goog.vec.Mat4'], ['goog.vec', 'goog.vec.Vec3', 'goog.vec.Vec4']); +goog.addDependency("vec/mat4_test.js", ['goog.vec.Mat4Test'], ['goog.vec.Mat4', 'goog.testing.jsunit']); +goog.addDependency("vec/mat4d.js", ['goog.vec.mat4d', 'goog.vec.mat4d.Type'], ['goog.vec', 'goog.vec.Quaternion', 'goog.vec.vec3d', 'goog.vec.vec4d']); +goog.addDependency("vec/mat4d_test.js", ['goog.vec.mat4dTest'], ['goog.vec.Quaternion', 'goog.vec.mat4d', 'goog.testing.jsunit']); +goog.addDependency("vec/mat4f.js", ['goog.vec.mat4f', 'goog.vec.mat4f.Type'], ['goog.vec', 'goog.vec.Quaternion', 'goog.vec.vec3f', 'goog.vec.vec4f']); +goog.addDependency("vec/mat4f_test.js", ['goog.vec.mat4fTest'], ['goog.vec.Quaternion', 'goog.vec.mat4f', 'goog.testing.jsunit']); +goog.addDependency("vec/quaternion.js", ['goog.vec.Quaternion', 'goog.vec.Quaternion.AnyType'], ['goog.vec', 'goog.vec.Vec3', 'goog.vec.Vec4']); +goog.addDependency("vec/quaternion_test.js", ['goog.vec.QuaternionTest'], ['goog.vec.Float32Array', 'goog.vec.Mat3', 'goog.vec.Mat4', 'goog.vec.Quaternion', 'goog.vec.Vec3', 'goog.vec.vec3f', 'goog.vec.Vec4', 'goog.testing.jsunit']); +goog.addDependency("vec/ray.js", ['goog.vec.Ray'], ['goog.vec.Vec3']); +goog.addDependency("vec/ray_test.js", ['goog.vec.RayTest'], ['goog.vec.Float32Array', 'goog.vec.Ray', 'goog.testing.jsunit']); +goog.addDependency("vec/vec.js", ['goog.vec', 'goog.vec.AnyType', 'goog.vec.ArrayType', 'goog.vec.Float32', 'goog.vec.Float64', 'goog.vec.Number'], ['goog.vec.Float32Array', 'goog.vec.Float64Array']); +goog.addDependency("vec/vec2.js", ['goog.vec.Vec2'], ['goog.vec']); +goog.addDependency("vec/vec2_test.js", ['goog.vec.Vec2Test'], ['goog.vec.Float32Array', 'goog.vec.Vec2', 'goog.testing.jsunit']); +goog.addDependency("vec/vec2d.js", ['goog.vec.vec2d', 'goog.vec.vec2d.Type'], ['goog.vec']); +goog.addDependency("vec/vec2d_test.js", ['goog.vec.vec2dTest'], ['goog.vec.Float64Array', 'goog.vec.vec2d', 'goog.testing.jsunit']); +goog.addDependency("vec/vec2f.js", ['goog.vec.vec2f', 'goog.vec.vec2f.Type'], ['goog.vec']); +goog.addDependency("vec/vec2f_test.js", ['goog.vec.vec2fTest'], ['goog.vec.Float32Array', 'goog.vec.vec2f', 'goog.testing.jsunit']); +goog.addDependency("vec/vec3.js", ['goog.vec.Vec3'], ['goog.vec']); +goog.addDependency("vec/vec3_test.js", ['goog.vec.Vec3Test'], ['goog.vec.Float32Array', 'goog.vec.Vec3', 'goog.testing.jsunit']); +goog.addDependency("vec/vec3d.js", ['goog.vec.vec3d', 'goog.vec.vec3d.Type'], ['goog.vec']); +goog.addDependency("vec/vec3d_test.js", ['goog.vec.vec3dTest'], ['goog.vec.Float64Array', 'goog.vec.vec3d', 'goog.testing.jsunit']); +goog.addDependency("vec/vec3f.js", ['goog.vec.vec3f', 'goog.vec.vec3f.Type'], ['goog.vec']); +goog.addDependency("vec/vec3f_test.js", ['goog.vec.vec3fTest'], ['goog.vec.Float32Array', 'goog.vec.vec3f', 'goog.testing.jsunit']); +goog.addDependency("vec/vec4.js", ['goog.vec.Vec4'], ['goog.vec']); +goog.addDependency("vec/vec4_test.js", ['goog.vec.Vec4Test'], ['goog.vec.Float32Array', 'goog.vec.Vec4', 'goog.testing.jsunit']); +goog.addDependency("vec/vec4d.js", ['goog.vec.vec4d', 'goog.vec.vec4d.Type'], ['goog.vec']); +goog.addDependency("vec/vec4d_test.js", ['goog.vec.vec4dTest'], ['goog.vec.Float64Array', 'goog.vec.vec4d', 'goog.testing.jsunit']); +goog.addDependency("vec/vec4f.js", ['goog.vec.vec4f', 'goog.vec.vec4f.Type'], ['goog.vec']); +goog.addDependency("vec/vec4f_test.js", ['goog.vec.vec4fTest'], ['goog.vec.Float32Array', 'goog.vec.vec4f', 'goog.testing.jsunit']); +goog.addDependency("webgl/webgl.js", ['goog.webgl'], []); +goog.addDependency("window/window.js", ['goog.window'], ['goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeUrl', 'goog.html.uncheckedconversions', 'goog.labs.userAgent.platform', 'goog.string', 'goog.string.Const', 'goog.userAgent']); +goog.addDependency("window/window_test.js", ['goog.windowTest'], ['goog.Promise', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.functions', 'goog.html.SafeUrl', 'goog.labs.userAgent.browser', 'goog.labs.userAgent.engine', 'goog.labs.userAgent.platform', 'goog.string', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.jsunit', 'goog.window']); + +// Load Blockly. +goog.require('Blockly'); +goog.require('Blockly.Block'); +goog.require('Blockly.BlockDragSurfaceSvg'); +goog.require('Blockly.BlockDragger'); +goog.require('Blockly.BlockSvg'); +goog.require('Blockly.BlockSvg.render'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.Bubble'); +goog.require('Blockly.BubbleDragger'); +goog.require('Blockly.C_Blockly'); +goog.require('Blockly.Comment'); +goog.require('Blockly.Connection'); +goog.require('Blockly.ConnectionDB'); +goog.require('Blockly.ContextMenu'); +goog.require('Blockly.Css'); +goog.require('Blockly.DraggedConnectionManager'); +goog.require('Blockly.Events'); +goog.require('Blockly.Extensions'); +goog.require('Blockly.Field'); +goog.require('Blockly.FieldAngle'); +goog.require('Blockly.FieldCheckbox'); +goog.require('Blockly.FieldColour'); +goog.require('Blockly.FieldDate'); +goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.FieldImage'); +goog.require('Blockly.FieldLabel'); +goog.require('Blockly.FieldMacro'); +goog.require('Blockly.FieldNumber'); +goog.require('Blockly.FieldTextInput'); +goog.require('Blockly.FieldVariable'); +goog.require('Blockly.Flyout'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.FlyoutDragger'); +goog.require('Blockly.Generator'); +goog.require('Blockly.Gesture'); +goog.require('Blockly.Grid'); +goog.require('Blockly.HorizontalFlyout'); +goog.require('Blockly.Icon'); +goog.require('Blockly.Input'); +goog.require('Blockly.Macro'); +goog.require('Blockly.Msg'); +goog.require('Blockly.Mutator'); +goog.require('Blockly.Names'); +goog.require('Blockly.Options'); +goog.require('Blockly.Procedures'); +goog.require('Blockly.RenderedConnection'); +goog.require('Blockly.Scrollbar'); +goog.require('Blockly.ScrollbarPair'); +goog.require('Blockly.StaticTyping'); +goog.require('Blockly.Toolbox'); +goog.require('Blockly.Tooltip'); +goog.require('Blockly.Touch'); +goog.require('Blockly.TouchGesture'); +goog.require('Blockly.Trashcan'); +goog.require('Blockly.Type'); +goog.require('Blockly.Types'); +goog.require('Blockly.VariableMap'); +goog.require('Blockly.VariableModel'); +goog.require('Blockly.Variables'); +goog.require('Blockly.VariablesDynamic'); +goog.require('Blockly.VerticalFlyout'); +goog.require('Blockly.Warning'); +goog.require('Blockly.WidgetDiv'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.WorkspaceAudio'); +goog.require('Blockly.WorkspaceDragSurfaceSvg'); +goog.require('Blockly.WorkspaceDragger'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('Blockly.Xml'); +goog.require('Blockly.ZoomControls'); +goog.require('Blockly.constants'); +goog.require('Blockly.inject'); +goog.require('Blockly.utils'); +goog.require('Blockly.utils.uiMenu'); + +delete root.BLOCKLY_DIR; +delete root.BLOCKLY_BOOT; +delete root.IS_NODE_JS; +}; +if (this.IS_NODE_JS) { + this.BLOCKLY_BOOT(this); + module.exports = Blockly; +} else { + // Delete any existing Closure (e.g. Soy's nogoog_shim). + document.write(''); + // Load fresh Closure Library. + document.write(''); + document.write(''); +} diff --git a/blocks/colour.js b/blocks/colour.js index 0d89dd5..c5beb6c 100644 --- a/blocks/colour.js +++ b/blocks/colour.js @@ -20,115 +20,118 @@ /** * @fileoverview Colour blocks for Blockly. + * + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author fraser@google.com (Neil Fraser) */ 'use strict'; -goog.provide('Blockly.Blocks.colour'); +goog.provide('Blockly.Blocks.colour'); // Deprecated +goog.provide('Blockly.Constants.Colour'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * This should be the same as Blockly.Msg.COLOUR_HUE. + * @readonly */ -Blockly.Blocks.colour.HUE = 20; +Blockly.Constants.Colour.HUE = 20; +/** @deprecated Use Blockly.Constants.Colour.HUE */ +Blockly.Blocks.colour.HUE = Blockly.Constants.Colour.HUE; -Blockly.Blocks['colour_picker'] = { - /** - * Block for colour picker. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1", - "args0": [ - { - "type": "field_colour", - "name": "COLOUR", - "colour": "#ff0000" - } - ], - "output": "Colour", - "colour": Blockly.Blocks.colour.HUE, - "helpUrl": Blockly.Msg.COLOUR_PICKER_HELPURL - }); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - // Colour block is trivial. Use tooltip of parent block if it exists. - this.setTooltip(function() { - var parent = thisBlock.getParent(); - return (parent && parent.getInputsInline() && parent.tooltip) || - Blockly.Msg.COLOUR_PICKER_TOOLTIP; - }); - } -}; +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for colour picker. + { + "type": "colour_picker", + "message0": "%1", + "args0": [ + { + "type": "field_colour", + "name": "COLOUR", + "colour": "#ff0000" + } + ], + "output": "Colour", + "colour": "%{BKY_COLOUR_HUE}", + "helpUrl": "%{BKY_COLOUR_PICKER_HELPURL}", + "tooltip": "%{BKY_COLOUR_PICKER_TOOLTIP}", + "extensions": ["parent_tooltip_when_inline"] + }, -Blockly.Blocks['colour_random'] = { - /** - * Block for random colour. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.COLOUR_RANDOM_TITLE, - "output": "Colour", - "colour": Blockly.Blocks.colour.HUE, - "tooltip": Blockly.Msg.COLOUR_RANDOM_TOOLTIP, - "helpUrl": Blockly.Msg.COLOUR_RANDOM_HELPURL - }); - } -}; + // Block for random colour. + { + "type": "colour_random", + "message0": "%{BKY_COLOUR_RANDOM_TITLE}", + "output": "Colour", + "colour": "%{BKY_COLOUR_HUE}", + "helpUrl": "%{BKY_COLOUR_RANDOM_HELPURL}", + "tooltip": "%{BKY_COLOUR_RANDOM_TOOLTIP}" + }, -Blockly.Blocks['colour_rgb'] = { - /** - * Block for composing a colour from RGB components. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.COLOUR_RGB_HELPURL); - this.setColour(Blockly.Blocks.colour.HUE); - this.appendValueInput('RED') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_RGB_TITLE) - .appendField(Blockly.Msg.COLOUR_RGB_RED); - this.appendValueInput('GREEN') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_RGB_GREEN); - this.appendValueInput('BLUE') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_RGB_BLUE); - this.setOutput(true, 'Colour'); - this.setTooltip(Blockly.Msg.COLOUR_RGB_TOOLTIP); - } -}; + // Block for composing a colour from RGB components. + { + "type": "colour_rgb", + "message0": "%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3", + "args0": [ + { + "type": "input_value", + "name": "RED", + "check": "Number", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "GREEN", + "check": "Number", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "BLUE", + "check": "Number", + "align": "RIGHT" + } + ], + "output": "Colour", + "colour": "%{BKY_COLOUR_HUE}", + "helpUrl": "%{BKY_COLOUR_RGB_HELPURL}", + "tooltip": "%{BKY_COLOUR_RGB_TOOLTIP}" + }, -Blockly.Blocks['colour_blend'] = { - /** - * Block for blending two colours together. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.COLOUR_BLEND_HELPURL); - this.setColour(Blockly.Blocks.colour.HUE); - this.appendValueInput('COLOUR1') - .setCheck('Colour') - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_BLEND_TITLE) - .appendField(Blockly.Msg.COLOUR_BLEND_COLOUR1); - this.appendValueInput('COLOUR2') - .setCheck('Colour') - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_BLEND_COLOUR2); - this.appendValueInput('RATIO') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.COLOUR_BLEND_RATIO); - this.setOutput(true, 'Colour'); - this.setTooltip(Blockly.Msg.COLOUR_BLEND_TOOLTIP); + // Block for blending two colours together. + { + "type": "colour_blend", + "message0": "%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} " + + "%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3", + "args0": [ + { + "type": "input_value", + "name": "COLOUR1", + "check": "Colour", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "COLOUR2", + "check": "Colour", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "RATIO", + "check": "Number", + "align": "RIGHT" + } + ], + "output": "Colour", + "colour": "%{BKY_COLOUR_HUE}", + "helpUrl": "%{BKY_COLOUR_BLEND_HELPURL}", + "tooltip": "%{BKY_COLOUR_BLEND_TOOLTIP}" } -}; +]); // END JSON EXTRACT (Do not delete this comment.) diff --git a/blocks/lists.js b/blocks/lists.js index 6d6e693..1700cb5 100644 --- a/blocks/lists.js +++ b/blocks/lists.js @@ -20,40 +20,116 @@ /** * @fileoverview List blocks for Blockly. + * + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author fraser@google.com (Neil Fraser) */ 'use strict'; -goog.provide('Blockly.Blocks.lists'); +goog.provide('Blockly.Blocks.lists'); // Deprecated +goog.provide('Blockly.Constants.Lists'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * This should be the same as Blockly.Msg.LISTS_HUE. + * @readonly */ -Blockly.Blocks.lists.HUE = 260; +Blockly.Constants.Lists.HUE = 260; +/** @deprecated Use Blockly.Constants.Lists.HUE */ +Blockly.Blocks.lists.HUE = Blockly.Constants.Lists.HUE; -Blockly.Blocks['lists_create_empty'] = { - /** - * Block for creating an empty list. - * The 'list_create_with' block is preferred as it is more flexible. - * - * - * - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LISTS_CREATE_EMPTY_TITLE, - "output": "Array", - "colour": Blockly.Blocks.lists.HUE, - "tooltip": Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP, - "helpUrl": Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL - }); + +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for creating an empty list + // The 'list_create_with' block is preferred as it is more flexible. + // + // + // + { + "type": "lists_create_empty", + "message0": "%{BKY_LISTS_CREATE_EMPTY_TITLE}", + "output": "Array", + "colour": "%{BKY_LISTS_HUE}", + "tooltip": "%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}", + "helpUrl": "%{BKY_LISTS_CREATE_EMPTY_HELPURL}" + }, + // Block for creating a list with one element repeated. + { + "type": "lists_repeat", + "message0": "%{BKY_LISTS_REPEAT_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "ITEM" + }, + { + "type": "input_value", + "name": "NUM", + "check": "Number" + } + ], + "output": "Array", + "colour": "%{BKY_LISTS_HUE}", + "tooltip": "%{BKY_LISTS_REPEAT_TOOLTIP}", + "helpUrl": "%{BKY_LISTS_REPEAT_HELPURL}" + }, + // Block for reversing a list. + { + "type": "lists_reverse", + "message0": "%{BKY_LISTS_REVERSE_MESSAGE0}", + "args0": [ + { + "type": "input_value", + "name": "LIST", + "check": "Array" + } + ], + "output": "Array", + "inputsInline": true, + "colour": "%{BKY_LISTS_HUE}", + "tooltip": "%{BKY_LISTS_REVERSE_TOOLTIP}", + "helpUrl": "%{BKY_LISTS_REVERSE_HELPURL}" + }, + // Block for checking if a list is empty + { + "type": "lists_isEmpty", + "message0": "%{BKY_LISTS_ISEMPTY_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ["String", "Array"] + } + ], + "output": "Boolean", + "colour": "%{BKY_LISTS_HUE}", + "tooltip": "%{BKY_LISTS_ISEMPTY_TOOLTIP}", + "helpUrl": "%{BKY_LISTS_ISEMPTY_HELPURL}" + }, + // Block for getting the list length + { + "type": "lists_length", + "message0": "%{BKY_LISTS_LENGTH_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ["String", "Array"] + } + ], + "output": "Number", + "colour": "%{BKY_LISTS_HUE}", + "tooltip": "%{BKY_LISTS_LENGTH_TOOLTIP}", + "helpUrl": "%{BKY_LISTS_LENGTH_HELPURL}" } -}; +]); // END JSON EXTRACT (Do not delete this comment.) Blockly.Blocks['lists_create_with'] = { /** @@ -65,34 +141,10 @@ Blockly.Blocks['lists_create_with'] = { this.setColour(Blockly.Blocks.lists.HUE); this.itemCount_ = 3; this.updateShape_(); - this.setPreviousStatement(true, null); - this.setNextStatement(true, null); + this.setOutput(true, 'Array'); this.setMutator(new Blockly.Mutator(['lists_create_with_item'])); this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP); }, - getVarType: function(varName) { - /** Array type. */ - return new Blockly.Type({ - typeId: 'Array', - typeMsgName: 'ARD_TYPE_CHAR', - typeAtom: Blockly.Types.getChildBlockType(this), - typeContent: this.getContent(), - typeLength: this.itemCount_, - compatibleTypes: [] - }); - }, - getContent: function(){ - var elements = new Array(this.itemCount_); - var variable_list_name = Blockly.mbed.variableDB_.getName(this.getFieldValue('list_name'), Blockly.Variables.NAME_TYPE); - // initialize an array with identifier as variable_list_name, with type as ___, with length as block.itemCount_ - - for (var i = 0; i < this.itemCount_; i++) { - elements[i] = Blockly.mbed.valueToCode(this, 'ADD' + i, - Blockly.mbed.ORDER_COMMA) || 'null'; - } - var content = '{' + elements.join(',') + '}'; - return content; - }, /** * Create XML to represent list inputs. * @return {!Element} XML storage element. @@ -193,10 +245,6 @@ Blockly.Blocks['lists_create_with'] = { if (i == 0) { input.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH); } - if (i == 1) { - input.appendField('ListName:') - .appendField(new Blockly.FieldVariable("item"), "list_name"); - } } } // Remove deleted inputs. @@ -224,7 +272,7 @@ Blockly.Blocks['lists_create_with_container'] = { Blockly.Blocks['lists_create_with_item'] = { /** - * Mutator bolck for adding items. + * Mutator block for adding items. * @this Blockly.Block */ init: function() { @@ -238,79 +286,6 @@ Blockly.Blocks['lists_create_with_item'] = { } }; -Blockly.Blocks['lists_repeat'] = { - /** - * Block for creating a list with one element repeated. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LISTS_REPEAT_TITLE, - "args0": [ - { - "type": "input_value", - "name": "ITEM" - }, - { - "type": "input_value", - "name": "NUM", - "check": Blockly.Types.NUMBER.checkList - } - ], - "output": "Array", - "colour": Blockly.Blocks.lists.HUE, - "tooltip": Blockly.Msg.LISTS_REPEAT_TOOLTIP, - "helpUrl": Blockly.Msg.LISTS_REPEAT_HELPURL - }); - } -}; - -Blockly.Blocks['lists_length'] = { - /** - * Block for list length. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LISTS_LENGTH_TITLE, - "args0": [ - { - "type": "input_value", - "name": "VALUE", - "check": Blockly.Types.TEXT.checkList.concat('Array') - } - ], - "output": Blockly.Types.NUMBER.output, - "colour": Blockly.Blocks.lists.HUE, - "tooltip": Blockly.Msg.LISTS_LENGTH_TOOLTIP, - "helpUrl": Blockly.Msg.LISTS_LENGTH_HELPURL - }); - } -}; - -Blockly.Blocks['lists_isEmpty'] = { - /** - * Block for is the list empty? - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LISTS_ISEMPTY_TITLE, - "args0": [ - { - "type": "input_value", - "name": "VALUE", - "check": Blockly.Types.TEXT.checkList.concat('Array') - } - ], - "output": Blockly.Types.BOOLEAN.output, - "colour": Blockly.Blocks.lists.HUE, - "tooltip": Blockly.Msg.LISTS_ISEMPTY_TOOLTIP, - "helpUrl": Blockly.Msg.LISTS_ISEMPTY_HELPURL - }); - } -}; - Blockly.Blocks['lists_indexOf'] = { /** * Block for finding an item in the list. @@ -318,18 +293,25 @@ Blockly.Blocks['lists_indexOf'] = { */ init: function() { var OPERATORS = - [[Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST']]; + [ + [Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_INDEX_OF_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); - this.setOutput(true, Blockly.Types.NUMBER.output); + this.setOutput(true, 'Number'); this.appendValueInput('VALUE') .setCheck('Array') .appendField(Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST); this.appendValueInput('FIND') .appendField(new Blockly.FieldDropdown(OPERATORS), 'END'); this.setInputsInline(true); - this.setTooltip(Blockly.Msg.LISTS_INDEX_OF_TOOLTIP); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + return Blockly.Msg.LISTS_INDEX_OF_TOOLTIP.replace('%1', + thisBlock.workspace.options.oneBasedIndex ? '0' : '-1'); + }); } }; @@ -340,11 +322,19 @@ Blockly.Blocks['lists_getIndex'] = { */ init: function() { var MODE = - [[Blockly.Msg.LISTS_GET_INDEX_GET, 'GET']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_GET, 'GET'], + [Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE, 'GET_REMOVE'], + [Blockly.Msg.LISTS_GET_INDEX_REMOVE, 'REMOVE'] + ]; this.WHERE_OPTIONS = - [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); var modeMenu = new Blockly.FieldDropdown(MODE, function(value) { @@ -368,9 +358,58 @@ Blockly.Blocks['lists_getIndex'] = { // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function() { - var combo = thisBlock.getFieldValue('MODE') + '_' + - thisBlock.getFieldValue('WHERE'); - return Blockly.Msg['LISTS_GET_INDEX_TOOLTIP_' + combo]; + var mode = thisBlock.getFieldValue('MODE'); + var where = thisBlock.getFieldValue('WHERE'); + var tooltip = ''; + switch (mode + ' ' + where) { + case 'GET FROM_START': + case 'GET FROM_END': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM; + break; + case 'GET FIRST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST; + break; + case 'GET LAST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST; + break; + case 'GET RANDOM': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM; + break; + case 'GET_REMOVE FROM_START': + case 'GET_REMOVE FROM_END': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM; + break; + case 'GET_REMOVE FIRST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST; + break; + case 'GET_REMOVE LAST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST; + break; + case 'GET_REMOVE RANDOM': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM; + break; + case 'REMOVE FROM_START': + case 'REMOVE FROM_END': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM; + break; + case 'REMOVE FIRST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST; + break; + case 'REMOVE LAST': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST; + break; + case 'REMOVE RANDOM': + tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM; + break; + } + if (where == 'FROM_START' || where == 'FROM_END') { + var msg = (where == 'FROM_START') ? + Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP : + Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP; + tooltip += ' ' + msg.replace('%1', + thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + } + return tooltip; }); }, /** @@ -434,7 +473,7 @@ Blockly.Blocks['lists_getIndex'] = { this.removeInput('ORDINAL', true); // Create either a value 'AT' input or a dummy input. if (isAt) { - this.appendValueInput('AT').setCheck(Blockly.Types.NUMBER.checkList); + this.appendValueInput('AT').setCheck('Number'); if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { this.appendDummyInput('ORDINAL') .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); @@ -468,11 +507,18 @@ Blockly.Blocks['lists_setIndex'] = { */ init: function() { var MODE = - [[Blockly.Msg.LISTS_SET_INDEX_SET, 'SET']]; + [ + [Blockly.Msg.LISTS_SET_INDEX_SET, 'SET'], + [Blockly.Msg.LISTS_SET_INDEX_INSERT, 'INSERT'] + ]; this.WHERE_OPTIONS = - [[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], - [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST']]; + [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'], + [Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); this.appendValueInput('LIST') @@ -492,9 +538,43 @@ Blockly.Blocks['lists_setIndex'] = { // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function() { - var combo = thisBlock.getFieldValue('MODE') + '_' + - thisBlock.getFieldValue('WHERE'); - return Blockly.Msg['LISTS_SET_INDEX_TOOLTIP_' + combo]; + var mode = thisBlock.getFieldValue('MODE'); + var where = thisBlock.getFieldValue('WHERE'); + var tooltip = ''; + switch (mode + ' ' + where) { + case 'SET FROM_START': + case 'SET FROM_END': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM; + break; + case 'SET FIRST': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST; + break; + case 'SET LAST': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST; + break; + case 'SET RANDOM': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM; + break; + case 'INSERT FROM_START': + case 'INSERT FROM_END': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM; + break; + case 'INSERT FIRST': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST; + break; + case 'INSERT LAST': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST; + break; + case 'INSERT RANDOM': + tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM; + break; + } + if (where == 'FROM_START' || where == 'FROM_END') { + tooltip += ' ' + Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP + .replace('%1', + thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + } + return tooltip; }); }, /** @@ -531,7 +611,7 @@ Blockly.Blocks['lists_setIndex'] = { this.removeInput('ORDINAL', true); // Create either a value 'AT' input or a dummy input. if (isAt) { - this.appendValueInput('AT').setCheck(Blockly.Types.NUMBER.checkList); + this.appendValueInput('AT').setCheck('Number'); if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { this.appendDummyInput('ORDINAL') .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); @@ -567,13 +647,17 @@ Blockly.Blocks['lists_getSublist'] = { */ init: function() { this['WHERE_OPTIONS_1'] = - [[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END, 'FROM_END'], - [Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST, 'FIRST']]; + [ + [Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST, 'FIRST'] + ]; this['WHERE_OPTIONS_2'] = - [[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START, 'FROM_START'], - [Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END, 'FROM_END'], - [Blockly.Msg.LISTS_GET_SUBLIST_END_LAST, 'LAST']]; + [ + [Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START, 'FROM_START'], + [Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END, 'FROM_END'], + [Blockly.Msg.LISTS_GET_SUBLIST_END_LAST, 'LAST'] + ]; this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); this.appendValueInput('LIST') @@ -630,7 +714,7 @@ Blockly.Blocks['lists_getSublist'] = { this.removeInput('ORDINAL' + n, true); // Create either a value 'AT' input or a dummy input. if (isAt) { - this.appendValueInput('AT' + n).setCheck(Blockly.Types.NUMBER.checkList); + this.appendValueInput('AT' + n).setCheck('Number'); if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { this.appendDummyInput('ORDINAL' + n) .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); @@ -640,17 +724,19 @@ Blockly.Blocks['lists_getSublist'] = { } var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n], function(value) { - var newAt = (value == 'FROM_START') || (value == 'FROM_END'); - // The 'isAt' variable is available due to this function being a closure. - if (newAt != isAt) { - var block = this.sourceBlock_; - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - return undefined; - }); + var newAt = (value == 'FROM_START') || (value == 'FROM_END'); + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt != isAt) { + var block = this.sourceBlock_; + block.updateAt_(n, newAt); + // This menu has been destroyed and replaced. + // Update the replacement. + block.setFieldValue(value, 'WHERE' + n); + return null; + } + return undefined; + }); this.getInput('AT' + n) .appendField(menu, 'WHERE' + n); if (n == 1) { @@ -714,18 +800,20 @@ Blockly.Blocks['lists_split'] = { // Assign 'this' to a variable for use in the closures below. var thisBlock = this; var dropdown = new Blockly.FieldDropdown( - [[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'], - [Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN']], + [ + [Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'], + [Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN'] + ], function(newMode) { thisBlock.updateType_(newMode); }); this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL); this.setColour(Blockly.Blocks.lists.HUE); this.appendValueInput('INPUT') - .setCheck(Blockly.Types.TEXT.checkList) + .setCheck('String') .appendField(dropdown, 'MODE'); this.appendValueInput('DELIM') - .setCheck(Blockly.Types.TEXT.checkList) + .setCheck('String') .appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER); this.setInputsInline(true); this.setOutput(true, 'Array'); @@ -748,9 +836,9 @@ Blockly.Blocks['lists_split'] = { updateType_: function(newMode) { if (newMode == 'SPLIT') { this.outputConnection.setCheck('Array'); - this.getInput('INPUT').setCheck(Blockly.Types.TEXT.checkList); + this.getInput('INPUT').setCheck('String'); } else { - this.outputConnection.setCheck(Blockly.Types.TEXT.checkList); + this.outputConnection.setCheck('String'); this.getInput('INPUT').setCheck('Array'); } }, diff --git a/blocks/logic.js b/blocks/logic.js index 32c9ecc..a37387d 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -20,55 +20,295 @@ /** * @fileoverview Logic blocks for Blockly. + * + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author q.neutron@gmail.com (Quynh Neutron) */ 'use strict'; -goog.provide('Blockly.Blocks.logic'); +goog.provide('Blockly.Blocks.logic'); // Deprecated +goog.provide('Blockly.Constants.Logic'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.LOGIC_HUE. + * @readonly */ -Blockly.Blocks.logic.HUE = 210; +Blockly.Constants.Logic.HUE = 210; +/** @deprecated Use Blockly.Constants.Logic.HUE */ +Blockly.Blocks.logic.HUE = Blockly.Constants.Logic.HUE; -Blockly.Blocks['controls_if'] = { - /** - * Block for if/elseif/else condition. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL); - this.setColour(Blockly.Blocks.logic.HUE); - this.appendValueInput('IF0') - .setCheck(Blockly.Types.BOOLEAN.checkList) - .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); - this.appendStatementInput('DO0') - .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN); - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setMutator(new Blockly.Mutator(['controls_if_elseif', - 'controls_if_else'])); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) { - return Blockly.Msg.CONTROLS_IF_TOOLTIP_1; - } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) { - return Blockly.Msg.CONTROLS_IF_TOOLTIP_2; - } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) { - return Blockly.Msg.CONTROLS_IF_TOOLTIP_3; - } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) { - return Blockly.Msg.CONTROLS_IF_TOOLTIP_4; +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for boolean data type: true and false. + { + "type": "logic_boolean", + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "BOOL", + "options": [ + ["%{BKY_LOGIC_BOOLEAN_TRUE}", "TRUE"], + ["%{BKY_LOGIC_BOOLEAN_FALSE}", "FALSE"] + ] } - return ''; - }); - this.elseifCount_ = 0; - this.elseCount_ = 0; + ], + "output": "Boolean", + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_LOGIC_BOOLEAN_TOOLTIP}", + "helpUrl": "%{BKY_LOGIC_BOOLEAN_HELPURL}" + }, + // Block for if/elseif/else condition. + { + "type": "controls_if", + "message0": "%{BKY_CONTROLS_IF_MSG_IF} %1", + "args0": [ + { + "type": "input_value", + "name": "IF0", + "check": "Boolean" + } + ], + "message1": "%{BKY_CONTROLS_IF_MSG_THEN} %1", + "args1": [ + { + "type": "input_statement", + "name": "DO0" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOGIC_HUE}", + "helpUrl": "%{BKY_CONTROLS_IF_HELPURL}", + "mutator": "controls_if_mutator", + "extensions": ["controls_if_tooltip"] + }, + // If/else block that does not use a mutator. + { + "type": "controls_ifelse", + "message0": "%{BKY_CONTROLS_IF_MSG_IF} %1", + "args0": [ + { + "type": "input_value", + "name": "IF0", + "check": "Boolean" + } + ], + "message1": "%{BKY_CONTROLS_IF_MSG_THEN} %1", + "args1": [ + { + "type": "input_statement", + "name": "DO0" + } + ], + "message2": "%{BKY_CONTROLS_IF_MSG_ELSE} %1", + "args2": [ + { + "type": "input_statement", + "name": "ELSE" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKYCONTROLS_IF_TOOLTIP_2}", + "helpUrl": "%{BKY_CONTROLS_IF_HELPURL}", + "extensions": ["controls_if_tooltip"] + }, + // Block for comparison operator. + { + "type": "logic_compare", + "message0": "%1 %2 %3", + "args0": [ + { + "type": "input_value", + "name": "A" + }, + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["=", "EQ"], + ["\u2260", "NEQ"], + ["<", "LT"], + ["\u2264", "LTE"], + [">", "GT"], + ["\u2265", "GTE"] + ] + }, + { + "type": "input_value", + "name": "B" + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": "%{BKY_LOGIC_HUE}", + "helpUrl": "%{BKY_LOGIC_COMPARE_HELPURL}", + "extensions": ["logic_compare", "logic_op_tooltip"] + }, + // Block for logical operations: 'and', 'or'. + { + "type": "logic_operation", + "message0": "%1 %2 %3", + "args0": [ + { + "type": "input_value", + "name": "A", + "check": "Boolean" + }, + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_LOGIC_OPERATION_AND}", "AND"], + ["%{BKY_LOGIC_OPERATION_OR}", "OR"] + ] + }, + { + "type": "input_value", + "name": "B", + "check": "Boolean" + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": "%{BKY_LOGIC_HUE}", + "helpUrl": "%{BKY_LOGIC_OPERATION_HELPURL}", + "extensions": ["logic_op_tooltip"] + }, + // Block for negation. + { + "type": "logic_negate", + "message0": "%{BKY_LOGIC_NEGATE_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "BOOL", + "check": "Boolean" + } + ], + "output": "Boolean", + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_LOGIC_NEGATE_TOOLTIP}", + "helpUrl": "%{BKY_LOGIC_NEGATE_HELPURL}" + }, + // Block for null data type. + { + "type": "logic_null", + "message0": "%{BKY_LOGIC_NULL}", + "output": null, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_LOGIC_NULL_TOOLTIP}", + "helpUrl": "%{BKY_LOGIC_NULL_HELPURL}" + }, + // Block for ternary operator. + { + "type": "logic_ternary", + "message0": "%{BKY_LOGIC_TERNARY_CONDITION} %1", + "args0": [ + { + "type": "input_value", + "name": "IF", + "check": "Boolean" + } + ], + "message1": "%{BKY_LOGIC_TERNARY_IF_TRUE} %1", + "args1": [ + { + "type": "input_value", + "name": "THEN" + } + ], + "message2": "%{BKY_LOGIC_TERNARY_IF_FALSE} %1", + "args2": [ + { + "type": "input_value", + "name": "ELSE" + } + ], + "output": null, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_LOGIC_TERNARY_TOOLTIP}", + "helpUrl": "%{BKY_LOGIC_TERNARY_HELPURL}", + "extensions": ["logic_ternary"] + } +]); // END JSON EXTRACT (Do not delete this comment.) + +Blockly.defineBlocksWithJsonArray([ // Mutator blocks. Do not extract. + // Block representing the if statement in the controls_if mutator. + { + "type": "controls_if_if", + "message0": "%{BKY_CONTROLS_IF_IF_TITLE_IF}", + "nextStatement": null, + "enableContextMenu": false, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_CONTROLS_IF_IF_TOOLTIP}" + }, + // Block representing the else-if statement in the controls_if mutator. + { + "type": "controls_if_elseif", + "message0": "%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}", + "previousStatement": null, + "nextStatement": null, + "enableContextMenu": false, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}" }, + // Block representing the else statement in the controls_if mutator. + { + "type": "controls_if_else", + "message0": "%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}", + "previousStatement": null, + "enableContextMenu": false, + "colour": "%{BKY_LOGIC_HUE}", + "tooltip": "%{BKY_CONTROLS_IF_ELSE_TOOLTIP}" + } +]); + +/** + * Tooltip text, keyed by block OP value. Used by logic_compare and + * logic_operation blocks. + * @see {Blockly.Extensions#buildTooltipForDropdown} + * @package + * @readonly + */ +Blockly.Constants.Logic.TOOLTIPS_BY_OP = { + // logic_compare + 'EQ': '%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}', + 'NEQ': '%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}', + 'LT': '%{BKY_LOGIC_COMPARE_TOOLTIP_LT}', + 'LTE': '%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}', + 'GT': '%{BKY_LOGIC_COMPARE_TOOLTIP_GT}', + 'GTE': '%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}', + + // logic_operation + 'AND': '%{BKY_LOGIC_OPERATION_TOOLTIP_AND}', + 'OR': '%{BKY_LOGIC_OPERATION_TOOLTIP_OR}' +}; + +Blockly.Extensions.register('logic_op_tooltip', + Blockly.Extensions.buildTooltipForDropdown( + 'OP', Blockly.Constants.Logic.TOOLTIPS_BY_OP)); + +/** + * Mutator methods added to controls_if blocks. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN = { + elseifCount_: 0, + elseCount_: 0, + /** * Create XML to represent the number of else-if and else inputs. * @return {Element} XML storage element. @@ -191,8 +431,8 @@ Blockly.Blocks['controls_if'] = { }, /** * Modify this block to have the correct number of inputs. - * @private * @this Blockly.Block + * @private */ updateShape_: function() { // Delete everything. @@ -220,96 +460,72 @@ Blockly.Blocks['controls_if'] = { } }; -Blockly.Blocks['controls_if_if'] = { - /** - * Mutator block for if container. - * @this Blockly.Block - */ - init: function() { - this.setColour(Blockly.Blocks.logic.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF); - this.setNextStatement(true); - this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP); - this.contextMenu = false; - } -}; +Blockly.Extensions.registerMutator('controls_if_mutator', + Blockly.Constants.Logic.CONTROLS_IF_MUTATOR_MIXIN, null, + ['controls_if_elseif', 'controls_if_else']); +/** + * "controls_if" extension function. Adds mutator, shape updating methods, and + * dynamic tooltip to "controls_if" blocks. + * @this Blockly.Block + * @package + */ +Blockly.Constants.Logic.CONTROLS_IF_TOOLTIP_EXTENSION = function() { -Blockly.Blocks['controls_if_elseif'] = { - /** - * Mutator bolck for else-if condition. - * @this Blockly.Block - */ - init: function() { - this.setColour(Blockly.Blocks.logic.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF); - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP); - this.contextMenu = false; - } + this.setTooltip(function() { + if (!this.elseifCount_ && !this.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_1; + } else if (!this.elseifCount_ && this.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_2; + } else if (this.elseifCount_ && !this.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_3; + } else if (this.elseifCount_ && this.elseCount_) { + return Blockly.Msg.CONTROLS_IF_TOOLTIP_4; + } + return ''; + }.bind(this)); }; -Blockly.Blocks['controls_if_else'] = { - /** - * Mutator block for else condition. - * @this Blockly.Block - */ - init: function() { - this.setColour(Blockly.Blocks.logic.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE); - this.setPreviousStatement(true); - this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP); - this.contextMenu = false; - } -}; +Blockly.Extensions.register('controls_if_tooltip', + Blockly.Constants.Logic.CONTROLS_IF_TOOLTIP_EXTENSION); + +/** + * Corrects the logic_compare dropdown label with respect to language direction. + * @this Blockly.Block + * @package + */ +Blockly.Constants.Logic.fixLogicCompareRtlOpLabels = + function() { + var rtlOpLabels = { + 'LT': '\u200F<\u200F', + 'LTE': '\u200F\u2264\u200F', + 'GT': '\u200F>\u200F', + 'GTE': '\u200F\u2265\u200F' + }; + var opDropdown = this.getField('OP'); + if (opDropdown) { + var options = opDropdown.getOptions(); + for (var i = 0; i < options.length; ++i) { + var tuple = options[i]; + var op = tuple[1]; + var rtlLabel = rtlOpLabels[op]; + if (goog.isString(tuple[0]) && rtlLabel) { + // Replace LTR text label + tuple[0] = rtlLabel; + } + } + } + }; + +/** + * Adds dynamic type validation for the left and right sides of a logic_compare block. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN = { + prevBlocks_: [null, null], -Blockly.Blocks['logic_compare'] = { - /** - * Block for comparison operator. - * @this Blockly.Block - */ - init: function() { - var OPERATORS = this.RTL ? [ - ['=', 'EQ'], - ['\u2260', 'NEQ'], - ['>', 'LT'], - ['\u2265', 'LTE'], - ['<', 'GT'], - ['\u2264', 'GTE'] - ] : [ - ['=', 'EQ'], - ['\u2260', 'NEQ'], - ['<', 'LT'], - ['\u2264', 'LTE'], - ['>', 'GT'], - ['\u2265', 'GTE'] - ]; - this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL); - this.setColour(Blockly.Blocks.logic.HUE); - this.setOutput(true, Blockly.Types.BOOLEAN.output); - this.appendValueInput('A'); - this.appendValueInput('B') - .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); - this.setInputsInline(true); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var op = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ, - 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ, - 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT, - 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE, - 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT, - 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE - }; - return TOOLTIPS[op]; - }); - this.prevBlocks_ = [null, null]; - }, /** * Called whenever anything on the workspace changes. * Prevent mismatched types from being compared. @@ -336,144 +552,40 @@ Blockly.Blocks['logic_compare'] = { } this.prevBlocks_[0] = blockA; this.prevBlocks_[1] = blockB; - }, - /** Assigns a type to the block, comparison operations result in booleans. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; } }; -Blockly.Blocks['logic_operation'] = { - /** - * Block for logical operations: 'and', 'or'. - * @this Blockly.Block - */ - init: function() { - var OPERATORS = - [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'], - [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']]; - this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL); - this.setColour(Blockly.Blocks.logic.HUE); - this.setOutput(true, Blockly.Types.BOOLEAN.output); - this.appendValueInput('A') - .setCheck(Blockly.Types.BOOLEAN.checkList); - this.appendValueInput('B') - .setCheck(Blockly.Types.BOOLEAN.checkList) - .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); - this.setInputsInline(true); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var op = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND, - 'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR - }; - return TOOLTIPS[op]; - }); - }, - /** Assigns a block type, logic comparison operations result in bools. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; +/** + * "logic_compare" extension function. Corrects direction of operators in the + * dropdown labels, and adds type left and right side type checking to + * "logic_compare" blocks. + * @this Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Logic.LOGIC_COMPARE_EXTENSION = function() { + // Fix operator labels in RTL. + if (this.RTL) { + Blockly.Constants.Logic.fixLogicCompareRtlOpLabels.apply(this); } -}; -Blockly.Blocks['logic_negate'] = { - /** - * Block for negation. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LOGIC_NEGATE_TITLE, - "args0": [ - { - "type": "input_value", - "name": "BOOL", - "check": Blockly.Types.BOOLEAN.checkList - } - ], - "output": Blockly.Types.BOOLEAN.output, - "colour": Blockly.Blocks.logic.HUE, - "tooltip": Blockly.Msg.LOGIC_NEGATE_TOOLTIP, - "helpUrl": Blockly.Msg.LOGIC_NEGATE_HELPURL - }); - }, - /** Assigns block type, 'block input' is meant to be a boolean, so same. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; - } + // Add onchange handler to ensure types are compatible. + this.mixin(Blockly.Constants.Logic.LOGIC_COMPARE_ONCHANGE_MIXIN); }; -Blockly.Blocks['logic_boolean'] = { - /** - * Block for boolean data type: true and false. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1", - "args0": [ - { - "type": "field_dropdown", - "name": "BOOL", - "options": [ - [Blockly.Msg.LOGIC_BOOLEAN_TRUE, "TRUE"], - [Blockly.Msg.LOGIC_BOOLEAN_FALSE, "FALSE"] - ] - } - ], - "output": Blockly.Types.BOOLEAN.output, - "colour": Blockly.Blocks.logic.HUE, - "tooltip": Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP, - "helpUrl": Blockly.Msg.LOGIC_BOOLEAN_HELPURL - }); - }, - /** Assigns a type to the boolean block. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; - } -}; +Blockly.Extensions.register('logic_compare', + Blockly.Constants.Logic.LOGIC_COMPARE_EXTENSION); -Blockly.Blocks['logic_null'] = { - /** - * Block for null data type. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.LOGIC_NULL, - "output": Blockly.Types.NULL.output, - "colour": Blockly.Blocks.logic.HUE, - "tooltip": Blockly.Msg.LOGIC_NULL_TOOLTIP, - "helpUrl": Blockly.Msg.LOGIC_NULL_HELPURL - }); - }, - /** Assigns a type to the NULL block. */ - getBlockType: function() { - return Blockly.Types.NULL; - } -}; +/** + * Adds type coordination between inputs and output. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN = { + prevParentConnection_: null, -Blockly.Blocks['logic_ternary'] = { - /** - * Block for ternary operator. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL); - this.setColour(Blockly.Blocks.logic.HUE); - this.appendValueInput('IF') - .setCheck(Blockly.Types.BOOLEAN.checkList) - .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION); - this.appendValueInput('THEN') - .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE); - this.appendValueInput('ELSE') - .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE); - this.setOutput(true); - this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP); - this.prevParentConnection_ = null; - }, /** * Called whenever anything on the workspace changes. * Prevent mismatched types. @@ -504,5 +616,7 @@ Blockly.Blocks['logic_ternary'] = { } this.prevParentConnection_ = parentConnection; } - //TODO: getBlockType that uses the type of the given inputs }; + +Blockly.Extensions.registerMixin('logic_ternary', + Blockly.Constants.Logic.LOGIC_TERNARY_ONCHANGE_MIXIN); diff --git a/blocks/loops.js b/blocks/loops.js index 928562c..ee80482 100644 --- a/blocks/loops.js +++ b/blocks/loops.js @@ -20,257 +20,300 @@ /** * @fileoverview Loop blocks for Blockly. + * + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author fraser@google.com (Neil Fraser) */ 'use strict'; -goog.provide('Blockly.Blocks.loops'); +goog.provide('Blockly.Blocks.loops'); // Deprecated +goog.provide('Blockly.Constants.Loops'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.LOOPS_HUE + * @readonly */ -Blockly.Blocks.loops.HUE = 120; +Blockly.Constants.Loops.HUE = 120; +/** @deprecated Use Blockly.Constants.Loops.HUE */ +Blockly.Blocks.loops.HUE = Blockly.Constants.Loops.HUE; -Blockly.Blocks['controls_repeat_ext'] = { - /** - * Block for repeat n times (external number). - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, - "args0": [ - { - "type": "input_value", - "name": "TIMES", - "check": Blockly.Types.NUMBER.checkList - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.loops.HUE, - "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, - "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL - }); - this.appendStatementInput('DO') - .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for repeat n times (external number). + { + "type": "controls_repeat_ext", + "message0": "%{BKY_CONTROLS_REPEAT_TITLE}", + "args0": [{ + "type": "input_value", + "name": "TIMES", + "check": "Number" + }], + "message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1", + "args1": [{ + "type": "input_statement", + "name": "DO" + }], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "tooltip": "%{BKY_CONTROLS_REPEAT_TOOLTIP}", + "helpUrl": "%{BKY_CONTROLS_REPEAT_HELPURL}" + }, + // Block for repeat n times (internal number). + // The 'controls_repeat_ext' block is preferred as it is more flexible. + { + "type": "controls_repeat", + "message0": "%{BKY_CONTROLS_REPEAT_TITLE}", + "args0": [{ + "type": "field_number", + "name": "TIMES", + "value": 10, + "min": 0, + "precision": 1 + }], + "message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1", + "args1": [{ + "type": "input_statement", + "name": "DO" + }], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "tooltip": "%{BKY_CONTROLS_REPEAT_TOOLTIP}", + "helpUrl": "%{BKY_CONTROLS_REPEAT_HELPURL}" + }, + // Block for 'do while/until' loop. + { + "type": "controls_whileUntil", + "message0": "%1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "MODE", + "options": [ + ["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}", "WHILE"], + ["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}", "UNTIL"] + ] + }, + { + "type": "input_value", + "name": "BOOL", + "check": "Boolean" + } + ], + "message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1", + "args1": [{ + "type": "input_statement", + "name": "DO" + }], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "helpUrl": "%{BKY_CONTROLS_WHILEUNTIL_HELPURL}", + "extensions": ["controls_whileUntil_tooltip"] + }, + // Block for 'for' loop. + { + "type": "controls_for", + "message0": "%{BKY_CONTROLS_FOR_TITLE}", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": null + }, + { + "type": "input_value", + "name": "FROM", + "check": "Number", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "TO", + "check": "Number", + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "BY", + "check": "Number", + "align": "RIGHT" + } + ], + "message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1", + "args1": [{ + "type": "input_statement", + "name": "DO" + }], + "inputsInline": true, + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "helpUrl": "%{BKY_CONTROLS_FOR_HELPURL}", + "extensions": [ + "contextMenu_newGetVariableBlock", + "controls_for_tooltip" + ] + }, + // Block for 'for each' loop. + { + "type": "controls_forEach", + "message0": "%{BKY_CONTROLS_FOREACH_TITLE}", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": null + }, + { + "type": "input_value", + "name": "LIST", + "check": "Array" + } + ], + "message1": "%{BKY_CONTROLS_REPEAT_INPUT_DO} %1", + "args1": [{ + "type": "input_statement", + "name": "DO" + }], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "helpUrl": "%{BKY_CONTROLS_FOREACH_HELPURL}", + "extensions": [ + "contextMenu_newGetVariableBlock", + "controls_forEach_tooltip" + ] + }, + // Block for flow statements: continue, break. + { + "type": "controls_flow_statements", + "message0": "%1", + "args0": [{ + "type": "field_dropdown", + "name": "FLOW", + "options": [ + ["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}", "BREAK"], + ["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}", "CONTINUE"] + ] + }], + "previousStatement": null, + "colour": "%{BKY_LOOPS_HUE}", + "helpUrl": "%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}", + "extensions": [ + "controls_flow_tooltip", + "controls_flow_in_loop_check" + ] } -}; +]); // END JSON EXTRACT (Do not delete this comment.) -Blockly.Blocks['controls_repeat'] = { - /** - * Block for repeat n times (internal number). - * The 'controls_repeat_ext' block is preferred as it is more flexible. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, - "args0": [ - { - "type": "field_number", - "name": "TIMES", - "check": Blockly.Types.NUMBER.checkList, - "text": "10" - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.loops.HUE, - "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, - "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL - }); - this.appendStatementInput('DO') - .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); - this.getField('TIMES').setValidator( - Blockly.FieldTextInput.nonnegativeIntegerValidator); - } +/** + * Tooltips for the 'controls_whileUntil' block, keyed by MODE value. + * @see {Blockly.Extensions#buildTooltipForDropdown} + * @package + * @readonly + */ +Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS = { + 'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}', + 'UNTIL': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}' }; -Blockly.Blocks['controls_whileUntil'] = { - /** - * Block for 'do while/until' loop. - * @this Blockly.Block - */ - init: function() { - var OPERATORS = - [[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'], - [Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']]; - this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL); - this.setColour(Blockly.Blocks.loops.HUE); - this.appendValueInput('BOOL') - .setCheck(Blockly.Types.BOOLEAN.checkList) - .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE'); - this.appendStatementInput('DO') - .appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO); - this.setPreviousStatement(true); - this.setNextStatement(true); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var op = thisBlock.getFieldValue('MODE'); - var TOOLTIPS = { - 'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE, - 'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL - }; - return TOOLTIPS[op]; - }); - } +Blockly.Extensions.register('controls_whileUntil_tooltip', + Blockly.Extensions.buildTooltipForDropdown( + 'MODE', Blockly.Constants.Loops.WHILE_UNTIL_TOOLTIPS)); + +/** + * Tooltips for the 'controls_flow_statements' block, keyed by FLOW value. + * @see {Blockly.Extensions#buildTooltipForDropdown} + * @package + * @readonly + */ +Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS = { + 'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}', + 'CONTINUE': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}' }; -Blockly.Blocks['controls_for'] = { - /** - * Block for 'for' loop. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.CONTROLS_FOR_TITLE, - "args0": [ - { - "type": "field_variable", - "name": "VAR", - "variable": null - }, - { - "type": "input_value", - "name": "FROM", - "check": Blockly.Types.NUMBER.checkList, - "align": "RIGHT" - }, - { - "type": "input_value", - "name": "TO", - "check": Blockly.Types.NUMBER.checkList, - "align": "RIGHT" - }, - { - "type": "input_value", - "name": "BY", - "check": Blockly.Types.NUMBER.checkList, - "align": "RIGHT" - } - ], - "inputsInline": true, - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.loops.HUE, - "helpUrl": Blockly.Msg.CONTROLS_FOR_HELPURL - }); - this.appendStatementInput('DO') - .appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1', - thisBlock.getFieldValue('VAR')); - }); - }, +Blockly.Extensions.register('controls_flow_tooltip', + Blockly.Extensions.buildTooltipForDropdown( + 'FLOW', Blockly.Constants.Loops.BREAK_CONTINUE_TOOLTIPS)); + +/** + * Mixin to add a context menu item to create a 'variables_get' block. + * Used by blocks 'controls_for' and 'controls_forEach'. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { /** - * Add menu option to create getter block for loop variable. + * Add context menu option to create getter block for the loop's variable. + * (customContextMenu support limited to web BlockSvg.) * @param {!Array} options List of menu options to add to. * @this Blockly.Block */ customContextMenu: function(options) { - if (!this.isCollapsed()) { + var varName = this.getFieldValue('VAR'); + if (!this.isCollapsed() && varName != null) { var option = {enabled: true}; - var name = this.getFieldValue('VAR'); - option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name); - var xmlField = goog.dom.createDom('field', null, name); + option.text = + Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', varName); + var xmlField = goog.dom.createDom('field', null, varName); xmlField.setAttribute('name', 'VAR'); var xmlBlock = goog.dom.createDom('block', null, xmlField); xmlBlock.setAttribute('type', 'variables_get'); option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); options.push(option); } - }, - /** - * Defines the type of the variable selected in the drop down, an integer. - * @return {string} String to indicate the type if it has not been defined - * before. - */ - getVarType: function(varName) { - return Blockly.Types.NUMBER; } }; -Blockly.Blocks['controls_forEach'] = { - /** - * Block for 'for each' loop. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.CONTROLS_FOREACH_TITLE, - "args0": [ - { - "type": "field_variable", - "name": "VAR", - "variable": null - }, - { - "type": "input_value", - "name": "LIST", - "check": Blockly.Types.ARRAY.checkList - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.loops.HUE, - "helpUrl": Blockly.Msg.CONTROLS_FOREACH_HELPURL - }); - this.appendStatementInput('DO') - .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1', - thisBlock.getFieldValue('VAR')); - }); - }, - customContextMenu: Blockly.Blocks['controls_for'].customContextMenu, - /** @returns {!string} The type of the variable used in this block */ - getVarType: function(varName) { - return Blockly.Types.NUMBER; - } -}; +Blockly.Extensions.registerMixin('contextMenu_newGetVariableBlock', + Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN); + +Blockly.Extensions.register('controls_for_tooltip', + Blockly.Extensions.buildTooltipWithFieldValue( + '%{BKY_CONTROLS_FOR_TOOLTIP}', 'VAR')); -Blockly.Blocks['controls_flow_statements'] = { +Blockly.Extensions.register('controls_forEach_tooltip', + Blockly.Extensions.buildTooltipWithFieldValue( + '%{BKY_CONTROLS_FOREACH_TOOLTIP}', 'VAR')); + +/** + * This mixin adds a check to make sure the 'controls_flow_statements' block + * is contained in a loop. Otherwise a warning is added to the block. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { /** - * Block for flow statements: continue, break. - * @this Blockly.Block + * List of block types that are loops and thus do not need warnings. + * To add a new loop type add this to your code: + * Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop'); */ - init: function() { - var OPERATORS = - [[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'], - [Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']]; - this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL); - this.setColour(Blockly.Blocks.loops.HUE); - this.appendDummyInput() - .appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW'); - this.setPreviousStatement(true); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var op = thisBlock.getFieldValue('FLOW'); - var TOOLTIPS = { - 'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK, - 'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE - }; - return TOOLTIPS[op]; - }); - }, + LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach', + 'controls_for', 'controls_whileUntil'], + /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. * @param {!Blockly.Events.Abstract} e Change event. * @this Blockly.Block */ - onchange: function(e) { + onchange: function(/* e */) { + if (!this.workspace.isDragging || this.workspace.isDragging()) { + return; // Don't change state at the start of a drag. + } var legal = false; // Is the block nested in a loop? var block = this; @@ -283,15 +326,17 @@ Blockly.Blocks['controls_flow_statements'] = { } while (block); if (legal) { this.setWarningText(null); + if (!this.isInFlyout) { + this.setDisabled(false); + } } else { this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING); + if (!this.isInFlyout && !this.getInheritedDisabled()) { + this.setDisabled(true); + } } - }, - /** - * List of block types that are loops and thus do not need warnings. - * To add a new loop type add this to your code: - * Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop'); - */ - LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach', - 'controls_for', 'controls_whileUntil'] + } }; + +Blockly.Extensions.registerMixin('controls_flow_in_loop_check', + Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN); diff --git a/blocks/math.js b/blocks/math.js index f34ca78..bc745dc 100644 --- a/blocks/math.js +++ b/blocks/math.js @@ -20,273 +20,420 @@ /** * @fileoverview Math blocks for Blockly. + * + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author q.neutron@gmail.com (Quynh Neutron) */ 'use strict'; -goog.provide('Blockly.Blocks.math'); +goog.provide('Blockly.Blocks.math'); // Deprecated +goog.provide('Blockly.Constants.Math'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.MATH_HUE + * @readonly */ -Blockly.Blocks.math.HUE = 230; +Blockly.Constants.Math.HUE = 230; +/** @deprecated Use Blockly.Constants.Math.HUE */ +Blockly.Blocks.math.HUE = Blockly.Constants.Math.HUE; -Blockly.Blocks['math_number'] = { - /** - * Block for numeric value. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL); - this.setColour(Blockly.Blocks.math.HUE); - this.appendDummyInput() - .appendField( - new Blockly.FieldTextInput( - '0', Blockly.FieldTextInput.numberValidator), - 'NUM'); - this.setOutput(true, Blockly.Types.NUMBER.output); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - // Number block is trivial. Use tooltip of parent block if it exists. - this.setTooltip(function() { - var parent = thisBlock.getParent(); - return (parent && parent.getInputsInline() && parent.tooltip) || - Blockly.Msg.MATH_NUMBER_TOOLTIP; - }); +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for numeric value. + { + "type": "math_number", + "message0": "%1", + "args0": [{ + "type": "field_number", + "name": "NUM", + "value": 0 + }], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_NUMBER_HELPURL}", + "tooltip": "%{BKY_MATH_NUMBER_TOOLTIP}", + "extensions": ["parent_tooltip_when_inline"] }, - /** - * Reads the numerical value from the block and assigns a block type. - * @this Blockly.Block - */ - getBlockType: function() { - var numString = this.getFieldValue('NUM'); - return Blockly.Types.identifyNumber(numString); - } -}; -Blockly.Blocks['math_arithmetic'] = { - /** - * Block for basic arithmetic operator. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1 %2 %3", - "args0": [ - { - "type": "input_value", - "name": "A", - "check": Blockly.Types.NUMBER.checkList - }, - { - "type": "field_dropdown", - "name": "OP", - "options": - [[Blockly.Msg.MATH_ADDITION_SYMBOL, 'ADD'], - [Blockly.Msg.MATH_SUBTRACTION_SYMBOL, 'MINUS'], - [Blockly.Msg.MATH_MULTIPLICATION_SYMBOL, 'MULTIPLY'], - [Blockly.Msg.MATH_DIVISION_SYMBOL, 'DIVIDE'], - [Blockly.Msg.MATH_POWER_SYMBOL, 'POWER'], - ['&', 'BITWISE_AND'], - ['|', 'BITWISE_OR']], - }, - { - "type": "input_value", - "name": "B", - "check": Blockly.Types.NUMBER.checkList - } - ], - "inputsInline": true, - "output": Blockly.Types.NUMBER.output, - "colour": Blockly.Blocks.math.HUE, - "helpUrl": Blockly.Msg.MATH_ARITHMETIC_HELPURL - }); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var mode = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'ADD': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD, - 'MINUS': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS, - 'MULTIPLY': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY, - 'DIVIDE': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE, - 'POWER': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER - }; - return TOOLTIPS[mode]; - }); - } - //TODO: a getBlockType based on the two input types following C++ rules -}; + // Block for basic arithmetic operator. + { + "type": "math_arithmetic", + "message0": "%1 %2 %3", + "args0": [ + { + "type": "input_value", + "name": "A", + "check": "Number" + }, + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_MATH_ADDITION_SYMBOL}", "ADD"], + ["%{BKY_MATH_SUBTRACTION_SYMBOL}", "MINUS"], + ["%{BKY_MATH_MULTIPLICATION_SYMBOL}", "MULTIPLY"], + ["%{BKY_MATH_DIVISION_SYMBOL}", "DIVIDE"], + ["%{BKY_MATH_POWER_SYMBOL}", "POWER"] + ] + }, + { + "type": "input_value", + "name": "B", + "check": "Number" + } + ], + "inputsInline": true, + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_ARITHMETIC_HELPURL}", + "extensions": ["math_op_tooltip"] + }, -Blockly.Blocks['math_single'] = { - /** - * Block for advanced math operators with single operand. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1 %2", - "args0": [ - { - "type": "field_dropdown", - "name": "OP", - "options": [ - [Blockly.Msg.MATH_SINGLE_OP_ROOT, 'ROOT'], - [Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE, 'ABS'], - ['-', 'NEG'], - ['~', 'BITWISE_NOT'], - ['ln', 'LN'], - ['log10', 'LOG10'], - ['e^', 'EXP'], - ['10^', 'POW10'] - ] - }, - { - "type": "input_value", - "name": "NUM", - "check": Blockly.Types.DECIMAL.checkList - } - ], - "output": Blockly.Types.DECIMAL.output, - "colour": Blockly.Blocks.math.HUE, - "helpUrl": Blockly.Msg.MATH_SINGLE_HELPURL - }); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var mode = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'ROOT': Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT, - 'ABS': Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS, - 'NEG': Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG, - 'LN': Blockly.Msg.MATH_SINGLE_TOOLTIP_LN, - 'LOG10': Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10, - 'EXP': Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP, - 'POW10': Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10 - }; - return TOOLTIPS[mode]; - }); + // Block for advanced math operators with single operand. + { + "type": "math_single", + "message0": "%1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_MATH_SINGLE_OP_ROOT}", 'ROOT'], + ["%{BKY_MATH_SINGLE_OP_ABSOLUTE}", 'ABS'], + ['-', 'NEG'], + ['ln', 'LN'], + ['log10', 'LOG10'], + ['e^', 'EXP'], + ['10^', 'POW10'] + ] + }, + { + "type": "input_value", + "name": "NUM", + "check": "Number" + } + ], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_SINGLE_HELPURL}", + "extensions": ["math_op_tooltip"] }, - /** @return {!string} Type of the block, all these operations are floats. */ - getBlockType: function() { - return Blockly.Types.DECIMAL; - } -}; -Blockly.Blocks['math_trig'] = { - /** - * Block for trigonometry operators. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1 %2", - "args0": [ - { - "type": "field_dropdown", - "name": "OP", - "options": [ - [Blockly.Msg.MATH_TRIG_SIN, 'SIN'], - [Blockly.Msg.MATH_TRIG_COS, 'COS'], - [Blockly.Msg.MATH_TRIG_TAN, 'TAN'], - [Blockly.Msg.MATH_TRIG_ASIN, 'ASIN'], - [Blockly.Msg.MATH_TRIG_ACOS, 'ACOS'], - [Blockly.Msg.MATH_TRIG_ATAN, 'ATAN'] - ] - }, - { - "type": "input_value", - "name": "NUM", - "check": Blockly.Types.DECIMAL.checkList - } - ], - "output": Blockly.Types.DECIMAL.output, - "colour": Blockly.Blocks.math.HUE, - "helpUrl": Blockly.Msg.MATH_TRIG_HELPURL - }); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - var mode = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'SIN': Blockly.Msg.MATH_TRIG_TOOLTIP_SIN, - 'COS': Blockly.Msg.MATH_TRIG_TOOLTIP_COS, - 'TAN': Blockly.Msg.MATH_TRIG_TOOLTIP_TAN, - 'ASIN': Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN, - 'ACOS': Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS, - 'ATAN': Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN - }; - return TOOLTIPS[mode]; - }); + // Block for trigonometry operators. + { + "type": "math_trig", + "message0": "%1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_MATH_TRIG_SIN}", "SIN"], + ["%{BKY_MATH_TRIG_COS}", "COS"], + ["%{BKY_MATH_TRIG_TAN}", "TAN"], + ["%{BKY_MATH_TRIG_ASIN}", "ASIN"], + ["%{BKY_MATH_TRIG_ACOS}", "ACOS"], + ["%{BKY_MATH_TRIG_ATAN}", "ATAN"] + ] + }, + { + "type": "input_value", + "name": "NUM", + "check": "Number" + } + ], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_TRIG_HELPURL}", + "extensions": ["math_op_tooltip"] }, - /** @return {!string} Type of the block, all these operations are floats. */ - getBlockType: function() { - return Blockly.Types.DECIMAL; - } -}; -Blockly.Blocks['math_constant'] = { - /** - * Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1", - "args0": [ - { - "type": "field_dropdown", - "name": "CONSTANT", - "options": [ - ['\u03c0', 'PI'], - ['e', 'E'], - ['\u03c6', 'GOLDEN_RATIO'], - ['sqrt(2)', 'SQRT2'], - ['sqrt(\u00bd)', 'SQRT1_2'], - ['\u221e', 'INFINITY'] - ] - } - ], - "output": Blockly.Types.DECIMAL.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_CONSTANT_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_CONSTANT_HELPURL - }); + // Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY. + { + "type": "math_constant", + "message0": "%1", + "args0": [ + { + "type": "field_dropdown", + "name": "CONSTANT", + "options": [ + ["\u03c0", "PI"], + ["e", "E"], + ["\u03c6", "GOLDEN_RATIO"], + ["sqrt(2)", "SQRT2"], + ["sqrt(\u00bd)", "SQRT1_2"], + ["\u221e", "INFINITY"] + ] + } + ], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_CONSTANT_TOOLTIP}", + "helpUrl": "%{BKY_MATH_CONSTANT_HELPURL}" + }, + + // Block for checking if a number is even, odd, prime, whole, positive, + // negative or if it is divisible by certain number. + { + "type": "math_number_property", + "message0": "%1 %2", + "args0": [ + { + "type": "input_value", + "name": "NUMBER_TO_CHECK", + "check": "Number" + }, + { + "type": "field_dropdown", + "name": "PROPERTY", + "options": [ + ["%{BKY_MATH_IS_EVEN}", "EVEN"], + ["%{BKY_MATH_IS_ODD}", "ODD"], + ["%{BKY_MATH_IS_PRIME}", "PRIME"], + ["%{BKY_MATH_IS_WHOLE}", "WHOLE"], + ["%{BKY_MATH_IS_POSITIVE}", "POSITIVE"], + ["%{BKY_MATH_IS_NEGATIVE}", "NEGATIVE"], + ["%{BKY_MATH_IS_DIVISIBLE_BY}", "DIVISIBLE_BY"] + ] + } + ], + "inputsInline": true, + "output": "Boolean", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_IS_TOOLTIP}", + "mutator": "math_is_divisibleby_mutator" + }, + + // Block for adding to a variable in place. + { + "type": "math_change", + "message0": "%{BKY_MATH_CHANGE_TITLE}", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_MATH_CHANGE_TITLE_ITEM}" + }, + { + "type": "input_value", + "name": "DELTA", + "check": "Number" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_VARIABLES_HUE}", + "helpUrl": "%{BKY_MATH_CHANGE_HELPURL}", + "extensions": ["math_change_tooltip"] + }, + + // Block for rounding functions. + { + "type": "math_round", + "message0": "%1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_MATH_ROUND_OPERATOR_ROUND}", "ROUND"], + ["%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}", "ROUNDUP"], + ["%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}", "ROUNDDOWN"] + ] + }, + { + "type": "input_value", + "name": "NUM", + "check": "Number" + } + ], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_ROUND_HELPURL}", + "tooltip": "%{BKY_MATH_ROUND_TOOLTIP}" + }, + + // Block for evaluating a list of numbers to return sum, average, min, max, + // etc. Some functions also work on text (min, max, mode, median). + { + "type": "math_on_list", + "message0": "%1 %2", + "args0": [ + { + "type": "field_dropdown", + "name": "OP", + "options": [ + ["%{BKY_MATH_ONLIST_OPERATOR_SUM}", "SUM"], + ["%{BKY_MATH_ONLIST_OPERATOR_MIN}", "MIN"], + ["%{BKY_MATH_ONLIST_OPERATOR_MAX}", "MAX"], + ["%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}", "AVERAGE"], + ["%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}", "MEDIAN"], + ["%{BKY_MATH_ONLIST_OPERATOR_MODE}", "MODE"], + ["%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}", "STD_DEV"], + ["%{BKY_MATH_ONLIST_OPERATOR_RANDOM}", "RANDOM"] + ] + }, + { + "type": "input_value", + "name": "LIST", + "check": "Array" + } + ], + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "helpUrl": "%{BKY_MATH_ONLIST_HELPURL}", + "mutator": "math_modes_of_list_mutator", + "extensions": ["math_op_tooltip"] + }, + + // Block for remainder of a division. + { + "type": "math_modulo", + "message0": "%{BKY_MATH_MODULO_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "DIVIDEND", + "check": "Number" + }, + { + "type": "input_value", + "name": "DIVISOR", + "check": "Number" + } + ], + "inputsInline": true, + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_MODULO_TOOLTIP}", + "helpUrl": "%{BKY_MATH_MODULO_HELPURL}" + }, + + // Block for constraining a number between two limits. + { + "type": "math_constrain", + "message0": "%{BKY_MATH_CONSTRAIN_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "Number" + }, + { + "type": "input_value", + "name": "LOW", + "check": "Number" + }, + { + "type": "input_value", + "name": "HIGH", + "check": "Number" + } + ], + "inputsInline": true, + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_CONSTRAIN_TOOLTIP}", + "helpUrl": "%{BKY_MATH_CONSTRAIN_HELPURL}" + }, + + // Block for random integer between [X] and [Y]. + { + "type": "math_random_int", + "message0": "%{BKY_MATH_RANDOM_INT_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "FROM", + "check": "Number" + }, + { + "type": "input_value", + "name": "TO", + "check": "Number" + } + ], + "inputsInline": true, + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_RANDOM_INT_TOOLTIP}", + "helpUrl": "%{BKY_MATH_RANDOM_INT_HELPURL}" + }, + + // Block for random integer between [X] and [Y]. + { + "type": "math_random_float", + "message0": "%{BKY_MATH_RANDOM_FLOAT_TITLE_RANDOM}", + "output": "Number", + "colour": "%{BKY_MATH_HUE}", + "tooltip": "%{BKY_MATH_RANDOM_FLOAT_TOOLTIP}", + "helpUrl": "%{BKY_MATH_RANDOM_FLOAT_HELPURL}" } +]); // END JSON EXTRACT (Do not delete this comment.) + +/** + * Mapping of math block OP value to tooltip message for blocks + * math_arithmetic, math_simple, math_trig, and math_on_lists. + * @see {Blockly.Extensions#buildTooltipForDropdown} + * @package + * @readonly + */ +Blockly.Constants.Math.TOOLTIPS_BY_OP = { + // math_arithmetic + 'ADD': '%{BKY_MATH_ARITHMETIC_TOOLTIP_ADD}', + 'MINUS': '%{BKY_MATH_ARITHMETIC_TOOLTIP_MINUS}', + 'MULTIPLY': '%{BKY_MATH_ARITHMETIC_TOOLTIP_MULTIPLY}', + 'DIVIDE': '%{BKY_MATH_ARITHMETIC_TOOLTIP_DIVIDE}', + 'POWER': '%{BKY_MATH_ARITHMETIC_TOOLTIP_POWER}', + + // math_simple + 'ROOT': '%{BKY_MATH_SINGLE_TOOLTIP_ROOT}', + 'ABS': '%{BKY_MATH_SINGLE_TOOLTIP_ABS}', + 'NEG': '%{BKY_MATH_SINGLE_TOOLTIP_NEG}', + 'LN': '%{BKY_MATH_SINGLE_TOOLTIP_LN}', + 'LOG10': '%{BKY_MATH_SINGLE_TOOLTIP_LOG10}', + 'EXP': '%{BKY_MATH_SINGLE_TOOLTIP_EXP}', + 'POW10': '%{BKY_MATH_SINGLE_TOOLTIP_POW10}', + + // math_trig + 'SIN': '%{BKY_MATH_TRIG_TOOLTIP_SIN}', + 'COS': '%{BKY_MATH_TRIG_TOOLTIP_COS}', + 'TAN': '%{BKY_MATH_TRIG_TOOLTIP_TAN}', + 'ASIN': '%{BKY_MATH_TRIG_TOOLTIP_ASIN}', + 'ACOS': '%{BKY_MATH_TRIG_TOOLTIP_ACOS}', + 'ATAN': '%{BKY_MATH_TRIG_TOOLTIP_ATAN}', + + // math_on_lists + 'SUM': '%{BKY_MATH_ONLIST_TOOLTIP_SUM}', + 'MIN': '%{BKY_MATH_ONLIST_TOOLTIP_MIN}', + 'MAX': '%{BKY_MATH_ONLIST_TOOLTIP_MAX}', + 'AVERAGE': '%{BKY_MATH_ONLIST_TOOLTIP_AVERAGE}', + 'MEDIAN': '%{BKY_MATH_ONLIST_TOOLTIP_MEDIAN}', + 'MODE': '%{BKY_MATH_ONLIST_TOOLTIP_MODE}', + 'STD_DEV': '%{BKY_MATH_ONLIST_TOOLTIP_STD_DEV}', + 'RANDOM': '%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}' }; -Blockly.Blocks['math_number_property'] = { - /** - * Block for checking if a number is even, odd, prime, whole, positive, - * negative or if it is divisible by certain number. - * @this Blockly.Block - */ - init: function() { - var PROPERTIES = - [[Blockly.Msg.MATH_IS_EVEN, 'EVEN'], - [Blockly.Msg.MATH_IS_ODD, 'ODD'], - [Blockly.Msg.MATH_IS_PRIME, 'PRIME'], - [Blockly.Msg.MATH_IS_WHOLE, 'WHOLE'], - [Blockly.Msg.MATH_IS_POSITIVE, 'POSITIVE'], - [Blockly.Msg.MATH_IS_NEGATIVE, 'NEGATIVE'], - [Blockly.Msg.MATH_IS_DIVISIBLE_BY, 'DIVISIBLE_BY']]; - this.setColour(Blockly.Blocks.math.HUE); - this.appendValueInput('NUMBER_TO_CHECK') - .setCheck(Blockly.Types.NUMBER.checkList); - var dropdown = new Blockly.FieldDropdown(PROPERTIES, function(option) { - var divisorInput = (option == 'DIVISIBLE_BY'); - this.sourceBlock_.updateShape_(divisorInput); - }); - this.appendDummyInput() - .appendField(dropdown, 'PROPERTY'); - this.setInputsInline(true); - this.setOutput(true, Blockly.Types.BOOLEAN.output); - this.setTooltip(Blockly.Msg.MATH_IS_TOOLTIP); - }, +Blockly.Extensions.register('math_op_tooltip', + Blockly.Extensions.buildTooltipForDropdown( + 'OP', Blockly.Constants.Math.TOOLTIPS_BY_OP)); + + +/** + * Mixin for mutator functions in the 'math_is_divisibleby_mutator' + * extension. + * @mixin + * @augments Blockly.Block + * @package + */ +Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN = { /** * Create XML to represent whether the 'divisorInput' should be present. * @return {Element} XML storage element. @@ -319,140 +466,57 @@ Blockly.Blocks['math_number_property'] = { if (divisorInput) { if (!inputExists) { this.appendValueInput('DIVISOR') - .setCheck(Blockly.Types.NUMBER.checkList); + .setCheck('Number'); } } else if (inputExists) { this.removeInput('DIVISOR'); } - }, - /** @return {!string} Type of the block, all these operations are bools. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; } }; -Blockly.Blocks['math_change'] = { - /** - * Block for adding to a variable in place. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.MATH_CHANGE_TITLE, - "args0": [ - { - "type": "field_variable", - "name": "VAR", - "variable": Blockly.Msg.MATH_CHANGE_TITLE_ITEM - }, - { - "type": "input_value", - "name": "DELTA", - "check": Blockly.Types.NUMBER.checkList, - "align": "RIGHT" - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.math.HUE, - "helpUrl": Blockly.Msg.MATH_CHANGE_HELPURL - }); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - return Blockly.Msg.MATH_CHANGE_TOOLTIP.replace('%1', - thisBlock.getFieldValue('VAR')); - }); - }, - /** - * Gets the variable type selected in the drop down, always an integer. - * @param {!string} varName Name of the variable selected in this block to - * check. - * @return {string} String to indicate the variable type. - */ - getVarType: function(varName) { - return Blockly.Types.NUMBER; - } +/** + * 'math_is_divisibleby_mutator' extension to the 'math_property' block that + * can update the block shape (add/remove divisor input) based on whether + * property is "divisble by". + * @this Blockly.Block + * @package + */ +Blockly.Constants.Math.IS_DIVISIBLE_MUTATOR_EXTENSION = function() { + this.getField('PROPERTY').setValidator(function(option) { + var divisorInput = (option == 'DIVISIBLE_BY'); + this.sourceBlock_.updateShape_(divisorInput); + }); }; -Blockly.Blocks['math_round'] = { - /** - * Block for rounding functions. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": "%1 %2", - "args0": [ - { - "type": "field_dropdown", - "name": "OP", - "options": [ - [Blockly.Msg.MATH_ROUND_OPERATOR_ROUND, 'ROUND'], - [Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP, 'ROUNDUP'], - [Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN, 'ROUNDDOWN'] - ] - }, - { - "type": "input_value", - "name": "NUM", - "check": Blockly.Types.DECIMAL.checkList - } - ], - "output": Blockly.Types.DECIMAL.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_ROUND_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_ROUND_HELPURL - }); - }, - /** @return {!string} Type of the block, round always returns a float. */ - getBlockType: function() { - return Blockly.Types.DECIMAL; - } +Blockly.Extensions.registerMutator('math_is_divisibleby_mutator', + Blockly.Constants.Math.IS_DIVISIBLEBY_MUTATOR_MIXIN, + Blockly.Constants.Math.IS_DIVISIBLE_MUTATOR_EXTENSION); + +/** + * Update the tooltip of 'math_change' block to reference the variable. + * @this Blockly.Block + * @package + */ +Blockly.Constants.Math.CHANGE_TOOLTIP_EXTENSION = function() { + this.setTooltip(function() { + return Blockly.Msg.MATH_CHANGE_TOOLTIP.replace('%1', + this.getFieldValue('VAR')); + }.bind(this)); }; -Blockly.Blocks['math_on_list'] = { - /** - * Block for evaluating a list of numbers to return sum, average, min, max, - * etc. Some functions also work on text (min, max, mode, median). - * @this Blockly.Block - */ - init: function() { - var OPERATORS = - [[Blockly.Msg.MATH_ONLIST_OPERATOR_SUM, 'SUM'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_MIN, 'MIN'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_MAX, 'MAX'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE, 'AVERAGE'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN, 'MEDIAN'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_MODE, 'MODE'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV, 'STD_DEV'], - [Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM, 'RANDOM']]; - // Assign 'this' to a variable for use in the closures below. - var thisBlock = this; - this.setHelpUrl(Blockly.Msg.MATH_ONLIST_HELPURL); - this.setColour(Blockly.Blocks.math.HUE); - this.setOutput(true, Blockly.Types.NUMBER.output); - var dropdown = new Blockly.FieldDropdown(OPERATORS, function(newOp) { - thisBlock.updateType_(newOp); - }); - this.appendValueInput('LIST') - .setCheck(Blockly.Types.ARRAY.checkList) - .appendField(dropdown, 'OP'); - this.setTooltip(function() { - var mode = thisBlock.getFieldValue('OP'); - var TOOLTIPS = { - 'SUM': Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM, - 'MIN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN, - 'MAX': Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX, - 'AVERAGE': Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE, - 'MEDIAN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN, - 'MODE': Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE, - 'STD_DEV': Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV, - 'RANDOM': Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM - }; - return TOOLTIPS[mode]; - }); - }, +Blockly.Extensions.register('math_change_tooltip', + Blockly.Extensions.buildTooltipWithFieldValue( + '%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR')); + +/** + * Mixin with mutator methods to support alternate output based if the + * 'math_on_list' block uses the 'MODE' operation. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN = { /** * Modify this block to have the correct output type. * @param {string} newOp Either 'MODE' or some op than returns a number. @@ -461,9 +525,9 @@ Blockly.Blocks['math_on_list'] = { */ updateType_: function(newOp) { if (newOp == 'MODE') { - this.outputConnection.setCheck(Blockly.Types.ARRAY.output); + this.outputConnection.setCheck('Array'); } else { - this.outputConnection.setCheck(Blockly.Types.NUMBER.output); + this.outputConnection.setCheck('Number'); } }, /** @@ -484,128 +548,20 @@ Blockly.Blocks['math_on_list'] = { domToMutation: function(xmlElement) { this.updateType_(xmlElement.getAttribute('op')); } - //TODO: a getBlockType once the list code is finished. -}; - -Blockly.Blocks['math_modulo'] = { - /** - * Block for remainder of a division. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.MATH_MODULO_TITLE, - "args0": [ - { - "type": "input_value", - "name": "DIVIDEND", - "check": Blockly.Types.NUMBER.checkList - }, - { - "type": "input_value", - "name": "DIVISOR", - "check": Blockly.Types.NUMBER.checkList - } - ], - "inputsInline": true, - "output": Blockly.Types.NUMBER.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_MODULO_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_MODULO_HELPURL - }); - }, - /** @return {!string} Type of the block, modulus only works on integers. */ - getBlockType: function() { - //TODO: Right now the block inputs are set to integer but will accept the - // "compatible" type float or plain "number", need to fix to integer. - return Blockly.Types.NUMBER; - } -}; - -Blockly.Blocks['math_constrain'] = { - /** - * Block for constraining a number between two limits. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.MATH_CONSTRAIN_TITLE, - "args0": [ - { - "type": "input_value", - "name": "VALUE", - "check": Blockly.Types.NUMBER.checkList - }, - { - "type": "input_value", - "name": "LOW", - "check": Blockly.Types.NUMBER.checkList - }, - { - "type": "input_value", - "name": "HIGH", - "check": Blockly.Types.NUMBER.checkList - } - ], - "inputsInline": true, - "output": Blockly.Types.NUMBER.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_CONSTRAIN_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_CONSTRAIN_HELPURL - }); - } - //TODO: a getBlockType of the same type as the inputs. }; -Blockly.Blocks['math_random_int'] = { - /** - * Block for random integer between [X] and [Y]. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.MATH_RANDOM_INT_TITLE, - "args0": [ - { - "type": "input_value", - "name": "FROM", - "check": Blockly.Types.NUMBER.checkList - }, - { - "type": "input_value", - "name": "TO", - "check": Blockly.Types.NUMBER.checkList - } - ], - "inputsInline": true, - "output": Blockly.Types.NUMBER.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_RANDOM_INT_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_RANDOM_INT_HELPURL - }); - }, - /** @return {!string} Type of the block, by definition always an integer. */ - getBlockType: function() { - return Blockly.Types.NUMBER; - } +/** + * Extension to 'math_on_list' blocks that allows support of + * modes operation (outputs a list of numbers). + * @this Blockly.Block + * @package + */ +Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION = function() { + this.getField('OP').setValidator(function(newOp) { + this.updateType_(newOp); + }.bind(this)); }; -Blockly.Blocks['math_random_float'] = { - /** - * Block for random fraction between 0 and 1. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM, - "output": Blockly.Types.DECIMAL.output, - "colour": Blockly.Blocks.math.HUE, - "tooltip": Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP, - "helpUrl": Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL - }); - }, - /** @return {!string} Type of the block, by definition always a float. */ - getBlockType: function() { - return Blockly.Types.DECIMAL; - } -}; +Blockly.Extensions.registerMutator('math_modes_of_list_mutator', + Blockly.Constants.Math.LIST_MODES_MUTATOR_MIXIN, + Blockly.Constants.Math.LIST_MODES_MUTATOR_EXTENSION); diff --git a/blocks/mbed/serial.js b/blocks/mbed/serial.js index b7c033a..bd1adeb 100644 --- a/blocks/mbed/serial.js +++ b/blocks/mbed/serial.js @@ -1,17 +1,13 @@ /** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** + * @license + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * {@link http://www.apache.org/licenses/LICENSE-2.0} * @fileoverview Blocks for the mbed serial communication functions. - * The mbed built in functions syntax can be found at: - * http://mbed.cc/en/Reference/HomePage - * - * TODO: There are more function that can be added: - * http://mbed.cc/en/Reference/Serial + * Last modified on 2/03/2018 */ -'use strict'; + + 'use strict'; goog.provide('Blockly.Blocks.serial'); @@ -22,13 +18,32 @@ goog.require('Blockly.Types'); /** Common HSV hue for all blocks in this category. */ Blockly.Blocks.serial.HUE = 160; -Blockly.Blocks['serial_setup'] = { - /** - * Block for setting the speed of the serial connection. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl('http://mbed.cc/en/Serial/Begin'); +/** + * @namespace Blockly.Blocks.serial_setup + */ +Blockly.Blocks.serial_setup = {}; +/** + * @namespace Blockly.Blocks.print_content + */ +Blockly.Blocks.print_content = {}; +/** + * @namespace Blockly.Blocks.serial_attach + */ +Blockly.Blocks.serial_attach = {}; +/** + * @namespace Blockly.Blocks.serial_print + */ +Blockly.Blocks.serial_print = {}; +/** + * @namespace Blockly.Blocks.serial_getc + */ +Blockly.Blocks.serial_getc = {}; +/** + * Block for setting the speed of the serial connection. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_setup.init = function() { this.setColour(Blockly.Blocks.serial.HUE); this.appendDummyInput() .appendField("Serial Setup RX:",'SERIAL_NAME') @@ -51,24 +66,25 @@ Blockly.Blocks['serial_setup'] = { this.setPreviousStatement(false, null); this.setNextStatement(true, null); this.setTooltip(Blockly.Msg.ARD_SERIAL_SETUP_TIP); - }, - /** - * Returns the serial instance name. - * @return {!string} Serial instance name. - * @this Blockly.Block - */ - getSerialSetupInstance: function() { +}; +/** + * Returns the serial instance name. + * @return {!string} Serial instance name. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_setup.getSerialSetupInstance = function() { return Blockly.mbed.Boards.selected.serialMapper[this.getFieldValue('SERIAL_ID')] - }, - onchange: function() { +}; +Blockly.Blocks.serial_setup.onchange = function() { if (!this.workspace) { return; } // Block has been deleted. //Get the Serial instance from this block var serialId = this.getFieldValue('SERIAL_ID'); var serialId_TX = this.getFieldValue('SERIAL_ID_TX'); - var serialRX=Blockly.mbed.Boards.selected.serialMapper[serialId]; - var serialTX=Blockly.mbed.Boards.selected.serialMapper[serialId_TX]; - if(serialRX==serialTX){ + var serialRX = Blockly.mbed.Boards.selected.serialMapper[serialId]; + var serialTX = Blockly.mbed.Boards.selected.serialMapper[serialId_TX]; + if(serialRX == serialTX){ this.setWarningText(null,'serial_rx_tx_mismatch'); this.setFieldValue('%1 Setup RX:'.replace('%1',serialRX),'SERIAL_NAME'); } @@ -76,22 +92,21 @@ Blockly.Blocks['serial_setup'] = { this.setWarningText(serialRX+" mismatches "+serialTX,'serial_rx_tx_mismatch'); this.setFieldValue('Serial Setup RX','SERIAL_NAME'); } - }, - /** - * Updates the content of the the serial related fields. - * @this Blockly.Block - */ - updateFields: function() { +}; +/** + * Updates the content of the the serial related fields. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_setup.updateFields = function() { Blockly.mbed.Boards.refreshBlockFieldDropdown( this, 'SERIAL_ID', 'digitalPins'); Blockly.mbed.Boards.refreshBlockFieldDropdown( this, 'SERIAL_ID_TX', 'digitalPins'); Blockly.mbed.Boards.refreshBlockFieldDropdown( this, 'SPEED', 'serialSpeed'); - } }; -Blockly.Blocks['print_content'] = { - init: function() { +Blockly.Blocks.print_content.init = function() { this.appendValueInput("format_content") .setCheck(null); this.setInputsInline(true); @@ -103,10 +118,9 @@ Blockly.Blocks['print_content'] = { this.setColour(Blockly.Blocks.serial.HUE); this.setTooltip("print format extra content"); this.setHelpUrl(""); - } }; -Blockly.Blocks['serial_attach'] = { - init: function() { + +Blockly.Blocks.serial_attach.init = function() { this.appendDummyInput() .appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPins), 'SERIAL_Pins') .appendField("attach"); @@ -119,15 +133,15 @@ Blockly.Blocks['serial_attach'] = { this.setTooltip("attach an interrupt function to configured serial"); this.setHelpUrl(""); this.arguments_ = []; - } }; -Blockly.Blocks['serial_print'] = { - /** - * Block for creating a write to serial com function. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl('http://www.mbed.cc/en/Serial/Print'); + + +/** + * Block for creating a write to serial com function. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_print.init = function() { this.setColour(Blockly.Blocks.serial.HUE); this.appendValueInput('CONTENT') .setCheck(Blockly.Types.TEXT.checkList) @@ -140,16 +154,16 @@ Blockly.Blocks['serial_print'] = { this.setInputsInline(false); this.setPreviousStatement(true, null); this.setNextStatement(true, null); - this.setTooltip(Blockly.Msg.ARD_SERIAL_PRINT_TIP); - - }, - /** - * Called whenever anything on the workspace changes. - * It checks the instances of serial_setup and attaches a warning to this - * block if not valid data is found. - * @this Blockly.Block - */ - onchange: function() { + this.setTooltip(Blockly.Msg.ARD_SERIAL_PRINT_TIP); +}; +/** + * Called whenever anything on the workspace changes. + * It checks the instances of serial_setup and attaches a warning to this + * block if not valid data is found. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_print.onchange = function() { if (!this.workspace) { return; } // Block has been deleted. //Get the Serial instance from this block @@ -172,38 +186,39 @@ Blockly.Blocks['serial_print'] = { } else { this.setWarningText(null, 'serial_setup'); } - }, - /** - * Updates the content of the the serial related fields. - * @this Blockly.Block - */ - updateFields: function() { +}; +/** + * Updates the content of the the serial related fields. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_print.updateFields = function() { Blockly.mbed.Boards.refreshBlockFieldDropdown( this, 'SERIAL_Pins', 'serialPins'); - } }; -Blockly.Blocks['serial_getc'] = { - /** - * Block for creating a write to serial com function. - * @this Blockly.Block - */ - init: function() { - this.setOutput(true, null); - this.setColour(Blockly.Blocks.serial.HUE); - this.appendDummyInput('one_character') - .appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPins), 'SERIAL_Pins') - .appendField("getc"); - this.setInputsInline(false); - this.setTooltip("Get one character from serial"); - - }, - /** - * Called whenever anything on the workspace changes. - * It checks the instances of serial_setup and attaches a warning to this - * block if not valid data is found. - * @this Blockly.Block - */ - onchange: function() { +/** + * Get one character from serial com. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_getc.init = function() { + this.setOutput(true, null); + this.setColour(Blockly.Blocks.serial.HUE); + this.appendDummyInput('one_character') + .appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPins), 'SERIAL_Pins') + .appendField("getc"); + this.setInputsInline(false); + this.setTooltip("Get one character from serial"); + +}; +/** + * Called whenever anything on the workspace changes. + * It checks the instances of serial_setup and attaches a warning to this + * block if not valid data is found. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_getc.onchange = function() { if (!this.workspace) { return; } // Block has been deleted. //Get the Serial instance from this block @@ -226,13 +241,13 @@ Blockly.Blocks['serial_getc'] = { } else { this.setWarningText(null, 'serial_setup'); } - }, - /** - * Updates the content of the the serial related fields. - * @this Blockly.Block - */ - updateFields: function() { - Blockly.mbed.Boards.refreshBlockFieldDropdown( - this, 'SERIAL_Pins', 'serialPins'); - } + }; +/** + * Updates the content of the the serial related fields. + * @this Blockly.Block + * @memberof Blockly.Blocks + */ +Blockly.Blocks.serial_getc.updateFields = function() { + Blockly.mbed.Boards.refreshBlockFieldDropdown( + this, 'SERIAL_Pins', 'serialPins'); }; diff --git a/blocks/mbed/servo.js b/blocks/mbed/servo.js index 87297c3..1018380 100644 --- a/blocks/mbed/servo.js +++ b/blocks/mbed/servo.js @@ -1,14 +1,10 @@ /** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** + * @license + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * {@link http://www.apache.org/licenses/LICENSE-2.0} * @fileoverview mbed blocks for the Servo library. - * The mbed Servo functions can be found in - * http://mbed.cc/en/reference/servo - * - * TODO: Add angle selector instead of block input. + * Last modified on 2/03/2018 */ 'use strict'; diff --git a/blocks/mbed/stepper.js b/blocks/mbed/stepper.js deleted file mode 100644 index 7da1743..0000000 --- a/blocks/mbed/stepper.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** - * @fileoverview Blocks for mbed Stepper library. - * The mbed Servo functions syntax can be found in the following URL: - * http://mbed.cc/en/Reference/Stepper - * Note that this block uses the Blockly.FieldInstance instead of - * Blockly.FieldDropdown which generates a unique instance per setup block - * in the workspace. - */ -'use strict'; - -goog.provide('Blockly.Blocks.stepper'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); - - -/** Common HSV hue for all blocks in this category. */ -Blockly.Blocks.stepper.HUE = 80; - -Blockly.Blocks['stepper_config'] = { - /** - * Block for for the stepper generator configuration including creating - * an object instance and setting up the speed. Info in the setHelpUrl link. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl('http://mbed.cc/en/Reference/StepperConstructor'); - this.setColour(Blockly.Blocks.stepper.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.ARD_STEPPER_SETUP) - .appendField( - new Blockly.FieldInstance('Stepper', - Blockly.Msg.ARD_STEPPER_DEFAULT_NAME, - true, true, false), - 'STEPPER_NAME') - .appendField(Blockly.Msg.ARD_STEPPER_MOTOR); - this.appendDummyInput() - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.ARD_STEPPER_PIN1) - .appendField(new Blockly.FieldDropdown( - Blockly.mbed.Boards.selected.digitalPins), 'STEPPER_PIN1') - .appendField(Blockly.Msg.ARD_STEPPER_PIN2) - .appendField(new Blockly.FieldDropdown( - Blockly.mbed.Boards.selected.digitalPins), 'STEPPER_PIN2'); - this.appendValueInput('STEPPER_STEPS') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.ARD_STEPPER_REVOLVS); - this.appendValueInput('STEPPER_SPEED') - .setCheck(Blockly.Types.NUMBER.checkList) - .setAlign(Blockly.ALIGN_RIGHT) - .appendField(Blockly.Msg.ARD_STEPPER_SPEED); - this.setTooltip(Blockly.Msg.ARD_STEPPER_SETUP_TIP); - }, - /** - * Updates the content of the the pin related fields. - * @this Blockly.Block - */ - updateFields: function() { - Blockly.Boards.refreshBlockFieldDropdown( - this, 'STEPPER_PIN1', 'digitalPins'); - Blockly.Boards.refreshBlockFieldDropdown( - this, 'STEPPER_PIN2', 'digitalPins'); - } -}; - -Blockly.Blocks['stepper_step'] = { - /** - * Block for for the stepper 'step()' function. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl('http://mbed.cc/en/Reference/StepperStep'); - this.setColour(Blockly.Blocks.stepper.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.ARD_STEPPER_STEP) - .appendField( - new Blockly.FieldInstance('Stepper', - Blockly.Msg.ARD_STEPPER_DEFAULT_NAME, - false, true, false), - 'STEPPER_NAME'); - this.appendValueInput('STEPPER_STEPS') - .setCheck(Blockly.Types.NUMBER.checkList); - this.appendDummyInput() - .appendField(Blockly.Msg.ARD_STEPPER_STEPS); - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setTooltip(Blockly.Msg.ARD_STEPPER_STEP_TIP); - }, - /** - * Called whenever anything on the workspace changes. - * It checks/warns if the selected stepper instance has a config block. - * @this Blockly.Block - */ - onchange: function() { - if (!this.workspace) return; // Block has been deleted. - - var instanceName = this.getFieldValue('STEPPER_NAME') - if (Blockly.Instances.isInstancePresent(instanceName, 'Stepper', this)) { - this.setWarningText(null); - } else { - // Set a warning to select a valid stepper config block - this.setWarningText( - Blockly.Msg.ARD_COMPONENT_WARN1.replace( - '%1', Blockly.Msg.ARD_STEPPER_COMPONENT).replace( - '%2', instanceName)); - } - } -}; diff --git a/blocks/procedures.js b/blocks/procedures.js index 70ac0fe..cb2cec2 100644 --- a/blocks/procedures.js +++ b/blocks/procedures.js @@ -27,7 +27,7 @@ goog.provide('Blockly.Blocks.procedures'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** @@ -35,292 +35,34 @@ goog.require('Blockly.Types'); */ Blockly.Blocks.procedures.HUE = 290; -Blockly.Blocks['InterruptIn'] = { - /** - * Block for defining a procedure with no return value. - * @this Blockly.Block - */ - init: function() { - var nameField = new Blockly.FieldTextInput( - Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE, - Blockly.Procedures.rename); - nameField.setSpellcheck(false); - this.appendDummyInput() - .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE) - .appendField(nameField, 'NAME') - .appendField('', 'PARAMS') - .appendField("Pin") - .appendField(new Blockly.FieldDropdown( - Blockly.mbed.Boards.selected.digitalPins), "InterruptPin"); - //this.setMutator(new Blockly.Mutator(['interrrupt_mutator_extension'])); - if (Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) { - this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT); - } - this.setColour(Blockly.Blocks.procedures.HUE); - this.setTooltip("Choose a pin bound to this interrupt function"); - this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL); - this.arguments_ = []; - this.setStatements_(true); - this.statementConnection_ = null; - }, - /** - * Initialization of the block has completed, clean up anything that may be - * inconsistent as a result of the XML loading. - * @this Blockly.Block - */ - validate: function () { - var name = Blockly.Procedures.findLegalName( - this.getFieldValue('NAME'), this); - this.setFieldValue(name, 'NAME'); - }, - /** - * Add or remove the statement block from this function definition. - * @param {boolean} hasStatements True if a statement block is needed. - * @this Blockly.Block - */ - setStatements_: function(hasStatements) { - if (this.hasStatements_ === hasStatements) { - return; - } - if (hasStatements) { - this.appendStatementInput('STACK') - .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO); - if (this.getInput('RETURN')) { - this.moveInputBefore('STACK', 'RETURN'); - } - } else { - this.removeInput('STACK', true); - } - this.hasStatements_ = hasStatements; - }, - /** - * Update the display of parameters for this procedure definition block. - * Display a warning if there are duplicately named parameters. - * @private - * @this Blockly.Block - */ - updateParams_: function() { - // Check for duplicated arguments. - var badArg = false; - var hash = {}; - for (var i = 0; i < this.arguments_.length; i++) { - if (hash['arg_' + this.arguments_[i].toLowerCase()]) { - badArg = true; - break; - } - hash['arg_' + this.arguments_[i].toLowerCase()] = true; - } - if (badArg) { - this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING); - } else { - this.setWarningText(null); - } - // Merge the arguments into a human-readable list. - var paramString = ''; - if (this.arguments_.length) { - paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS + - ' ' + this.arguments_.join(', '); - } - // The params field is deterministic based on the mutation, - // no need to fire a change event. - Blockly.Events.disable(); - this.setFieldValue(paramString, 'PARAMS'); - Blockly.Events.enable(); - }, - /** - * Create XML to represent the argument inputs. - * @param {=boolean} opt_paramIds If true include the IDs of the parameter - * quarks. Used by Blockly.Procedures.mutateCallers for reconnection. - * @return {!Element} XML storage element. - * @this Blockly.Block - */ - /** - * Parse XML to restore the argument inputs. - * @param {!Element} xmlElement XML storage element. - * @this Blockly.Block - */ - /** - * Populate the mutator's dialog with this block's components. - * @param {!Blockly.Workspace} workspace Mutator's workspace. - * @return {!Blockly.Block} Root block in mutator. - * @this Blockly.Block - */ - /** - * Reconfigure this block based on the mutator dialog's components. - * @param {!Blockly.Block} containerBlock Root block in mutator. - * @this Blockly.Block - */ - /** - * Dispose of any callers. - * @this Blockly.Block - */ - dispose: function() { - var name = this.getFieldValue('NAME'); - Blockly.Procedures.disposeCallers(name, this.workspace); - // Call parent's destructor. - this.constructor.prototype.dispose.apply(this, arguments); - }, - /** - * Return the signature of this procedure definition. - * @return {!Array} Tuple containing three elements: - * - the name of the defined procedure, - * - a list of all its arguments, - * - that it DOES NOT have a return value. - * @this Blockly.Block - */ - getProcedureDef: function() { - return [this.getFieldValue('NAME'), this.arguments_, false]; - }, - /** - * Return all variables referenced by this block. - * @return {!Array.} List of variable names. - * @this Blockly.Block - */ - getVars: function() { - return this.arguments_; - }, - /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. - * @this Blockly.Block - */ - renameVar: function(oldName, newName) { - var change = false; - for (var i = 0; i < this.arguments_.length; i++) { - if (Blockly.Names.equals(oldName, this.arguments_[i])) { - this.arguments_[i] = newName; - change = true; - } - } - if (change) { - this.updateParams_(); - // Update the mutator's variables if the mutator is open. - if (this.mutator.isVisible()) { - var blocks = this.mutator.workspace_.getAllBlocks(); - for (var i = 0, block; block = blocks[i]; i++) { - if (block.type == 'procedures_mutatorarg' && - Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { - block.setFieldValue(newName, 'NAME'); - } - } - } - } - }, - /** - * Add custom menu options to this block's context menu. - * @param {!Array} options List of menu options to add to. - * @this Blockly.Block - */ - customContextMenu: function(options) { - // Add option to create caller. - var option = {enabled: true}; - var name = this.getFieldValue('NAME'); - option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name); - var xmlMutation = goog.dom.createDom('mutation'); - xmlMutation.setAttribute('name', name); - for (var i = 0; i < this.arguments_.length; i++) { - var xmlArg = goog.dom.createDom('arg'); - xmlArg.setAttribute('name', this.arguments_[i]); - xmlMutation.appendChild(xmlArg); - } - var xmlBlock = goog.dom.createDom('block', null, xmlMutation); - xmlBlock.setAttribute('type', this.callType_); - option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); - - // Add options to create getters for each parameter. - if (!this.isCollapsed()) { - for (var i = 0; i < this.arguments_.length; i++) { - var option = {enabled: true}; - var name = this.arguments_[i]; - option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name); - var xmlField = goog.dom.createDom('field', null, name); - xmlField.setAttribute('name', 'VAR'); - var xmlBlock = goog.dom.createDom('block', null, xmlField); - xmlBlock.setAttribute('type', 'variables_get'); - option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); - } - } - }, - callType_: 'procedures_callnoreturn', - /** @return {!string} This block does not define type, so 'undefined' */ - getVarType: function(varName) { - return Blockly.Types.UNDEF; - }, - /** Contains the type of the arguments added with mutators. */ - argsTypes: {}, - /** - * Searches through a list of variables with type to assign the type of the - * arguments. - * @this Blockly.Block - * @param {Array} existingVars Associative array variable already - * defined, names as key, type as value. - */ - setArgsType: function(existingVars) { - var varNames = this.arguments_; - - // Check if variable has been defined already and save type - for (var name in existingVars) { - for (var i = 0, length_ = varNames.length; i < length_; i++) { - if (name === varNames[i]) { - this.argsTypes[name] = existingVars[name]; - } - } - } - }, - /** - * Retrieves the type of the arguments, types defined at setArgsType. - * @this Blockly.Block - * @return {string} Type of the argument indicated in the input. - */ - getArgType: function(varName) { - for (var name in this.argsTypes) { - if (name == varName) { - return this.argsTypes[varName]; - } - } - return null; - } -}; - Blockly.Blocks['procedures_defnoreturn'] = { /** * Block for defining a procedure with no return value. * @this Blockly.Block */ init: function() { - var nameField = new Blockly.FieldTextInput( - Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE, + var nameField = new Blockly.FieldTextInput('', Blockly.Procedures.rename); nameField.setSpellcheck(false); this.appendDummyInput() .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE) .appendField(nameField, 'NAME') .appendField('', 'PARAMS'); - //this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); - if (Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) { + this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); + if ((this.workspace.options.comments || + (this.workspace.options.parentWorkspace && + this.workspace.options.parentWorkspace.options.comments)) && + Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) { this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT); } this.setColour(Blockly.Blocks.procedures.HUE); this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.setStatements_(true); this.statementConnection_ = null; }, - /** - * Initialization of the block has completed, clean up anything that may be - * inconsistent as a result of the XML loading. - * @this Blockly.Block - */ - validate: function () { - var name = Blockly.Procedures.findLegalName( - this.getFieldValue('NAME'), this); - this.setFieldValue(name, 'NAME'); - }, /** * Add or remove the statement block from this function definition. * @param {boolean} hasStatements True if a statement block is needed. @@ -372,12 +114,15 @@ Blockly.Blocks['procedures_defnoreturn'] = { // The params field is deterministic based on the mutation, // no need to fire a change event. Blockly.Events.disable(); - this.setFieldValue(paramString, 'PARAMS'); - Blockly.Events.enable(); + try { + this.setFieldValue(paramString, 'PARAMS'); + } finally { + Blockly.Events.enable(); + } }, /** * Create XML to represent the argument inputs. - * @param {=boolean} opt_paramIds If true include the IDs of the parameter + * @param {boolean=} opt_paramIds If true include the IDs of the parameter * quarks. Used by Blockly.Procedures.mutateCallers for reconnection. * @return {!Element} XML storage element. * @this Blockly.Block @@ -387,9 +132,11 @@ Blockly.Blocks['procedures_defnoreturn'] = { if (opt_paramIds) { container.setAttribute('name', this.getFieldValue('NAME')); } - for (var i = 0; i < this.arguments_.length; i++) { + for (var i = 0; i < this.argumentVarModels_.length; i++) { var parameter = document.createElement('arg'); - parameter.setAttribute('name', this.arguments_[i]); + var argModel = this.argumentVarModels_[i]; + parameter.setAttribute('name', argModel.name); + parameter.setAttribute('varId', argModel.getId()); if (opt_paramIds && this.paramIds_) { parameter.setAttribute('paramId', this.paramIds_[i]); } @@ -409,9 +156,15 @@ Blockly.Blocks['procedures_defnoreturn'] = { */ domToMutation: function(xmlElement) { this.arguments_ = []; + this.argumentVarModels_ = []; for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { if (childNode.nodeName.toLowerCase() == 'arg') { - this.arguments_.push(childNode.getAttribute('name')); + var varName = childNode.getAttribute('name'); + var varId = childNode.getAttribute('varId'); + this.arguments_.push(varName); + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace, varId, varName, ''); + this.argumentVarModels_.push(variable); } } this.updateParams_(); @@ -432,8 +185,8 @@ Blockly.Blocks['procedures_defnoreturn'] = { // Check/uncheck the allow statement box. if (this.getInput('RETURN')) { - containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE', - 'STATEMENTS'); + containerBlock.setFieldValue( + this.hasStatements_ ? 'TRUE' : 'FALSE', 'STATEMENTS'); } else { containerBlock.getInput('STATEMENT_INPUT').setVisible(false); } @@ -462,9 +215,13 @@ Blockly.Blocks['procedures_defnoreturn'] = { // Parameter list. this.arguments_ = []; this.paramIds_ = []; + this.argumentVarModels_ = []; var paramBlock = containerBlock.getInputTargetBlock('STACK'); while (paramBlock) { - this.arguments_.push(paramBlock.getFieldValue('NAME')); + var varName = paramBlock.getFieldValue('NAME'); + this.arguments_.push(varName); + var variable = this.workspace.getVariable(varName, ''); + this.argumentVarModels_.push(variable); this.paramIds_.push(paramBlock.id); paramBlock = paramBlock.nextConnection && paramBlock.nextConnection.targetBlock(); @@ -496,16 +253,6 @@ Blockly.Blocks['procedures_defnoreturn'] = { } } }, - /** - * Dispose of any callers. - * @this Blockly.Block - */ - dispose: function() { - var name = this.getFieldValue('NAME'); - Blockly.Procedures.disposeCallers(name, this.workspace); - // Call parent's destructor. - this.constructor.prototype.dispose.apply(this, arguments); - }, /** * Return the signature of this procedure definition. * @return {!Array} Tuple containing three elements: @@ -525,31 +272,79 @@ Blockly.Blocks['procedures_defnoreturn'] = { getVars: function() { return this.arguments_; }, + /** + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. + * @this Blockly.Block + */ + getVarModels: function() { + return this.argumentVarModels_; + }, /** * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * If the ID matches one of this block's variables, rename it. + * @param {string} oldId ID of variable to rename. + * @param {string} newId ID of new variable. May be the same as oldId, but + * with an updated name. Guaranteed to be the same type as the old + * variable. * @this Blockly.Block */ - renameVar: function(oldName, newName) { + renameVarById: function(oldId, newId) { + var oldVariable = this.workspace.getVariableById(oldId); + if (oldVariable.type != '') { + // Procedure arguments always have the empty type. + return; + } + var oldName = oldVariable.name; + var newVar = this.workspace.getVariableById(newId); + var change = false; - for (var i = 0; i < this.arguments_.length; i++) { - if (Blockly.Names.equals(oldName, this.arguments_[i])) { + for (var i = 0; i < this.argumentVarModels_.length; i++) { + if (this.argumentVarModels_[i].getId() == oldId) { + this.arguments_[i] = newVar.name; + this.argumentVarModels_[i] = newVar; + change = true; + } + } + if (change) { + this.displayRenamedVar_(oldName, newVar.name); + } + }, + /** + * Notification that a variable is renaming but keeping the same ID. If the + * variable is in use on this block, rerender to show the new name. + * @param {!Blockly.VariableModel} variable The variable being renamed. + * @package + */ + updateVarName: function(variable) { + var newName = variable.name; + var change = false; + for (var i = 0; i < this.argumentVarModels_.length; i++) { + if (this.argumentVarModels_[i].getId() == variable.getId()) { + var oldName = this.arguments_[i]; this.arguments_[i] = newName; change = true; } } if (change) { - this.updateParams_(); - // Update the mutator's variables if the mutator is open. - if (this.mutator.isVisible()) { - var blocks = this.mutator.workspace_.getAllBlocks(); - for (var i = 0, block; block = blocks[i]; i++) { - if (block.type == 'procedures_mutatorarg' && - Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { - block.setFieldValue(newName, 'NAME'); - } + this.displayRenamedVar_(oldName, newName); + } + }, + /** + * Update the display to reflect a newly renamed argument. + * @param {string} oldName The old display name of the argument. + * @param {string} newName The new display name of the argument. + * @private + */ + displayRenamedVar_: function(oldName, newName) { + this.updateParams_(); + // Update the mutator's variables if the mutator is open. + if (this.mutator.isVisible()) { + var blocks = this.mutator.workspace_.getAllBlocks(); + for (var i = 0, block; block = blocks[i]; i++) { + if (block.type == 'procedures_mutatorarg' && + Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { + block.setFieldValue(newName, 'NAME'); } } } @@ -591,45 +386,7 @@ Blockly.Blocks['procedures_defnoreturn'] = { } } }, - callType_: 'procedures_callnoreturn', - /** @return {!string} This block does not define type, so 'undefined' */ - getVarType: function(varName) { - return Blockly.Types.UNDEF; - }, - /** Contains the type of the arguments added with mutators. */ - argsTypes: {}, - /** - * Searches through a list of variables with type to assign the type of the - * arguments. - * @this Blockly.Block - * @param {Array} existingVars Associative array variable already - * defined, names as key, type as value. - */ - setArgsType: function(existingVars) { - var varNames = this.arguments_; - - // Check if variable has been defined already and save type - for (var name in existingVars) { - for (var i = 0, length_ = varNames.length; i < length_; i++) { - if (name === varNames[i]) { - this.argsTypes[name] = existingVars[name]; - } - } - } - }, - /** - * Retrieves the type of the arguments, types defined at setArgsType. - * @this Blockly.Block - * @return {string} Type of the argument indicated in the input. - */ - getArgType: function(varName) { - for (var name in this.argsTypes) { - if (name == varName) { - return this.argsTypes[varName]; - } - } - return null; - } + callType_: 'procedures_callnoreturn' }; Blockly.Blocks['procedures_defreturn'] = { @@ -638,8 +395,7 @@ Blockly.Blocks['procedures_defreturn'] = { * @this Blockly.Block */ init: function() { - var nameField = new Blockly.FieldTextInput( - Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE, + var nameField = new Blockly.FieldTextInput('', Blockly.Procedures.rename); nameField.setSpellcheck(false); this.appendDummyInput() @@ -649,25 +405,27 @@ Blockly.Blocks['procedures_defreturn'] = { this.appendValueInput('RETURN') .setAlign(Blockly.ALIGN_RIGHT) .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); - //this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); - if (Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT) { + this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); + if ((this.workspace.options.comments || + (this.workspace.options.parentWorkspace && + this.workspace.options.parentWorkspace.options.comments)) && + Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT) { this.setCommentText(Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT); } this.setColour(Blockly.Blocks.procedures.HUE); this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.setStatements_(true); this.statementConnection_ = null; }, setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_, - validate: Blockly.Blocks['procedures_defnoreturn'].validate, updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_, mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom, domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation, decompose: Blockly.Blocks['procedures_defnoreturn'].decompose, compose: Blockly.Blocks['procedures_defnoreturn'].compose, - dispose: Blockly.Blocks['procedures_defnoreturn'].dispose, /** * Return the signature of this procedure definition. * @return {!Array} Tuple containing three elements: @@ -680,32 +438,12 @@ Blockly.Blocks['procedures_defreturn'] = { return [this.getFieldValue('NAME'), this.arguments_, true]; }, getVars: Blockly.Blocks['procedures_defnoreturn'].getVars, - renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar, + getVarModels: Blockly.Blocks['procedures_defnoreturn'].getVarModels, + renameVarById: Blockly.Blocks['procedures_defnoreturn'].renameVarById, + updateVarName: Blockly.Blocks['procedures_defnoreturn'].updateVarName, + displayRenamedVar_: Blockly.Blocks['procedures_defnoreturn'].displayRenamedVar_, customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu, - callType_: 'procedures_callreturn', - getVarType: Blockly.Blocks['procedures_defnoreturn'].getVarType, - argsTypes: {}, - setArgsType: Blockly.Blocks['procedures_defnoreturn'].setArgsType, - getArgType: Blockly.Blocks['procedures_defnoreturn'].getArgType, - /** - * Searches through the nested blocks in the return input to find a variable - * type or returns NULL. - * @this Blockly.Block - * @return {string} String to indicate the type or NULL. - */ - getReturnType: function() { - var returnType = Blockly.Types.NULL; - var returnBlock = this.getInputTargetBlock('RETURN'); - if (returnBlock) { - // First check if the block itself has a type already - if (returnBlock.getBlockType) { - returnType = returnBlock.getBlockType(); - } else { - returnType = Blockly.Types.getChildBlockType(returnBlock); - } - } - return returnType; - } + callType_: 'procedures_callreturn' }; Blockly.Blocks['procedures_mutatorcontainer'] = { @@ -732,27 +470,81 @@ Blockly.Blocks['procedures_mutatorarg'] = { * @this Blockly.Block */ init: function() { + var field = new Blockly.FieldTextInput('x', this.validator_); + // Hack: override showEditor to do just a little bit more work. + // We don't have a good place to hook into the start of a text edit. + field.oldShowEditorFn_ = field.showEditor_; + var newShowEditorFn = function() { + this.createdVariables_ = []; + this.oldShowEditorFn_(); + }; + field.showEditor_ = newShowEditorFn; + this.appendDummyInput() .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE) - .appendField(new Blockly.FieldTextInput('x', this.validator_), 'NAME'); + .appendField(field, 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); this.setColour(Blockly.Blocks.procedures.HUE); this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP); this.contextMenu = false; + + // Create the default variable when we drag the block in from the flyout. + // Have to do this after installing the field on the block. + field.onFinishEditing_ = this.deleteIntermediateVars_; + // Create an empty list so onFinishEditing_ has something to look at, even + // though the editor was never opened. + field.createdVariables_ = []; + field.onFinishEditing_('x'); }, /** - * Obtain a valid name for the procedure. + * Obtain a valid name for the procedure argument. Create a variable if + * necessary. * Merge runs of whitespace. Strip leading and trailing whitespace. * Beyond this, all names are legal. - * @param {string} newVar User-supplied name. + * @param {string} varName User-supplied name. * @return {?string} Valid name, or null if a name was not specified. * @private - * @this Blockly.Block + * @this Blockly.FieldTextInput + */ + validator_: function(varName) { + var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace); + varName = varName.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (!varName) { + return null; + } + var model = outerWs.getVariable(varName, ''); + if (model && model.name != varName) { + // Rename the variable (case change) + outerWs.renameVarById(model.getId(), varName); + } + if (!model) { + model = outerWs.createVariable(varName, ''); + if (model && this.createdVariables_) { + this.createdVariables_.push(model); + } + } + return varName; + }, + /** + * Called when focusing away from the text field. + * Deletes all variables that were created as the user typed their intended + * variable name. + * @param {string} newText The new variable name. + * @private + * @this Blockly.FieldTextInput */ - validator_: function(newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - return newVar || null; + deleteIntermediateVars_: function(newText) { + var outerWs = Blockly.Mutator.findParentWs(this.sourceBlock_.workspace); + if (!outerWs) { + return; + } + for (var i = 0; i < this.createdVariables_.length; i++) { + var model = this.createdVariables_[i]; + if (model.name != newText) { + outerWs.deleteVariableById(model.getId()); + } + } } }; @@ -770,6 +562,7 @@ Blockly.Blocks['procedures_callnoreturn'] = { // Tooltip is set in renameProcedure. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL); this.arguments_ = []; + this.argumentVarModels_ = []; this.quarkConnections_ = {}; this.quarkIds_ = null; }, @@ -792,10 +585,10 @@ Blockly.Blocks['procedures_callnoreturn'] = { renameProcedure: function(oldName, newName) { if (Blockly.Names.equals(oldName, this.getProcedureCall())) { this.setFieldValue(newName, 'NAME'); - this.setTooltip( - (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP : - Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP) - .replace('%1', newName)); + var baseMsg = this.outputConnection ? + Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP : + Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP; + this.setTooltip(baseMsg.replace('%1', newName)); } }, /** @@ -868,6 +661,14 @@ Blockly.Blocks['procedures_callnoreturn'] = { } // Rebuild the block's arguments. this.arguments_ = [].concat(paramNames); + // And rebuild the argument model list. + this.argumentVarModels_ = []; + for (var i = 0; i < this.arguments_.length; i++) { + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace, null, this.arguments_[i], ''); + this.argumentVarModels_.push(variable); + } + this.updateShape_(); this.quarkIds_ = paramIds; // Reconnect any child blocks. @@ -902,8 +703,11 @@ Blockly.Blocks['procedures_callnoreturn'] = { // The argument name field is deterministic based on the mutation, // no need to fire a change event. Blockly.Events.disable(); - field.setValue(this.arguments_[i]); - Blockly.Events.enable(); + try { + field.setValue(this.arguments_[i]); + } finally { + Blockly.Events.enable(); + } } else { // Add new input. field = new Blockly.FieldLabel(this.arguments_[i]); @@ -967,17 +771,77 @@ Blockly.Blocks['procedures_callnoreturn'] = { this.setProcedureParameters_(args, paramIds); }, /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. * @this Blockly.Block */ - renameVar: function(oldName, newName) { - for (var i = 0; i < this.arguments_.length; i++) { - if (Blockly.Names.equals(oldName, this.arguments_[i])) { - this.arguments_[i] = newName; - this.getField('ARGNAME' + i).setValue(newName); + getVarModels: function() { + return this.argumentVarModels_; + }, + /** + * Procedure calls cannot exist without the corresponding procedure + * definition. Enforce this link whenever an event is fired. + * @param {!Blockly.Events.Abstract} event Change event. + * @this Blockly.Block + */ + onchange: function(event) { + if (!this.workspace || this.workspace.isFlyout) { + // Block is deleted or is in a flyout. + return; + } + if (event.type == Blockly.Events.BLOCK_CREATE && + event.ids.indexOf(this.id) != -1) { + // Look for the case where a procedure call was created (usually through + // paste) and there is no matching definition. In this case, create + // an empty definition block with the correct signature. + var name = this.getProcedureCall(); + var def = Blockly.Procedures.getDefinition(name, this.workspace); + if (def && (def.type != this.defType_ || + JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) { + // The signatures don't match. + def = null; + } + if (!def) { + Blockly.Events.setGroup(event.group); + /** + * Create matching definition block. + * + * + * + * + * + * test + * + * + */ + var xml = goog.dom.createDom('xml'); + var block = goog.dom.createDom('block'); + block.setAttribute('type', this.defType_); + var xy = this.getRelativeToSurfaceXY(); + var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1); + var y = xy.y + Blockly.SNAP_RADIUS * 2; + block.setAttribute('x', x); + block.setAttribute('y', y); + var mutation = this.mutationToDom(); + block.appendChild(mutation); + var field = goog.dom.createDom('field'); + field.setAttribute('name', 'NAME'); + field.appendChild(document.createTextNode(this.getProcedureCall())); + block.appendChild(field); + xml.appendChild(block); + Blockly.Xml.domToWorkspace(xml, this.workspace); + Blockly.Events.setGroup(false); + } + } else if (event.type == Blockly.Events.BLOCK_DELETE) { + // Look for the case where a procedure definition has been deleted, + // leaving this block (a procedure call) orphaned. In this case, delete + // the orphan. + var name = this.getProcedureCall(); + var def = Blockly.Procedures.getDefinition(name, this.workspace); + if (!def) { + Blockly.Events.setGroup(event.group); + this.dispose(true, false); + Blockly.Events.setGroup(false); } } }, @@ -996,7 +860,8 @@ Blockly.Blocks['procedures_callnoreturn'] = { def && def.select(); }; options.push(option); - } + }, + defType_: 'procedures_defnoreturn' }; Blockly.Blocks['procedures_callreturn'] = { @@ -1022,8 +887,11 @@ Blockly.Blocks['procedures_callreturn'] = { updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_, mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom, domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation, - renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar, - customContextMenu: Blockly.Blocks['procedures_callnoreturn'].customContextMenu + getVarModels: Blockly.Blocks['procedures_callnoreturn'].getVarModels, + onchange: Blockly.Blocks['procedures_callnoreturn'].onchange, + customContextMenu: + Blockly.Blocks['procedures_callnoreturn'].customContextMenu, + defType_: 'procedures_defreturn' }; Blockly.Blocks['procedures_ifreturn'] = { @@ -1033,7 +901,7 @@ Blockly.Blocks['procedures_ifreturn'] = { */ init: function() { this.appendValueInput('CONDITION') - .setCheck(Blockly.Types.BOOLEAN.checkList) + .setCheck('Boolean') .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); this.appendValueInput('VALUE') .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); @@ -1066,7 +934,7 @@ Blockly.Blocks['procedures_ifreturn'] = { if (!this.hasReturnValue_) { this.removeInput('VALUE'); this.appendDummyInput('VALUE') - .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); + .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); } }, /** @@ -1075,7 +943,10 @@ Blockly.Blocks['procedures_ifreturn'] = { * @param {!Blockly.Events.Abstract} e Change event. * @this Blockly.Block */ - onchange: function(e) { + onchange: function(/* e */) { + if (!this.workspace.isDragging || this.workspace.isDragging()) { + return; // Don't change state at the start of a drag. + } var legal = false; // Is the block nested in a procedure? var block = this; @@ -1091,18 +962,24 @@ Blockly.Blocks['procedures_ifreturn'] = { if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) { this.removeInput('VALUE'); this.appendDummyInput('VALUE') - .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); + .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); this.hasReturnValue_ = false; } else if (block.type == 'procedures_defreturn' && !this.hasReturnValue_) { this.removeInput('VALUE'); this.appendValueInput('VALUE') - .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); + .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); this.hasReturnValue_ = true; } this.setWarningText(null); + if (!this.isInFlyout) { + this.setDisabled(false); + } } else { this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING); + if (!this.isInFlyout && !this.getInheritedDisabled()) { + this.setDisabled(true); + } } }, /** diff --git a/blocks/text.js b/blocks/text.js index 59abdd0..2252815 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -24,688 +24,878 @@ */ 'use strict'; -goog.provide('Blockly.Blocks.texts'); +goog.provide('Blockly.Blocks.texts'); // Deprecated +goog.provide('Blockly.Constants.Text'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.TEXTS_HUE + * @readonly */ -Blockly.Blocks.texts.HUE = 160; +Blockly.Constants.Text.HUE = 160; +/** @deprecated Use Blockly.Constants.Text.HUE */ +Blockly.Blocks.texts.HUE = Blockly.Constants.Text.HUE; -Blockly.Blocks['text'] = { - /** - * Block for text value. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - this.appendDummyInput() - .appendField(this.newQuote_(true)) - .appendField(new Blockly.FieldTextInput(''), 'TEXT') - .appendField(this.newQuote_(false)); - this.setOutput(true, Blockly.Types.TEXT.output); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - // Text block is trivial. Use tooltip of parent block if it exists. - this.setTooltip(function() { - var parent = thisBlock.getParent(); - return (parent && parent.getInputsInline() && parent.tooltip) || - Blockly.Msg.TEXT_TEXT_TOOLTIP; - }); +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for text value + { + "type": "text", + "message0": "%1", + "args0": [{ + "type": "field_input", + "name": "TEXT", + "text": "" + }], + "output": "String", + "colour": "%{BKY_TEXTS_HUE}", + "helpUrl": "%{BKY_TEXT_TEXT_HELPURL}", + "tooltip": "%{BKY_TEXT_TEXT_TOOLTIP}", + "extensions": [ + "text_quotes", + "parent_tooltip_when_inline" + ] }, - /** - * Create an image of an open or closed quote. - * @param {boolean} open True if open quote, false if closed. - * @return {!Blockly.FieldImage} The field image of the quote. - * @this Blockly.Block - * @private - */ - newQuote_: function(open) { - if (open == this.RTL) { - var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg=='; - } else { - var file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC'; - } - return new Blockly.FieldImage(file, 12, 12, '"'); + { + "type": "text_join", + "message0": "", + "output": "String", + "colour": "%{BKY_TEXTS_HUE}", + "helpUrl": "%{BKY_TEXT_JOIN_HELPURL}", + "tooltip": "%{BKY_TEXT_JOIN_TOOLTIP}", + "mutator": "text_join_mutator" + + }, + { + "type": "text_create_join_container", + "message0": "%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2", + "args0": [{ + "type": "input_dummy" + }, + { + "type": "input_statement", + "name": "STACK" + }], + "colour": "%{BKY_TEXTS_HUE}", + "tooltip": "%{BKY_TEXT_CREATE_JOIN_TOOLTIP}", + "enableContextMenu": false + }, + { + "type": "text_create_join_item", + "message0": "%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}", + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_TEXTS_HUE}", + "tooltip": "{%BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}", + "enableContextMenu": false + }, + { + "type": "text_append", + "message0": "%{BKY_TEXT_APPEND_TITLE}", + "args0": [{ + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_TEXT_APPEND_VARIABLE}" + }, + { + "type": "input_value", + "name": "TEXT" + }], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_TEXTS_HUE}", + "extensions": [ + "text_append_tooltip" + ] + }, + { + "type": "text_length", + "message0": "%{BKY_TEXT_LENGTH_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ['String', 'Array'] + } + ], + "output": 'Number', + "colour": "%{BKY_TEXTS_HUE}", + "tooltip": "%{BKY_TEXT_LENGTH_TOOLTIP}", + "helpUrl": "%{BKY_TEXT_LENGTH_HELPURL}" + }, + { + "type": "text_isEmpty", + "message0": "%{BKY_TEXT_ISEMPTY_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ['String', 'Array'] + } + ], + "output": 'Boolean', + "colour": "%{BKY_TEXTS_HUE}", + "tooltip": "%{BKY_TEXT_ISEMPTY_TOOLTIP}", + "helpUrl": "%{BKY_TEXT_ISEMPTY_HELPURL}" + }, + { + "type": "text_indexOf", + "message0": "%{BKY_TEXT_INDEXOF_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "String" + }, + { + "type": "field_dropdown", + "name": "END", + "options": [ + [ + "%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}", + "FIRST" + ], + [ + "%{BKY_TEXT_INDEXOF_OPERATOR_LAST}", + "LAST" + ] + ] + }, + { + "type": "input_value", + "name": "FIND", + "check": "String" + } + ], + "output": "Number", + "colour": "%{BKY_TEXTS_HUE}", + "helpUrl": "%{BKY_TEXT_INDEXOF_HELPURL}", + "inputsInline": true, + "extensions": [ + "text_indexOf_tooltip" + ] }, - /** @return {!string} Type of the block, text block always a string. */ - getBlockType: function() { - return Blockly.Types.TEXT; + { + "type": "text_charAt", + "message0": "%{BKY_TEXT_CHARAT_TITLE}", // "in text %1 %2" + "args0": [ + { + "type":"input_value", + "name": "VALUE", + "check": "String" + }, + { + "type": "field_dropdown", + "name": "WHERE", + "options": [ + ["%{BKY_TEXT_CHARAT_FROM_START}", "FROM_START"], + ["%{BKY_TEXT_CHARAT_FROM_END}", "FROM_END"], + ["%{BKY_TEXT_CHARAT_FIRST}", "FIRST"], + ["%{BKY_TEXT_CHARAT_LAST}", "LAST"], + ["%{BKY_TEXT_CHARAT_RANDOM}", "RANDOM"] + ] + } + ], + "output": "String", + "colour": "%{BKY_TEXTS_HUE}", + "helpUrl": "%{BKY_TEXT_CHARAT_HELPURL}", + "inputsInline": true, + "mutator": "text_charAt_mutator" } -}; +]); // END JSON EXTRACT (Do not delete this comment.) -Blockly.Blocks['text_join'] = { +Blockly.Blocks['text_getSubstring'] = { /** - * Block for creating a string made up of any number of elements of any type. + * Block for getting substring. * @this Blockly.Block */ init: function() { - this.setHelpUrl(Blockly.Msg.TEXT_JOIN_HELPURL); + this['WHERE_OPTIONS_1'] = [ + [Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START, 'FROM_START'], + [Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END, 'FROM_END'], + [Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST, 'FIRST'] + ]; + this['WHERE_OPTIONS_2'] = [ + [Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START, 'FROM_START'], + [Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END, 'FROM_END'], + [Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST, 'LAST'] + ]; + this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL); this.setColour(Blockly.Blocks.texts.HUE); - this.itemCount_ = 2; - this.updateShape_(); - this.setOutput(true, Blockly.Types.TEXT.output); - this.setMutator(new Blockly.Mutator(['text_create_join_item'])); - this.setTooltip(Blockly.Msg.TEXT_JOIN_TOOLTIP); + this.appendValueInput('STRING') + .setCheck('String') + .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT); + this.appendDummyInput('AT1'); + this.appendDummyInput('AT2'); + if (Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) { + this.appendDummyInput('TAIL') + .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL); + } + this.setInputsInline(true); + this.setOutput(true, 'String'); + this.updateAt_(1, true); + this.updateAt_(2, true); + this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP); }, /** - * Create XML to represent number of text inputs. + * Create XML to represent whether there are 'AT' inputs. * @return {!Element} XML storage element. * @this Blockly.Block */ mutationToDom: function() { var container = document.createElement('mutation'); - container.setAttribute('items', this.itemCount_); + var isAt1 = this.getInput('AT1').type == Blockly.INPUT_VALUE; + container.setAttribute('at1', isAt1); + var isAt2 = this.getInput('AT2').type == Blockly.INPUT_VALUE; + container.setAttribute('at2', isAt2); return container; }, /** - * Parse XML to restore the text inputs. + * Parse XML to restore the 'AT' inputs. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function(xmlElement) { - this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10); - this.updateShape_(); - }, - /** - * Populate the mutator's dialog with this block's components. - * @param {!Blockly.Workspace} workspace Mutator's workspace. - * @return {!Blockly.Block} Root block in mutator. - * @this Blockly.Block - */ - decompose: function(workspace) { - var containerBlock = workspace.newBlock('text_create_join_container'); - containerBlock.initSvg(); - var connection = containerBlock.getInput('STACK').connection; - for (var i = 0; i < this.itemCount_; i++) { - var itemBlock = workspace.newBlock('text_create_join_item'); - itemBlock.initSvg(); - connection.connect(itemBlock.previousConnection); - connection = itemBlock.nextConnection; - } - return containerBlock; - }, - /** - * Reconfigure this block based on the mutator dialog's components. - * @param {!Blockly.Block} containerBlock Root block in mutator. - * @this Blockly.Block - */ - compose: function(containerBlock) { - var itemBlock = containerBlock.getInputTargetBlock('STACK'); - // Count number of inputs. - var connections = []; - while (itemBlock) { - connections.push(itemBlock.valueConnection_); - itemBlock = itemBlock.nextConnection && - itemBlock.nextConnection.targetBlock(); - } - // Disconnect any children that don't belong. - for (var i = 0; i < this.itemCount_; i++) { - var connection = this.getInput('ADD' + i).connection.targetConnection; - if (connection && connections.indexOf(connection) == -1) { - connection.disconnect(); - } - } - this.itemCount_ = connections.length; - this.updateShape_(); - // Reconnect any child blocks. - for (var i = 0; i < this.itemCount_; i++) { - Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i); - } - }, - /** - * Store pointers to any connected child blocks. - * @param {!Blockly.Block} containerBlock Root block in mutator. - * @this Blockly.Block - */ - saveConnections: function(containerBlock) { - var itemBlock = containerBlock.getInputTargetBlock('STACK'); - var i = 0; - while (itemBlock) { - var input = this.getInput('ADD' + i); - itemBlock.valueConnection_ = input && input.connection.targetConnection; - i++; - itemBlock = itemBlock.nextConnection && - itemBlock.nextConnection.targetBlock(); - } + var isAt1 = (xmlElement.getAttribute('at1') == 'true'); + var isAt2 = (xmlElement.getAttribute('at2') == 'true'); + this.updateAt_(1, isAt1); + this.updateAt_(2, isAt2); }, /** - * Modify this block to have the correct number of inputs. + * Create or delete an input for a numeric index. + * This block has two such inputs, independant of each other. + * @param {number} n Specify first or second input (1 or 2). + * @param {boolean} isAt True if the input should exist. * @private * @this Blockly.Block */ - updateShape_: function() { - if (this.itemCount_ && this.getInput('EMPTY')) { - this.removeInput('EMPTY'); - } else if (!this.itemCount_ && !this.getInput('EMPTY')) { - this.appendDummyInput('EMPTY') - .appendField(this.newQuote_(true)) - .appendField(this.newQuote_(false)); - } - // Add new inputs. - for (var i = 0; i < this.itemCount_; i++) { - if (!this.getInput('ADD' + i)) { - var input = this.appendValueInput('ADD' + i); - if (i == 0) { - input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH); - } + updateAt_: function(n, isAt) { + // Create or delete an input for the numeric index. + // Destroy old 'AT' and 'ORDINAL' inputs. + this.removeInput('AT' + n); + this.removeInput('ORDINAL' + n, true); + // Create either a value 'AT' input or a dummy input. + if (isAt) { + this.appendValueInput('AT' + n).setCheck('Number'); + if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { + this.appendDummyInput('ORDINAL' + n) + .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); } + } else { + this.appendDummyInput('AT' + n); } - // Remove deleted inputs. - while (this.getInput('ADD' + i)) { - this.removeInput('ADD' + i); - i++; + // Move tail, if present, to end of block. + if (n == 2 && Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) { + this.removeInput('TAIL', true); + this.appendDummyInput('TAIL') + .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL); } - }, - newQuote_: Blockly.Blocks['text'].newQuote_, - /** @return {!string} Type of the block, text join always a string. */ - getBlockType: function() { - return Blockly.Types.TEXT; - } -}; + var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n], + function(value) { + var newAt = (value == 'FROM_START') || (value == 'FROM_END'); + // The 'isAt' variable is available due to this function being a + // closure. + if (newAt != isAt) { + var block = this.sourceBlock_; + block.updateAt_(n, newAt); + // This menu has been destroyed and replaced. + // Update the replacement. + block.setFieldValue(value, 'WHERE' + n); + return null; + } + return undefined; + }); -Blockly.Blocks['text_create_join_container'] = { - /** - * Mutator block for container. - * @this Blockly.Block - */ - init: function() { - this.setColour(Blockly.Blocks.texts.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN); - this.appendStatementInput('STACK'); - this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP); - this.contextMenu = false; + this.getInput('AT' + n) + .appendField(menu, 'WHERE' + n); + if (n == 1) { + this.moveInputBefore('AT1', 'AT2'); + } } }; -Blockly.Blocks['text_create_join_item'] = { +Blockly.Blocks['text_changeCase'] = { /** - * Mutator block for add items. + * Block for changing capitalization. * @this Blockly.Block */ init: function() { + var OPERATORS = [ + [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE, 'UPPERCASE'], + [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE, 'LOWERCASE'], + [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE, 'TITLECASE'] + ]; + this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL); this.setColour(Blockly.Blocks.texts.HUE); - this.appendDummyInput() - .appendField(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM); - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setTooltip(Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP); - this.contextMenu = false; + this.appendValueInput('TEXT') + .setCheck('String') + .appendField(new Blockly.FieldDropdown(OPERATORS), 'CASE'); + this.setOutput(true, 'String'); + this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP); } }; -Blockly.Blocks['text_append'] = { +Blockly.Blocks['text_trim'] = { /** - * Block for appending to a variable in place. + * Block for trimming spaces. * @this Blockly.Block */ init: function() { - this.setHelpUrl(Blockly.Msg.TEXT_APPEND_HELPURL); + var OPERATORS = [ + [Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH, 'BOTH'], + [Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT, 'LEFT'], + [Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT, 'RIGHT'] + ]; + this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL); this.setColour(Blockly.Blocks.texts.HUE); this.appendValueInput('TEXT') - .appendField(Blockly.Msg.TEXT_APPEND_TO) - .appendField(new Blockly.FieldVariable( - Blockly.Msg.TEXT_APPEND_VARIABLE), 'VAR') - .appendField(Blockly.Msg.TEXT_APPEND_APPENDTEXT); - this.setPreviousStatement(true); - this.setNextStatement(true); - // Assign 'this' to a variable for use in the tooltip closure below. - var thisBlock = this; - this.setTooltip(function() { - return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1', - thisBlock.getFieldValue('VAR')); - }); - }, - /** - * Set's the type of the variable selected in the drop down list. As there is - * only one possible option, the variable input is not really checked. - * @param {!string} varName Name of the variable to check type. - * @return {string} String to indicate the variable type. - */ - getVarType: function(varName) { - return Blockly.Types.TEXT; + .setCheck('String') + .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE'); + this.setOutput(true, 'String'); + this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP); } }; -Blockly.Blocks['text_length'] = { +Blockly.Blocks['text_print'] = { /** - * Block for string length. + * Block for print statement. * @this Blockly.Block */ init: function() { this.jsonInit({ - "message0": Blockly.Msg.TEXT_LENGTH_TITLE, + "message0": Blockly.Msg.TEXT_PRINT_TITLE, "args0": [ { "type": "input_value", - "name": "VALUE", - "check": Blockly.Types.TEXT.checkList.concat('Array'), + "name": "TEXT" } ], - "output": Blockly.Types.NUMBER.output, + "previousStatement": null, + "nextStatement": null, "colour": Blockly.Blocks.texts.HUE, - "tooltip": Blockly.Msg.TEXT_LENGTH_TOOLTIP, - "helpUrl": Blockly.Msg.TEXT_LENGTH_HELPURL + "tooltip": Blockly.Msg.TEXT_PRINT_TOOLTIP, + "helpUrl": Blockly.Msg.TEXT_PRINT_HELPURL }); - }, - /** @return {!string} Type of the block, text length always an integer. */ - getBlockType: function() { - return Blockly.Types.NUMBER; } }; -Blockly.Blocks['text_isEmpty'] = { +Blockly.Blocks['text_prompt_ext'] = { /** - * Block for is the string null? + * Block for prompt function (external message). * @this Blockly.Block */ init: function() { - this.jsonInit({ - "message0": Blockly.Msg.TEXT_ISEMPTY_TITLE, - "args0": [ - { - "type": "input_value", - "name": "VALUE", - "check": Blockly.Types.TEXT.checkList.concat('Array'), - } - ], - "output": Blockly.Types.BOOLEAN.output, - "colour": Blockly.Blocks.texts.HUE, - "tooltip": Blockly.Msg.TEXT_ISEMPTY_TOOLTIP, - "helpUrl": Blockly.Msg.TEXT_ISEMPTY_HELPURL + var TYPES = [ + [Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, 'TEXT'], + [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, 'NUMBER'] + ]; + this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL); + this.setColour(Blockly.Blocks.texts.HUE); + // Assign 'this' to a variable for use in the closures below. + var thisBlock = this; + var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) { + thisBlock.updateType_(newOp); + }); + this.appendValueInput('TEXT') + .appendField(dropdown, 'TYPE'); + this.setOutput(true, 'String'); + this.setTooltip(function() { + return (thisBlock.getFieldValue('TYPE') == 'TEXT') ? + Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT : + Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER; }); }, - /** @return {!string} Type of the block, check always returns a boolean. */ - getBlockType: function() { - return Blockly.Types.BOOLEAN; - } -}; - -Blockly.Blocks['text_indexOf'] = { - /** - * Block for finding a substring in the text. - * @this Blockly.Block - */ - init: function() { - var OPERATORS = - [[Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST, 'FIRST'], - [Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST, 'LAST']]; - this.setHelpUrl(Blockly.Msg.TEXT_INDEXOF_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - this.setOutput(true, Blockly.Types.NUMBER.output); - this.appendValueInput('VALUE') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT); - this.appendValueInput('FIND') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(new Blockly.FieldDropdown(OPERATORS), 'END'); - if (Blockly.Msg.TEXT_INDEXOF_TAIL) { - this.appendDummyInput().appendField(Blockly.Msg.TEXT_INDEXOF_TAIL); - } - this.setInputsInline(true); - this.setTooltip(Blockly.Msg.TEXT_INDEXOF_TOOLTIP); - } -}; - -Blockly.Blocks['text_charAt'] = { /** - * Block for getting a character from the string. + * Modify this block to have the correct output type. + * @param {string} newOp Either 'TEXT' or 'NUMBER'. + * @private * @this Blockly.Block */ - init: function() { - this.WHERE_OPTIONS = - [[Blockly.Msg.TEXT_CHARAT_FROM_START, 'FROM_START'], - [Blockly.Msg.TEXT_CHARAT_FROM_END, 'FROM_END'], - [Blockly.Msg.TEXT_CHARAT_FIRST, 'FIRST'], - [Blockly.Msg.TEXT_CHARAT_LAST, 'LAST'], - [Blockly.Msg.TEXT_CHARAT_RANDOM, 'RANDOM']]; - this.setHelpUrl(Blockly.Msg.TEXT_CHARAT_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - this.setOutput(true, Blockly.Types.TEXT.output); - this.appendValueInput('VALUE') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT); - this.appendDummyInput('AT'); - this.setInputsInline(true); - this.updateAt_(true); - this.setTooltip(Blockly.Msg.TEXT_CHARAT_TOOLTIP); + updateType_: function(newOp) { + this.outputConnection.setCheck(newOp == 'NUMBER' ? 'Number' : 'String'); }, /** - * Create XML to represent whether there is an 'AT' input. + * Create XML to represent the output type. * @return {!Element} XML storage element. * @this Blockly.Block */ mutationToDom: function() { var container = document.createElement('mutation'); - var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE; - container.setAttribute('at', isAt); + container.setAttribute('type', this.getFieldValue('TYPE')); return container; }, /** - * Parse XML to restore the 'AT' input. + * Parse XML to restore the output type. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function(xmlElement) { - // Note: Until January 2013 this block did not have mutations, - // so 'at' defaults to true. - var isAt = (xmlElement.getAttribute('at') != 'false'); - this.updateAt_(isAt); - }, - /** - * Create or delete an input for the numeric index. - * @param {boolean} isAt True if the input should exist. - * @private - * @this Blockly.Block - */ - updateAt_: function(isAt) { - // Destroy old 'AT' and 'ORDINAL' inputs. - this.removeInput('AT'); - this.removeInput('ORDINAL', true); - // Create either a value 'AT' input or a dummy input. - if (isAt) { - this.appendValueInput('AT').setCheck(Blockly.Types.NUMBER.checkList); - if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { - this.appendDummyInput('ORDINAL') - .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); - } - } else { - this.appendDummyInput('AT'); - } - if (Blockly.Msg.TEXT_CHARAT_TAIL) { - this.removeInput('TAIL', true); - this.appendDummyInput('TAIL') - .appendField(Blockly.Msg.TEXT_CHARAT_TAIL); - } - var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) { - var newAt = (value == 'FROM_START') || (value == 'FROM_END'); - // The 'isAt' variable is available due to this function being a closure. - if (newAt != isAt) { - var block = this.sourceBlock_; - block.updateAt_(newAt); - // This menu has been destroyed and replaced. Update the replacement. - block.setFieldValue(value, 'WHERE'); - return null; - } - return undefined; - }); - this.getInput('AT').appendField(menu, 'WHERE'); + this.updateType_(xmlElement.getAttribute('type')); } }; -Blockly.Blocks['text_getSubstring'] = { +Blockly.Blocks['text_prompt'] = { /** - * Block for getting substring. + * Block for prompt function (internal message). + * The 'text_prompt_ext' block is preferred as it is more flexible. * @this Blockly.Block */ init: function() { - this['WHERE_OPTIONS_1'] = - [[Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START, 'FROM_START'], - [Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END, 'FROM_END'], - [Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST, 'FIRST']]; - this['WHERE_OPTIONS_2'] = - [[Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START, 'FROM_START'], - [Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END, 'FROM_END'], - [Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST, 'LAST']]; - this.setHelpUrl(Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL); + this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN); + var TYPES = [ + [Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, 'TEXT'], + [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, 'NUMBER'] + ]; + + // Assign 'this' to a variable for use in the closures below. + var thisBlock = this; + this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL); this.setColour(Blockly.Blocks.texts.HUE); - this.appendValueInput('STRING') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT); - this.appendDummyInput('AT1'); - this.appendDummyInput('AT2'); - if (Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) { - this.appendDummyInput('TAIL') - .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL); - } - this.setInputsInline(true); - this.setOutput(true, Blockly.Types.TEXT.output); - this.updateAt_(1, true); - this.updateAt_(2, true); - this.setTooltip(Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP); - }, - /** - * Create XML to represent whether there are 'AT' inputs. - * @return {!Element} XML storage element. - * @this Blockly.Block - */ - mutationToDom: function() { - var container = document.createElement('mutation'); - var isAt1 = this.getInput('AT1').type == Blockly.INPUT_VALUE; - container.setAttribute('at1', isAt1); - var isAt2 = this.getInput('AT2').type == Blockly.INPUT_VALUE; - container.setAttribute('at2', isAt2); - return container; - }, - /** - * Parse XML to restore the 'AT' inputs. - * @param {!Element} xmlElement XML storage element. - * @this Blockly.Block - */ - domToMutation: function(xmlElement) { - var isAt1 = (xmlElement.getAttribute('at1') == 'true'); - var isAt2 = (xmlElement.getAttribute('at2') == 'true'); - this.updateAt_(1, isAt1); - this.updateAt_(2, isAt2); - }, - /** - * Create or delete an input for a numeric index. - * This block has two such inputs, independant of each other. - * @param {number} n Specify first or second input (1 or 2). - * @param {boolean} isAt True if the input should exist. - * @private - * @this Blockly.Block - */ - updateAt_: function(n, isAt) { - // Create or delete an input for the numeric index. - // Destroy old 'AT' and 'ORDINAL' inputs. - this.removeInput('AT' + n); - this.removeInput('ORDINAL' + n, true); - // Create either a value 'AT' input or a dummy input. - if (isAt) { - this.appendValueInput('AT' + n).setCheck(Blockly.Types.NUMBER.checkList); - if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { - this.appendDummyInput('ORDINAL' + n) - .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); - } - } else { - this.appendDummyInput('AT' + n); - } - // Move tail, if present, to end of block. - if (n == 2 && Blockly.Msg.TEXT_GET_SUBSTRING_TAIL) { - this.removeInput('TAIL', true); - this.appendDummyInput('TAIL') - .appendField(Blockly.Msg.TEXT_GET_SUBSTRING_TAIL); - } - var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n], - function(value) { - var newAt = (value == 'FROM_START') || (value == 'FROM_END'); - // The 'isAt' variable is available due to this function being a closure. - if (newAt != isAt) { - var block = this.sourceBlock_; - block.updateAt_(n, newAt); - // This menu has been destroyed and replaced. Update the replacement. - block.setFieldValue(value, 'WHERE' + n); - return null; - } - return undefined; + var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) { + thisBlock.updateType_(newOp); }); - this.getInput('AT' + n) - .appendField(menu, 'WHERE' + n); - if (n == 1) { - this.moveInputBefore('AT1', 'AT2'); - } - } + this.appendDummyInput() + .appendField(dropdown, 'TYPE') + .appendField(this.newQuote_(true)) + .appendField(new Blockly.FieldTextInput(''), 'TEXT') + .appendField(this.newQuote_(false)); + this.setOutput(true, 'String'); + this.setTooltip(function() { + return (thisBlock.getFieldValue('TYPE') == 'TEXT') ? + Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT : + Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER; + }); + }, + updateType_: Blockly.Blocks['text_prompt_ext'].updateType_, + mutationToDom: Blockly.Blocks['text_prompt_ext'].mutationToDom, + domToMutation: Blockly.Blocks['text_prompt_ext'].domToMutation }; -Blockly.Blocks['text_changeCase'] = { +Blockly.Blocks['text_count'] = { /** - * Block for changing capitalization. + * Block for counting how many times one string appears within another string. * @this Blockly.Block */ init: function() { - var OPERATORS = - [[Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE, 'UPPERCASE'], - [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE, 'LOWERCASE'], - [Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE, 'TITLECASE']]; - this.setHelpUrl(Blockly.Msg.TEXT_CHANGECASE_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - this.appendValueInput('TEXT') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(new Blockly.FieldDropdown(OPERATORS), 'CASE'); - this.setOutput(true, Blockly.Types.TEXT.output); - this.setTooltip(Blockly.Msg.TEXT_CHANGECASE_TOOLTIP); + this.jsonInit({ + "message0": Blockly.Msg.TEXT_COUNT_MESSAGE0, + "args0": [ + { + "type": "input_value", + "name": "SUB", + "check": "String" + }, + { + "type": "input_value", + "name": "TEXT", + "check": "String" + } + ], + "output": "Number", + "inputsInline": true, + "colour": Blockly.Blocks.texts.HUE, + "tooltip": Blockly.Msg.TEXT_COUNT_TOOLTIP, + "helpUrl": Blockly.Msg.TEXT_COUNT_HELPURL + }); } }; -Blockly.Blocks['text_trim'] = { +Blockly.Blocks['text_replace'] = { /** - * Block for trimming spaces. + * Block for replacing one string with another in the text. * @this Blockly.Block */ init: function() { - var OPERATORS = - [[Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH, 'BOTH'], - [Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT, 'LEFT'], - [Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT, 'RIGHT']]; - this.setHelpUrl(Blockly.Msg.TEXT_TRIM_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - this.appendValueInput('TEXT') - .setCheck(Blockly.Types.TEXT.checkList) - .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE'); - this.setOutput(true, Blockly.Types.TEXT.output); - this.setTooltip(Blockly.Msg.TEXT_TRIM_TOOLTIP); - }, - /** Assigns a type to the block, trim always takes and returns a string. */ - getBlockType: function() { - return Blockly.Types.TEXT; + this.jsonInit({ + "message0": Blockly.Msg.TEXT_REPLACE_MESSAGE0, + "args0": [ + { + "type": "input_value", + "name": "FROM", + "check": "String" + }, + { + "type": "input_value", + "name": "TO", + "check": "String" + }, + { + "type": "input_value", + "name": "TEXT", + "check": "String" + } + ], + "output": "String", + "inputsInline": true, + "colour": Blockly.Blocks.texts.HUE, + "tooltip": Blockly.Msg.TEXT_REPLACE_TOOLTIP, + "helpUrl": Blockly.Msg.TEXT_REPLACE_HELPURL + }); } }; -Blockly.Blocks['text_print'] = { +Blockly.Blocks['text_reverse'] = { /** - * Block for print statement. + * Block for reversing a string. * @this Blockly.Block */ init: function() { this.jsonInit({ - "message0": Blockly.Msg.TEXT_PRINT_TITLE, + "message0": Blockly.Msg.TEXT_REVERSE_MESSAGE0, "args0": [ { "type": "input_value", - "name": "TEXT" + "name": "TEXT", + "check": "String" } ], - "previousStatement": null, - "nextStatement": null, + "output": "String", + "inputsInline": true, "colour": Blockly.Blocks.texts.HUE, - "tooltip": Blockly.Msg.TEXT_PRINT_TOOLTIP, - "helpUrl": Blockly.Msg.TEXT_PRINT_HELPURL + "tooltip": Blockly.Msg.TEXT_REVERSE_TOOLTIP, + "helpUrl": Blockly.Msg.TEXT_REVERSE_HELPURL }); } }; -Blockly.Blocks['text_prompt_ext'] = { +/** + * + * @mixin + * @package + * @readonly + */ +Blockly.Constants.Text.QUOTE_IMAGE_MIXIN = { /** - * Block for prompt function (external message). + * Image data URI of an LTR opening double quote (same as RTL closing double quote). + * @readonly + */ + QUOTE_IMAGE_LEFT_DATAURI: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + + 'n0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY' + + '1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1' + + 'HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMf' + + 'z9AylsaRRgGzvZAAAAAElFTkSuQmCC', + /** + * Image data URI of an LTR closing double quote (same as RTL opening double quote). + * @readonly + */ + QUOTE_IMAGE_RIGHT_DATAURI: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' + + 'qUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhg' + + 'gONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvB' + + 'O3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5Aos' + + 'lLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==', + /** + * Pixel width of QUOTE_IMAGE_LEFT_DATAURI and QUOTE_IMAGE_RIGHT_DATAURI. + * @readonly + */ + QUOTE_IMAGE_WIDTH: 12, + /** + * Pixel height of QUOTE_IMAGE_LEFT_DATAURI and QUOTE_IMAGE_RIGHT_DATAURI. + * @readonly + */ + QUOTE_IMAGE_HEIGHT: 12, + + /** + * Inserts appropriate quote images before and after the named field. + * @param {string} fieldName The name of the field to wrap with quotes. * @this Blockly.Block */ - init: function() { - var TYPES = - [[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, Blockly.Types.TEXT.output], - [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, Blockly.Types.NUMBER.output]]; - this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - // Assign 'this' to a variable for use in the closures below. - var thisBlock = this; - var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) { - thisBlock.updateType_(newOp); - }); - this.appendValueInput('TEXT') - .appendField(dropdown, 'TYPE'); - this.setOutput(true, Blockly.Types.TEXT.output); - this.setTooltip(function() { - return (thisBlock.getFieldValue('TYPE') == Blockly.Types.TEXT.output) ? - Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT : - Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER; - }); + quoteField_: function(fieldName) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (fieldName == field.name) { + input.insertFieldAt(j, this.newQuote_(true)); + input.insertFieldAt(j + 2, this.newQuote_(false)); + return; + } + } + } + console.warn('field named "' + fieldName + '" not found in ' + this.toDevString()); }, + /** - * Modify this block to have the correct output type. - * @param {string} newOp Either 'TEXT' or 'NUMBER'. - * @private + * A helper function that generates a FieldImage of an opening or + * closing double quote. The selected quote will be adapted for RTL blocks. + * @param {boolean} open If the image should be open quote (“ in LTR). + * Otherwise, a closing quote is used (” in LTR). + * @returns {!Blockly.FieldImage} The new field. * @this Blockly.Block */ - updateType_: function(newOp) { - this.outputConnection.setCheck(newOp == Blockly.Types.NUMBER.output ? - Blockly.Types.NUMBER.checkList : Blockly.Types.TEXT.checkList); - }, + newQuote_: function(open) { + var isLeft = this.RTL? !open : open; + var dataUri = isLeft ? + this.QUOTE_IMAGE_LEFT_DATAURI : + this.QUOTE_IMAGE_RIGHT_DATAURI; + return new Blockly.FieldImage( + dataUri, + this.QUOTE_IMAGE_WIDTH, + this.QUOTE_IMAGE_HEIGHT, + isLeft ? '\u201C' : '\u201D'); + } +}; + +/** Wraps TEXT field with images of double quote characters. */ +Blockly.Constants.Text.TEXT_QUOTES_EXTENSION = function() { + this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN); + this.quoteField_('TEXT'); +}; + +/** + * Mixin for mutator functions in the 'text_join_mutator' extension. + * @mixin + * @augments Blockly.Block + * @package + */ +Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN = { /** - * Create XML to represent the output type. + * Create XML to represent number of text inputs. * @return {!Element} XML storage element. * @this Blockly.Block */ mutationToDom: function() { var container = document.createElement('mutation'); - container.setAttribute('type', this.getFieldValue('TYPE')); + container.setAttribute('items', this.itemCount_); return container; }, /** - * Parse XML to restore the output type. + * Parse XML to restore the text inputs. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function(xmlElement) { - this.updateType_(xmlElement.getAttribute('type')); + this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10); + this.updateShape_(); }, - /** @return {!string} Type of the block, prompt always returns a string. */ - getBlockType: function() { - return (this.getFieldValue('TYPE') == Blockly.Types.TEXT.output) ? - Blockly.Types.TEXT : Blockly.Types.NUMBER; + /** + * Populate the mutator's dialog with this block's components. + * @param {!Blockly.Workspace} workspace Mutator's workspace. + * @return {!Blockly.Block} Root block in mutator. + * @this Blockly.Block + */ + decompose: function(workspace) { + var containerBlock = workspace.newBlock('text_create_join_container'); + containerBlock.initSvg(); + var connection = containerBlock.getInput('STACK').connection; + for (var i = 0; i < this.itemCount_; i++) { + var itemBlock = workspace.newBlock('text_create_join_item'); + itemBlock.initSvg(); + connection.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + } + return containerBlock; + }, + /** + * Reconfigure this block based on the mutator dialog's components. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + compose: function(containerBlock) { + var itemBlock = containerBlock.getInputTargetBlock('STACK'); + // Count number of inputs. + var connections = []; + while (itemBlock) { + connections.push(itemBlock.valueConnection_); + itemBlock = itemBlock.nextConnection && + itemBlock.nextConnection.targetBlock(); + } + // Disconnect any children that don't belong. + for (var i = 0; i < this.itemCount_; i++) { + var connection = this.getInput('ADD' + i).connection.targetConnection; + if (connection && connections.indexOf(connection) == -1) { + connection.disconnect(); + } + } + this.itemCount_ = connections.length; + this.updateShape_(); + // Reconnect any child blocks. + for (var i = 0; i < this.itemCount_; i++) { + Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i); + } + }, + /** + * Store pointers to any connected child blocks. + * @param {!Blockly.Block} containerBlock Root block in mutator. + * @this Blockly.Block + */ + saveConnections: function(containerBlock) { + var itemBlock = containerBlock.getInputTargetBlock('STACK'); + var i = 0; + while (itemBlock) { + var input = this.getInput('ADD' + i); + itemBlock.valueConnection_ = input && input.connection.targetConnection; + i++; + itemBlock = itemBlock.nextConnection && + itemBlock.nextConnection.targetBlock(); + } + }, + /** + * Modify this block to have the correct number of inputs. + * @private + * @this Blockly.Block + */ + updateShape_: function() { + if (this.itemCount_ && this.getInput('EMPTY')) { + this.removeInput('EMPTY'); + } else if (!this.itemCount_ && !this.getInput('EMPTY')) { + this.appendDummyInput('EMPTY') + .appendField(this.newQuote_(true)) + .appendField(this.newQuote_(false)); + } + // Add new inputs. + for (var i = 0; i < this.itemCount_; i++) { + if (!this.getInput('ADD' + i)) { + var input = this.appendValueInput('ADD' + i); + if (i == 0) { + input.appendField(Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH); + } + } + } + // Remove deleted inputs. + while (this.getInput('ADD' + i)) { + this.removeInput('ADD' + i); + i++; + } } }; -Blockly.Blocks['text_prompt'] = { +// Performs final setup of a text_join block. +Blockly.Constants.Text.TEXT_JOIN_EXTENSION = function() { + // Add the quote mixin for the itemCount_ = 0 case. + this.mixin(Blockly.Constants.Text.QUOTE_IMAGE_MIXIN); + // Initialize the mutator values. + this.itemCount_ = 2; + this.updateShape_(); + // Configure the mutator UI. + this.setMutator(new Blockly.Mutator(['text_create_join_item'])); +}; + +Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION = function() { + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + if (Blockly.Msg.TEXT_APPEND_TOOLTIP) { + return Blockly.Msg.TEXT_APPEND_TOOLTIP.replace('%1', + thisBlock.getFieldValue('VAR')); + } + return ''; + }); +}; + +Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION = function() { + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + return Blockly.Msg.TEXT_INDEXOF_TOOLTIP.replace('%1', + thisBlock.workspace.options.oneBasedIndex ? '0' : '-1'); + }); +}; + +/** + * Mixin for mutator functions in the 'text_charAt_mutator' extension. + * @mixin + * @augments Blockly.Block + * @package + */ +Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN = { /** - * Block for prompt function (internal message). - * The 'text_prompt_ext' block is preferred as it is more flexible. + * Create XML to represent whether there is an 'AT' input. + * @return {!Element} XML storage element. * @this Blockly.Block */ - init: function() { - var TYPES = - [[Blockly.Msg.TEXT_PROMPT_TYPE_TEXT, Blockly.Types.TEXT.output], - [Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER, Blockly.Types.NUMBER.output]]; - // Assign 'this' to a variable for use in the closure below. - var thisBlock = this; - this.setHelpUrl(Blockly.Msg.TEXT_PROMPT_HELPURL); - this.setColour(Blockly.Blocks.texts.HUE); - var dropdown = new Blockly.FieldDropdown(TYPES, function(newOp) { - thisBlock.updateType_(newOp); - }); - this.appendDummyInput() - .appendField(dropdown, 'TYPE') - .appendField(this.newQuote_(true)) - .appendField(new Blockly.FieldTextInput(''), 'TEXT') - .appendField(this.newQuote_(false)); - this.setOutput(true, Blockly.Types.TEXT.output); - this.setTooltip(function() { - return (thisBlock.getFieldValue('TYPE') == Blockly.Types.TEXT.output) ? - Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT : - Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER; - }); + mutationToDom: function() { + var container = document.createElement('mutation'); + container.setAttribute('at', !!this.isAt_); + return container; }, - newQuote_: Blockly.Blocks['text'].newQuote_, - updateType_: Blockly.Blocks['text_prompt_ext'].updateType_, - mutationToDom: Blockly.Blocks['text_prompt_ext'].mutationToDom, - domToMutation: Blockly.Blocks['text_prompt_ext'].domToMutation, - /** Assigns a type to the block, prompt always returns a string. */ - getBlockType: function() { - return (this.getFieldValue('TYPE') == Blockly.Types.NUMBER.output) ? - Blockly.Types.NUMBER : Blockly.Types.TEXT; + /** + * Parse XML to restore the 'AT' input. + * @param {!Element} xmlElement XML storage element. + * @this Blockly.Block + */ + domToMutation: function(xmlElement) { + // Note: Until January 2013 this block did not have mutations, + // so 'at' defaults to true. + var isAt = (xmlElement.getAttribute('at') != 'false'); + this.updateAt_(isAt); + }, + /** + * Create or delete an input for the numeric index. + * @param {boolean} isAt True if the input should exist. + * @private + * @this Blockly.Block + */ + updateAt_: function(isAt) { + // Destroy old 'AT' and 'ORDINAL' inputs. + this.removeInput('AT', true); + this.removeInput('ORDINAL', true); + // Create either a value 'AT' input or a dummy input. + if (isAt) { + this.appendValueInput('AT').setCheck('Number'); + if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { + this.appendDummyInput('ORDINAL') + .appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX); + } + } + if (Blockly.Msg.TEXT_CHARAT_TAIL) { + this.removeInput('TAIL', true); + this.appendDummyInput('TAIL') + .appendField(Blockly.Msg.TEXT_CHARAT_TAIL); + } + + this.isAt_ = isAt; } }; + +// Does the initial mutator update of text_charAt and adds the tooltip +Blockly.Constants.Text.TEXT_CHARAT_EXTENSION = function() { + var dropdown = this.getField('WHERE'); + dropdown.setValidator(function(value) { + var newAt = (value == 'FROM_START') || (value == 'FROM_END'); + if (newAt != this.isAt_) { + var block = this.sourceBlock_; + block.updateAt_(newAt); + // This menu has been destroyed and replaced. Update the replacement. + block.setFieldValue(value, 'WHERE'); + return null; + } + return undefined; + }); + this.updateAt_(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function() { + var where = thisBlock.getFieldValue('WHERE'); + var tooltip = Blockly.Msg.TEXT_CHARAT_TOOLTIP; + if (where == 'FROM_START' || where == 'FROM_END') { + var msg = (where == 'FROM_START') ? + Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP : + Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP; + if (msg) { + tooltip += ' ' + msg.replace('%1', + thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0'); + } + } + return tooltip; + }); +}; + +Blockly.Extensions.register('text_indexOf_tooltip', + Blockly.Constants.Text.TEXT_INDEXOF_TOOLTIP_EXTENSION); + +Blockly.Extensions.register('text_quotes', + Blockly.Constants.Text.TEXT_QUOTES_EXTENSION); + +Blockly.Extensions.register('text_append_tooltip', + Blockly.Constants.Text.TEXT_APPEND_TOOLTIP_EXTENSION); + +Blockly.Extensions.registerMutator('text_join_mutator', + Blockly.Constants.Text.TEXT_JOIN_MUTATOR_MIXIN, + Blockly.Constants.Text.TEXT_JOIN_EXTENSION); + +Blockly.Extensions.registerMutator('text_charAt_mutator', + Blockly.Constants.Text.TEXT_CHARAT_MUTATOR_MIXIN, + Blockly.Constants.Text.TEXT_CHARAT_EXTENSION); diff --git a/blocks/variables.js b/blocks/variables.js index 96f0a05..5cfa0d0 100644 --- a/blocks/variables.js +++ b/blocks/variables.js @@ -20,108 +20,112 @@ /** * @fileoverview Variable blocks for Blockly. + + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. * @author fraser@google.com (Neil Fraser) */ 'use strict'; -goog.provide('Blockly.Blocks.variables'); +goog.provide('Blockly.Blocks.variables'); // Deprecated. +goog.provide('Blockly.Constants.Variables'); goog.require('Blockly.Blocks'); -goog.require('Blockly.Types'); +goog.require('Blockly'); /** * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.VARIABLES_HUE. + * @readonly */ -Blockly.Blocks.variables.HUE = 330; +Blockly.Constants.Variables.HUE = 330; +/** @deprecated Use Blockly.Constants.Variables.HUE */ +Blockly.Blocks.variables.HUE = Blockly.Constants.Variables.HUE; -Blockly.Blocks['variables_get'] = { - /** - * Block for variable getter. - * @this Blockly.Block - */ - init: function() { - this.setHelpUrl(Blockly.Msg.VARIABLES_GET_HELPURL); - this.setColour(Blockly.Blocks.variables.HUE); - this.appendDummyInput() - .appendField(new Blockly.FieldVariable( - Blockly.Msg.VARIABLES_DEFAULT_NAME), 'VAR'); - this.setOutput(true); - this.setTooltip(Blockly.Msg.VARIABLES_GET_TOOLTIP); - this.contextMenuMsg_ = Blockly.Msg.VARIABLES_GET_CREATE_SET; +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for variable getter. + { + "type": "variables_get", + "message0": "%1", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_VARIABLES_DEFAULT_NAME}" + } + ], + "output": null, + "colour": "%{BKY_VARIABLES_HUE}", + "helpUrl": "%{BKY_VARIABLES_GET_HELPURL}", + "tooltip": "%{BKY_VARIABLES_GET_TOOLTIP}", + "extensions": ["contextMenu_variableSetterGetter"] }, - contextMenuType_: 'variables_set', + // Block for variable setter. + { + "type": "variables_set", + "message0": "%{BKY_VARIABLES_SET}", + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_VARIABLES_DEFAULT_NAME}" + }, + { + "type": "input_value", + "name": "VALUE" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_VARIABLES_HUE}", + "tooltip": "%{BKY_VARIABLES_SET_TOOLTIP}", + "helpUrl": "%{BKY_VARIABLES_SET_HELPURL}", + "extensions": ["contextMenu_variableSetterGetter"] + } +]); // END JSON EXTRACT (Do not delete this comment.) + +/** + * Mixin to add context menu items to create getter/setter blocks for this + * setter/getter. + * Used by blocks 'variables_set' and 'variables_get'. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Add menu option to create getter/setter block for this setter/getter. * @param {!Array} options List of menu options to add to. * @this Blockly.Block */ customContextMenu: function(options) { - var option = {enabled: true}; + if(this.isInFlyout){ + return; + } + // Getter blocks have the option to create a setter block, and vice versa. + if (this.type == 'variables_get') { + var opposite_type = 'variables_set'; + var contextMenuMsg = Blockly.Msg.VARIABLES_GET_CREATE_SET; + } else { + var opposite_type = 'variables_get'; + var contextMenuMsg = Blockly.Msg.VARIABLES_SET_CREATE_GET; + } + + var option = {enabled: this.workspace.remainingCapacity() > 0}; var name = this.getFieldValue('VAR'); - option.text = this.contextMenuMsg_.replace('%1', name); + option.text = contextMenuMsg.replace('%1', name); var xmlField = goog.dom.createDom('field', null, name); xmlField.setAttribute('name', 'VAR'); var xmlBlock = goog.dom.createDom('block', null, xmlField); - xmlBlock.setAttribute('type', this.contextMenuType_); + xmlBlock.setAttribute('type', opposite_type); option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); options.push(option); - }, - /** - * @return {!string} Retrieves the type (stored in varType) of this block. - * @this Blockly.Block - */ - getBlockType: function() { - return [Blockly.Types.UNDEF, this.getFieldValue('VAR')]; - }, - /** - * Gets the stored type of the variable indicated in the argument. As only one - * variable is stored in this block, no need to check input - * @this Blockly. - * @param {!string} varName Name of this block variable to check type. - * @return {!string} String to indicate the type of this block. - */ - getVarType: function(varName) { - return [Blockly.Types.UNDEF, this.getFieldValue('VAR')]; - }, -}; - -Blockly.Blocks['variables_set'] = { - /** - * Block for variable setter. - * @this Blockly.Block - */ - init: function() { - this.jsonInit({ - "message0": Blockly.Msg.VARIABLES_SET, - "args0": [ - { - "type": "field_variable", - "name": "VAR", - "variable": Blockly.Msg.VARIABLES_DEFAULT_NAME - }, - { - "type": "input_value", - "name": "VALUE" - } - ], - "previousStatement": null, - "nextStatement": null, - "colour": Blockly.Blocks.variables.HUE, - "tooltip": Blockly.Msg.VARIABLES_SET_TOOLTIP, - "helpUrl": Blockly.Msg.VARIABLES_SET_HELPURL - }); - this.contextMenuMsg_ = Blockly.Msg.VARIABLES_SET_CREATE_GET; - }, - contextMenuType_: 'variables_get', - customContextMenu: Blockly.Blocks['variables_get'].customContextMenu, - /** - * Searches through the nested blocks to find a variable type. - * @this Blockly.Block - * @param {!string} varName Name of this block variable to check type. - * @return {string} String to indicate the type of this block. - */ - getVarType: function(varName) { - return Blockly.Types.getChildBlockType(this); } }; + +Blockly.Extensions.registerMixin('contextMenu_variableSetterGetter', + Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.js new file mode 100644 index 0000000..5afa267 --- /dev/null +++ b/blocks/variables_dynamic.js @@ -0,0 +1,138 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Variable blocks for Blockly. + + * This file is scraped to extract a .json file of block definitions. The array + * passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes + * only, no outside references, no functions, no trailing commas, etc. The one + * exception is end-of-line comments, which the scraper will remove. + * @author duzc2dtw@gmail.com (Du Tian Wei) + */ +'use strict'; + +goog.provide('Blockly.Constants.VariablesDynamic'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly'); + + +/** + * Common HSV hue for all blocks in this category. + * Should be the same as Blockly.Msg.VARIABLES_DYNAMIC_HUE. + * @readonly + */ +Blockly.Constants.VariablesDynamic.HUE = 310; + +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for variable getter. + { + "type": "variables_get_dynamic", + "message0": "%1", + "args0": [{ + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_VARIABLES_DEFAULT_NAME}" + }], + "output": null, + "colour": "%{BKY_VARIABLES_DYNAMIC_HUE}", + "helpUrl": "%{BKY_VARIABLES_GET_HELPURL}", + "tooltip": "%{BKY_VARIABLES_GET_TOOLTIP}", + "extensions": ["contextMenu_variableDynamicSetterGetter"] + }, + // Block for variable setter. + { + "type": "variables_set_dynamic", + "message0": "%{BKY_VARIABLES_SET}", + "args0": [{ + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_VARIABLES_DEFAULT_NAME}" + }, + { + "type": "input_value", + "name": "VALUE" + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": "%{BKY_VARIABLES_DYNAMIC_HUE}", + "tooltip": "%{BKY_VARIABLES_SET_TOOLTIP}", + "helpUrl": "%{BKY_VARIABLES_SET_HELPURL}", + "extensions": ["contextMenu_variableDynamicSetterGetter"] + } +]); // END JSON EXTRACT (Do not delete this comment.) + +/** + * Mixin to add context menu items to create getter/setter blocks for this + * setter/getter. + * Used by blocks 'variables_set_dynamic' and 'variables_get_dynamic'. + * @mixin + * @augments Blockly.Block + * @package + * @readonly + */ +Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { + /** + * Add menu option to create getter/setter block for this setter/getter. + * @param {!Array} options List of menu options to add to. + * @this Blockly.Block + */ + customContextMenu: function(options) { + // Getter blocks have the option to create a setter block, and vice versa. + if (this.isInFlyout) { + return; + } + var opposite_type; + var contextMenuMsg; + if (this.type == 'variables_get_dynamic') { + opposite_type = 'variables_set_dynamic'; + contextMenuMsg = Blockly.Msg.VARIABLES_GET_CREATE_SET; + } else { + opposite_type = 'variables_get_dynamic'; + contextMenuMsg = Blockly.Msg.VARIABLES_SET_CREATE_GET; + } + + var option = { enabled: this.workspace.remainingCapacity() > 0 }; + var name = this.getFieldValue('VAR'); + option.text = contextMenuMsg.replace('%1', name); + var xmlField = goog.dom.createDom('field', null, name); + xmlField.setAttribute('name', 'VAR'); + var variableModel = this.workspace.getVariable(name); + xmlField.setAttribute('variabletype', variableModel.type); + var xmlBlock = goog.dom.createDom('block', null, xmlField); + xmlBlock.setAttribute('type', opposite_type); + option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); + options.push(option); + }, + onchange: function() { + var id = this.getFieldValue('VAR'); + var variableModel = this.workspace.getVariableById(id); + if (this.type == 'variables_get_dynamic') { + this.outputConnection.setCheck(variableModel.type); + } else { + this.getInput('VALUE').connection.setCheck(variableModel.type); + } + } +}; + +Blockly.Extensions.registerMixin('contextMenu_variableDynamicSetterGetter', + Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN); diff --git a/build.py b/build.py deleted file mode 100644 index 26d4c91..0000000 --- a/build.py +++ /dev/null @@ -1,511 +0,0 @@ -# This file is used to collect block core library (js files) and its depedencies, -# we generate blockly_compressed.js - - - -import sys -#if sys.version_info[0] != 2: -# raise Exception("Blockly build only compatible with Python 2.x.\n" -# "You are using: " + sys.version) - -for arg in sys.argv[1:len(sys.argv)]: - if (arg != 'core' and - arg != 'accessible' and - arg != 'generators' and - arg != 'langfiles'): - raise Exception("Invalid argument: \"" + arg + "\". Usage: build.py <0 or more of accessible," + - " core, generators, langfiles>") - -import errno, glob, json, os, re, subprocess, threading, urllib - - -def import_path(fullpath): - """Import a file with full path specification. - Allows one to import from any directory, something __import__ does not do. - - Args: - fullpath: Path and filename of import. - - Returns: - An imported module. - """ - path, filename = os.path.split(fullpath) - filename, ext = os.path.splitext(filename) - sys.path.append(path) - module = __import__(filename) - from importlib import reload - reload(module) # Might be out of date. - del sys.path[-1] - return module - - -HEADER = ("// Do not edit this file; automatically generated by build.py.\n" - "'use strict';\n") - - -class Gen_uncompressed(threading.Thread): - """Generate a JavaScript file that loads Blockly's raw files. - Runs in a separate thread. - """ - def __init__(self, search_paths, target_filename): - threading.Thread.__init__(self) - self.search_paths = search_paths - self.target_filename = target_filename - - def run(self): - f = open(self.target_filename, 'w') - f.write(HEADER) - f.write(""" -var isNodeJS = !!(typeof module !== 'undefined' && module.exports && - typeof window === 'undefined'); - -if (isNodeJS) { - var window = {}; - require('closure-library'); -} - -window.BLOCKLY_DIR = (function() { - if (!isNodeJS) { - // Find name of current directory. - var scripts = document.getElementsByTagName('script'); - var re = new RegExp('(.+)[\/]blockly_(.*)uncompressed\.js$'); - for (var i = 0, script; script = scripts[i]; i++) { - var match = re.exec(script.src); - if (match) { - return match[1]; - } - } - alert('Could not detect Blockly\\'s directory name.'); - } - return ''; -})(); - -window.BLOCKLY_BOOT = function() { - var dir = ''; - if (isNodeJS) { - require('closure-library'); - dir = 'blockly'; - } else { - // Execute after Closure has loaded. - if (!window.goog) { - alert('Error: Closure not found. Read this:\\n' + - 'developers.google.com/blockly/guides/modify/web/closure'); - } - dir = window.BLOCKLY_DIR.match(/[^\\/]+$/)[0]; - } -""") - add_dependency = [] - base_path = calcdeps.FindClosureBasePath(self.search_paths) - for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths): - add_dependency.append(calcdeps.GetDepsLine(dep, base_path)) - add_dependency.sort() # Deterministic build. - add_dependency = '\n'.join(add_dependency) - # Find the Blockly directory name and replace it with a JS variable. - # This allows blockly_uncompressed.js to be compiled on one computer and be - # used on another, even if the directory name differs. - m = re.search('[\\/]([^\\/]+)[\\/]core[\\/]blockly.js', add_dependency) - add_dependency = re.sub('([\\/])' + re.escape(m.group(1)) + - '([\\/]core[\\/])', '\\1" + dir + "\\2', add_dependency) - f.write(add_dependency + '\n') - - provides = [] - for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths): - if not dep.filename.startswith(os.pardir + os.sep): # '../' - provides.extend(dep.provides) - provides.sort() # Deterministic build. - f.write('\n') - f.write('// Load Blockly.\n') - for provide in provides: - f.write("goog.require('%s');\n" % provide) - - f.write(""" -delete this.BLOCKLY_DIR; -delete this.BLOCKLY_BOOT; -}; - -if (isNodeJS) { - window.BLOCKLY_BOOT(); - module.exports = Blockly; -} else { - // Delete any existing Closure (e.g. Soy's nogoog_shim). - document.write(''); - // Load fresh Closure Library. - document.write(''); - document.write(''); -} -""") - f.close() - print("SUCCESS: " + self.target_filename) - - -class Gen_compressed(threading.Thread): - """Generate a JavaScript file that contains all of Blockly's core and all - required parts of Closure, compiled together. - Uses the Closure Compiler's online API. - Runs in a separate thread. - """ - def __init__(self, search_paths, bundles): - threading.Thread.__init__(self) - self.search_paths = search_paths - self.bundles = bundles - - def run(self): - if ('core' in self.bundles): - self.gen_core() - - if ('accessible' in self.bundles): - self.gen_accessible() - - if ('core' in self.bundles or 'accessible' in self.bundles): - self.gen_blocks() - - if ('generators' in self.bundles): - self.gen_generator("javascript") - self.gen_generator("python") - self.gen_generator("php") - self.gen_generator("dart") - self.gen_generator("lua") - - def gen_core(self): - target_filename = "blockly_compressed.js" - # Define the parameters for the POST request. - params = [ - ("compilation_level", "SIMPLE_OPTIMIZATIONS"), - ("use_closure_library", "true"), - ("output_format", "json"), - ("output_info", "compiled_code"), - ("output_info", "warnings"), - ("output_info", "errors"), - ("output_info", "statistics"), - ] - - # Read in all the source files. - filenames = calcdeps.CalculateDependencies(self.search_paths, - [os.path.join("core", "blockly.js")]) - #filenames.sort() # Deterministic build. - #we save the file name to json file format - import json - with open('core_build_list.json','w') as f: - f.write(json.dumps(filenames)) - f.close() - exit() - for filename in filenames: - # Filter out the Closure files (the compiler will add them). - if filename.startswith(os.pardir + os.sep): # '../' - continue - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - - self.do_compile(params, target_filename, filenames, "") - - def gen_accessible(self): - target_filename = "blockly_accessible_compressed.js" - # Define the parameters for the POST request. - params = [ - ("compilation_level", "SIMPLE_OPTIMIZATIONS"), - ("use_closure_library", "true"), - ("language_out", "ES5"), - ("output_format", "json"), - ("output_info", "compiled_code"), - ("output_info", "warnings"), - ("output_info", "errors"), - ("output_info", "statistics"), - ] - - # Read in all the source files. - filenames = calcdeps.CalculateDependencies(self.search_paths, - [os.path.join("accessible", "app.component.js")]) - filenames.sort() # Deterministic build. - for filename in filenames: - # Filter out the Closure files (the compiler will add them). - if filename.startswith(os.pardir + os.sep): # '../' - continue - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - - self.do_compile(params, target_filename, filenames, "") - - def gen_accessible(self): - target_filename = "blockly_accessible_compressed.js" - # Define the parameters for the POST request. - params = [ - ("compilation_level", "SIMPLE_OPTIMIZATIONS"), - ("use_closure_library", "true"), - ("language_out", "ES5"), - ("output_format", "json"), - ("output_info", "compiled_code"), - ("output_info", "warnings"), - ("output_info", "errors"), - ("output_info", "statistics"), - ] - - # Read in all the source files. - filenames = calcdeps.CalculateDependencies(self.search_paths, - [os.path.join("accessible", "app.component.js")]) - for filename in filenames: - # Filter out the Closure files (the compiler will add them). - if filename.startswith(os.pardir + os.sep): # '../' - continue - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - - self.do_compile(params, target_filename, filenames, "") - - def gen_blocks(self): - target_filename = "blocks_compressed.js" - # Define the parameters for the POST request. - params = [ - ("compilation_level", "SIMPLE_OPTIMIZATIONS"), - ("output_format", "json"), - ("output_info", "compiled_code"), - ("output_info", "warnings"), - ("output_info", "errors"), - ("output_info", "statistics"), - ] - - # Read in all the source files. - # Add Blockly.Blocks to be compatible with the compiler. - params.append(("js_code", "goog.provide('Blockly.Blocks');")) - filenames = glob.glob(os.path.join("blocks", "*.js")) - filenames.sort() # Deterministic build. - for filename in filenames: - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - - # Remove Blockly.Blocks to be compatible with Blockly. - remove = "var Blockly={Blocks:{}};" - self.do_compile(params, target_filename, filenames, remove) - - def gen_generator(self, language): - target_filename = language + "_compressed.js" - # Define the parameters for the POST request. - params = [ - ("compilation_level", "SIMPLE_OPTIMIZATIONS"), - ("output_format", "json"), - ("output_info", "compiled_code"), - ("output_info", "warnings"), - ("output_info", "errors"), - ("output_info", "statistics"), - ] - - # Read in all the source files. - # Add Blockly.Generator to be compatible with the compiler. - params.append(("js_code", "goog.provide('Blockly.Generator');")) - filenames = glob.glob( - os.path.join("generators", language, "*.js")) - filenames.sort() # Deterministic build. - filenames.insert(0, os.path.join("generators", language + ".js")) - for filename in filenames: - f = open(filename) - params.append(("js_code", "".join(f.readlines()))) - f.close() - filenames.insert(0, "[goog.provide]") - - # Remove Blockly.Generator to be compatible with Blockly. - remove = "var Blockly={Generator:{}};" - self.do_compile(params, target_filename, filenames, remove) - - def do_compile(self, params, target_filename, filenames, remove): - # Send the request to Google. - headers = {"Content-type": "application/x-www-form-urlencoded"} - conn = httplib.HTTPConnection("closure-compiler.appspot.com") - conn.request("POST", "/compile", urllib.urlencode(params), headers) - response = conn.getresponse() - json_str = response.read() - conn.close() - - # Parse the JSON response. - json_data = json.loads(json_str) - - def file_lookup(name): - if not name.startswith("Input_"): - return "???" - n = int(name[6:]) - 1 - return filenames[n] - - if json_data.has_key("serverErrors"): - errors = json_data["serverErrors"] - for error in errors: - print("SERVER ERROR: %s" % target_filename) - print(error["error"]) - elif json_data.has_key("errors"): - errors = json_data["errors"] - for error in errors: - print("FATAL ERROR") - print(error["error"]) - if error["file"]: - print("%s at line %d:" % ( - file_lookup(error["file"]), error["lineno"])) - print(error["line"]) - print((" " * error["charno"]) + "^") - sys.exit(1) - else: - if json_data.has_key("warnings"): - warnings = json_data["warnings"] - for warning in warnings: - print("WARNING") - print(warning["warning"]) - if warning["file"]: - print("%s at line %d:" % ( - file_lookup(warning["file"]), warning["lineno"])) - print(warning["line"]) - print((" " * warning["charno"]) + "^") - print() - - if not json_data.has_key("compiledCode"): - print("FATAL ERROR: Compiler did not return compiledCode.") - sys.exit(1) - - code = HEADER + "\n" + json_data["compiledCode"] - code = code.replace(remove, "") - stats = json_data["statistics"] - original_b = stats["originalSize"] - compressed_b = stats["compressedSize"] - if original_b > 0 and compressed_b > 0: - f = open(target_filename, "w") - f.write(code) - f.close() - - original_kb = int(original_b / 1024 + 0.5) - compressed_kb = int(compressed_b / 1024 + 0.5) - ratio = int(float(compressed_b) / float(original_b) * 100 + 0.5) - print("SUCCESS: " + target_filename) - print("Size changed from %d KB to %d KB (%d%%)." % ( - original_kb, compressed_kb, ratio)) - else: - print("UNKNOWN ERROR") - - -class Gen_langfiles(threading.Thread): - """Generate JavaScript file for each natural language supported. - - Runs in a separate thread. - """ - - def __init__(self, force_gen): - threading.Thread.__init__(self) - self.force_gen = force_gen - - def _rebuild(self, srcs, dests): - # Determine whether any of the files in srcs is newer than any in dests. - try: - return (max(os.path.getmtime(src) for src in srcs) > - min(os.path.getmtime(dest) for dest in dests)) - except OSError as e: - # Was a file not found? - if e.errno == errno.ENOENT: - # If it was a source file, we can't proceed. - if e.filename in srcs: - print("Source file missing: " + e.filename) - sys.exit(1) - else: - # If a destination file was missing, rebuild. - return True - else: - print("Error checking file creation times: " + e) - - def run(self): - # The files msg/json/{en,qqq,synonyms}.json depend on msg/messages.js. - if (self.force_gen or - self._rebuild([os.path.join("msg", "messages.js")], - [os.path.join("msg", "json", f) for f in - ["en.json", "qqq.json", "synonyms.json"]])): - try: - subprocess.check_call([ - "python", - os.path.join("i18n", "js_to_json.py"), - "--input_file", "msg/messages.js", - "--output_dir", "msg/json/", - "--quiet"]) - except (subprocess.CalledProcessError, OSError) as e: - # Documentation for subprocess.check_call says that CalledProcessError - # will be raised on failure, but I found that OSError is also possible. - print("Error running i18n/js_to_json.py: ", e) - sys.exit(1) - - # Checking whether it is necessary to rebuild the js files would be a lot of - # work since we would have to compare each .json file with each - # .js file. Rebuilding is easy and cheap, so just go ahead and do it. - try: - # Use create_messages.py to create .js files from .json files. - cmd = [ - "python", - os.path.join("i18n", "create_messages.py"), - "--source_lang_file", os.path.join("msg", "json", "en.json"), - "--source_synonym_file", os.path.join("msg", "json", "synonyms.json"), - "--source_constants_file", os.path.join("msg", "json", "constants.json"), - "--key_file", os.path.join("msg", "json", "keys.json"), - "--output_dir", os.path.join("msg", "js"), - "--quiet"] - json_files = glob.glob(os.path.join("msg", "json", "*.json")) - json_files = [file for file in json_files if not - (file.endswith(("keys.json", "synonyms.json", "qqq.json", "constants.json")))] - cmd.extend(json_files) - subprocess.check_call(cmd) - except (subprocess.CalledProcessError, OSError) as e: - print("Error running i18n/create_messages.py: ", e) - sys.exit(1) - - # Output list of .js files created. - for f in json_files: - # This assumes the path to the current directory does not contain "json". - f = f.replace("json", "js") - if os.path.isfile(f): - print("SUCCESS: " + f) - else: - print("FAILED to create " + f) - - -if __name__ == "__main__": - try: - calcdeps = import_path(os.path.join(os.path.pardir, "closure-library", "closure", "bin", "calcdeps.py")) - except ImportError: - if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")): - # Dir got renamed when Closure moved from Google Code to GitHub in 2014. - print("Error: Closure directory needs to be renamed from" - "'closure-library-read-only' to 'closure-library'.\n" - "Please rename this directory.") - elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")): - # When Closure is installed by npm, it is named "google-closure-library". - #calcdeps = import_path(os.path.join( - # os.path.pardir, "google-closure-library", "closure", "bin", "calcdeps.py")) - print("Error: Closure directory needs to be renamed from" - "'google-closure-library' to 'closure-library'.\n" - "Please rename this directory.") - else: - print("""Error: Closure not found. Read this: -developers.google.com/blockly/guides/modify/web/closure""") - sys.exit(1) - core_search_paths = list(calcdeps.ExpandDirectories(["core", os.path.join(os.path.pardir, "closure-library")])) - core_search_paths.sort() # Deterministic build. - full_search_paths = list(calcdeps.ExpandDirectories(["accessible", "core", os.path.join(os.path.pardir, "closure-library")])) - full_search_paths.sort() # Deterministic build. - #import code - #code.interact(local=locals()) - - if (len(sys.argv) == 1): - args = ['core', 'accessible', 'generators', 'defaultlangfiles'] - else: - args = sys.argv - - # Uncompressed and compressed are run in parallel threads. - # Uncompressed is limited by processor speed. - #if ('core' in args): - # Gen_uncompressed(core_search_paths, 'blockly_uncompressed.js').start() - - #if ('accessible' in args): - # Gen_uncompressed(full_search_paths, 'blockly_accessible_uncompressed.js').start() - - # Compressed is limited by network and server speed. - Gen_compressed(full_search_paths, args).start() - - # This is run locally in a separate thread - # defaultlangfiles checks for changes in the msg files, while manually asking - # to build langfiles will force the messages to be rebuilt. - # if ('langfiles' in args or 'defaultlangfiles' in args): - # Gen_langfiles('langfiles' in args).start() \ No newline at end of file diff --git a/c_core/c_blockly.js b/c_core/c_blockly.js new file mode 100644 index 0000000..1c31272 --- /dev/null +++ b/c_core/c_blockly.js @@ -0,0 +1,18 @@ +/** + * @license + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * {@link http://www.apache.org/licenses/LICENSE-2.0} + * @fileoverview mbed blocks for the Servo library. + * Last modified on 2/03/2018 + * @author 616545598@qq.com (zhaofeng_shu33) + */ +'use strict'; + + +goog.provide('Blockly.C_Blockly'); + +goog.require('Blockly.StaticTyping'); +goog.require('Blockly.Macro'); +goog.require('Blockly.FieldMacro'); + diff --git a/core/field_macro.js b/c_core/field_macro.js similarity index 100% rename from core/field_macro.js rename to c_core/field_macro.js diff --git a/core/macro.js b/c_core/macro.js similarity index 100% rename from core/macro.js rename to c_core/macro.js diff --git a/core/static_typing.js b/c_core/static_typing.js similarity index 100% rename from core/static_typing.js rename to c_core/static_typing.js diff --git a/core/type.js b/c_core/type.js similarity index 100% rename from core/type.js rename to c_core/type.js diff --git a/core/types.js b/c_core/types.js similarity index 100% rename from core/types.js rename to c_core/types.js diff --git a/core/.svn/entries b/core/.svn/entries new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/core/.svn/entries @@ -0,0 +1 @@ +12 diff --git a/core/.svn/format b/core/.svn/format new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/core/.svn/format @@ -0,0 +1 @@ +12 diff --git a/core/.svn/pristine/0a/0a1de9a2b9b524d85a2bd6fe1abcc710cc9e7a9e.svn-base b/core/.svn/pristine/0a/0a1de9a2b9b524d85a2bd6fe1abcc710cc9e7a9e.svn-base new file mode 100644 index 0000000..fc13cfa --- /dev/null +++ b/core/.svn/pristine/0a/0a1de9a2b9b524d85a2bd6fe1abcc710cc9e7a9e.svn-base @@ -0,0 +1,663 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Components for creating connections between blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Connection'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); + + +/** + * Class for a connection between blocks. + * @param {!Blockly.Block} source The block establishing this connection. + * @param {number} type The type of the connection. + * @constructor + */ +Blockly.Connection = function(source, type) { + /** + * @type {!Blockly.Block} + * @private + */ + this.sourceBlock_ = source; + /** @type {number} */ + this.type = type; + // Shortcut for the databases for this connection's workspace. + if (source.workspace.connectionDBList) { + this.db_ = source.workspace.connectionDBList[type]; + this.dbOpposite_ = + source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]]; + this.hidden_ = !this.db_; + } +}; + +/** + * Constants for checking whether two connections are compatible. + */ +Blockly.Connection.CAN_CONNECT = 0; +Blockly.Connection.REASON_SELF_CONNECTION = 1; +Blockly.Connection.REASON_WRONG_TYPE = 2; +Blockly.Connection.REASON_TARGET_NULL = 3; +Blockly.Connection.REASON_CHECKS_FAILED = 4; +Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5; +Blockly.Connection.REASON_SHADOW_PARENT = 6; + +/** + * Connection this connection connects to. Null if not connected. + * @type {Blockly.Connection} + */ +Blockly.Connection.prototype.targetConnection = null; + +/** + * List of compatible value types. Null if all types are compatible. + * @type {Array} + * @private + */ +Blockly.Connection.prototype.check_ = null; + +/** + * DOM representation of a shadow block, or null if none. + * @type {Element} + * @private + */ +Blockly.Connection.prototype.shadowDom_ = null; + +/** + * Horizontal location of this connection. + * @type {number} + * @private + */ +Blockly.Connection.prototype.x_ = 0; + +/** + * Vertical location of this connection. + * @type {number} + * @private + */ +Blockly.Connection.prototype.y_ = 0; + +/** + * Has this connection been added to the connection database? + * @type {boolean} + * @private + */ +Blockly.Connection.prototype.inDB_ = false; + +/** + * Connection database for connections of this type on the current workspace. + * @type {Blockly.ConnectionDB} + * @private + */ +Blockly.Connection.prototype.db_ = null; + +/** + * Connection database for connections compatible with this type on the + * current workspace. + * @type {Blockly.ConnectionDB} + * @private + */ +Blockly.Connection.prototype.dbOpposite_ = null; + +/** + * Whether this connections is hidden (not tracked in a database) or not. + * @type {boolean} + * @private + */ +Blockly.Connection.prototype.hidden_ = null; + +/** + * Connect two connections together. This is the connection on the superior + * block. + * @param {!Blockly.Connection} childConnection Connection on inferior block. + * @private + */ +Blockly.Connection.prototype.connect_ = function(childConnection) { + var parentConnection = this; + var parentBlock = parentConnection.getSourceBlock(); + var childBlock = childConnection.getSourceBlock(); + // Disconnect any existing parent on the child connection. + if (childConnection.isConnected()) { + childConnection.disconnect(); + } + if (parentConnection.isConnected()) { + // Other connection is already connected to something. + // Disconnect it and reattach it or bump it as needed. + var orphanBlock = parentConnection.targetBlock(); + var shadowDom = parentConnection.getShadowDom(); + // Temporarily set the shadow DOM to null so it does not respawn. + parentConnection.setShadowDom(null); + // Displaced shadow blocks dissolve rather than reattaching or bumping. + if (orphanBlock.isShadow()) { + // Save the shadow block so that field values are preserved. + shadowDom = Blockly.Xml.blockToDom(orphanBlock); + orphanBlock.dispose(); + orphanBlock = null; + } else if (parentConnection.type == Blockly.INPUT_VALUE) { + // Value connections. + // If female block is already connected, disconnect and bump the male. + if (!orphanBlock.outputConnection) { + throw 'Orphan block does not have an output connection.'; + } + // Attempt to reattach the orphan at the end of the newly inserted + // block. Since this block may be a row, walk down to the end + // or to the first (and only) shadow block. + var connection = Blockly.Connection.lastConnectionInRow_( + childBlock, orphanBlock); + if (connection) { + orphanBlock.outputConnection.connect(connection); + orphanBlock = null; + } + } else if (parentConnection.type == Blockly.NEXT_STATEMENT) { + // Statement connections. + // Statement blocks may be inserted into the middle of a stack. + // Split the stack. + if (!orphanBlock.previousConnection) { + throw 'Orphan block does not have a previous connection.'; + } + // Attempt to reattach the orphan at the bottom of the newly inserted + // block. Since this block may be a stack, walk down to the end. + var newBlock = childBlock; + while (newBlock.nextConnection) { + var nextBlock = newBlock.getNextBlock(); + if (nextBlock && !nextBlock.isShadow()) { + newBlock = nextBlock; + } else { + if (orphanBlock.previousConnection.checkType_( + newBlock.nextConnection)) { + newBlock.nextConnection.connect(orphanBlock.previousConnection); + orphanBlock = null; + } + break; + } + } + } + if (orphanBlock) { + // Unable to reattach orphan. + parentConnection.disconnect(); + if (Blockly.Events.recordUndo) { + // Bump it off to the side after a moment. + var group = Blockly.Events.getGroup(); + setTimeout(function() { + // Verify orphan hasn't been deleted or reconnected (user on meth). + if (orphanBlock.workspace && !orphanBlock.getParent()) { + Blockly.Events.setGroup(group); + if (orphanBlock.outputConnection) { + orphanBlock.outputConnection.bumpAwayFrom_(parentConnection); + } else if (orphanBlock.previousConnection) { + orphanBlock.previousConnection.bumpAwayFrom_(parentConnection); + } + Blockly.Events.setGroup(false); + } + }, Blockly.BUMP_DELAY); + } + } + // Restore the shadow DOM. + parentConnection.setShadowDom(shadowDom); + } + + var event; + if (Blockly.Events.isEnabled()) { + event = new Blockly.Events.BlockMove(childBlock); + } + // Establish the connections. + Blockly.Connection.connectReciprocally_(parentConnection, childConnection); + // Demote the inferior block so that one is a child of the superior one. + childBlock.setParent(parentBlock); + if (event) { + event.recordNew(); + Blockly.Events.fire(event); + } +}; + +/** + * Sever all links to this connection (not including from the source object). + */ +Blockly.Connection.prototype.dispose = function() { + if (this.isConnected()) { + throw 'Disconnect connection before disposing of it.'; + } + if (this.inDB_) { + this.db_.removeConnection_(this); + } + this.db_ = null; + this.dbOpposite_ = null; +}; + +/** + * Get the source block for this connection. + * @return {Blockly.Block} The source block, or null if there is none. + */ +Blockly.Connection.prototype.getSourceBlock = function() { + return this.sourceBlock_; +}; + +/** + * Does the connection belong to a superior block (higher in the source stack)? + * @return {boolean} True if connection faces down or right. + */ +Blockly.Connection.prototype.isSuperior = function() { + return this.type == Blockly.INPUT_VALUE || + this.type == Blockly.NEXT_STATEMENT; +}; + +/** + * Is the connection connected? + * @return {boolean} True if connection is connected to another connection. + */ +Blockly.Connection.prototype.isConnected = function() { + return !!this.targetConnection; +}; + +/** + * Checks whether the current connection can connect with the target + * connection. + * @param {Blockly.Connection} target Connection to check compatibility with. + * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, + * an error code otherwise. + * @private + */ +Blockly.Connection.prototype.canConnectWithReason_ = function(target) { + if (!target) { + return Blockly.Connection.REASON_TARGET_NULL; + } + if (this.isSuperior()) { + var blockA = this.sourceBlock_; + var blockB = target.getSourceBlock(); + } else { + var blockB = this.sourceBlock_; + var blockA = target.getSourceBlock(); + } + if (blockA && blockA == blockB) { + return Blockly.Connection.REASON_SELF_CONNECTION; + } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { + return Blockly.Connection.REASON_WRONG_TYPE; + } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { + return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; + } else if (!this.checkType_(target)) { + return Blockly.Connection.REASON_CHECKS_FAILED; + } else if (blockA.isShadow() && !blockB.isShadow()) { + return Blockly.Connection.REASON_SHADOW_PARENT; + } + return Blockly.Connection.CAN_CONNECT; +}; + +/** + * Checks whether the current connection and target connection are compatible + * and throws an exception if they are not. + * @param {Blockly.Connection} target The connection to check compatibility + * with. + * @private + */ +Blockly.Connection.prototype.checkConnection_ = function(target) { + switch (this.canConnectWithReason_(target)) { + case Blockly.Connection.CAN_CONNECT: + break; + case Blockly.Connection.REASON_SELF_CONNECTION: + throw 'Attempted to connect a block to itself.'; + case Blockly.Connection.REASON_DIFFERENT_WORKSPACES: + // Usually this means one block has been deleted. + throw 'Blocks not on same workspace.'; + case Blockly.Connection.REASON_WRONG_TYPE: + throw 'Attempt to connect incompatible types.'; + case Blockly.Connection.REASON_TARGET_NULL: + throw 'Target connection is null.'; + case Blockly.Connection.REASON_CHECKS_FAILED: + var msg = 'Connection checks failed. '; + msg += this + ' expected ' + this.check_ + ', found ' + target.check_; + throw msg; + case Blockly.Connection.REASON_SHADOW_PARENT: + throw 'Connecting non-shadow to shadow block.'; + default: + throw 'Unknown connection failure: this should never happen!'; + } +}; + +/** + * Check if the two connections can be dragged to connect to each other. + * @param {!Blockly.Connection} candidate A nearby connection to check. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { + // Type checking. + var canConnect = this.canConnectWithReason_(candidate); + if (canConnect != Blockly.Connection.CAN_CONNECT) { + return false; + } + + // Don't offer to connect an already connected left (male) value plug to + // an available right (female) value plug. Don't offer to connect the + // bottom of a statement block to one that's already connected. + if (candidate.type == Blockly.OUTPUT_VALUE || + candidate.type == Blockly.PREVIOUS_STATEMENT) { + if (candidate.isConnected() || this.isConnected()) { + return false; + } + } + + // Offering to connect the left (male) of a value block to an already + // connected value pair is ok, we'll splice it in. + // However, don't offer to splice into an immovable block. + if (candidate.type == Blockly.INPUT_VALUE && candidate.isConnected() && + !candidate.targetBlock().isMovable() && + !candidate.targetBlock().isShadow()) { + return false; + } + + // Don't let a block with no next connection bump other blocks out of the + // stack. But covering up a shadow block or stack of shadow blocks is fine. + // Similarly, replacing a terminal statement with another terminal statement + // is allowed. + if (this.type == Blockly.PREVIOUS_STATEMENT && + candidate.isConnected() && + !this.sourceBlock_.nextConnection && + !candidate.targetBlock().isShadow() && + candidate.targetBlock().nextConnection) { + return false; + } + + // Don't let blocks try to connect to themselves or ones they nest. + if (Blockly.draggingConnections_.indexOf(candidate) != -1) { + return false; + } + + return true; +}; + +/** + * Connect this connection to another connection. + * @param {!Blockly.Connection} otherConnection Connection to connect to. + */ +Blockly.Connection.prototype.connect = function(otherConnection) { + if (this.targetConnection == otherConnection) { + // Already connected together. NOP. + return; + } + this.checkConnection_(otherConnection); + // Determine which block is superior (higher in the source stack). + if (this.isSuperior()) { + // Superior block. + this.connect_(otherConnection); + } else { + // Inferior block. + otherConnection.connect_(this); + } +}; + +/** + * Update two connections to target each other. + * @param {Blockly.Connection} first The first connection to update. + * @param {Blockly.Connection} second The second connection to update. + * @private + */ +Blockly.Connection.connectReciprocally_ = function(first, second) { + goog.asserts.assert(first && second, 'Cannot connect null connections.'); + first.targetConnection = second; + second.targetConnection = first; +}; + +/** + * Does the given block have one and only one connection point that will accept + * an orphaned block? + * @param {!Blockly.Block} block The superior block. + * @param {!Blockly.Block} orphanBlock The inferior block. + * @return {Blockly.Connection} The suitable connection point on 'block', + * or null. + * @private + */ +Blockly.Connection.singleConnection_ = function(block, orphanBlock) { + var connection = false; + for (var i = 0; i < block.inputList.length; i++) { + var thisConnection = block.inputList[i].connection; + if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE && + orphanBlock.outputConnection.checkType_(thisConnection)) { + if (connection) { + return null; // More than one connection. + } + connection = thisConnection; + } + } + return connection; +}; + +/** + * Walks down a row a blocks, at each stage checking if there are any + * connections that will accept the orphaned block. If at any point there + * are zero or multiple eligible connections, returns null. Otherwise + * returns the only input on the last block in the chain. + * Terminates early for shadow blocks. + * @param {!Blockly.Block} startBlock The block on which to start the search. + * @param {!Blockly.Block} orphanBlock The block that is looking for a home. + * @return {Blockly.Connection} The suitable connection point on the chain + * of blocks, or null. + * @private + */ +Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) { + var newBlock = startBlock; + var connection; + while (connection = Blockly.Connection.singleConnection_( + /** @type {!Blockly.Block} */ (newBlock), orphanBlock)) { + // '=' is intentional in line above. + newBlock = connection.targetBlock(); + if (!newBlock || newBlock.isShadow()) { + return connection; + } + } + return null; +}; + +/** + * Disconnect this connection. + */ +Blockly.Connection.prototype.disconnect = function() { + var otherConnection = this.targetConnection; + goog.asserts.assert(otherConnection, 'Source connection not connected.'); + goog.asserts.assert(otherConnection.targetConnection == this, + 'Target connection not connected to source connection.'); + + var parentBlock, childBlock, parentConnection; + if (this.isSuperior()) { + // Superior block. + parentBlock = this.sourceBlock_; + childBlock = otherConnection.getSourceBlock(); + parentConnection = this; + } else { + // Inferior block. + parentBlock = otherConnection.getSourceBlock(); + childBlock = this.sourceBlock_; + parentConnection = otherConnection; + } + this.disconnectInternal_(parentBlock, childBlock); + parentConnection.respawnShadow_(); +}; + +/** + * Disconnect two blocks that are connected by this connection. + * @param {!Blockly.Block} parentBlock The superior block. + * @param {!Blockly.Block} childBlock The inferior block. + * @private + */ +Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock, + childBlock) { + var event; + if (Blockly.Events.isEnabled()) { + event = new Blockly.Events.BlockMove(childBlock); + } + var otherConnection = this.targetConnection; + otherConnection.targetConnection = null; + this.targetConnection = null; + childBlock.setParent(null); + if (event) { + event.recordNew(); + Blockly.Events.fire(event); + } +}; + +/** + * Respawn the shadow block if there was one connected to the this connection. + * @private + */ +Blockly.Connection.prototype.respawnShadow_ = function() { + var parentBlock = this.getSourceBlock(); + var shadow = this.getShadowDom(); + if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { + var blockShadow = + Blockly.Xml.domToBlock(shadow, parentBlock.workspace); + if (blockShadow.outputConnection) { + this.connect(blockShadow.outputConnection); + } else if (blockShadow.previousConnection) { + this.connect(blockShadow.previousConnection); + } else { + throw 'Child block does not have output or previous statement.'; + } + } +}; + +/** + * Returns the block that this connection connects to. + * @return {Blockly.Block} The connected block or null if none is connected. + */ +Blockly.Connection.prototype.targetBlock = function() { + if (this.isConnected()) { + return this.targetConnection.getSourceBlock(); + } + return null; +}; + +/** + * Is this connection compatible with another connection with respect to the + * value type system. E.g. square_root("Hello") is not compatible. + * @param {!Blockly.Connection} otherConnection Connection to compare against. + * @return {boolean} True if the connections share a type. + * @private + */ +Blockly.Connection.prototype.checkType_ = function(otherConnection) { + if (!this.check_ || !otherConnection.check_) { + // One or both sides are promiscuous enough that anything will fit. + return true; + } + // Find any intersection in the check lists. + for (var i = 0; i < this.check_.length; i++) { + if (otherConnection.check_.indexOf(this.check_[i]) != -1) { + return true; + } + } + // No intersection. + return false; +}; + +/** + * Function to be called when this connection's compatible types have changed. + * @private + */ +Blockly.Connection.prototype.onCheckChanged_ = function() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && !this.checkType_(this.targetConnection)) { + var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + } +}; + +/** + * Change a connection's compatibility. + * @param {*} check Compatible value type or list of value types. + * Null if all types are compatible. + * @return {!Blockly.Connection} The connection being modified + * (to allow chaining). + */ +Blockly.Connection.prototype.setCheck = function(check) { + if (check) { + // Ensure that check is in an array. + if (!goog.isArray(check)) { + check = [check]; + } + this.check_ = check; + this.onCheckChanged_(); + } else { + this.check_ = null; + } + return this; +}; + +/** + * Change a connection's shadow block. + * @param {Element} shadow DOM representation of a block or null. + */ +Blockly.Connection.prototype.setShadowDom = function(shadow) { + this.shadowDom_ = shadow; +}; + +/** + * Return a connection's shadow block. + * @return {Element} shadow DOM representation of a block or null. + */ +Blockly.Connection.prototype.getShadowDom = function() { + return this.shadowDom_; +}; + +/** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * + * Headless configurations (the default) do not have neighboring connection, + * and always return an empty list (the default). + * {@link Blockly.RenderedConnection} overrides this behavior with a list + * computed from the rendered positioning. + * @param {number} maxLimit The maximum radius to another connection. + * @return {!Array.} List of connections. + * @private + */ +Blockly.Connection.prototype.neighbours_ = function(/* maxLimit */) { + return []; +}; + +/** + * This method returns a string describing this Connection in developer terms + * (English only). Intended to on be used in console logs and errors. + * @return {string} The description. + */ +Blockly.Connection.prototype.toString = function() { + var msg; + var block = this.sourceBlock_; + if (!block) { + return 'Orphan Connection'; + } else if (block.outputConnection == this) { + msg = 'Output Connection of '; + } else if (block.previousConnection == this) { + msg = 'Previous Connection of '; + } else if (block.nextConnection == this) { + msg = 'Next Connection of '; + } else { + var parentInput = goog.array.find(block.inputList, function(input) { + return input.connection == this; + }, this); + if (parentInput) { + msg = 'Input "' + parentInput.name + '" connection on '; + } else { + console.warn('Connection not actually connected to sourceBlock_'); + return 'Orphan Connection'; + } + } + return msg + block.toDevString(); +}; diff --git a/core/.svn/pristine/0c/0c06d9f8bb0233d247f1846a44c85dca7afbfad4.svn-base b/core/.svn/pristine/0c/0c06d9f8bb0233d247f1846a44c85dca7afbfad4.svn-base new file mode 100644 index 0000000..f9ab040 --- /dev/null +++ b/core/.svn/pristine/0c/0c06d9f8bb0233d247f1846a44c85dca7afbfad4.svn-base @@ -0,0 +1,237 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Object that controls settings for the workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Options'); + + +/** + * Parse the user-specified options, using reasonable defaults where behaviour + * is unspecified. + * @param {!Object} options Dictionary of options. Specification: + * https://developers.google.com/blockly/guides/get-started/web#configuration + * @constructor + */ +Blockly.Options = function(options) { + var readOnly = !!options['readOnly']; + if (readOnly) { + var languageTree = null; + var hasCategories = false; + var hasTrashcan = false; + var hasCollapse = false; + var hasComments = false; + var hasDisable = false; + var hasSounds = false; + } else { + var languageTree = Blockly.Options.parseToolboxTree(options['toolbox']); + var hasCategories = Boolean(languageTree && + languageTree.getElementsByTagName('category').length); + var hasTrashcan = options['trashcan']; + if (hasTrashcan === undefined) { + hasTrashcan = hasCategories; + } + var hasCollapse = options['collapse']; + if (hasCollapse === undefined) { + hasCollapse = hasCategories; + } + var hasComments = options['comments']; + if (hasComments === undefined) { + hasComments = hasCategories; + } + var hasDisable = options['disable']; + if (hasDisable === undefined) { + hasDisable = hasCategories; + } + var hasSounds = options['sounds']; + if (hasSounds === undefined) { + hasSounds = true; + } + } + var rtl = !!options['rtl']; + var horizontalLayout = options['horizontalLayout']; + if (horizontalLayout === undefined) { + horizontalLayout = false; + } + var toolboxAtStart = options['toolboxPosition']; + if (toolboxAtStart === 'end') { + toolboxAtStart = false; + } else { + toolboxAtStart = true; + } + + if (horizontalLayout) { + var toolboxPosition = toolboxAtStart ? + Blockly.TOOLBOX_AT_TOP : Blockly.TOOLBOX_AT_BOTTOM; + } else { + var toolboxPosition = (toolboxAtStart == rtl) ? + Blockly.TOOLBOX_AT_RIGHT : Blockly.TOOLBOX_AT_LEFT; + } + + var hasScrollbars = options['scrollbars']; + if (hasScrollbars === undefined) { + hasScrollbars = hasCategories; + } + var hasCss = options['css']; + if (hasCss === undefined) { + hasCss = true; + } + var pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + if (options['media']) { + pathToMedia = options['media']; + } else if (options['path']) { + // 'path' is a deprecated option which has been replaced by 'media'. + pathToMedia = options['path'] + 'media/'; + } + if (options['oneBasedIndex'] === undefined) { + var oneBasedIndex = true; + } else { + var oneBasedIndex = !!options['oneBasedIndex']; + } + + this.RTL = rtl; + this.oneBasedIndex = oneBasedIndex; + this.collapse = hasCollapse; + this.comments = hasComments; + this.disable = hasDisable; + this.readOnly = readOnly; + this.maxBlocks = options['maxBlocks'] || Infinity; + this.pathToMedia = pathToMedia; + this.hasCategories = hasCategories; + this.hasScrollbars = hasScrollbars; + this.hasTrashcan = hasTrashcan; + this.hasSounds = hasSounds; + this.hasCss = hasCss; + this.horizontalLayout = horizontalLayout; + this.languageTree = languageTree; + this.gridOptions = Blockly.Options.parseGridOptions_(options); + this.zoomOptions = Blockly.Options.parseZoomOptions_(options); + this.toolboxPosition = toolboxPosition; +}; + +/** + * The parent of the current workspace, or null if there is no parent workspace. + * @type {Blockly.Workspace} + **/ +Blockly.Options.prototype.parentWorkspace = null; + +/** + * If set, sets the translation of the workspace to match the scrollbars. + */ +Blockly.Options.prototype.setMetrics = null; + +/** + * Return an object with the metrics required to size the workspace. + * @return {Object} Contains size and position metrics, or null. + */ +Blockly.Options.prototype.getMetrics = null; + +/** + * Parse the user-specified zoom options, using reasonable defaults where + * behaviour is unspecified. See zoom documentation: + * https://developers.google.com/blockly/guides/configure/web/zoom + * @param {!Object} options Dictionary of options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseZoomOptions_ = function(options) { + var zoom = options['zoom'] || {}; + var zoomOptions = {}; + if (zoom['controls'] === undefined) { + zoomOptions.controls = false; + } else { + zoomOptions.controls = !!zoom['controls']; + } + if (zoom['wheel'] === undefined) { + zoomOptions.wheel = false; + } else { + zoomOptions.wheel = !!zoom['wheel']; + } + if (zoom['startScale'] === undefined) { + zoomOptions.startScale = 1; + } else { + zoomOptions.startScale = parseFloat(zoom['startScale']); + } + if (zoom['maxScale'] === undefined) { + zoomOptions.maxScale = 3; + } else { + zoomOptions.maxScale = parseFloat(zoom['maxScale']); + } + if (zoom['minScale'] === undefined) { + zoomOptions.minScale = 0.3; + } else { + zoomOptions.minScale = parseFloat(zoom['minScale']); + } + if (zoom['scaleSpeed'] === undefined) { + zoomOptions.scaleSpeed = 1.2; + } else { + zoomOptions.scaleSpeed = parseFloat(zoom['scaleSpeed']); + } + return zoomOptions; +}; + +/** + * Parse the user-specified grid options, using reasonable defaults where + * behaviour is unspecified. See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @param {!Object} options Dictionary of options. + * @return {!Object} A dictionary of normalized options. + * @private + */ +Blockly.Options.parseGridOptions_ = function(options) { + var grid = options['grid'] || {}; + var gridOptions = {}; + gridOptions.spacing = parseFloat(grid['spacing']) || 0; + gridOptions.colour = grid['colour'] || '#888'; + gridOptions.length = parseFloat(grid['length']) || 1; + gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap']; + return gridOptions; +}; + +/** + * Parse the provided toolbox tree into a consistent DOM format. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + * @return {Node} DOM tree of blocks, or null. + */ +Blockly.Options.parseToolboxTree = function(tree) { + if (tree) { + if (typeof tree != 'string') { + if (typeof XSLTProcessor == 'undefined' && tree.outerHTML) { + // In this case the tree will not have been properly built by the + // browser. The HTML will be contained in the element, but it will + // not have the proper DOM structure since the browser doesn't support + // XSLTProcessor (XML -> HTML). This is the case in IE 9+. + tree = tree.outerHTML; + } else if (!(tree instanceof Element)) { + tree = null; + } + } + if (typeof tree == 'string') { + tree = Blockly.Xml.textToDom(tree); + } + } else { + tree = null; + } + return tree; +}; diff --git a/core/.svn/pristine/0d/0d0881855e1ee8bbae3bfc06fb841f78343a1b3d.svn-base b/core/.svn/pristine/0d/0d0881855e1ee8bbae3bfc06fb841f78343a1b3d.svn-base new file mode 100644 index 0000000..ae4087c --- /dev/null +++ b/core/.svn/pristine/0d/0d0881855e1ee8bbae3bfc06fb841f78343a1b3d.svn-base @@ -0,0 +1,1527 @@ +/** + * @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 Methods for graphically rendering a block as SVG. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.BlockSvg'); + +goog.require('Blockly.Block'); +goog.require('Blockly.ContextMenu'); +goog.require('Blockly.Grid'); +goog.require('Blockly.RenderedConnection'); +goog.require('Blockly.Tooltip'); +goog.require('Blockly.Touch'); +goog.require('Blockly.utils'); +goog.require('goog.Timer'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for a block's SVG representation. + * Not normally called directly, workspace.newBlock() is preferred. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @extends {Blockly.Block} + * @constructor + */ +Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { + // Create core elements for the block. + /** + * @type {SVGElement} + * @private + */ + this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, null); + this.svgGroup_.translate_ = ''; + + /** + * @type {SVGElement} + * @private + */ + this.svgPathDark_ = Blockly.utils.createSvgElement('path', + {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}, + this.svgGroup_); + + /** + * @type {SVGElement} + * @private + */ + this.svgPath_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyPath'}, + this.svgGroup_); + + /** + * @type {SVGElement} + * @private + */ + this.svgPathLight_ = Blockly.utils.createSvgElement('path', + {'class': 'blocklyPathLight'}, this.svgGroup_); + this.svgPath_.tooltip = this; + + /** @type {boolean} */ + this.rendered = false; + + /** + * Whether to move the block to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + this.useDragSurface_ = Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_; + + Blockly.Tooltip.bindMouseEvents(this.svgPath_); + Blockly.BlockSvg.superClass_.constructor.call(this, + workspace, prototypeName, opt_id); +}; +goog.inherits(Blockly.BlockSvg, Blockly.Block); + +/** + * Height of this block, not including any statement blocks above or below. + * Height is in workspace units. + */ +Blockly.BlockSvg.prototype.height = 0; +/** + * Width of this block, including any connected value blocks. + * Width is in workspace units. + */ +Blockly.BlockSvg.prototype.width = 0; + +/** + * Original location of block being dragged. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.BlockSvg.prototype.dragStartXY_ = null; + +/** + * Map from IDs for warnings text to PIDs of functions to apply them. + * Used to be able to maintain multiple warnings. + * @type {Object.} + * @private + */ +Blockly.BlockSvg.prototype.warningTextDb_ = null; + +/** + * Constant for identifying rows that are to be rendered inline. + * Don't collide with Blockly.INPUT_VALUE and friends. + * @const + */ +Blockly.BlockSvg.INLINE = -1; + +/** + * Create and initialize the SVG representation of the block. + * May be called more than once. + */ +Blockly.BlockSvg.prototype.initSvg = function() { + goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.'); + for (var i = 0, input; input = this.inputList[i]; i++) { + input.init(); + } + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].createIcon(); + } + this.updateColour(); + this.updateMovable(); + if (!this.workspace.options.readOnly && !this.eventsInit_) { + Blockly.bindEventWithChecks_( + this.getSvgRoot(), 'mousedown', this, this.onMouseDown_); + } + this.eventsInit_ = true; + + if (!this.getSvgRoot().parentNode) { + this.workspace.getCanvas().appendChild(this.getSvgRoot()); + } +}; + +/** + * Select this block. Highlight it visually. + */ +Blockly.BlockSvg.prototype.select = function() { + if (this.isShadow() && this.getParent()) { + // Shadow blocks should not be selected. + this.getParent().select(); + return; + } + if (Blockly.selected == this) { + return; + } + var oldId = null; + if (Blockly.selected) { + oldId = Blockly.selected.id; + // Unselect any previously selected block. + Blockly.Events.disable(); + try { + Blockly.selected.unselect(); + } finally { + Blockly.Events.enable(); + } + } + var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = this; + this.addSelect(); +}; + +/** + * Unselect this block. Remove its highlighting. + */ +Blockly.BlockSvg.prototype.unselect = function() { + if (Blockly.selected != this) { + return; + } + var event = new Blockly.Events.Ui(null, 'selected', this.id, null); + event.workspaceId = this.workspace.id; + Blockly.Events.fire(event); + Blockly.selected = null; + this.removeSelect(); +}; + +/** + * Block's mutator icon (if any). + * @type {Blockly.Mutator} + */ +Blockly.BlockSvg.prototype.mutator = null; + +/** + * Block's comment icon (if any). + * @type {Blockly.Comment} + */ +Blockly.BlockSvg.prototype.comment = null; + +/** + * Block's warning icon (if any). + * @type {Blockly.Warning} + */ +Blockly.BlockSvg.prototype.warning = null; + +/** + * Returns a list of mutator, comment, and warning icons. + * @return {!Array} List of icons. + */ +Blockly.BlockSvg.prototype.getIcons = function() { + var icons = []; + if (this.mutator) { + icons.push(this.mutator); + } + if (this.comment) { + icons.push(this.comment); + } + if (this.warning) { + icons.push(this.warning); + } + return icons; +}; + +/** + * Set parent of this block to be a new block or null. + * @param {Blockly.BlockSvg} newParent New parent block. + */ +Blockly.BlockSvg.prototype.setParent = function(newParent) { + if (newParent == this.parentBlock_) { + return; + } + var svgRoot = this.getSvgRoot(); + if (this.parentBlock_ && svgRoot) { + // Move this block up the DOM. Keep track of x/y translations. + var xy = this.getRelativeToSurfaceXY(); + this.workspace.getCanvas().appendChild(svgRoot); + svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')'); + } + + Blockly.Field.startCache(); + Blockly.BlockSvg.superClass_.setParent.call(this, newParent); + Blockly.Field.stopCache(); + + if (newParent) { + var oldXY = this.getRelativeToSurfaceXY(); + newParent.getSvgRoot().appendChild(svgRoot); + var newXY = this.getRelativeToSurfaceXY(); + // Move the connections to match the child's new position. + this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y); + } +}; + +/** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0), in workspace units. + * If the block is on the workspace, (0, 0) is the origin of the workspace + * coordinate system. + * This does not change with workspace scale. + * @return {!goog.math.Coordinate} Object with .x and .y properties in + * workspace coordinates. + */ +Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() { + var x = 0; + var y = 0; + + var dragSurfaceGroup = this.useDragSurface_ ? + this.workspace.blockDragSurface_.getGroup() : null; + + var element = this.getSvgRoot(); + if (element) { + do { + // Loop through this block and every parent. + var xy = Blockly.utils.getRelativeXY(element); + x += xy.x; + y += xy.y; + // If this element is the current element on the drag surface, include + // the translation of the drag surface itself. + if (this.useDragSurface_ && + this.workspace.blockDragSurface_.getCurrentBlock() == element) { + var surfaceTranslation = this.workspace.blockDragSurface_.getSurfaceTranslation(); + x += surfaceTranslation.x; + y += surfaceTranslation.y; + } + element = element.parentNode; + } while (element && element != this.workspace.getCanvas() && + element != dragSurfaceGroup); + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset in workspace units. + * @param {number} dy Vertical offset in workspace units. + */ +Blockly.BlockSvg.prototype.moveBy = function(dx, dy) { + goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); + var event = new Blockly.Events.BlockMove(this); + var xy = this.getRelativeToSurfaceXY(); + this.translate(xy.x + dx, xy.y + dy); + this.moveConnections_(dx, dy); + event.recordNew(); + this.workspace.resizeContents(); + Blockly.Events.fire(event); +}; + +/** + * Transforms a block by setting the translation on the transform attribute + * of the block's SVG. + * @param {number} x The x coordinate of the translation in workspace units. + * @param {number} y The y coordinate of the translation in workspace units. + */ +Blockly.BlockSvg.prototype.translate = function(x, y) { + this.getSvgRoot().setAttribute('transform', + 'translate(' + x + ',' + y + ')'); +}; + +/** + * Move this block to its workspace's drag surface, accounting for positioning. + * Generally should be called at the same time as setDragging_(true). + * Does nothing if useDragSurface_ is false. + * @private + */ +Blockly.BlockSvg.prototype.moveToDragSurface_ = function() { + if (!this.useDragSurface_) { + return; + } + // The translation for drag surface blocks, + // is equal to the current relative-to-surface position, + // to keep the position in sync as it move on/off the surface. + // This is in workspace coordinates. + var xy = this.getRelativeToSurfaceXY(); + this.clearTransformAttributes_(); + this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y); + // Execute the move on the top-level SVG component + this.workspace.blockDragSurface_.setBlocksAndShow(this.getSvgRoot()); +}; + +/** + * Move this block back to the workspace block canvas. + * Generally should be called at the same time as setDragging_(false). + * Does nothing if useDragSurface_ is false. + * @param {!goog.math.Coordinate} newXY The position the block should take on + * on the workspace canvas, in workspace coordinates. + * @private + */ +Blockly.BlockSvg.prototype.moveOffDragSurface_ = function(newXY) { + if (!this.useDragSurface_) { + return; + } + // Translate to current position, turning off 3d. + this.translate(newXY.x, newXY.y); + this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas()); +}; + +/** + * Move this block during a drag, taking into account whether we are using a + * drag surface to translate blocks. + * This block must be a top-level block. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) { + if (this.useDragSurface_) { + this.workspace.blockDragSurface_.translateSurface(newLoc.x, newLoc.y); + } else { + this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')'; + this.svgGroup_.setAttribute('transform', + this.svgGroup_.translate_ + this.svgGroup_.skew_); + } +}; + +/** + * Clear the block of transform="..." attributes. + * Used when the block is switching from 3d to 2d transform or vice versa. + * @private + */ +Blockly.BlockSvg.prototype.clearTransformAttributes_ = function() { + Blockly.utils.removeAttribute(this.getSvgRoot(), 'transform'); +}; + +/** + * Snap this block to the nearest grid point. + */ +Blockly.BlockSvg.prototype.snapToGrid = function() { + if (!this.workspace) { + return; // Deleted block. + } + if (this.workspace.isDragging()) { + return; // Don't bump blocks during a drag. + } + if (this.getParent()) { + return; // Only snap top-level blocks. + } + if (this.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + var grid = this.workspace.getGrid(); + if (!grid || !grid.shouldSnap()) { + return; // Config says no snapping. + } + var spacing = grid.getSpacing(); + var half = spacing / 2; + var xy = this.getRelativeToSurfaceXY(); + var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x; + var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y; + dx = Math.round(dx); + dy = Math.round(dy); + if (dx != 0 || dy != 0) { + this.moveBy(dx, dy); + } +}; + +/** + * Returns the coordinates of a bounding box describing the dimensions of this + * block and any blocks stacked below it. + * Coordinate system: workspace coordinates. + * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}} + * Object with top left and bottom right coordinates of the bounding box. + */ +Blockly.BlockSvg.prototype.getBoundingRectangle = function() { + var blockXY = this.getRelativeToSurfaceXY(this); + var tab = this.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockBounds = this.getHeightWidth(); + var topLeft; + var bottomRight; + if (this.RTL) { + // Width has the tab built into it already so subtract it here. + topLeft = new goog.math.Coordinate(blockXY.x - (blockBounds.width - tab), + blockXY.y); + // Add the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + bottomRight = new goog.math.Coordinate(blockXY.x + tab, + blockXY.y + blockBounds.height); + } else { + // Subtract the width of the tab/puzzle piece knob to the x coordinate + // since X is the corner of the rectangle, not the whole puzzle piece. + topLeft = new goog.math.Coordinate(blockXY.x - tab, blockXY.y); + // Width has the tab built into it already so subtract it here. + bottomRight = new goog.math.Coordinate(blockXY.x + blockBounds.width - tab, + blockXY.y + blockBounds.height); + } + return {topLeft: topLeft, bottomRight: bottomRight}; +}; + +/** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ +Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) { + if (this.collapsed_ == collapsed) { + return; + } + var renderList = []; + // Show/hide the inputs. + for (var i = 0, input; input = this.inputList[i]; i++) { + renderList.push.apply(renderList, input.setVisible(!collapsed)); + } + + var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; + if (collapsed) { + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].setVisible(false); + } + var text = this.toString(Blockly.COLLAPSE_CHARS); + this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init(); + } else { + this.removeInput(COLLAPSED_INPUT_NAME); + // Clear any warnings inherited from enclosed blocks. + this.setWarningText(null); + } + Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed); + + if (!renderList.length) { + // No child blocks, just render this block. + renderList[0] = this; + } + if (this.rendered) { + for (var i = 0, block; block = renderList[i]; i++) { + block.render(); + } + // Don't bump neighbours. + // Although bumping neighbours would make sense, users often collapse + // all their functions and store them next to each other. Expanding and + // bumping causes all their definitions to go out of alignment. + } +}; + +/** + * Open the next (or previous) FieldTextInput. + * @param {Blockly.Field|Blockly.Block} start Current location. + * @param {boolean} forward If true go forward, otherwise backward. + */ +Blockly.BlockSvg.prototype.tab = function(start, forward) { + var list = this.createTabList_(); + var i = list.indexOf(start); + if (i == -1) { + // No start location, start at the beginning or end. + i = forward ? -1 : list.length; + } + var target = list[forward ? i + 1 : i - 1]; + if (!target) { + // Ran off of list. + var parent = this.getParent(); + if (parent) { + parent.tab(this, forward); + } + } else if (target instanceof Blockly.Field) { + target.showEditor_(); + } else { + target.tab(null, forward); + } +}; + +/** + * Create an ordered list of all text fields and connected inputs. + * @return {!Array.} The ordered list. + * @private + */ +Blockly.BlockSvg.prototype.createTabList_ = function() { + // This function need not be efficient since it runs once on a keypress. + var list = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldTextInput) { + // TODO(# 1276): Also support dropdown fields. + list.push(field); + } + } + if (input.connection) { + var block = input.connection.targetBlock(); + if (block) { + list.push(block); + } + } + } + return list; +}; + +/** + * Handle a mouse-down on an SVG block. + * @param {!Event} e Mouse down event or touch start event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { + var gesture = this.workspace.getGesture(e); + if (gesture) { + gesture.handleBlockStart(e, this); + } +}; + +/** + * Load the block's help page in a new window. + * @private + */ +Blockly.BlockSvg.prototype.showHelp_ = function() { + var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; + if (url) { + window.open(url); + } +}; + +/** + * Show the context menu for this block. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { + if (this.workspace.options.readOnly || !this.contextMenu) { + return; + } + // Save the current block in a variable for use in closures. + var block = this; + var menuOptions = []; + + if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { + menuOptions.push(Blockly.ContextMenu.blockDuplicateOption(block)); + if (this.isEditable() && !this.collapsed_ && + this.workspace.options.comments) { + menuOptions.push(Blockly.ContextMenu.blockCommentOption(block)); + } + + // Option to make block inline. + if (!this.collapsed_) { + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT && + this.inputList[i].type != Blockly.NEXT_STATEMENT) { + // Only display this option if there are two value or dummy inputs + // next to each other. + var inlineOption = {enabled: true}; + var isInline = this.getInputsInline(); + inlineOption.text = isInline ? + Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS; + inlineOption.callback = function() { + block.setInputsInline(!isInline); + }; + menuOptions.push(inlineOption); + break; + } + } + } + + if (this.workspace.options.collapse) { + // Option to collapse/expand block. + if (this.collapsed_) { + var expandOption = {enabled: true}; + expandOption.text = Blockly.Msg.EXPAND_BLOCK; + expandOption.callback = function() { + block.setCollapsed(false); + }; + menuOptions.push(expandOption); + } else { + var collapseOption = {enabled: true}; + collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK; + collapseOption.callback = function() { + block.setCollapsed(true); + }; + menuOptions.push(collapseOption); + } + } + + if (this.workspace.options.disable) { + // Option to disable/enable block. + var disableOption = { + text: this.disabled ? + Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK, + enabled: !this.getInheritedDisabled(), + callback: function() { + block.setDisabled(!block.disabled); + } + }; + menuOptions.push(disableOption); + } + + menuOptions.push(Blockly.ContextMenu.blockDeleteOption(block)); + } + + menuOptions.push(Blockly.ContextMenu.blockHelpOption(block)); + + // Allow the block to add or modify menuOptions. + if (this.customContextMenu) { + this.customContextMenu(menuOptions); + } + + Blockly.ContextMenu.show(e, menuOptions, this.RTL); + Blockly.ContextMenu.currentBlock = this; +}; + +/** + * Move the connections for this block and all blocks attached under it. + * Also update any attached bubbles. + * @param {number} dx Horizontal offset from current location, in workspace + * units. + * @param {number} dy Vertical offset from current location, in workspace + * units. + * @private + */ +Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) { + if (!this.rendered) { + // Rendering is required to lay out the blocks. + // This is probably an invisible block attached to a collapsed block. + return; + } + var myConnections = this.getConnections_(false); + for (var i = 0; i < myConnections.length; i++) { + myConnections[i].moveBy(dx, dy); + } + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].computeIconLocation(); + } + + // Recurse through all blocks attached under this one. + for (var i = 0; i < this.childBlocks_.length; i++) { + this.childBlocks_[i].moveConnections_(dx, dy); + } +}; + +/** + * Recursively adds or removes the dragging class to this node and its children. + * @param {boolean} adding True if adding, false if removing. + * @package + */ +Blockly.BlockSvg.prototype.setDragging = function(adding) { + if (adding) { + var group = this.getSvgRoot(); + group.translate_ = ''; + group.skew_ = ''; + Blockly.draggingConnections_ = + Blockly.draggingConnections_.concat(this.getConnections_(true)); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } else { + Blockly.draggingConnections_ = []; + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); + } + // Recurse through all blocks attached under this one. + for (var i = 0; i < this.childBlocks_.length; i++) { + this.childBlocks_[i].setDragging(adding); + } +}; + +/** + * Add or remove the UI indicating if this block is movable or not. + */ +Blockly.BlockSvg.prototype.updateMovable = function() { + if (this.isMovable()) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); + } else { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); + } +}; + +/** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ +Blockly.BlockSvg.prototype.setMovable = function(movable) { + Blockly.BlockSvg.superClass_.setMovable.call(this, movable); + this.updateMovable(); +}; + +/** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ +Blockly.BlockSvg.prototype.setEditable = function(editable) { + Blockly.BlockSvg.superClass_.setEditable.call(this, editable); + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].updateEditable(); + } +}; + +/** + * Set whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + */ +Blockly.BlockSvg.prototype.setShadow = function(shadow) { + Blockly.BlockSvg.superClass_.setShadow.call(this, shadow); + this.updateColour(); +}; + +/** + * Return the root node of the SVG or null if none exists. + * @return {Element} The root SVG node (probably a group). + */ +Blockly.BlockSvg.prototype.getSvgRoot = function() { + return this.svgGroup_; +}; + +/** + * Dispose of this block. + * @param {boolean} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + * @param {boolean} animate If true, show a disposal animation and sound. + */ +Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { + if (!this.workspace) { + // The block has already been deleted. + return; + } + Blockly.Tooltip.hide(); + Blockly.Field.startCache(); + // Save the block's workspace temporarily so we can resize the + // contents once the block is disposed. + var blockWorkspace = this.workspace; + // If this block is being dragged, unlink the mouse events. + if (Blockly.selected == this) { + this.unselect(); + this.workspace.cancelCurrentGesture(); + } + // If this block has a context menu open, close it. + if (Blockly.ContextMenu.currentBlock == this) { + Blockly.ContextMenu.hide(); + } + + if (animate && this.rendered) { + this.unplug(healStack); + this.disposeUiEffect(); + } + // Stop rerendering. + this.rendered = false; + + // Clear pending warnings. + if (this.warningTextDb_) { + for (var n in this.warningTextDb_) { + clearTimeout(this.warningTextDb_[n]); + } + this.warningTextDb_ = null; + } + + Blockly.Events.disable(); + try { + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].dispose(); + } + } finally { + Blockly.Events.enable(); + } + Blockly.BlockSvg.superClass_.dispose.call(this, healStack); + + goog.dom.removeNode(this.svgGroup_); + blockWorkspace.resizeContents(); + // Sever JavaScript to DOM connections. + this.svgGroup_ = null; + this.svgPath_ = null; + this.svgPathLight_ = null; + this.svgPathDark_ = null; + Blockly.Field.stopCache(); +}; + +/** + * Play some UI effects (sound, animation) when disposing of a block. + */ +Blockly.BlockSvg.prototype.disposeUiEffect = function() { + this.workspace.getAudioManager().play('delete'); + + var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); + // Deeply clone the current block. + var clone = this.svgGroup_.cloneNode(true); + clone.translateX_ = xy.x; + clone.translateY_ = xy.y; + clone.setAttribute('transform', + 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')'); + this.workspace.getParentSvg().appendChild(clone); + clone.bBox_ = clone.getBBox(); + // Start the animation. + Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date, + this.workspace.scale); +}; + +/** + * Animate a cloned block and eventually dispose of it. + * This is a class method, not an instance method since the original block has + * been destroyed and is no longer accessible. + * @param {!Element} clone SVG element to animate and dispose of. + * @param {boolean} rtl True if RTL, false if LTR. + * @param {!Date} start Date of animation's start. + * @param {number} workspaceScale Scale of workspace. + * @private + */ +Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(clone); + } else { + var x = clone.translateX_ + + (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent; + var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent; + var scale = (1 - percent) * workspaceScale; + clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + + ' scale(' + scale + ')'); + setTimeout( + Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start, workspaceScale); + } +}; + +/** + * Play some UI effects (sound, ripple) after a connection has been established. + */ +Blockly.BlockSvg.prototype.connectionUiEffect = function() { + this.workspace.getAudioManager().play('click'); + if (this.workspace.scale < 1) { + return; // Too small to care about visual effects. + } + // Determine the absolute coordinates of the inferior block. + var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); + // Offset the coordinates based on the two connection types, fix scale. + if (this.outputConnection) { + xy.x += (this.RTL ? 3 : -3) * this.workspace.scale; + xy.y += 13 * this.workspace.scale; + } else if (this.previousConnection) { + xy.x += (this.RTL ? -23 : 23) * this.workspace.scale; + xy.y += 3 * this.workspace.scale; + } + var ripple = Blockly.utils.createSvgElement('circle', + { + 'cx': xy.x, + 'cy': xy.y, + 'r': 0, + 'fill': 'none', + 'stroke': '#888', + 'stroke-width': 10 + }, + this.workspace.getParentSvg()); + // Start the animation. + Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); +}; + +/** + * Expand a ripple around a connection. + * @param {!Element} ripple Element to animate. + * @param {!Date} start Date of animation's start. + * @param {number} workspaceScale Scale of workspace. + * @private + */ +Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { + var ms = new Date - start; + var percent = ms / 150; + if (percent > 1) { + goog.dom.removeNode(ripple); + } else { + ripple.setAttribute('r', percent * 25 * workspaceScale); + ripple.style.opacity = 1 - percent; + Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout( + Blockly.BlockSvg.connectionUiStep_, 10, ripple, start, workspaceScale); + } +}; + +/** + * Play some UI effects (sound, animation) when disconnecting a block. + */ +Blockly.BlockSvg.prototype.disconnectUiEffect = function() { + this.workspace.getAudioManager().play('disconnect'); + if (this.workspace.scale < 1) { + return; // Too small to care about visual effects. + } + // Horizontal distance for bottom of block to wiggle. + var DISPLACEMENT = 10; + // Scale magnitude of skew to height of block. + var height = this.getHeightWidth().height; + var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; + if (!this.RTL) { + magnitude *= -1; + } + // Start the animation. + Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date); +}; + +/** + * Animate a brief wiggle of a disconnected block. + * @param {!Element} group SVG element to animate. + * @param {number} magnitude Maximum degrees skew (reversed for RTL). + * @param {!Date} start Date of animation's start. + * @private + */ +Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { + var DURATION = 200; // Milliseconds. + var WIGGLES = 3; // Half oscillations. + + var ms = new Date - start; + var percent = ms / DURATION; + + if (percent > 1) { + group.skew_ = ''; + } else { + var skew = Math.round( + Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); + group.skew_ = 'skewX(' + skew + ')'; + Blockly.BlockSvg.disconnectUiStop_.group = group; + Blockly.BlockSvg.disconnectUiStop_.pid = + setTimeout( + Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude, start); + } + group.setAttribute('transform', group.translate_ + group.skew_); +}; + +/** + * Stop the disconnect UI animation immediately. + * @private + */ +Blockly.BlockSvg.disconnectUiStop_ = function() { + if (Blockly.BlockSvg.disconnectUiStop_.group) { + clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid); + var group = Blockly.BlockSvg.disconnectUiStop_.group; + group.skew_ = ''; + group.setAttribute('transform', group.translate_); + Blockly.BlockSvg.disconnectUiStop_.group = null; + } +}; + +/** + * PID of disconnect UI animation. There can only be one at a time. + * @type {number} + */ +Blockly.BlockSvg.disconnectUiStop_.pid = 0; + +/** + * SVG group of wobbling block. There can only be one at a time. + * @type {Element} + */ +Blockly.BlockSvg.disconnectUiStop_.group = null; + +/** + * Change the colour of a block. + */ +Blockly.BlockSvg.prototype.updateColour = function() { + if (this.disabled) { + // Disabled blocks don't have colour. + return; + } + var hexColour = this.getColour(); + var rgb = goog.color.hexToRgb(hexColour); + if (this.isShadow()) { + rgb = goog.color.lighten(rgb, 0.6); + hexColour = goog.color.rgbArrayToHex(rgb); + this.svgPathLight_.style.display = 'none'; + this.svgPathDark_.setAttribute('fill', hexColour); + } else { + this.svgPathLight_.style.display = ''; + var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3)); + var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2)); + this.svgPathLight_.setAttribute('stroke', hexLight); + this.svgPathDark_.setAttribute('fill', hexDark); + } + this.svgPath_.setAttribute('fill', hexColour); + + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].updateColour(); + } + + // Bump every dropdown to change its colour. + // TODO (#1456) + for (var x = 0, input; input = this.inputList[x]; x++) { + for (var y = 0, field; field = input.fieldRow[y]; y++) { + field.forceRerender(); + } + } +}; + +/** + * Enable or disable a block. + */ +Blockly.BlockSvg.prototype.updateDisabled = function() { + if (this.disabled || this.getInheritedDisabled()) { + var added = Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (added) { + this.svgPath_.setAttribute('fill', + 'url(#' + this.workspace.options.disabledPatternId + ')'); + } + } else { + var removed = Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (removed) { + this.updateColour(); + } + } + var children = this.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + child.updateDisabled(); + } +}; + +/** + * Returns the comment on this block (or '' if none). + * @return {string} Block's comment. + */ +Blockly.BlockSvg.prototype.getCommentText = function() { + if (this.comment) { + var comment = this.comment.getText(); + // Trim off trailing whitespace. + return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n'); + } + return ''; +}; + +/** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ +Blockly.BlockSvg.prototype.setCommentText = function(text) { + var changedState = false; + if (goog.isString(text)) { + if (!this.comment) { + this.comment = new Blockly.Comment(this); + changedState = true; + } + this.comment.setText(/** @type {string} */ (text)); + } else { + if (this.comment) { + this.comment.dispose(); + changedState = true; + } + } + if (changedState && this.rendered) { + this.render(); + // Adding or removing a comment icon will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Set this block's warning text. + * @param {?string} text The text, or null to delete. + * @param {string=} opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. + */ +Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { + if (!this.warningTextDb_) { + // Create a database of warning PIDs. + // Only runs once per block (and only those with warnings). + this.warningTextDb_ = Object.create(null); + } + var id = opt_id || ''; + if (!id) { + // Kill all previous pending processes, this edit supersedes them all. + for (var n in this.warningTextDb_) { + clearTimeout(this.warningTextDb_[n]); + delete this.warningTextDb_[n]; + } + } else if (this.warningTextDb_[id]) { + // Only queue up the latest change. Kill any earlier pending process. + clearTimeout(this.warningTextDb_[id]); + delete this.warningTextDb_[id]; + } + if (this.workspace.isDragging()) { + // Don't change the warning text during a drag. + // Wait until the drag finishes. + var thisBlock = this; + this.warningTextDb_[id] = setTimeout(function() { + if (thisBlock.workspace) { // Check block wasn't deleted. + delete thisBlock.warningTextDb_[id]; + thisBlock.setWarningText(text, id); + } + }, 100); + return; + } + if (this.isInFlyout) { + text = null; + } + + // Bubble up to add a warning on top-most collapsed block. + var parent = this.getSurroundParent(); + var collapsedParent = null; + while (parent) { + if (parent.isCollapsed()) { + collapsedParent = parent; + } + parent = parent.getSurroundParent(); + } + if (collapsedParent) { + collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id); + } + + var changedState = false; + if (goog.isString(text)) { + if (!this.warning) { + this.warning = new Blockly.Warning(this); + changedState = true; + } + this.warning.setText(/** @type {string} */ (text), id); + } else { + // Dispose all warnings if no ID is given. + if (this.warning && !id) { + this.warning.dispose(); + changedState = true; + } else if (this.warning) { + var oldText = this.warning.getText(); + this.warning.setText('', id); + var newText = this.warning.getText(); + if (!newText) { + this.warning.dispose(); + } + changedState = oldText != newText; + } + } + if (changedState && this.rendered) { + this.render(); + // Adding or removing a warning icon will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Give this block a mutator dialog. + * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. + */ +Blockly.BlockSvg.prototype.setMutator = function(mutator) { + if (this.mutator && this.mutator !== mutator) { + this.mutator.dispose(); + } + if (mutator) { + mutator.block_ = this; + this.mutator = mutator; + mutator.createIcon(); + } +}; + +/** + * Set whether the block is disabled or not. + * @param {boolean} disabled True if disabled. + */ +Blockly.BlockSvg.prototype.setDisabled = function(disabled) { + if (this.disabled != disabled) { + Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled); + if (this.rendered) { + this.updateDisabled(); + } + } +}; + +/** + * Set whether the block is highlighted or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. + */ +Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) { + if (!this.rendered) { + return; + } + if (highlighted) { + this.svgPath_.setAttribute('filter', + 'url(#' + this.workspace.options.embossFilterId + ')'); + this.svgPathLight_.style.display = 'none'; + } else { + Blockly.utils.removeAttribute(this.svgPath_, 'filter'); + delete this.svgPathLight_.style.display; + } +}; + +/** + * Select this block. Highlight it visually. + */ +Blockly.BlockSvg.prototype.addSelect = function() { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); +}; + +/** + * Unselect this block. Remove its highlighting. + */ +Blockly.BlockSvg.prototype.removeSelect = function() { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); +}; + +/** + * Update the cursor over this block by adding or removing a class. + * @param {boolean} enable True if the delete cursor should be shown, false + * otherwise. + * @package + */ +Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) { + if (enable) { + Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggingDelete'); + } else { + Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggingDelete'); + } +}; + +// Overrides of functions on Blockly.Block that take into account whether the +// block has been rendered. + +/** + * Change the colour of a block. + * @param {number|string} colour HSV hue value, or #RRGGBB string. + */ +Blockly.BlockSvg.prototype.setColour = function(colour) { + Blockly.BlockSvg.superClass_.setColour.call(this, colour); + + if (this.rendered) { + this.updateColour(); + } +}; + +/** + * Move this block to the front of the visible workspace. + * tags do not respect z-index so SVG renders them in the + * order that they are in the DOM. By placing this block first within the + * block group's , it will render on top of any other blocks. + * @package + */ +Blockly.BlockSvg.prototype.bringToFront = function() { + var block = this; + do { + var root = block.getSvgRoot(); + root.parentNode.appendChild(root); + block = block.getParent(); + } while (block); +}; + +/** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {(string|Array.|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.BlockSvg.prototype.setPreviousStatement = function(newBoolean, + opt_check) { + Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, + opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {(string|Array.|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { + Blockly.BlockSvg.superClass_.setNextStatement.call(this, newBoolean, + opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {(string|Array.|null)=} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ +Blockly.BlockSvg.prototype.setOutput = function(newBoolean, opt_check) { + Blockly.BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ +Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) { + Blockly.BlockSvg.superClass_.setInputsInline.call(this, newBoolean); + + if (this.rendered) { + this.render(); + this.bumpNeighbours_(); + } +}; + +/** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent error if input is not present. + * @throws {goog.asserts.AssertionError} if the input is not present and + * opt_quiet is not true. + */ +Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) { + Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet); + + if (this.rendered) { + this.render(); + // Removing an input will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved input. + */ +Blockly.BlockSvg.prototype.moveNumberedInputBefore = function( + inputIndex, refIndex) { + Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex, + refIndex); + + if (this.rendered) { + this.render(); + // Moving an input will cause the block to change shape. + this.bumpNeighbours_(); + } +}; + +/** + * Add a value input, statement input or local variable to this block. + * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or + * Blockly.DUMMY_INPUT. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + * @private + */ +Blockly.BlockSvg.prototype.appendInput_ = function(type, name) { + var input = Blockly.BlockSvg.superClass_.appendInput_.call(this, type, name); + + if (this.rendered) { + this.render(); + // Adding an input will cause the block to change shape. + this.bumpNeighbours_(); + } + return input; +}; + +/** + * Returns connections originating from this block. + * @param {boolean} all If true, return all connections even hidden ones. + * Otherwise, for a non-rendered block return an empty list, and for a + * collapsed block don't return inputs connections. + * @return {!Array.} Array of connections. + * @package + */ +Blockly.BlockSvg.prototype.getConnections_ = function(all) { + var myConnections = []; + if (all || this.rendered) { + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + if (all || !this.collapsed_) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + } + } + return myConnections; +}; + +/** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!Blockly.RenderedConnection} A new connection of the specified type. + * @private + */ +Blockly.BlockSvg.prototype.makeConnection_ = function(type) { + return new Blockly.RenderedConnection(this, type); +}; + +/** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + * @private + */ +Blockly.BlockSvg.prototype.bumpNeighbours_ = function() { + if (!this.workspace) { + return; // Deleted block. + } + if (this.workspace.isDragging()) { + return; // Don't bump blocks during a drag. + } + var rootBlock = this.getRootBlock(); + if (rootBlock.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + // Loop through every connection on this block. + var myConnections = this.getConnections_(false); + for (var i = 0, connection; connection = myConnections[i]; i++) { + + // Spider down from this block bumping all sub-blocks. + if (connection.isConnected() && connection.isSuperior()) { + connection.targetBlock().bumpNeighbours_(); + } + + var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS); + for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) { + + // If both connections are connected, that's probably fine. But if + // either one of them is unconnected, then there could be confusion. + if (!connection.isConnected() || !otherConnection.isConnected()) { + // Only bump blocks if they are from different tree structures. + if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) { + + // Always bump the inferior block. + if (connection.isSuperior()) { + otherConnection.bumpAwayFrom_(connection); + } else { + connection.bumpAwayFrom_(otherConnection); + } + } + } + } + } +}; + +/** + * Schedule snapping to grid and bumping neighbours to occur after a brief + * delay. + * @package + */ +Blockly.BlockSvg.prototype.scheduleSnapAndBump = function() { + var block = this; + // Ensure that any snap and bump are part of this move's event group. + var group = Blockly.Events.getGroup(); + + setTimeout(function() { + Blockly.Events.setGroup(group); + block.snapToGrid(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY / 2); + + setTimeout(function() { + Blockly.Events.setGroup(group); + block.bumpNeighbours_(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY); +}; diff --git a/core/.svn/pristine/14/143bc3be450a206de85aa8b69dc686623b33058c.svn-base b/core/.svn/pristine/14/143bc3be450a206de85aa8b69dc686623b33058c.svn-base new file mode 100644 index 0000000..acb044c --- /dev/null +++ b/core/.svn/pristine/14/143bc3be450a206de85aa8b69dc686623b33058c.svn-base @@ -0,0 +1,300 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Components for managing connections between blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.ConnectionDB'); + +goog.require('Blockly.Connection'); + + +/** + * Database of connections. + * Connections are stored in order of their vertical component. This way + * connections in an area may be looked up quickly using a binary search. + * @constructor + */ +Blockly.ConnectionDB = function() { +}; + +Blockly.ConnectionDB.prototype = new Array(); +/** + * Don't inherit the constructor from Array. + * @type {!Function} + */ +Blockly.ConnectionDB.constructor = Blockly.ConnectionDB; + +/** + * Add a connection to the database. Must not already exist in DB. + * @param {!Blockly.Connection} connection The connection to be added. + */ +Blockly.ConnectionDB.prototype.addConnection = function(connection) { + if (connection.inDB_) { + throw 'Connection already in database.'; + } + if (connection.getSourceBlock().isInFlyout) { + // Don't bother maintaining a database of connections in a flyout. + return; + } + var position = this.findPositionForConnection_(connection); + this.splice(position, 0, connection); + connection.inDB_ = true; +}; + +/** + * Find the given connection. + * Starts by doing a binary search to find the approximate location, then + * linearly searches nearby for the exact connection. + * @param {!Blockly.Connection} conn The connection to find. + * @return {number} The index of the connection, or -1 if the connection was + * not found. + */ +Blockly.ConnectionDB.prototype.findConnection = function(conn) { + if (!this.length) { + return -1; + } + + var bestGuess = this.findPositionForConnection_(conn); + if (bestGuess >= this.length) { + // Not in list + return -1; + } + + var yPos = conn.y_; + // Walk forward and back on the y axis looking for the connection. + var pointerMin = bestGuess; + var pointerMax = bestGuess; + while (pointerMin >= 0 && this[pointerMin].y_ == yPos) { + if (this[pointerMin] == conn) { + return pointerMin; + } + pointerMin--; + } + + while (pointerMax < this.length && this[pointerMax].y_ == yPos) { + if (this[pointerMax] == conn) { + return pointerMax; + } + pointerMax++; + } + return -1; +}; + +/** + * Finds a candidate position for inserting this connection into the list. + * This will be in the correct y order but makes no guarantees about ordering in + * the x axis. + * @param {!Blockly.Connection} connection The connection to insert. + * @return {number} The candidate index. + * @private + */ +Blockly.ConnectionDB.prototype.findPositionForConnection_ = function( + connection) { + if (!this.length) { + return 0; + } + var pointerMin = 0; + var pointerMax = this.length; + while (pointerMin < pointerMax) { + var pointerMid = Math.floor((pointerMin + pointerMax) / 2); + if (this[pointerMid].y_ < connection.y_) { + pointerMin = pointerMid + 1; + } else if (this[pointerMid].y_ > connection.y_) { + pointerMax = pointerMid; + } else { + pointerMin = pointerMid; + break; + } + } + return pointerMin; +}; + +/** + * Remove a connection from the database. Must already exist in DB. + * @param {!Blockly.Connection} connection The connection to be removed. + * @private + */ +Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) { + if (!connection.inDB_) { + throw 'Connection not in database.'; + } + var removalIndex = this.findConnection(connection); + if (removalIndex == -1) { + throw 'Unable to find connection in connectionDB.'; + } + connection.inDB_ = false; + this.splice(removalIndex, 1); +}; + +/** + * Find all nearby connections to the given connection. + * Type checking does not apply, since this function is used for bumping. + * @param {!Blockly.Connection} connection The connection whose neighbours + * should be returned. + * @param {number} maxRadius The maximum radius to another connection. + * @return {!Array.} List of connections. + */ +Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) { + var db = this; + var currentX = connection.x_; + var currentY = connection.y_; + + // Binary search to find the closest y location. + var pointerMin = 0; + var pointerMax = db.length - 2; + var pointerMid = pointerMax; + while (pointerMin < pointerMid) { + if (db[pointerMid].y_ < currentY) { + pointerMin = pointerMid; + } else { + pointerMax = pointerMid; + } + pointerMid = Math.floor((pointerMin + pointerMax) / 2); + } + + var neighbours = []; + /** + * Computes if the current connection is within the allowed radius of another + * connection. + * This function is a closure and has access to outside variables. + * @param {number} yIndex The other connection's index in the database. + * @return {boolean} True if the current connection's vertical distance from + * the other connection is less than the allowed radius. + */ + function checkConnection_(yIndex) { + var dx = currentX - db[yIndex].x_; + var dy = currentY - db[yIndex].y_; + var r = Math.sqrt(dx * dx + dy * dy); + if (r <= maxRadius) { + neighbours.push(db[yIndex]); + } + return dy < maxRadius; + } + + // Walk forward and back on the y axis looking for the closest x,y point. + pointerMin = pointerMid; + pointerMax = pointerMid; + if (db.length) { + while (pointerMin >= 0 && checkConnection_(pointerMin)) { + pointerMin--; + } + do { + pointerMax++; + } while (pointerMax < db.length && checkConnection_(pointerMax)); + } + + return neighbours; +}; + + +/** + * Is the candidate connection close to the reference connection. + * Extremely fast; only looks at Y distance. + * @param {number} index Index in database of candidate connection. + * @param {number} baseY Reference connection's Y value. + * @param {number} maxRadius The maximum radius to another connection. + * @return {boolean} True if connection is in range. + * @private + */ +Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) { + return (Math.abs(this[index].y_ - baseY) <= maxRadius); +}; + +/** + * Find the closest compatible connection to this connection. + * @param {!Blockly.Connection} conn The connection searching for a compatible + * mate. + * @param {number} maxRadius The maximum radius to another connection. + * @param {!goog.math.Coordinate} dxy Offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two + * properties:' connection' which is either another connection or null, + * and 'radius' which is the distance. + */ +Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, + dxy) { + // Don't bother. + if (!this.length) { + return {connection: null, radius: maxRadius}; + } + + // Stash the values of x and y from before the drag. + var baseY = conn.y_; + var baseX = conn.x_; + + conn.x_ = baseX + dxy.x; + conn.y_ = baseY + dxy.y; + + // findPositionForConnection finds an index for insertion, which is always + // after any block with the same y index. We want to search both forward + // and back, so search on both sides of the index. + var closestIndex = this.findPositionForConnection_(conn); + + var bestConnection = null; + var bestRadius = maxRadius; + var temp; + + // Walk forward and back on the y axis looking for the closest x,y point. + var pointerMin = closestIndex - 1; + while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) { + temp = this[pointerMin]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMin--; + } + + var pointerMax = closestIndex; + while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_, + maxRadius)) { + temp = this[pointerMax]; + if (conn.isConnectionAllowed(temp, bestRadius)) { + bestConnection = temp; + bestRadius = temp.distanceFrom(conn); + } + pointerMax++; + } + + // Reset the values of x and y. + conn.x_ = baseX; + conn.y_ = baseY; + + // If there were no valid connections, bestConnection will be null. + return {connection: bestConnection, radius: bestRadius}; +}; + +/** + * Initialize a set of connection DBs for a specified workspace. + * @param {!Blockly.Workspace} workspace The workspace this DB is for. + */ +Blockly.ConnectionDB.init = function(workspace) { + // Create four databases, one for each connection type. + var dbList = []; + dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB(); + dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB(); + dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB(); + workspace.connectionDBList = dbList; +}; diff --git a/core/.svn/pristine/18/18c09eb14284a3f3cb5d5308e163b3c9a7d3981b.svn-base b/core/.svn/pristine/18/18c09eb14284a3f3cb5d5308e163b3c9a7d3981b.svn-base new file mode 100644 index 0000000..6f7b062 --- /dev/null +++ b/core/.svn/pristine/18/18c09eb14284a3f3cb5d5308e163b3c9a7d3981b.svn-base @@ -0,0 +1,516 @@ +/** + * @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 Object representing a workspace. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Workspace'); + +goog.require('Blockly.VariableMap'); +goog.require('goog.array'); +goog.require('goog.math'); + + +/** + * Class for a workspace. This is a data structure that contains blocks. + * There is no UI, and can be created headlessly. + * @param {!Blockly.Options=} opt_options Dictionary of options. + * @constructor + */ +Blockly.Workspace = function(opt_options) { + /** @type {string} */ + this.id = Blockly.utils.genUid(); + Blockly.Workspace.WorkspaceDB_[this.id] = this; + /** @type {!Blockly.Options} */ + this.options = opt_options || {}; + /** @type {boolean} */ + this.RTL = !!this.options.RTL; + /** @type {boolean} */ + this.horizontalLayout = !!this.options.horizontalLayout; + /** @type {number} */ + this.toolboxPosition = this.options.toolboxPosition; + + /** + * @type {!Array.} + * @private + */ + this.topBlocks_ = []; + /** + * @type {!Array.} + * @private + */ + this.listeners_ = []; + /** + * @type {!Array.} + * @private + */ + this.undoStack_ = []; + /** + * @type {!Array.} + * @private + */ + this.redoStack_ = []; + /** + * @type {!Object} + * @private + */ + this.blockDB_ = Object.create(null); + + /** + * @type {!Blockly.VariableMap} + * A map from variable type to list of variable names. The lists contain all + * of the named variables in the workspace, including variables + * that are not currently in use. + * @private + */ + this.variableMap_ = new Blockly.VariableMap(this); + + /** + * Blocks in the flyout can refer to variables that don't exist in the main + * workspace. For instance, the "get item in list" block refers to an "item" + * variable regardless of whether the variable has been created yet. + * A FieldVariable must always refer to a Blockly.VariableModel. We reconcile + * these by tracking "potential" variables in the flyout. These variables + * become real when references to them are dragged into the main workspace. + * @type {!Blockly.VariableMap} + * @private + */ + this.potentialVariableMap_ = null; +}; + +/** + * Returns `true` if the workspace is visible and `false` if it's headless. + * @type {boolean} + */ +Blockly.Workspace.prototype.rendered = false; + +/** + * Maximum number of undo events in stack. `0` turns off undo, `Infinity` sets it to unlimited. + * @type {number} + */ +Blockly.Workspace.prototype.MAX_UNDO = 1024; + +/** + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Workspace.prototype.dispose = function() { + this.listeners_.length = 0; + this.clear(); + // Remove from workspace database. + delete Blockly.Workspace.WorkspaceDB_[this.id]; +}; + +/** + * Angle away from the horizontal to sweep for blocks. Order of execution is + * generally top to bottom, but a small angle changes the scan to give a bit of + * a left to right bias (reversed in RTL). Units are in degrees. + * See: http://tvtropes.org/pmwiki/pmwiki.php/Main/DiagonalBilling. + */ +Blockly.Workspace.SCAN_ANGLE = 3; + +/** + * Add a block to the list of top blocks. + * @param {!Blockly.Block} block Block to add. + */ +Blockly.Workspace.prototype.addTopBlock = function(block) { + this.topBlocks_.push(block); +}; + +/** + * Remove a block from the list of top blocks. + * @param {!Blockly.Block} block Block to remove. + */ +Blockly.Workspace.prototype.removeTopBlock = function(block) { + if (!goog.array.remove(this.topBlocks_, block)) { + throw 'Block not present in workspace\'s list of top-most blocks.'; + } +}; + +/** + * Finds the top-level blocks and returns them. Blocks are optionally sorted + * by position; top to bottom (with slight LTR or RTL bias). + * @param {boolean} ordered Sort the list if true. + * @return {!Array.} The top-level block objects. + */ +Blockly.Workspace.prototype.getTopBlocks = function(ordered) { + // Copy the topBlocks_ list. + var blocks = [].concat(this.topBlocks_); + if (ordered && blocks.length > 1) { + var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE)); + if (this.RTL) { + offset *= -1; + } + blocks.sort(function(a, b) { + var aXY = a.getRelativeToSurfaceXY(); + var bXY = b.getRelativeToSurfaceXY(); + return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x); + }); + } + return blocks; +}; + +/** + * Find all blocks in workspace. No particular order. + * @return {!Array.} Array of blocks. + */ +Blockly.Workspace.prototype.getAllBlocks = function() { + var blocks = this.getTopBlocks(false); + for (var i = 0; i < blocks.length; i++) { + blocks.push.apply(blocks, blocks[i].getChildren()); + } + return blocks; +}; + +/** + * Dispose of all blocks in workspace. + */ +Blockly.Workspace.prototype.clear = function() { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + while (this.topBlocks_.length) { + this.topBlocks_[0].dispose(); + } + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + this.variableMap_.clear(); + if (this.potentialVariableMap_) { + this.potentialVariableMap_.clear(); + } +}; + +/* Begin functions that are just pass-throughs to the variable map. */ +/** + * Rename a variable by updating its name in the variable map. Identify the + * variable to rename with the given ID. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + */ +Blockly.Workspace.prototype.renameVariableById = function(id, newName) { + this.variableMap_.renameVariableById(id, newName); +}; + +/** + * Create a variable with a given name, optional type, and optional ID. + * @param {!string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {?Blockly.VariableModel} The newly created variable. + */ +Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) { + return this.variableMap_.createVariable(name, opt_type, opt_id); +}; + +/** + * Find all the uses of the given variable, which is identified by ID. + * @param {string} id ID of the variable to find. + * @return {!Array.} Array of block usages. + */ +Blockly.Workspace.prototype.getVariableUsesById = function(id) { + return this.variableMap_.getVariableUsesById(id); +}; + +/** + * Delete a variables by the passed in ID and all of its uses from this + * workspace. May prompt the user for confirmation. + * @param {string} id ID of variable to delete. + */ +Blockly.Workspace.prototype.deleteVariableById = function(id) { + this.variableMap_.deleteVariableById(id); +}; + +/** + * Deletes a variable and all of its uses from this workspace without asking the + * user for confirmation. + * @param {!Blockly.VariableModel} variable Variable to delete. + * @param {!Array.} uses An array of uses of the variable. + * @private + */ +Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable, uses) { + this.variableMap_.deleteVariableInternal_(variable, uses); +}; + +/** + * Check whether a variable exists with the given name. The check is + * case-insensitive. + * @param {string} name The name to check for. + * @return {number} The index of the name in the variable list, or -1 if it is + * not present. + * @deprecated April 2017 + */ + +Blockly.Workspace.prototype.variableIndexOf = function( + /* eslint-disable no-unused-vars */ name + /* eslint-enable no-unused-vars */) { + console.warn( + 'Deprecated call to Blockly.Workspace.prototype.variableIndexOf'); + return -1; +}; + +/** + * Find the variable by the given name and return it. Return null if it is not + * found. + * TODO (#1199): Possibly delete this function. + * @param {!string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. + * @return {?Blockly.VariableModel} the variable with the given name. + */ +Blockly.Workspace.prototype.getVariable = function(name, opt_type) { + return this.variableMap_.getVariable(name, opt_type); +}; + +/** + * Find the variable by the given ID and return it. Return null if it is not + * found. + * @param {!string} id The ID to check for. + * @return {?Blockly.VariableModel} The variable with the given ID. + */ +Blockly.Workspace.prototype.getVariableById = function(id) { + return this.variableMap_.getVariableById(id); +}; + +/** + * Find the variable with the specified type. If type is null, return list of + * variables with empty string type. + * @param {?string} type Type of the variables to find. + * @return {Array.} The sought after variables of the + * passed in type. An empty array if none are found. + */ +Blockly.Workspace.prototype.getVariablesOfType = function(type) { + return this.variableMap_.getVariablesOfType(type); +}; + +/** + * Return all variable types. + * @return {!Array.} List of variable types. + * @package + */ +Blockly.Workspace.prototype.getVariableTypes = function() { + return this.variableMap_.getVariableTypes(); +}; + +/** + * Return all variables of all types. + * @return {!Array.} List of variable models. + */ +Blockly.Workspace.prototype.getAllVariables = function() { + return this.variableMap_.getAllVariables(); +}; + +/* End functions that are just pass-throughs to the variable map. */ + +/** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * Not relevant for a headless workspace. + * @return {number} Width. + */ +Blockly.Workspace.prototype.getWidth = function() { + return 0; +}; + +/** + * Obtain a newly created block. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @return {!Blockly.Block} The created block. + */ +Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.Block(this, prototypeName, opt_id); +}; + +/** + * The number of blocks that may be added to the workspace before reaching + * the maxBlocks. + * @return {number} Number of blocks left. + */ +Blockly.Workspace.prototype.remainingCapacity = function() { + if (isNaN(this.options.maxBlocks)) { + return Infinity; + } + return this.options.maxBlocks - this.getAllBlocks().length; +}; + +/** + * Undo or redo the previous action. + * @param {boolean} redo False if undo, true if redo. + */ +Blockly.Workspace.prototype.undo = function(redo) { + var inputStack = redo ? this.redoStack_ : this.undoStack_; + var outputStack = redo ? this.undoStack_ : this.redoStack_; + var inputEvent = inputStack.pop(); + if (!inputEvent) { + return; + } + var events = [inputEvent]; + // Do another undo/redo if the next one is of the same group. + while (inputStack.length && inputEvent.group && + inputEvent.group == inputStack[inputStack.length - 1].group) { + events.push(inputStack.pop()); + } + // Push these popped events on the opposite stack. + for (var i = 0, event; event = events[i]; i++) { + outputStack.push(event); + } + events = Blockly.Events.filter(events, redo); + Blockly.Events.recordUndo = false; + try { + for (var i = 0, event; event = events[i]; i++) { + event.run(redo); + } + } finally { + Blockly.Events.recordUndo = true; + } +}; + +/** + * Clear the undo/redo stacks. + */ +Blockly.Workspace.prototype.clearUndo = function() { + this.undoStack_.length = 0; + this.redoStack_.length = 0; + // Stop any events already in the firing queue from being undoable. + Blockly.Events.clearPendingUndo(); +}; + +/** + * When something in this workspace changes, call a function. + * @param {!Function} func Function to call. + * @return {!Function} Function that can be passed to + * removeChangeListener. + */ +Blockly.Workspace.prototype.addChangeListener = function(func) { + this.listeners_.push(func); + return func; +}; + +/** + * Stop listening for this workspace's changes. + * @param {Function} func Function to stop calling. + */ +Blockly.Workspace.prototype.removeChangeListener = function(func) { + goog.array.remove(this.listeners_, func); +}; + +/** + * Fire a change event. + * @param {!Blockly.Events.Abstract} event Event to fire. + */ +Blockly.Workspace.prototype.fireChangeListener = function(event) { + if (event.recordUndo) { + this.undoStack_.push(event); + this.redoStack_.length = 0; + if (this.undoStack_.length > this.MAX_UNDO) { + this.undoStack_.unshift(); + } + } + for (var i = 0, func; func = this.listeners_[i]; i++) { + func(event); + } +}; + +/** + * Find the block on this workspace with the specified ID. + * @param {string} id ID of block to find. + * @return {Blockly.Block} The sought after block or null if not found. + */ +Blockly.Workspace.prototype.getBlockById = function(id) { + return this.blockDB_[id] || null; +}; + +/** + * Checks whether all value and statement inputs in the workspace are filled + * with blocks. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling + * whether shadow blocks are counted as filled. Defaults to true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ +Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) { + var blocks = this.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (!block.allInputsFilled(opt_shadowBlocksAreFilled)) { + return false; + } + } + return true; +}; + +/** + * Return the variable map that contains "potential" variables. These exist in + * the flyout but not in the workspace. + * @return {?Blockly.VariableMap} The potential variable map. + * @package + */ +Blockly.Workspace.prototype.getPotentialVariableMap = function() { + return this.potentialVariableMap_; +}; + +/** + * Create and store the potential variable map for this workspace. + * @package + */ +Blockly.Workspace.prototype.createPotentialVariableMap = function() { + this.potentialVariableMap_ = new Blockly.VariableMap(this); +}; + +/** + * Return the map of all variables on the workspace. + * @return {?Blockly.VariableMap} The variable map. + */ +Blockly.Workspace.prototype.getVariableMap = function() { + return this.variableMap_; +}; + +/** + * Database of all workspaces. + * @private + */ +Blockly.Workspace.WorkspaceDB_ = Object.create(null); + +/** + * Find the workspace with the specified ID. + * @param {string} id ID of workspace to find. + * @return {Blockly.Workspace} The sought after workspace or null if not found. + */ +Blockly.Workspace.getById = function(id) { + return Blockly.Workspace.WorkspaceDB_[id] || null; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear; +Blockly.Workspace.prototype['clearUndo'] = + Blockly.Workspace.prototype.clearUndo; +Blockly.Workspace.prototype['addChangeListener'] = + Blockly.Workspace.prototype.addChangeListener; +Blockly.Workspace.prototype['removeChangeListener'] = + Blockly.Workspace.prototype.removeChangeListener; diff --git a/core/.svn/pristine/19/195ac3f51b87d91c45e1febaa477e14dc05e5368.svn-base b/core/.svn/pristine/19/195ac3f51b87d91c45e1febaa477e14dc05e5368.svn-base new file mode 100644 index 0000000..4297202 --- /dev/null +++ b/core/.svn/pristine/19/195ac3f51b87d91c45e1febaa477e14dc05e5368.svn-base @@ -0,0 +1,705 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Toolbox from whence to create blocks. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Toolbox'); + +goog.require('Blockly.Flyout'); +goog.require('Blockly.HorizontalFlyout'); +goog.require('Blockly.Touch'); +goog.require('Blockly.VerticalFlyout'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.html.SafeHtml'); +goog.require('goog.html.SafeStyle'); +goog.require('goog.math.Rect'); +goog.require('goog.style'); +goog.require('goog.ui.tree.TreeControl'); +goog.require('goog.ui.tree.TreeNode'); + + +/** + * Class for a Toolbox. + * Creates the toolbox's DOM. + * @param {!Blockly.Workspace} workspace The workspace in which to create new + * blocks. + * @constructor + */ +Blockly.Toolbox = function(workspace) { + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = workspace; + + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = workspace.options.RTL; + + /** + * Whether the toolbox should be laid out horizontally. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = workspace.options.horizontalLayout; + + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + */ + this.toolboxPosition = workspace.options.toolboxPosition; + + /** + * Configuration constants for Closure's tree UI. + * @type {Object.} + * @private + */ + this.config_ = { + indentWidth: 19, + cssRoot: 'blocklyTreeRoot', + cssHideRoot: 'blocklyHidden', + cssItem: '', + cssTreeRow: 'blocklyTreeRow', + cssItemLabel: 'blocklyTreeLabel', + cssTreeIcon: 'blocklyTreeIcon', + cssExpandedFolderIcon: 'blocklyTreeIconOpen', + cssFileIcon: 'blocklyTreeIconNone', + cssSelectedRow: 'blocklyTreeSelected' + }; + + + /** + * Configuration constants for tree separator. + * @type {Object.} + * @private + */ + this.treeSeparatorConfig_ = { + cssTreeRow: 'blocklyTreeSeparator' + }; + + if (this.horizontalLayout_) { + this.config_['cssTreeRow'] = + this.config_['cssTreeRow'] + + (workspace.RTL ? + ' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree'); + + this.treeSeparatorConfig_['cssTreeRow'] = + 'blocklyTreeSeparatorHorizontal ' + + (workspace.RTL ? + 'blocklyHorizontalTreeRtl' : 'blocklyHorizontalTree'); + this.config_['cssTreeIcon'] = ''; + } +}; + +/** + * Width of the toolbox, which changes only in vertical layout. + * @type {number} + */ +Blockly.Toolbox.prototype.width = 0; + +/** + * Height of the toolbox, which changes only in horizontal layout. + * @type {number} + */ +Blockly.Toolbox.prototype.height = 0; + +/** + * The SVG group currently selected. + * @type {SVGGElement} + * @private + */ +Blockly.Toolbox.prototype.selectedOption_ = null; + +/** + * The tree node most recently selected. + * @type {goog.ui.tree.BaseNode} + * @private + */ +Blockly.Toolbox.prototype.lastCategory_ = null; + +/** + * Initializes the toolbox. + */ +Blockly.Toolbox.prototype.init = function() { + var workspace = this.workspace_; + var svg = this.workspace_.getParentSvg(); + + /** + * HTML container for the Toolbox menu. + * @type {Element} + */ + this.HtmlDiv = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyToolboxDiv'); + this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR'); + svg.parentNode.insertBefore(this.HtmlDiv, svg); + + // Clicking on toolbox closes popups. + Blockly.bindEventWithChecks_(this.HtmlDiv, 'mousedown', this, + function(e) { + if (Blockly.utils.isRightButton(e) || e.target == this.HtmlDiv) { + // Close flyout. + Blockly.hideChaff(false); + } else { + // Just close popups. + Blockly.hideChaff(true); + } + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. + }, /*opt_noCaptureIdentifier*/ false, /*opt_noPreventDefault*/ true); + var workspaceOptions = { + disabledPatternId: workspace.options.disabledPatternId, + parentWorkspace: workspace, + RTL: workspace.RTL, + oneBasedIndex: workspace.options.oneBasedIndex, + horizontalLayout: workspace.horizontalLayout, + toolboxPosition: workspace.options.toolboxPosition + }; + /** + * @type {!Blockly.Flyout} + * @private + */ + this.flyout_ = null; + if (workspace.horizontalLayout) { + this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions); + } else { + this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions); + } + goog.dom.insertSiblingAfter( + this.flyout_.createDom('svg'), this.workspace_.getParentSvg()); + this.flyout_.init(workspace); + + this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; + this.config_['cssCollapsedFolderIcon'] = + 'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr'); + var tree = new Blockly.Toolbox.TreeControl(this, this.config_); + this.tree_ = tree; + tree.setShowRootNode(false); + tree.setShowLines(false); + tree.setShowExpandIcons(false); + tree.setSelectedItem(null); + var openNode = this.populate_(workspace.options.languageTree); + tree.render(this.HtmlDiv); + if (openNode) { + tree.setSelectedItem(openNode); + } + this.addColour_(); + this.position(); +}; + +/** + * Dispose of this toolbox. + */ +Blockly.Toolbox.prototype.dispose = function() { + this.flyout_.dispose(); + this.tree_.dispose(); + goog.dom.removeNode(this.HtmlDiv); + this.workspace_ = null; + this.lastCategory_ = null; +}; + +/** + * Get the width of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.Toolbox.prototype.getWidth = function() { + return this.width; +}; + +/** + * Get the height of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.Toolbox.prototype.getHeight = function() { + return this.height; +}; + +/** + * Move the toolbox to the edge. + */ +Blockly.Toolbox.prototype.position = function() { + var treeDiv = this.HtmlDiv; + if (!treeDiv) { + // Not initialized yet. + return; + } + var svg = this.workspace_.getParentSvg(); + var svgSize = Blockly.svgSize(svg); + if (this.horizontalLayout_) { + treeDiv.style.left = '0'; + treeDiv.style.height = 'auto'; + treeDiv.style.width = svgSize.width + 'px'; + this.height = treeDiv.offsetHeight; + if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top + treeDiv.style.top = '0'; + } else { // Bottom + treeDiv.style.bottom = '0'; + } + } else { + if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right + treeDiv.style.right = '0'; + } else { // Left + treeDiv.style.left = '0'; + } + treeDiv.style.height = svgSize.height + 'px'; + this.width = treeDiv.offsetWidth; + } + this.flyout_.position(); +}; + +/** + * Fill the toolbox with categories and blocks. + * @param {!Node} newTree DOM tree of blocks. + * @return {Node} Tree node to open at startup (or null). + * @private + */ +Blockly.Toolbox.prototype.populate_ = function(newTree) { + this.tree_.removeChildren(); // Delete any existing content. + this.tree_.blocks = []; + this.hasColours_ = false; + var openNode = + this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia); + + if (this.tree_.blocks.length) { + throw 'Toolbox cannot have both blocks and categories in the root level.'; + } + + // Fire a resize event since the toolbox may have changed width and height. + this.workspace_.resizeContents(); + return openNode; +}; + +/** + * Sync trees of the toolbox. + * @param {!Node} treeIn DOM tree of blocks. + * @param {!Blockly.Toolbox.TreeControl} treeOut The TreeContorol object built + * from treeIn. + * @param {string} pathToMedia The path to the Blockly media directory. + * @return {Node} Tree node to open at startup (or null). + * @private + */ +Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { + var openNode = null; + var lastElement = null; + for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) { + if (!childIn.tagName) { + // Skip over text. + continue; + } + switch (childIn.tagName.toUpperCase()) { + case 'CATEGORY': + // Decode the category name for any potential message references + // (eg. `%{BKY_CATEGORY_NAME_LOGIC}`). + var categoryName = Blockly.utils.replaceMessageReferences( + childIn.getAttribute('name')); + var childOut = this.tree_.createNode(categoryName); + childOut.blocks = []; + treeOut.add(childOut); + var custom = childIn.getAttribute('custom'); + if (custom) { + // Variables and procedures are special dynamic categories. + childOut.blocks = custom; + } else { + var newOpenNode = this.syncTrees_(childIn, childOut, pathToMedia); + if (newOpenNode) { + openNode = newOpenNode; + } + } + // Decode the colour for any potential message references + // (eg. `%{BKY_MATH_HUE}`). + var colour = Blockly.utils.replaceMessageReferences( + childIn.getAttribute('colour')); + if (goog.isString(colour)) { + if (/^#[0-9a-fA-F]{6}$/.test(colour)) { + childOut.hexColour = colour; + } else { + childOut.hexColour = Blockly.hueToRgb(Number(colour)); + } + this.hasColours_ = true; + } else { + childOut.hexColour = ''; + } + if (childIn.getAttribute('expanded') == 'true') { + if (childOut.blocks.length) { + // This is a category that directly contains blocks. + // After the tree is rendered, open this category and show flyout. + openNode = childOut; + } + childOut.setExpanded(true); + } else { + childOut.setExpanded(false); + } + lastElement = childIn; + break; + case 'SEP': + if (lastElement) { + if (lastElement.tagName.toUpperCase() == 'CATEGORY') { + // Separator between two categories. + // + treeOut.add(new Blockly.Toolbox.TreeSeparator( + this.treeSeparatorConfig_)); + } else { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseFloat(childIn.getAttribute('gap')); + if (!isNaN(newGap) && lastElement) { + lastElement.setAttribute('gap', newGap); + } + } + } + break; + case 'BLOCK': + case 'SHADOW': + case 'LABEL': + case 'BUTTON': + treeOut.blocks.push(childIn); + lastElement = childIn; + break; + } + } + return openNode; +}; + +/** + * Recursively add colours to this toolbox. + * @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree. + * Defaults to the root node. + * @private + */ +Blockly.Toolbox.prototype.addColour_ = function(opt_tree) { + var tree = opt_tree || this.tree_; + var children = tree.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + var element = child.getRowElement(); + if (element) { + if (this.hasColours_) { + var border = '8px solid ' + (child.hexColour || '#ddd'); + } else { + var border = 'none'; + } + if (this.workspace_.RTL) { + element.style.borderRight = border; + } else { + element.style.borderLeft = border; + } + } + this.addColour_(child); + } +}; + +/** + * Unhighlight any previously specified option. + */ +Blockly.Toolbox.prototype.clearSelection = function() { + this.tree_.setSelectedItem(null); +}; + +/** + * Adds a style on the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to add. + * @package + */ +Blockly.Toolbox.prototype.addStyle = function(style) { + Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv), style); +}; + +/** + * Removes a style from the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to remove. + * @package + */ +Blockly.Toolbox.prototype.removeStyle = function(style) { + Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv), style); +}; + +/** + * Return the deletion rectangle for this toolbox. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.Toolbox.prototype.getClientRect = function() { + if (!this.HtmlDiv) { + return null; + } + + // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox + // area are still deleted. Must be smaller than Infinity, but larger than + // the largest screen size. + var BIG_NUM = 10000000; + var toolboxRect = this.HtmlDiv.getBoundingClientRect(); + + var x = toolboxRect.left; + var y = toolboxRect.top; + var width = toolboxRect.width; + var height = toolboxRect.height; + + // Assumes that the toolbox is on the SVG edge. If this changes + // (e.g. toolboxes in mutators) then this code will need to be more complex. + if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + return new goog.math.Rect(-BIG_NUM, -BIG_NUM, BIG_NUM + x + width, + 2 * BIG_NUM); + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, 2 * BIG_NUM); + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { + return new goog.math.Rect(-BIG_NUM, -BIG_NUM, 2 * BIG_NUM, + BIG_NUM + y + height); + } else { // Bottom + return new goog.math.Rect(0, y, 2 * BIG_NUM, BIG_NUM + width); + } +}; + +/** + * Update the flyout's contents without closing it. Should be used in response + * to a change in one of the dynamic categories, such as variables or + * procedures. + */ +Blockly.Toolbox.prototype.refreshSelection = function() { + var selectedItem = this.tree_.getSelectedItem(); + if (selectedItem && selectedItem.blocks) { + this.flyout_.show(selectedItem.blocks); + } +}; + +// Extending Closure's Tree UI. + +/** + * Extension of a TreeControl object that uses a custom tree node. + * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree. + * @param {Object} config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. + * @constructor + * @extends {goog.ui.tree.TreeControl} + */ +Blockly.Toolbox.TreeControl = function(toolbox, config) { + this.toolbox_ = toolbox; + goog.ui.tree.TreeControl.call(this, goog.html.SafeHtml.EMPTY, config); +}; +goog.inherits(Blockly.Toolbox.TreeControl, goog.ui.tree.TreeControl); + +/** + * Adds touch handling to TreeControl. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.enterDocument = function() { + Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this); + + // Add touch handler. + if (goog.events.BrowserFeature.TOUCH_ENABLED) { + var el = this.getElement(); + Blockly.bindEventWithChecks_(el, goog.events.EventType.TOUCHEND, this, + this.handleTouchEvent_); + } +}; + +/** + * Handles touch events. + * @param {!goog.events.BrowserEvent} e The browser event. + * @private + */ +Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) { + var node = this.getNodeFromEvent_(e); + if (node && e.type === goog.events.EventType.TOUCHEND) { + // Fire asynchronously since onMouseDown takes long enough that the browser + // would fire the default mouse event before this method returns. + setTimeout(function() { + node.onClick_(e); // Same behaviour for click and touch. + }, 1); + } +}; + +/** + * Creates a new tree node using a custom tree node. + * @param {string=} opt_html The HTML content of the node label. + * @return {!goog.ui.tree.TreeNode} The new item. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) { + var html = opt_html ? + goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY; + return new Blockly.Toolbox.TreeNode(this.toolbox_, html, + this.getConfig(), this.getDomHelper()); +}; + +/** + * Display/hide the flyout when an item is selected. + * @param {goog.ui.tree.BaseNode} node The item to select. + * @override + */ +Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) { + var toolbox = this.toolbox_; + if (node == this.selectedItem_ || node == toolbox.tree_) { + return; + } + if (toolbox.lastCategory_) { + toolbox.lastCategory_.getRowElement().style.backgroundColor = ''; + } + if (node) { + var hexColour = node.hexColour || '#57e'; + node.getRowElement().style.backgroundColor = hexColour; + // Add colours to child nodes which may have been collapsed and thus + // not rendered. + toolbox.addColour_(node); + } + var oldNode = this.getSelectedItem(); + goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this, node); + if (node && node.blocks && node.blocks.length) { + toolbox.flyout_.show(node.blocks); + // Scroll the flyout to the top if the category has changed. + if (toolbox.lastCategory_ != node) { + toolbox.flyout_.scrollToStart(); + } + } else { + // Hide the flyout. + toolbox.flyout_.hide(); + } + if (oldNode != node && oldNode != this) { + var event = new Blockly.Events.Ui(null, 'category', + oldNode && oldNode.getHtml(), node && node.getHtml()); + event.workspaceId = toolbox.workspace_.id; + Blockly.Events.fire(event); + } + if (node) { + toolbox.lastCategory_ = node; + } +}; + +/** + * A single node in the tree, customized for Blockly's UI. + * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree. + * @param {!goog.html.SafeHtml} html The HTML content of the node label. + * @param {Object=} opt_config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config + * will be used. + * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper. + * @constructor + * @extends {goog.ui.tree.TreeNode} + */ +Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { + goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper); + if (toolbox) { + var resize = function() { + // Even though the div hasn't changed size, the visible workspace + // surface of the workspace has, so we may need to reposition everything. + Blockly.svgResize(toolbox.workspace_); + }; + // Fire a resize event since the toolbox may have changed width. + goog.events.listen(toolbox.tree_, + goog.ui.tree.BaseNode.EventType.EXPAND, resize); + goog.events.listen(toolbox.tree_, + goog.ui.tree.BaseNode.EventType.COLLAPSE, resize); + } +}; +goog.inherits(Blockly.Toolbox.TreeNode, goog.ui.tree.TreeNode); + +/** + * Suppress population of the +/- icon. + * @return {!goog.html.SafeHtml} The source for the icon. + * @override + */ +Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml = function() { + return goog.html.SafeHtml.create('span'); +}; + +/** + * Expand or collapse the node on mouse click. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + */ +Blockly.Toolbox.TreeNode.prototype.onClick_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { + // Expand icon. + if (this.hasChildren() && this.isUserCollapsible_) { + this.toggle(); + this.select(); + } else if (this.isSelected()) { + this.getTree().setSelectedItem(null); + } else { + this.select(); + } + this.updateRow(); +}; + +/** + * Suppress the inherited mouse down behaviour. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onMouseDown = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { + // NOPE. +}; + +/** + * Suppress the inherited double-click behaviour. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { + // NOP. +}; + +/** + * Remap event.keyCode in horizontalLayout so that arrow + * keys work properly and call original onKeyDown handler. + * @param {!goog.events.BrowserEvent} e The browser event. + * @return {boolean} The handled value. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) { + if (this.tree.toolbox_.horizontalLayout_) { + var map = {}; + var next = goog.events.KeyCodes.DOWN; + var prev = goog.events.KeyCodes.UP; + map[goog.events.KeyCodes.RIGHT] = this.rightToLeft_ ? prev : next; + map[goog.events.KeyCodes.LEFT] = this.rightToLeft_ ? next : prev; + map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT; + map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT; + + var newKeyCode = map[e.keyCode]; + e.keyCode = newKeyCode || e.keyCode; + } + return Blockly.Toolbox.TreeNode.superClass_.onKeyDown.call(this, e); +}; + +/** + * A blank separator node in the tree. + * @param {Object=} config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config + * will be used. + * @constructor + * @extends {Blockly.Toolbox.TreeNode} + */ +Blockly.Toolbox.TreeSeparator = function(config) { + Blockly.Toolbox.TreeNode.call(this, null, goog.html.SafeHtml.EMPTY, config); +}; +goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode); diff --git a/core/.svn/pristine/1e/1ee603ca908fdbe08aa236b65aa99b4dfb7a66c5.svn-base b/core/.svn/pristine/1e/1ee603ca908fdbe08aa236b65aa99b4dfb7a66c5.svn-base new file mode 100644 index 0000000..c3909ea --- /dev/null +++ b/core/.svn/pristine/1e/1ee603ca908fdbe08aa236b65aa99b4dfb7a66c5.svn-base @@ -0,0 +1,83 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a flyout visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FlyoutDragger'); + +goog.require('Blockly.WorkspaceDragger'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a flyout dragger. It moves a flyout workspace around when it is + * being dragged by a mouse or touch. + * Note that the workspace itself manages whether or not it has a drag surface + * and how to do translations based on that. This simply passes the right + * commands based on events. + * @param {!Blockly.Flyout} flyout The flyout to drag. + * @constructor + */ +Blockly.FlyoutDragger = function(flyout) { + Blockly.FlyoutDragger.superClass_.constructor.call(this, + flyout.getWorkspace()); + + /** + * The scrollbar to update to move the flyout. + * Unlike the main workspace, the flyout has only one scrollbar, in either the + * horizontal or the vertical direction. + * @type {!Blockly.Scrollbar} + * @private + */ + this.scrollbar_ = flyout.scrollbar_; + + /** + * Whether the flyout scrolls horizontally. If false, the flyout scrolls + * vertically. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = flyout.horizontalLayout_; +}; +goog.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger); + +/** + * Move the appropriate scrollbar to drag the flyout. + * Since flyouts only scroll in one direction at a time, this will discard one + * of the calculated values. + * x and y are in pixels. + * @param {number} x The new x position to move the scrollbar to. + * @param {number} y The new y position to move the scrollbar to. + * @private + */ +Blockly.FlyoutDragger.prototype.updateScroll_ = function(x, y) { + // Move the scrollbar and the flyout will scroll automatically. + if (this.horizontalLayout_) { + this.scrollbar_.set(x); + } else { + this.scrollbar_.set(y); + } +}; diff --git a/core/.svn/pristine/20/20c3336d6cf57cc8aa20b17a20eb52fc7793ae06.svn-base b/core/.svn/pristine/20/20c3336d6cf57cc8aa20b17a20eb52fc7793ae06.svn-base new file mode 100644 index 0000000..1675493 --- /dev/null +++ b/core/.svn/pristine/20/20c3336d6cf57cc8aa20b17a20eb52fc7793ae06.svn-base @@ -0,0 +1,1894 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2014 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 Object representing a workspace rendered as SVG. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceSvg'); + +// TODO(scr): Fix circular dependencies +//goog.require('Blockly.BlockSvg'); +goog.require('Blockly.ConnectionDB'); +goog.require('Blockly.constants'); +goog.require('Blockly.Gesture'); +goog.require('Blockly.Grid'); +goog.require('Blockly.Options'); +goog.require('Blockly.ScrollbarPair'); +goog.require('Blockly.Touch'); +goog.require('Blockly.Trashcan'); +goog.require('Blockly.VariablesDynamic'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.WorkspaceAudio'); +goog.require('Blockly.WorkspaceDragSurfaceSvg'); +goog.require('Blockly.Xml'); +goog.require('Blockly.ZoomControls'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for a workspace. This is an onscreen area with optional trashcan, + * scrollbars, bubbles, and dragging. + * @param {!Blockly.Options} options Dictionary of options. + * @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for + * blocks. + * @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for + * the workspace. + * @extends {Blockly.Workspace} + * @constructor + */ +Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface) { + Blockly.WorkspaceSvg.superClass_.constructor.call(this, options); + this.getMetrics = + options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_; + this.setMetrics = + options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_; + + Blockly.ConnectionDB.init(this); + + if (opt_blockDragSurface) { + this.blockDragSurface_ = opt_blockDragSurface; + } + + if (opt_wsDragSurface) { + this.workspaceDragSurface_ = opt_wsDragSurface; + } + + this.useWorkspaceDragSurface_ = + this.workspaceDragSurface_ && Blockly.utils.is3dSupported(); + + /** + * List of currently highlighted blocks. Block highlighting is often used to + * visually mark blocks currently being executed. + * @type !Array. + * @private + */ + this.highlightedBlocks_ = []; + + /** + * Object in charge of loading, storing, and playing audio for a workspace. + * @type {Blockly.WorkspaceAudio} + * @private + */ + this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace); + + /** + * This workspace's grid object or null. + * @type {Blockly.Grid} + * @private + */ + this.grid_ = this.options.gridPattern ? + new Blockly.Grid(options.gridPattern, options.gridOptions) : null; + + if (Blockly.Variables && Blockly.Variables.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME, + Blockly.Variables.flyoutCategory); + } + if (Blockly.VariablesDynamic && Blockly.VariablesDynamic.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME, + Blockly.VariablesDynamic.flyoutCategory); + } + if (Blockly.Procedures && Blockly.Procedures.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME, + Blockly.Procedures.flyoutCategory); + } +}; +goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace); + +/** + * A wrapper function called when a resize event occurs. + * You can pass the result to `unbindEvent_`. + * @type {Array.} + */ +Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null; + +/** + * The render status of an SVG workspace. + * Returns `true` for visible workspaces and `false` for non-visible, + * or headless, workspaces. + * @type {boolean} + */ +Blockly.WorkspaceSvg.prototype.rendered = true; + +/** + * Is this workspace the surface for a flyout? + * @type {boolean} + */ +Blockly.WorkspaceSvg.prototype.isFlyout = false; + +/** + * Is this workspace the surface for a mutator? + * @type {boolean} + * @package + */ +Blockly.WorkspaceSvg.prototype.isMutator = false; + +/** + * Whether this workspace has resizes enabled. + * Disable during batch operations for a performance improvement. + * @type {boolean} + * @private + */ +Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true; + +/** + * Current horizontal scrolling offset in pixel units. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollX = 0; + +/** + * Current vertical scrolling offset in pixel units. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollY = 0; + +/** + * Horizontal scroll value when scrolling started in pixel units. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.startScrollX = 0; + +/** + * Vertical scroll value when scrolling started in pixel units. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.startScrollY = 0; + +/** + * Distance from mouse to object being dragged. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.WorkspaceSvg.prototype.dragDeltaXY_ = null; + +/** + * Current scale. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scale = 1; + +/** + * The workspace's trashcan (if any). + * @type {Blockly.Trashcan} + */ +Blockly.WorkspaceSvg.prototype.trashcan = null; + +/** + * This workspace's scrollbars, if they exist. + * @type {Blockly.ScrollbarPair} + */ +Blockly.WorkspaceSvg.prototype.scrollbar = null; + +/** + * The current gesture in progress on this workspace, if any. + * @type {Blockly.Gesture} + * @private + */ +Blockly.WorkspaceSvg.prototype.currentGesture_ = null; + +/** + * This workspace's surface for dragging blocks, if it exists. + * @type {Blockly.BlockDragSurfaceSvg} + * @private + */ +Blockly.WorkspaceSvg.prototype.blockDragSurface_ = null; + +/** + * This workspace's drag surface, if it exists. + * @type {Blockly.WorkspaceDragSurfaceSvg} + * @private + */ +Blockly.WorkspaceSvg.prototype.workspaceDragSurface_ = null; + +/** + * Whether to move workspace to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ +Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false; + +/** + * Whether the drag surface is actively in use. When true, calls to + * translate will translate the drag surface instead of the translating the + * workspace directly. + * This is set to true in setupDragSurface and to false in resetDragSurface. + * @type {boolean} + * @private + */ +Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false; + +/** + * Last known position of the page scroll. + * This is used to determine whether we have recalculated screen coordinate + * stuff since the page scrolled. + * @type {!goog.math.Coordinate} + * @private + */ +Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null; + +/** + * Map from function names to callbacks, for deciding what to do when a button + * is clicked. + * @type {!Object.} + * @private + */ +Blockly.WorkspaceSvg.prototype.flyoutButtonCallbacks_ = {}; + +/** + * Map from function names to callbacks, for deciding what to do when a custom + * toolbox category is opened. + * @type {!Object.>} + * @private + */ +Blockly.WorkspaceSvg.prototype.toolboxCategoryCallbacks_ = {}; + +/** + * In a flyout, the target workspace where blocks should be placed after a drag. + * Otherwise null. + * @type {?Blockly.WorkspaceSvg} + * @package + */ +Blockly.WorkspaceSvg.prototype.targetWorkspace = null; + +/** + * Inverted screen CTM, for use in mouseToSvg. + * @type {SVGMatrix} + * @private + */ +Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null; + +/** + * Getter for the inverted screen CTM. + * @return {SVGMatrix} The matrix to use in mouseToSvg + */ +Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() { + return this.inverseScreenCTM_; +}; + +/** + * Update the inverted screen CTM. + */ +Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() { + var ctm = this.getParentSvg().getScreenCTM(); + if (ctm) { + this.inverseScreenCTM_ = ctm.inverse(); + } +}; + +/** + * Return the absolute coordinates of the top-left corner of this element, + * scales that after canvas SVG element, if it's a descendant. + * The origin (0,0) is the top-left corner of the Blockly SVG. + * @param {!Element} element Element to find the coordinates of. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) { + var x = 0; + var y = 0; + var scale = 1; + if (goog.dom.contains(this.getCanvas(), element) || + goog.dom.contains(this.getBubbleCanvas(), element)) { + // Before the SVG canvas, scale the coordinates. + scale = this.scale; + } + do { + // Loop through this block and every parent. + var xy = Blockly.utils.getRelativeXY(element); + if (element == this.getCanvas() || + element == this.getBubbleCanvas()) { + // After the SVG canvas, don't scale the coordinates. + scale = 1; + } + x += xy.x * scale; + y += xy.y * scale; + element = element.parentNode; + } while (element && element != this.getParentSvg()); + return new goog.math.Coordinate(x, y); +}; + +/** + * Return the position of the workspace origin relative to the injection div + * origin in pixels. + * The workspace origin is where a block would render at position (0, 0). + * It is not the upper left corner of the workspace SVG. + * @return {!goog.math.Coordinate} Offset in pixels. + * @package + */ +Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() { + return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_); +}; + +/** + * Save resize handler data so we can delete it later in dispose. + * @param {!Array.} handler Data that can be passed to unbindEvent_. + */ +Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) { + this.resizeHandlerWrapper_ = handler; +}; + +/** + * Create the workspace DOM elements. + * @param {string=} opt_backgroundClass Either 'blocklyMainBackground' or + * 'blocklyMutatorBackground'. + * @return {!Element} The workspace's SVG group. + */ +Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { + /** + * + * + * [Trashcan and/or flyout may go here] + * + * + * + * @type {SVGElement} + */ + this.svgGroup_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyWorkspace'}, null); + + // Note that a alone does not receive mouse events--it must have a + // valid target inside it. If no background class is specified, as in the + // flyout, the workspace will not receive mouse events. + if (opt_backgroundClass) { + /** @type {SVGElement} */ + this.svgBackground_ = Blockly.utils.createSvgElement('rect', + {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, + this.svgGroup_); + + if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) { + this.svgBackground_.style.fill = + 'url(#' + this.grid_.getPatternId() + ')'; + } + } + /** @type {SVGElement} */ + this.svgBlockCanvas_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyBlockCanvas'}, this.svgGroup_); + /** @type {SVGElement} */ + this.svgBubbleCanvas_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyBubbleCanvas'}, this.svgGroup_); + var bottom = Blockly.Scrollbar.scrollbarThickness; + if (this.options.hasTrashcan) { + bottom = this.addTrashcan_(bottom); + } + if (this.options.zoomOptions && this.options.zoomOptions.controls) { + this.addZoomControls_(bottom); + } + + if (!this.isFlyout) { + Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this, + this.onMouseDown_); + if (this.options.zoomOptions && this.options.zoomOptions.wheel) { + // Mouse-wheel. + Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, + this.onMouseWheel_); + } + } + + // Determine if there needs to be a category tree, or a simple list of + // blocks. This cannot be changed later, since the UI is very different. + if (this.options.hasCategories) { + /** + * @type {Blockly.Toolbox} + * @private + */ + this.toolbox_ = new Blockly.Toolbox(this); + } + if (this.grid_) { + this.grid_.update(this.scale); + } + this.recordDeleteAreas(); + return this.svgGroup_; +}; + +/** + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.WorkspaceSvg.prototype.dispose = function() { + // Stop rerendering. + this.rendered = false; + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } + Blockly.WorkspaceSvg.superClass_.dispose.call(this); + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBlockCanvas_ = null; + this.svgBubbleCanvas_ = null; + if (this.toolbox_) { + this.toolbox_.dispose(); + this.toolbox_ = null; + } + if (this.flyout_) { + this.flyout_.dispose(); + this.flyout_ = null; + } + if (this.trashcan) { + this.trashcan.dispose(); + this.trashcan = null; + } + if (this.scrollbar) { + this.scrollbar.dispose(); + this.scrollbar = null; + } + if (this.zoomControls_) { + this.zoomControls_.dispose(); + this.zoomControls_ = null; + } + + if (this.audioManager_) { + this.audioManager_.dispose(); + this.audioManager_ = null; + } + + if (this.grid_) { + this.grid_.dispose(); + this.grid_ = null; + } + + if (this.toolboxCategoryCallbacks_) { + this.toolboxCategoryCallbacks_ = null; + } + if (this.flyoutButtonCallbacks_) { + this.flyoutButtonCallbacks_ = null; + } + if (!this.options.parentWorkspace) { + // Top-most workspace. Dispose of the div that the + // SVG is injected into (i.e. injectionDiv). + goog.dom.removeNode(this.getParentSvg().parentNode); + } + if (this.resizeHandlerWrapper_) { + Blockly.unbindEvent_(this.resizeHandlerWrapper_); + this.resizeHandlerWrapper_ = null; + } +}; + +/** + * Obtain a newly created block. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @return {!Blockly.BlockSvg} The created block. + */ +Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName, opt_id) { + return new Blockly.BlockSvg(this, prototypeName, opt_id); +}; + +/** + * Add a trashcan. + * @param {number} bottom Distance from workspace bottom to bottom of trashcan. + * @return {number} Distance from workspace bottom to the top of trashcan. + * @private + */ +Blockly.WorkspaceSvg.prototype.addTrashcan_ = function(bottom) { + /** @type {Blockly.Trashcan} */ + this.trashcan = new Blockly.Trashcan(this); + var svgTrashcan = this.trashcan.createDom(); + this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_); + return this.trashcan.init(bottom); +}; + +/** + * Add zoom controls. + * @param {number} bottom Distance from workspace bottom to bottom of controls. + * @return {number} Distance from workspace bottom to the top of controls. + * @private + */ +Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) { + /** @type {Blockly.ZoomControls} */ + this.zoomControls_ = new Blockly.ZoomControls(this); + var svgZoomControls = this.zoomControls_.createDom(); + this.svgGroup_.appendChild(svgZoomControls); + return this.zoomControls_.init(bottom); +}; + +/** + * Add a flyout element in an element with the given tag name. + * @param {string} tagName What type of tag the flyout belongs in. + * @return {!Element} The element containing the flyout DOM. + * @private + */ +Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) { + var workspaceOptions = { + disabledPatternId: this.options.disabledPatternId, + parentWorkspace: this, + RTL: this.RTL, + oneBasedIndex: this.options.oneBasedIndex, + horizontalLayout: this.horizontalLayout, + toolboxPosition: this.options.toolboxPosition + }; + /** + * @type {!Blockly.Flyout} + * @private + */ + this.flyout_ = null; + if (this.horizontalLayout) { + this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions); + } else { + this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions); + } + this.flyout_.autoClose = false; + + // Return the element so that callers can place it in their desired + // spot in the DOM. For example, mutator flyouts do not go in the same place + // as main workspace flyouts. + return this.flyout_.createDom(tagName); +}; + +/** + * Getter for the flyout associated with this workspace. This flyout may be + * owned by either the toolbox or the workspace, depending on toolbox + * configuration. It will be null if there is no flyout. + * @return {Blockly.Flyout} The flyout on this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getFlyout_ = function() { + if (this.flyout_) { + return this.flyout_; + } + if (this.toolbox_) { + return this.toolbox_.flyout_; + } + return null; +}; + +/** + * Update items that use screen coordinate calculations + * because something has changed (e.g. scroll position, window size). + * @private + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() { + this.updateInverseScreenCTM(); + this.recordDeleteAreas(); +}; + +/** + * If enabled, resize the parts of the workspace that change when the workspace + * contents (e.g. block positions) change. This will also scroll the + * workspace contents if needed. + * @package + */ +Blockly.WorkspaceSvg.prototype.resizeContents = function() { + if (!this.resizesEnabled_ || !this.rendered) { + return; + } + if (this.scrollbar) { + // TODO(picklesrus): Once rachel-fenichel's scrollbar refactoring + // is complete, call the method that only resizes scrollbar + // based on contents. + this.scrollbar.resize(); + } + this.updateInverseScreenCTM(); +}; + +/** + * Resize and reposition all of the workspace chrome (toolbox, + * trash, scrollbars etc.) + * This should be called when something changes that + * requires recalculating dimensions and positions of the + * trash, zoom, toolbox, etc. (e.g. window resize). + */ +Blockly.WorkspaceSvg.prototype.resize = function() { + if (this.toolbox_) { + this.toolbox_.position(); + } + if (this.flyout_) { + this.flyout_.position(); + } + if (this.trashcan) { + this.trashcan.position(); + } + if (this.zoomControls_) { + this.zoomControls_.position(); + } + if (this.scrollbar) { + this.scrollbar.resize(); + } + this.updateScreenCalculations_(); +}; + +/** + * Resizes and repositions workspace chrome if the page has a new + * scroll position. + * @package + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled = + function() { + /* eslint-disable indent */ + var currScroll = goog.dom.getDocumentScroll(); + if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_, + currScroll)) { + this.lastRecordedPageScroll_ = currScroll; + this.updateScreenCalculations_(); + } +}; /* eslint-enable indent */ + +/** + * Get the SVG element that forms the drawing surface. + * @return {!Element} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getCanvas = function() { + return this.svgBlockCanvas_; +}; + +/** + * Get the SVG element that forms the bubble surface. + * @return {!SVGGElement} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getBubbleCanvas = function() { + return this.svgBubbleCanvas_; +}; + +/** + * Get the SVG element that contains this workspace. + * @return {Element} SVG element. + */ +Blockly.WorkspaceSvg.prototype.getParentSvg = function() { + if (this.cachedParentSvg_) { + return this.cachedParentSvg_; + } + var element = this.svgGroup_; + while (element) { + if (element.tagName == 'svg') { + this.cachedParentSvg_ = element; + return element; + } + element = element.parentNode; + } + return null; +}; + +/** + * Translate this workspace to new coordinates. + * @param {number} x Horizontal translation. + * @param {number} y Vertical translation. + */ +Blockly.WorkspaceSvg.prototype.translate = function(x, y) { + if (this.useWorkspaceDragSurface_ && this.isDragSurfaceActive_) { + this.workspaceDragSurface_.translateSurface(x,y); + } else { + var translation = 'translate(' + x + ',' + y + ') ' + + 'scale(' + this.scale + ')'; + this.svgBlockCanvas_.setAttribute('transform', translation); + this.svgBubbleCanvas_.setAttribute('transform', translation); + } + // Now update the block drag surface if we're using one. + if (this.blockDragSurface_) { + this.blockDragSurface_.translateAndScaleGroup(x, y, this.scale); + } +}; + +/** + * Called at the end of a workspace drag to take the contents + * out of the drag surface and put them back into the workspace SVG. + * Does nothing if the workspace drag surface is not enabled. + * @package + */ +Blockly.WorkspaceSvg.prototype.resetDragSurface = function() { + // Don't do anything if we aren't using a drag surface. + if (!this.useWorkspaceDragSurface_) { + return; + } + + this.isDragSurfaceActive_ = false; + + var trans = this.workspaceDragSurface_.getSurfaceTranslation(); + this.workspaceDragSurface_.clearAndHide(this.svgGroup_); + var translation = 'translate(' + trans.x + ',' + trans.y + ') ' + + 'scale(' + this.scale + ')'; + this.svgBlockCanvas_.setAttribute('transform', translation); + this.svgBubbleCanvas_.setAttribute('transform', translation); +}; + +/** + * Called at the beginning of a workspace drag to move contents of + * the workspace to the drag surface. + * Does nothing if the drag surface is not enabled. + * @package + */ +Blockly.WorkspaceSvg.prototype.setupDragSurface = function() { + // Don't do anything if we aren't using a drag surface. + if (!this.useWorkspaceDragSurface_) { + return; + } + + // This can happen if the user starts a drag, mouses up outside of the + // document where the mouseup listener is registered (e.g. outside of an + // iframe) and then moves the mouse back in the workspace. On mobile and ff, + // we get the mouseup outside the frame. On chrome and safari desktop we do + // not. + if (this.isDragSurfaceActive_) { + return; + } + + this.isDragSurfaceActive_ = true; + + // Figure out where we want to put the canvas back. The order + // in the is important because things are layered. + var previousElement = this.svgBlockCanvas_.previousSibling; + var width = this.getParentSvg().getAttribute('width'); + var height = this.getParentSvg().getAttribute('height'); + var coord = Blockly.utils.getRelativeXY(this.svgBlockCanvas_); + this.workspaceDragSurface_.setContentsAndShow(this.svgBlockCanvas_, + this.svgBubbleCanvas_, previousElement, width, height, this.scale); + this.workspaceDragSurface_.translateSurface(coord.x, coord.y); +}; + +/** + * @return {?Blockly.BlockDragSurfaceSvg} This workspace's block drag surface, + * if one is in use. + * @package + */ +Blockly.WorkspaceSvg.prototype.getBlockDragSurface = function() { + return this.blockDragSurface_; +}; + +/** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * @return {number} Width. + */ +Blockly.WorkspaceSvg.prototype.getWidth = function() { + var metrics = this.getMetrics(); + return metrics ? metrics.viewWidth / this.scale : 0; +}; + +/** + * Toggles the visibility of the workspace. + * Currently only intended for main workspace. + * @param {boolean} isVisible True if workspace should be visible. + */ +Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) { + + // Tell the scrollbar whether its container is visible so it can + // tell when to hide itself. + if (this.scrollbar) { + this.scrollbar.setContainerVisible(isVisible); + } + + // Tell the flyout whether its container is visible so it can + // tell when to hide itself. + if (this.getFlyout_()) { + this.getFlyout_().setContainerVisible(isVisible); + } + + this.getParentSvg().style.display = isVisible ? 'block' : 'none'; + if (this.toolbox_) { + // Currently does not support toolboxes in mutators. + this.toolbox_.HtmlDiv.style.display = isVisible ? 'block' : 'none'; + } + if (isVisible) { + this.render(); + if (this.toolbox_) { + this.toolbox_.position(); + } + } else { + Blockly.hideChaff(true); + } +}; + +/** + * Render all blocks in workspace. + */ +Blockly.WorkspaceSvg.prototype.render = function() { + // Generate list of all blocks. + var blocks = this.getAllBlocks(); + // Render each block. + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].render(false); + } +}; + +/** + * Was used back when block highlighting (for execution) and block selection + * (for editing) were the same thing. + * Any calls of this function can be deleted. + * @deprecated October 2016 + */ +Blockly.WorkspaceSvg.prototype.traceOn = function() { + console.warn('Deprecated call to traceOn, delete this.'); +}; + +/** + * Highlight or unhighlight a block in the workspace. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {?string} id ID of block to highlight/unhighlight, + * or null for no block (used to unhighlight all blocks). + * @param {boolean=} opt_state If undefined, highlight specified block and + * automatically unhighlight all others. If true or false, manually + * highlight/unhighlight the specified block. + */ +Blockly.WorkspaceSvg.prototype.highlightBlock = function(id, opt_state) { + if (opt_state === undefined) { + // Unhighlight all blocks. + for (var i = 0, block; block = this.highlightedBlocks_[i]; i++) { + block.setHighlighted(false); + } + this.highlightedBlocks_.length = 0; + } + // Highlight/unhighlight the specified block. + var block = id ? this.getBlockById(id) : null; + if (block) { + var state = (opt_state === undefined) || opt_state; + // Using Set here would be great, but at the cost of IE10 support. + if (!state) { + goog.array.remove(this.highlightedBlocks_, block); + } else if (this.highlightedBlocks_.indexOf(block) == -1) { + this.highlightedBlocks_.push(block); + } + block.setHighlighted(state); + } +}; + +/** + * Paste the provided block onto the workspace. + * @param {!Element} xmlBlock XML block element. + */ +Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { + if (!this.rendered || xmlBlock.getElementsByTagName('block').length >= + this.remainingCapacity()) { + return; + } + if (this.currentGesture_) { + this.currentGesture_.cancel(); // Dragging while pasting? No. + } + Blockly.Events.disable(); + try { + var block = Blockly.Xml.domToBlock(xmlBlock, this); + // Move the duplicate to original position. + var blockX = parseInt(xmlBlock.getAttribute('x'), 10); + var blockY = parseInt(xmlBlock.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + if (this.RTL) { + blockX = -blockX; + } + // Offset block until not clobbering another block and not in connection + // distance with neighbouring blocks. + do { + var collide = false; + var allBlocks = this.getAllBlocks(); + for (var i = 0, otherBlock; otherBlock = allBlocks[i]; i++) { + var otherXY = otherBlock.getRelativeToSurfaceXY(); + if (Math.abs(blockX - otherXY.x) <= 1 && + Math.abs(blockY - otherXY.y) <= 1) { + collide = true; + break; + } + } + if (!collide) { + // Check for blocks in snap range to any of its connections. + var connections = block.getConnections_(false); + for (var i = 0, connection; connection = connections[i]; i++) { + var neighbour = connection.closest(Blockly.SNAP_RADIUS, + new goog.math.Coordinate(blockX, blockY)); + if (neighbour.connection) { + collide = true; + break; + } + } + } + if (collide) { + if (this.RTL) { + blockX -= Blockly.SNAP_RADIUS; + } else { + blockX += Blockly.SNAP_RADIUS; + } + blockY += Blockly.SNAP_RADIUS * 2; + } + } while (collide); + block.moveBy(blockX, blockY); + } + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled() && !block.isShadow()) { + Blockly.Events.fire(new Blockly.Events.BlockCreate(block)); + } + block.select(); +}; + +/** + * Refresh the toolbox unless there's a drag in progress. + * @package + */ +Blockly.WorkspaceSvg.prototype.refreshToolboxSelection = function() { + var ws = this.isFlyout ? this.targetWorkspace : this; + if (ws && !ws.currentGesture_ && ws.toolbox_ && ws.toolbox_.flyout_) { + ws.toolbox_.refreshSelection(); + } +}; + +/** + * Rename a variable by updating its name in the variable map. Update the + * flyout to show the renamed variable immediately. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + * @package + */ +Blockly.WorkspaceSvg.prototype.renameVariableById = function(id, newName) { + Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this, id, newName); + this.refreshToolboxSelection(); +}; + +/** + * Delete a variable by the passed in ID. Update the flyout to show + * immediately that the variable is deleted. + * @param {string} id ID of variable to delete. + * @package + */ +Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) { + Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id); + this.refreshToolboxSelection(); +}; + +/** + * Create a new variable with the given name. Update the flyout to show the new + * variable immediately. + * @param {string} name The new variable's name. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {?Blockly.VariableModel} The newly created variable. + * @package + */ +Blockly.WorkspaceSvg.prototype.createVariable = function(name, opt_type, opt_id) { + var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call( + this, name, opt_type, opt_id); + this.refreshToolboxSelection(); + return newVar; +}; + +/** + * Make a list of all the delete areas for this workspace. + */ +Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { + if (this.trashcan) { + this.deleteAreaTrash_ = this.trashcan.getClientRect(); + } else { + this.deleteAreaTrash_ = null; + } + if (this.flyout_) { + this.deleteAreaToolbox_ = this.flyout_.getClientRect(); + } else if (this.toolbox_) { + this.deleteAreaToolbox_ = this.toolbox_.getClientRect(); + } else { + this.deleteAreaToolbox_ = null; + } +}; + +/** + * Is the mouse event over a delete area (toolbox or non-closing flyout)? + * @param {!Event} e Mouse move event. + * @return {?number} Null if not over a delete area, or an enum representing + * which delete area the event is over. + */ +Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { + var xy = new goog.math.Coordinate(e.clientX, e.clientY); + if (this.deleteAreaTrash_ && this.deleteAreaTrash_.contains(xy)) { + return Blockly.DELETE_AREA_TRASH; + } + if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) { + return Blockly.DELETE_AREA_TOOLBOX; + } + return Blockly.DELETE_AREA_NONE; +}; + +/** + * Handle a mouse-down on SVG drawing surface. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { + var gesture = this.getGesture(e); + if (gesture) { + gesture.handleWsStart(e, this); + } +}; + +/** + * Start tracking a drag of an object on this workspace. + * @param {!Event} e Mouse down event. + * @param {!goog.math.Coordinate} xy Starting location of object. + */ +Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { + // Record the starting offset between the bubble's location and the mouse. + var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + // Fix scale of mouse event. + point.x /= this.scale; + point.y /= this.scale; + this.dragDeltaXY_ = goog.math.Coordinate.difference(xy, point); +}; + +/** + * Track a drag of an object on this workspace. + * @param {!Event} e Mouse move event. + * @return {!goog.math.Coordinate} New location of object. + */ +Blockly.WorkspaceSvg.prototype.moveDrag = function(e) { + var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + // Fix scale of mouse event. + point.x /= this.scale; + point.y /= this.scale; + return goog.math.Coordinate.sum(this.dragDeltaXY_, point); +}; + +/** + * Is the user currently dragging a block or scrolling the flyout/workspace? + * @return {boolean} True if currently dragging or scrolling. + */ +Blockly.WorkspaceSvg.prototype.isDragging = function() { + return this.currentGesture_ != null && this.currentGesture_.isDragging(); +}; + +/** + * Is this workspace draggable and scrollable? + * @return {boolean} True if this workspace may be dragged. + */ +Blockly.WorkspaceSvg.prototype.isDraggable = function() { + return !!this.scrollbar; +}; + +/** + * Handle a mouse-wheel on SVG drawing surface. + * @param {!Event} e Mouse wheel event. + * @private + */ +Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) { + // TODO: Remove gesture cancellation and compensate for coordinate skew during + // zoom. + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } + // The vertical scroll distance that corresponds to a click of a zoom button. + var PIXELS_PER_ZOOM_STEP = 50; + var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP; + var position = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); + this.zoom(position.x, position.y, delta); + e.preventDefault(); +}; + +/** + * Calculate the bounding box for the blocks on the workspace. + * Coordinate system: workspace coordinates. + * + * @return {Object} Contains the position and size of the bounding box + * containing the blocks on the workspace. + */ +Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() { + var topBlocks = this.getTopBlocks(false); + // There are no blocks, return empty rectangle. + if (!topBlocks.length) { + return {x: 0, y: 0, width: 0, height: 0}; + } + + // Initialize boundary using the first block. + var boundary = topBlocks[0].getBoundingRectangle(); + + // Start at 1 since the 0th block was used for initialization + for (var i = 1; i < topBlocks.length; i++) { + var blockBoundary = topBlocks[i].getBoundingRectangle(); + if (blockBoundary.topLeft.x < boundary.topLeft.x) { + boundary.topLeft.x = blockBoundary.topLeft.x; + } + if (blockBoundary.bottomRight.x > boundary.bottomRight.x) { + boundary.bottomRight.x = blockBoundary.bottomRight.x; + } + if (blockBoundary.topLeft.y < boundary.topLeft.y) { + boundary.topLeft.y = blockBoundary.topLeft.y; + } + if (blockBoundary.bottomRight.y > boundary.bottomRight.y) { + boundary.bottomRight.y = blockBoundary.bottomRight.y; + } + } + return { + x: boundary.topLeft.x, + y: boundary.topLeft.y, + width: boundary.bottomRight.x - boundary.topLeft.x, + height: boundary.bottomRight.y - boundary.topLeft.y + }; +}; + +/** + * Clean up the workspace by ordering all the blocks in a column. + */ +Blockly.WorkspaceSvg.prototype.cleanUp = function() { + this.setResizesEnabled(false); + Blockly.Events.setGroup(true); + var topBlocks = this.getTopBlocks(true); + var cursorY = 0; + for (var i = 0, block; block = topBlocks[i]; i++) { + var xy = block.getRelativeToSurfaceXY(); + block.moveBy(-xy.x, cursorY - xy.y); + block.snapToGrid(); + cursorY = block.getRelativeToSurfaceXY().y + + block.getHeightWidth().height + Blockly.BlockSvg.MIN_BLOCK_Y; + } + Blockly.Events.setGroup(false); + this.setResizesEnabled(true); +}; + +/** + * Show the context menu for the workspace. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { + if (this.options.readOnly || this.isFlyout) { + return; + } + var menuOptions = []; + var topBlocks = this.getTopBlocks(true); + var eventGroup = Blockly.utils.genUid(); + var ws = this; + + // Options to undo/redo previous action. + var undoOption = {}; + undoOption.text = Blockly.Msg.UNDO; + undoOption.enabled = this.undoStack_.length > 0; + undoOption.callback = this.undo.bind(this, false); + menuOptions.push(undoOption); + var redoOption = {}; + redoOption.text = Blockly.Msg.REDO; + redoOption.enabled = this.redoStack_.length > 0; + redoOption.callback = this.undo.bind(this, true); + menuOptions.push(redoOption); + + // Option to clean up blocks. + if (this.scrollbar) { + var cleanOption = {}; + cleanOption.text = Blockly.Msg.CLEAN_UP; + cleanOption.enabled = topBlocks.length > 1; + cleanOption.callback = this.cleanUp.bind(this); + menuOptions.push(cleanOption); + } + + // Add a little animation to collapsing and expanding. + var DELAY = 10; + if (this.options.collapse) { + var hasCollapsedBlocks = false; + var hasExpandedBlocks = false; + for (var i = 0; i < topBlocks.length; i++) { + var block = topBlocks[i]; + while (block) { + if (block.isCollapsed()) { + hasCollapsedBlocks = true; + } else { + hasExpandedBlocks = true; + } + block = block.getNextBlock(); + } + } + + /** + * Option to collapse or expand top blocks. + * @param {boolean} shouldCollapse Whether a block should collapse. + * @private + */ + var toggleOption = function(shouldCollapse) { + var ms = 0; + for (var i = 0; i < topBlocks.length; i++) { + var block = topBlocks[i]; + while (block) { + setTimeout(block.setCollapsed.bind(block, shouldCollapse), ms); + block = block.getNextBlock(); + ms += DELAY; + } + } + }; + + // Option to collapse top blocks. + var collapseOption = {enabled: hasExpandedBlocks}; + collapseOption.text = Blockly.Msg.COLLAPSE_ALL; + collapseOption.callback = function() { + toggleOption(true); + }; + menuOptions.push(collapseOption); + + // Option to expand top blocks. + var expandOption = {enabled: hasCollapsedBlocks}; + expandOption.text = Blockly.Msg.EXPAND_ALL; + expandOption.callback = function() { + toggleOption(false); + }; + menuOptions.push(expandOption); + } + + // Option to delete all blocks. + // Count the number of blocks that are deletable. + var deleteList = []; + function addDeletableBlocks(block) { + if (block.isDeletable()) { + deleteList = deleteList.concat(block.getDescendants()); + } else { + var children = block.getChildren(); + for (var i = 0; i < children.length; i++) { + addDeletableBlocks(children[i]); + } + } + } + for (var i = 0; i < topBlocks.length; i++) { + addDeletableBlocks(topBlocks[i]); + } + + function deleteNext() { + Blockly.Events.setGroup(eventGroup); + var block = deleteList.shift(); + if (block) { + if (block.workspace) { + block.dispose(false, true); + setTimeout(deleteNext, DELAY); + } else { + deleteNext(); + } + } + Blockly.Events.setGroup(false); + } + + var deleteOption = { + text: deleteList.length == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)), + enabled: deleteList.length > 0, + callback: function() { + if (ws.currentGesture_) { + ws.currentGesture_.cancel(); + } + if (deleteList.length < 2 ) { + deleteNext(); + } else { + Blockly.confirm( + Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', deleteList.length), + function(ok) { + if (ok) { + deleteNext(); + } + }); + } + } + }; + menuOptions.push(deleteOption); + + Blockly.ContextMenu.show(e, menuOptions, this.RTL); +}; + +/** + * Modify the block tree on the existing toolbox. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + */ +Blockly.WorkspaceSvg.prototype.updateToolbox = function(tree) { + tree = Blockly.Options.parseToolboxTree(tree); + if (!tree) { + if (this.options.languageTree) { + throw 'Can\'t nullify an existing toolbox.'; + } + return; // No change (null to null). + } + if (!this.options.languageTree) { + throw 'Existing toolbox is null. Can\'t create new toolbox.'; + } + if (tree.getElementsByTagName('category').length) { + if (!this.toolbox_) { + throw 'Existing toolbox has no categories. Can\'t change mode.'; + } + this.options.languageTree = tree; + this.toolbox_.populate_(tree); + this.toolbox_.addColour_(); + } else { + if (!this.flyout_) { + throw 'Existing toolbox has categories. Can\'t change mode.'; + } + this.options.languageTree = tree; + this.flyout_.show(tree.childNodes); + } +}; + +/** + * Mark this workspace as the currently focused main workspace. + */ +Blockly.WorkspaceSvg.prototype.markFocused = function() { + if (this.options.parentWorkspace) { + this.options.parentWorkspace.markFocused(); + } else { + Blockly.mainWorkspace = this; + // We call e.preventDefault in many event handlers which means we + // need to explicitly grab focus (e.g from a textarea) because + // the browser will not do it for us. How to do this is browser dependant. + this.setBrowserFocus(); + } +}; + +/** + * Set the workspace to have focus in the browser. + * @private + */ +Blockly.WorkspaceSvg.prototype.setBrowserFocus = function() { + // Blur whatever was focused since explcitly grabbing focus below does not + // work in Edge. + if (document.activeElement) { + document.activeElement.blur(); + } + try { + // Focus the workspace SVG - this is for Chrome and Firefox. + this.getParentSvg().focus(); + } catch (e) { + // IE and Edge do not support focus on SVG elements. When that fails + // above, get the injectionDiv (the workspace's parent) and focus that + // instead. This doesn't work in Chrome. + try { + // In IE11, use setActive (which is IE only) so the page doesn't scroll + // to the workspace gaining focus. + this.getParentSvg().parentNode.setActive(); + } catch (e) { + // setActive support was discontinued in Edge so when that fails, call + // focus instead. + this.getParentSvg().parentNode.focus(); + } + } +}; + +/** + * Zooming the blocks centered in (x, y) coordinate with zooming in or out. + * @param {number} x X coordinate of center. + * @param {number} y Y coordinate of center. + * @param {number} amount Amount of zooming + * (negative zooms out and positive zooms in). + */ +Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) { + var speed = this.options.zoomOptions.scaleSpeed; + var metrics = this.getMetrics(); + var center = this.getParentSvg().createSVGPoint(); + center.x = x; + center.y = y; + center = center.matrixTransform(this.getCanvas().getCTM().inverse()); + x = center.x; + y = center.y; + var canvas = this.getCanvas(); + // Scale factor. + var scaleChange = Math.pow(speed, amount); + // Clamp scale within valid range. + var newScale = this.scale * scaleChange; + if (newScale > this.options.zoomOptions.maxScale) { + scaleChange = this.options.zoomOptions.maxScale / this.scale; + } else if (newScale < this.options.zoomOptions.minScale) { + scaleChange = this.options.zoomOptions.minScale / this.scale; + } + if (this.scale == newScale) { + return; // No change in zoom. + } + if (this.scrollbar) { + var matrix = canvas.getCTM() + .translate(x * (1 - scaleChange), y * (1 - scaleChange)) + .scale(scaleChange); + // newScale and matrix.a should be identical (within a rounding error). + // ScrollX and scrollY are in pixels. + this.scrollX = matrix.e - metrics.absoluteLeft; + this.scrollY = matrix.f - metrics.absoluteTop; + } + this.setScale(newScale); +}; + +/** + * Zooming the blocks centered in the center of view with zooming in or out. + * @param {number} type Type of zooming (-1 zooming out and 1 zooming in). + */ +Blockly.WorkspaceSvg.prototype.zoomCenter = function(type) { + var metrics = this.getMetrics(); + var x = metrics.viewWidth / 2; + var y = metrics.viewHeight / 2; + this.zoom(x, y, type); +}; + +/** + * Zoom the blocks to fit in the workspace if possible. + */ +Blockly.WorkspaceSvg.prototype.zoomToFit = function() { + var metrics = this.getMetrics(); + var blocksBox = this.getBlocksBoundingBox(); + var blocksWidth = blocksBox.width; + var blocksHeight = blocksBox.height; + if (!blocksWidth) { + return; // Prevents zooming to infinity. + } + var workspaceWidth = metrics.viewWidth; + var workspaceHeight = metrics.viewHeight; + if (this.flyout_) { + workspaceWidth -= this.flyout_.width_; + } + if (!this.scrollbar) { + // Origin point of 0,0 is fixed, blocks will not scroll to center. + blocksWidth += metrics.contentLeft; + blocksHeight += metrics.contentTop; + } + var ratioX = workspaceWidth / blocksWidth; + var ratioY = workspaceHeight / blocksHeight; + this.setScale(Math.min(ratioX, ratioY)); + this.scrollCenter(); +}; + +/** + * Center the workspace. + */ +Blockly.WorkspaceSvg.prototype.scrollCenter = function() { + if (!this.scrollbar) { + // Can't center a non-scrolling workspace. + return; + } + var metrics = this.getMetrics(); + var x = (metrics.contentWidth - metrics.viewWidth) / 2; + if (this.flyout_) { + x -= this.flyout_.width_ / 2; + } + var y = (metrics.contentHeight - metrics.viewHeight) / 2; + this.scrollbar.set(x, y); +}; + +/** + * Set the workspace's zoom factor. + * @param {number} newScale Zoom factor. + */ +Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { + if (this.options.zoomOptions.maxScale && + newScale > this.options.zoomOptions.maxScale) { + newScale = this.options.zoomOptions.maxScale; + } else if (this.options.zoomOptions.minScale && + newScale < this.options.zoomOptions.minScale) { + newScale = this.options.zoomOptions.minScale; + } + this.scale = newScale; + if (this.grid_) { + this.grid_.update(this.scale); + } + if (this.scrollbar) { + this.scrollbar.resize(); + } else { + this.translate(this.scrollX, this.scrollY); + } + Blockly.hideChaff(false); + if (this.flyout_) { + // No toolbox, resize flyout. + this.flyout_.reflow(); + } +}; + +/** + * Get the dimensions of the given workspace component, in pixels. + * @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the + * dimensions of, or null. It should be a toolbox or flyout, and should + * implement getWidth() and getHeight(). + * @return {!Object} An object containing width and height attributes, which + * will both be zero if elem did not exist. + * @private + */ +Blockly.WorkspaceSvg.getDimensionsPx_ = function(elem) { + var width = 0; + var height = 0; + if (elem) { + width = elem.getWidth(); + height = elem.getHeight(); + } + return { + width: width, + height: height + }; +}; + +/** + * Get the content dimensions of the given workspace, taking into account + * whether or not it is scrollable and what size the workspace div is on screen. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing at least + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensions_ = function(ws, svgSize) { + if (ws.scrollbar) { + return Blockly.WorkspaceSvg.getContentDimensionsBounded_(ws, svgSize); + } else { + return Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + } +}; + +/** + * Get the bounding box for all workspace contents, in pixels. + * @param {!Blockly.WorkspaceSvg} ws The workspace to inspect. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left, right, top and bottom in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) { + // Block bounding box is in workspace coordinates. + var blockBox = ws.getBlocksBoundingBox(); + var scale = ws.scale; + + // Convert to pixels. + var width = blockBox.width * scale; + var height = blockBox.height * scale; + var left = blockBox.x * scale; + var top = blockBox.y * scale; + + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height + }; +}; + +/** + * Calculate the size of a scrollable workspace, which should include room for a + * half screen border around the workspace contents. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) { + var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + + // View height and width are both in pixels, and are the same as the SVG size. + var viewWidth = svgSize.width; + var viewHeight = svgSize.height; + var halfWidth = viewWidth / 2; + var halfHeight = viewHeight / 2; + + // Add a border around the content that is at least half a screenful wide. + // Ensure border is wide enough that blocks can scroll over entire screen. + var left = Math.min(content.left - halfWidth, content.right - viewWidth); + var right = Math.max(content.right + halfWidth, content.left + viewWidth); + + var top = Math.min(content.top - halfHeight, content.bottom - viewHeight); + var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight); + + var dimensions = { + left: left, + top: top, + height: bottom - top, + width: right - left + }; + return dimensions; +}; + +/** + * Return an object with all the metrics required to size scrollbars for a + * top level workspace. The following properties are computed: + * Coordinate system: pixel coordinates. + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the content, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .viewLeft: Offset of left edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .contentLeft: Offset of the left-most content from the x=0 coordinate. + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero. + * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero. + * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. + * .flyoutHeight: Height of flyout if it is always open. Otherwise zero. + * .toolboxPosition: Top, bottom, left or right. + * @return {!Object} Contains size and position metrics of a top level + * workspace. + * @private + * @this Blockly.WorkspaceSvg + */ +Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() { + + var toolboxDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.toolbox_); + var flyoutDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.flyout_); + + // Contains height and width in CSS pixels. + // svgSize is equivalent to the size of the injectionDiv at this point. + var svgSize = Blockly.svgSize(this.getParentSvg()); + if (this.toolbox_) { + if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP || + this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + svgSize.height -= toolboxDimensions.height; + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT || + this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + svgSize.width -= toolboxDimensions.width; + } + } + + // svgSize is now the space taken up by the Blockly workspace, not including + // the toolbox. + var contentDimensions = + Blockly.WorkspaceSvg.getContentDimensions_(this, svgSize); + + var absoluteLeft = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + absoluteLeft = toolboxDimensions.width; + } + var absoluteTop = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { + absoluteTop = toolboxDimensions.height; + } + + var metrics = { + contentHeight: contentDimensions.height, + contentWidth: contentDimensions.width, + contentTop: contentDimensions.top, + contentLeft: contentDimensions.left, + + viewHeight: svgSize.height, + viewWidth: svgSize.width, + viewTop: -this.scrollY, // Must be in pixels, somehow. + viewLeft: -this.scrollX, // Must be in pixels, somehow. + + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft, + + toolboxWidth: toolboxDimensions.width, + toolboxHeight: toolboxDimensions.height, + + flyoutWidth: flyoutDimensions.width, + flyoutHeight: flyoutDimensions.height, + + toolboxPosition: this.toolboxPosition + }; + return metrics; +}; + +/** + * Sets the X/Y translations of a top level workspace to match the scrollbars. + * @param {!Object} xyRatio Contains an x and/or y property which is a float + * between 0 and 1 specifying the degree of scrolling. + * @private + * @this Blockly.WorkspaceSvg + */ +Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) { + if (!this.scrollbar) { + throw 'Attempt to set top level workspace scroll without scrollbars.'; + } + var metrics = this.getMetrics(); + if (goog.isNumber(xyRatio.x)) { + this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; + } + if (goog.isNumber(xyRatio.y)) { + this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; + } + var x = this.scrollX + metrics.absoluteLeft; + var y = this.scrollY + metrics.absoluteTop; + this.translate(x, y); + if (this.grid_) { + this.grid_.moveTo(x, y); + } +}; + +/** + * Update whether this workspace has resizes enabled. + * If enabled, workspace will resize when appropriate. + * If disabled, workspace will not resize until re-enabled. + * Use to avoid resizing during a batch operation, for performance. + * @param {boolean} enabled Whether resizes should be enabled. + */ +Blockly.WorkspaceSvg.prototype.setResizesEnabled = function(enabled) { + var reenabled = (!this.resizesEnabled_ && enabled); + this.resizesEnabled_ = enabled; + if (reenabled) { + // Newly enabled. Trigger a resize. + this.resizeContents(); + } +}; + +/** + * Dispose of all blocks in workspace, with an optimization to prevent resizes. + */ +Blockly.WorkspaceSvg.prototype.clear = function() { + this.setResizesEnabled(false); + Blockly.WorkspaceSvg.superClass_.clear.call(this); + this.setResizesEnabled(true); +}; + +/** + * Register a callback function associated with a given key, for clicks on + * buttons and labels in the flyout. + * For instance, a button specified by the XML + * + * should be matched by a call to + * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction). + * @param {string} key The name to use to look up this function. + * @param {function(!Blockly.FlyoutButton)} func The function to call when the + * given button is clicked. + */ +Blockly.WorkspaceSvg.prototype.registerButtonCallback = function(key, func) { + goog.asserts.assert(goog.isFunction(func), + 'Button callbacks must be functions.'); + this.flyoutButtonCallbacks_[key] = func; +}; + +/** + * Get the callback function associated with a given key, for clicks on buttons + * and labels in the flyout. + * @param {string} key The name to use to look up the function. + * @return {?function(!Blockly.FlyoutButton)} The function corresponding to the + * given key for this workspace; null if no callback is registered. + */ +Blockly.WorkspaceSvg.prototype.getButtonCallback = function(key) { + var result = this.flyoutButtonCallbacks_[key]; + return result ? result : null; +}; + +/** + * Remove a callback for a click on a button in the flyout. + * @param {string} key The name associated with the callback function. + */ +Blockly.WorkspaceSvg.prototype.removeButtonCallback = function(key) { + this.flyoutButtonCallbacks_[key] = null; +}; + +/** + * Register a callback function associated with a given key, for populating + * custom toolbox categories in this workspace. See the variable and procedure + * categories as an example. + * @param {string} key The name to use to look up this function. + * @param {function(!Blockly.Workspace):!Array.} func The function to + * call when the given toolbox category is opened. + */ +Blockly.WorkspaceSvg.prototype.registerToolboxCategoryCallback = function(key, + func) { + goog.asserts.assert(goog.isFunction(func), + 'Toolbox category callbacks must be functions.'); + this.toolboxCategoryCallbacks_[key] = func; +}; + +/** + * Get the callback function associated with a given key, for populating + * custom toolbox categories in this workspace. + * @param {string} key The name to use to look up the function. + * @return {?function(!Blockly.Workspace):!Array.} The function + * corresponding to the given key for this workspace, or null if no function + * is registered. + */ +Blockly.WorkspaceSvg.prototype.getToolboxCategoryCallback = function(key) { + var result = this.toolboxCategoryCallbacks_[key]; + return result ? result : null; +}; + +/** + * Remove a callback for a click on a custom category's name in the toolbox. + * @param {string} key The name associated with the callback function. + */ +Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) { + this.toolboxCategoryCallbacks_[key] = null; +}; + +/** + * Look up the gesture that is tracking this touch stream on this workspace. + * May create a new gesture. + * @param {!Event} e Mouse event or touch event. + * @return {Blockly.Gesture} The gesture that is tracking this touch stream, + * or null if no valid gesture exists. + * @package + */ +Blockly.WorkspaceSvg.prototype.getGesture = function(e) { + var isStart = (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown'); + + var gesture = this.currentGesture_; + if (gesture) { + if (isStart && gesture.hasStarted()) { + console.warn('tried to start the same gesture twice'); + // That's funny. We must have missed a mouse up. + // Cancel it, rather than try to retrieve all of the state we need. + gesture.cancel(); + return null; + } + return gesture; + } + + // No gesture existed on this workspace, but this looks like the start of a + // new gesture. + if (isStart) { + this.currentGesture_ = new Blockly.Gesture(e, this); + return this.currentGesture_; + } + // No gesture existed and this event couldn't be the start of a new gesture. + return null; +}; + +/** + * Clear the reference to the current gesture. + * @package + */ +Blockly.WorkspaceSvg.prototype.clearGesture = function() { + this.currentGesture_ = null; +}; + +/** + * Cancel the current gesture, if one exists. + * @package + */ +Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() { + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } +}; + +/** + * Get the audio manager for this workspace. + * @return {Blockly.WorkspaceAudio} The audio manager for this workspace. + */ +Blockly.WorkspaceSvg.prototype.getAudioManager = function() { + return this.audioManager_; +}; + +/** + * Get the grid object for this workspace, or null if there is none. + * @return {Blockly.Grid} The grid object for this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getGrid = function() { + return this.grid_; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +Blockly.WorkspaceSvg.prototype['setVisible'] = + Blockly.WorkspaceSvg.prototype.setVisible; diff --git a/core/.svn/pristine/21/21da2d6fa610ae6d0f2342337f332401828c660a.svn-base b/core/.svn/pristine/21/21da2d6fa610ae6d0f2342337f332401828c660a.svn-base new file mode 100644 index 0000000..13ce2b1 --- /dev/null +++ b/core/.svn/pristine/21/21da2d6fa610ae6d0f2342337f332401828c660a.svn-base @@ -0,0 +1,263 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Class for a button in the flyout. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FlyoutButton'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a button in the flyout. + * @param {!Blockly.WorkspaceSvg} workspace The workspace in which to place this + * button. + * @param {!Blockly.WorkspaceSvg} targetWorkspace The flyout's target workspace. + * @param {!Element} xml The XML specifying the label/button. + * @param {boolean} isLabel Whether this button should be styled as a label. + * @constructor + */ +Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) { + // Labels behave the same as buttons, but are styled differently. + + /** + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * @type {!Blockly.Workspace} + * @private + */ + this.targetWorkspace_ = targetWorkspace; + + /** + * @type {string} + * @private + */ + this.text_ = xml.getAttribute('text'); + + /** + * @type {!goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); + + /** + * Whether this button should be styled as a label. + * @type {boolean} + * @private + */ + this.isLabel_ = isLabel; + + /** + * Function to call when this button is clicked. + * @type {function(!Blockly.FlyoutButton)} + * @private + */ + this.callback_ = null; + + var callbackKey = xml.getAttribute('callbackKey'); + if (this.isLabel_ && callbackKey) { + console.warn('Labels should not have callbacks. Label text: ' + this.text_); + } else if (!this.isLabel_ && + !(callbackKey && targetWorkspace.getButtonCallback(callbackKey))) { + console.warn('Buttons should have callbacks. Button text: ' + this.text_); + } else { + this.callback_ = targetWorkspace.getButtonCallback(callbackKey); + } + + /** + * If specified, a CSS class to add to this button. + * @type {?string} + * @private + */ + this.cssClass_ = xml.getAttribute('web-class') || null; +}; + +/** + * The margin around the text in the button. + */ +Blockly.FlyoutButton.MARGIN = 5; + +/** + * The width of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.width = 0; + +/** + * The height of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.height = 0; + +/** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {Array.} + * @private + */ +Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null; + +/** + * Create the button elements. + * @return {!Element} The button's SVG group. + */ +Blockly.FlyoutButton.prototype.createDom = function() { + var cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'; + if (this.cssClass_) { + cssClass += ' ' + this.cssClass_; + } + + this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': cssClass}, + this.workspace_.getCanvas()); + + if (!this.isLabel_) { + // Shadow rectangle (light source does not mirror in RTL). + var shadow = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyFlyoutButtonShadow', + 'rx': 4, 'ry': 4, 'x': 1, 'y': 1 + }, + this.svgGroup_); + } + // Background rectangle. + var rect = Blockly.utils.createSvgElement('rect', + { + 'class': this.isLabel_ ? + 'blocklyFlyoutLabelBackground' : 'blocklyFlyoutButtonBackground', + 'rx': 4, 'ry': 4 + }, + this.svgGroup_); + + var svgText = Blockly.utils.createSvgElement('text', + { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', + 'x': 0, + 'y': 0, + 'text-anchor': 'middle' + }, + this.svgGroup_); + svgText.textContent = this.text_; + + this.width = Blockly.Field.getCachedWidth(svgText); + this.height = 20; // Can't compute it :( + + if (!this.isLabel_) { + this.width += 2 * Blockly.FlyoutButton.MARGIN; + shadow.setAttribute('width', this.width); + shadow.setAttribute('height', this.height); + } + rect.setAttribute('width', this.width); + rect.setAttribute('height', this.height); + + svgText.setAttribute('x', this.width / 2); + svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN); + + this.updateTransform_(); + + this.mouseUpWrapper_ = Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup', + this, this.onMouseUp_); + return this.svgGroup_; +}; + +/** + * Correctly position the flyout button and make it visible. + */ +Blockly.FlyoutButton.prototype.show = function() { + this.updateTransform_(); + this.svgGroup_.setAttribute('display', 'block'); +}; + +/** + * Update SVG attributes to match internal state. + * @private + */ +Blockly.FlyoutButton.prototype.updateTransform_ = function() { + this.svgGroup_.setAttribute('transform', + 'translate(' + this.position_.x + ',' + this.position_.y + ')'); +}; + +/** + * Move the button to the given x, y coordinates. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + */ +Blockly.FlyoutButton.prototype.moveTo = function(x, y) { + this.position_.x = x; + this.position_.y = y; + this.updateTransform_(); +}; + +/** + * Location of the button. + * @return {!goog.math.Coordinate} x, y coordinates. + * @package + */ +Blockly.FlyoutButton.prototype.getPosition = function() { + return this.position_; +}; + +/** + * Get the button's target workspace. + * @return {!Blockly.WorkspaceSvg} The target workspace of the flyout where this + * button resides. + */ +Blockly.FlyoutButton.prototype.getTargetWorkspace = function() { + return this.targetWorkspace_; +}; + +/** + * Dispose of this button. + */ +Blockly.FlyoutButton.prototype.dispose = function() { + if (this.onMouseUpWrapper_) { + Blockly.unbindEvent_(this.onMouseUpWrapper_); + } + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.workspace_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Do something when the button is clicked. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.FlyoutButton.prototype.onMouseUp_ = function(e) { + var gesture = this.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.cancel(); + } + + // Call the callback registered to this button. + if (this.callback_) { + this.callback_(this); + } +}; diff --git a/core/.svn/pristine/26/26b9fba11562d27674b9421cb2a48a241c3811c1.svn-base b/core/.svn/pristine/26/26b9fba11562d27674b9421cb2a48a241c3811c1.svn-base new file mode 100644 index 0000000..7b79772 --- /dev/null +++ b/core/.svn/pristine/26/26b9fba11562d27674b9421cb2a48a241c3811c1.svn-base @@ -0,0 +1,197 @@ +/** + * @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 and procedure names. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Names'); + + +/** + * Class for a database of entity names (variables, functions, etc). + * @param {string} reservedWords A comma-separated string of words that are + * illegal for use as names in a language (e.g. 'new,if,this,...'). + * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace + * before all variable names. + * @constructor + */ +Blockly.Names = function(reservedWords, opt_variablePrefix) { + this.variablePrefix_ = opt_variablePrefix || ''; + this.reservedDict_ = Object.create(null); + if (reservedWords) { + var splitWords = reservedWords.split(','); + for (var i = 0; i < splitWords.length; i++) { + this.reservedDict_[splitWords[i]] = true; + } + } + this.reset(); +}; + +/** + * Constant to separate developer variable names from user-defined variable + * names when running generators. + * A developer variable will be declared as a global in the generated code, but + * will never be shown to the user in the workspace or stored in the variable + * map. + */ +Blockly.Names.DEVELOPER_VARIABLE_TYPE = 'DEVELOPER_VARIABLE'; + +/** + * When JavaScript (or most other languages) is generated, variable 'foo' and + * procedure 'foo' would collide. However, Blockly has no such problems since + * variable get 'foo' and procedure call 'foo' are unambiguous. + * Therefore, Blockly keeps a separate type name to disambiguate. + * getName('foo', 'variable') -> 'foo' + * getName('foo', 'procedure') -> 'foo2' + */ + +/** + * Empty the database and start from scratch. The reserved words are kept. + */ +Blockly.Names.prototype.reset = function() { + this.db_ = Object.create(null); + this.dbReverse_ = Object.create(null); + this.variableMap_ = null; +}; + +/** + * Set the variable map that maps from variable name to variable object. + * @param {!Blockly.VariableMap} map The map to track. + * @package + */ +Blockly.Names.prototype.setVariableMap = function(map) { + this.variableMap_ = map; +}; + +/** + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of type Blockly.Variables.NAME_TYPE. + * @param {string} id The ID to look up in the variable map. + * @return {?string} The name of the referenced variable, or null if there was + * no variable map or the variable was not found in the map. + * @private + */ +Blockly.Names.prototype.getNameForUserVariable_ = function(id) { + if (!this.variableMap_) { + console.log('Deprecated call to Blockly.Names.prototype.getName without ' + + 'defining a variable map. To fix, add the folowing code in your ' + + 'generator\'s init() function:\n' + + 'Blockly.YourGeneratorName.variableDB_.setVariableMap(' + + 'workspace.getVariableMap());'); + return null; + } + var variable = this.variableMap_.getVariableById(id); + if (variable) { + return variable.name; + } else { + return null; + } +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name that is legal in the exported language. + */ +Blockly.Names.prototype.getName = function(name, type) { + if (type == Blockly.Variables.NAME_TYPE) { + var varName = this.getNameForUserVariable_(name); + if (varName) { + name = varName; + } + } + var normalized = name.toLowerCase() + '_' + type; + + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + + var prefix = isVarType ? this.variablePrefix_ : ''; + if (normalized in this.db_) { + return prefix + this.db_[normalized]; + } + var safeName = this.getDistinctName(name, type); + this.db_[normalized] = safeName.substr(prefix.length); + return safeName; +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name that is legal in the exported language. + */ +Blockly.Names.prototype.getDistinctName = function(name, type) { + var safeName = this.safeName_(name); + var i = ''; + while (this.dbReverse_[safeName + i] || + (safeName + i) in this.reservedDict_) { + // Collision with existing name. Create a unique name. + i = i ? i + 1 : 2; + } + safeName += i; + this.dbReverse_[safeName] = true; + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + var prefix = isVarType ? this.variablePrefix_ : ''; + return prefix + safeName; +}; + +/** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variables. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ +Blockly.Names.prototype.safeName_ = function(name) { + if (!name) { + name = 'unnamed'; + } else { + // Unfortunately names in non-latin characters will look like + // _E9_9F_B3_E4_B9_90 which is pretty meaningless. + name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); + // Most languages don't allow names with leading numbers. + if ('0123456789'.indexOf(name[0]) != -1) { + name = 'my_' + name; + } + } + return name; +}; + +/** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ +Blockly.Names.equals = function(name1, name2) { + return name1.toLowerCase() == name2.toLowerCase(); +}; diff --git a/core/.svn/pristine/2c/2cb4b5a5a336a8a1ea91428f36b35fc2d6257f43.svn-base b/core/.svn/pristine/2c/2cb4b5a5a336a8a1ea91428f36b35fc2d6257f43.svn-base new file mode 100644 index 0000000..6cf7744 --- /dev/null +++ b/core/.svn/pristine/2c/2cb4b5a5a336a8a1ea91428f36b35fc2d6257f43.svn-base @@ -0,0 +1,1146 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Events fired as a result of actions in Blockly's editor. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * Events fired as a result of actions in Blockly's editor. + * @namespace Blockly.Events + */ +goog.provide('Blockly.Events'); + +goog.require('goog.array'); +goog.require('goog.math.Coordinate'); + + +/** + * Group ID for new events. Grouped events are indivisible. + * @type {string} + * @private + */ +Blockly.Events.group_ = ''; + +/** + * Sets whether the next event should be added to the undo stack. + * @type {boolean} + */ +Blockly.Events.recordUndo = true; + +/** + * Allow change events to be created and fired. + * @type {number} + * @private + */ +Blockly.Events.disabled_ = 0; + +/** + * Name of event that creates a block. Will be deprecated for BLOCK_CREATE. + * @const + */ +Blockly.Events.CREATE = 'create'; + +/** + * Name of event that creates a block. + * @const + */ +Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE; + +/** + * Name of event that deletes a block. Will be deprecated for BLOCK_DELETE. + * @const + */ +Blockly.Events.DELETE = 'delete'; + +/** + * Name of event that deletes a block. + * @const + */ +Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE; + +/** + * Name of event that changes a block. Will be deprecated for BLOCK_CHANGE. + * @const + */ +Blockly.Events.CHANGE = 'change'; + +/** + * Name of event that changes a block. + * @const + */ +Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE; + +/** + * Name of event that moves a block. Will be deprecated for BLOCK_MOVE. + * @const + */ +Blockly.Events.MOVE = 'move'; + +/** + * Name of event that moves a block. + * @const + */ +Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE; + +/** + * Name of event that creates a variable. + * @const + */ +Blockly.Events.VAR_CREATE = 'var_create'; + +/** + * Name of event that deletes a variable. + * @const + */ +Blockly.Events.VAR_DELETE = 'var_delete'; + +/** + * Name of event that renames a variable. + * @const + */ +Blockly.Events.VAR_RENAME = 'var_rename'; + +/** + * Name of event that records a UI change. + * @const + */ +Blockly.Events.UI = 'ui'; + +/** + * List of events queued for firing. + * @private + */ +Blockly.Events.FIRE_QUEUE_ = []; + +/** + * Create a custom event and fire it. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.fire = function(event) { + if (!Blockly.Events.isEnabled()) { + return; + } + if (!Blockly.Events.FIRE_QUEUE_.length) { + // First event added; schedule a firing of the event queue. + setTimeout(Blockly.Events.fireNow_, 0); + } + Blockly.Events.FIRE_QUEUE_.push(event); +}; + +/** + * Fire all queued events. + * @private + */ +Blockly.Events.fireNow_ = function() { + var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true); + Blockly.Events.FIRE_QUEUE_.length = 0; + for (var i = 0, event; event = queue[i]; i++) { + var workspace = Blockly.Workspace.getById(event.workspaceId); + if (workspace) { + workspace.fireChangeListener(event); + } + } +}; + +/** + * Filter the queued events and merge duplicates. + * @param {!Array.} queueIn Array of events. + * @param {boolean} forward True if forward (redo), false if backward (undo). + * @return {!Array.} Array of filtered events. + */ +Blockly.Events.filter = function(queueIn, forward) { + var queue = goog.array.clone(queueIn); + if (!forward) { + // Undo is merged in reverse order. + queue.reverse(); + } + var mergedQueue = []; + var hash = Object.create(null); + // Merge duplicates. + for (var i = 0, event; event = queue[i]; i++) { + if (!event.isNull()) { + var key = [event.type, event.blockId, event.workspaceId].join(' '); + var lastEvent = hash[key]; + if (!lastEvent) { + hash[key] = event; + mergedQueue.push(event); + } else if (event.type == Blockly.Events.MOVE) { + // Merge move events. + lastEvent.newParentId = event.newParentId; + lastEvent.newInputName = event.newInputName; + lastEvent.newCoordinate = event.newCoordinate; + } else if (event.type == Blockly.Events.CHANGE && + event.element == lastEvent.element && + event.name == lastEvent.name) { + // Merge change events. + lastEvent.newValue = event.newValue; + } else if (event.type == Blockly.Events.UI && + event.element == 'click' && + (lastEvent.element == 'commentOpen' || + lastEvent.element == 'mutatorOpen' || + lastEvent.element == 'warningOpen')) { + // Merge click events. + lastEvent.newValue = event.newValue; + } else { + // Collision: newer events should merge into this event to maintain order + hash[key] = event; + mergedQueue.push(event); + } + } + } + // Filter out any events that have become null due to merging. + queue = mergedQueue.filter(function(e) { return !e.isNull(); }); + if (!forward) { + // Restore undo order. + queue.reverse(); + } + // Move mutation events to the top of the queue. + // Intentionally skip first event. + for (var i = 1, event; event = queue[i]; i++) { + if (event.type == Blockly.Events.CHANGE && + event.element == 'mutation') { + queue.unshift(queue.splice(i, 1)[0]); + } + } + return queue; +}; + +/** + * Modify pending undo events so that when they are fired they don't land + * in the undo stack. Called by Blockly.Workspace.clearUndo. + */ +Blockly.Events.clearPendingUndo = function() { + for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) { + event.recordUndo = false; + } +}; + +/** + * Stop sending events. Every call to this function MUST also call enable. + */ +Blockly.Events.disable = function() { + Blockly.Events.disabled_++; +}; + +/** + * Start sending events. Unless events were already disabled when the + * corresponding call to disable was made. + */ +Blockly.Events.enable = function() { + Blockly.Events.disabled_--; +}; + +/** + * Returns whether events may be fired or not. + * @return {boolean} True if enabled. + */ +Blockly.Events.isEnabled = function() { + return Blockly.Events.disabled_ == 0; +}; + +/** + * Current group. + * @return {string} ID string. + */ +Blockly.Events.getGroup = function() { + return Blockly.Events.group_; +}; + +/** + * Start or stop a group. + * @param {boolean|string} state True to start new group, false to end group. + * String to set group explicitly. + */ +Blockly.Events.setGroup = function(state) { + if (typeof state == 'boolean') { + Blockly.Events.group_ = state ? Blockly.utils.genUid() : ''; + } else { + Blockly.Events.group_ = state; + } +}; + +/** + * Compute a list of the IDs of the specified block and all its descendants. + * @param {!Blockly.Block} block The root block. + * @return {!Array.} List of block IDs. + * @private + */ +Blockly.Events.getDescendantIds_ = function(block) { + var ids = []; + var descendants = block.getDescendants(); + for (var i = 0, descendant; descendant = descendants[i]; i++) { + ids[i] = descendant.id; + } + return ids; +}; + +/** + * Decode the JSON into an event. + * @param {!Object} json JSON representation. + * @param {!Blockly.Workspace} workspace Target workspace for event. + * @return {!Blockly.Events.Abstract} The event represented by the JSON. + */ +Blockly.Events.fromJson = function(json, workspace) { + var event; + switch (json.type) { + case Blockly.Events.CREATE: + event = new Blockly.Events.Create(null); + break; + case Blockly.Events.DELETE: + event = new Blockly.Events.Delete(null); + break; + case Blockly.Events.CHANGE: + event = new Blockly.Events.Change(null, '', '', '', ''); + break; + case Blockly.Events.MOVE: + event = new Blockly.Events.Move(null); + break; + case Blockly.Events.VAR_CREATE: + event = new Blockly.Events.VarCreate(null); + break; + case Blockly.Events.VAR_DELETE: + event = new Blockly.Events.VarDelete(null); + break; + case Blockly.Events.VAR_RENAME: + event = new Blockly.Events.VarRename(null, ''); + break; + case Blockly.Events.UI: + event = new Blockly.Events.Ui(null); + break; + default: + throw 'Unknown event type.'; + } + event.fromJson(json); + event.workspaceId = workspace.id; + return event; +}; + +/** + * Abstract class for an event. + * @param {Blockly.Block|Blockly.VariableModel} elem The block or variable. + * @constructor + */ +Blockly.Events.Abstract = function(elem) { + /** + * The block id for the block this event pertains to, if appropriate for the + * event type. + * @type {string|undefined} + */ + this.blockId = undefined; + + /** + * The variable id for the variable this event pertains to. Only set in + * VarCreate, VarDelete, and VarRename events. + * @type {string|undefined} + */ + this.varId = undefined; + + /** + * The workspace identifier for this event. + * @type {string|undefined} + */ + this.workspaceId = undefined; + + /** + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} + */ + this.group = undefined; + + /** + * Sets whether the event should be added to the undo stack. + * @type {boolean} + */ + this.recordUndo = undefined; + + if (elem instanceof Blockly.Block) { + this.blockId = elem.id; + this.workspaceId = elem.workspace.id; + } else if (elem instanceof Blockly.VariableModel) { + this.workspaceId = elem.workspace.id; + this.varId = elem.getId(); + } + this.group = Blockly.Events.group_; + this.recordUndo = Blockly.Events.recordUndo; +}; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Abstract.prototype.toJson = function() { + var json = { + 'type': this.type + }; + if (this.blockId) { + json['blockId'] = this.blockId; + } + if (this.varId) { + json['varId'] = this.varId; + } + if (this.group) { + json['group'] = this.group; + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Abstract.prototype.fromJson = function(json) { + this.blockId = json['blockId']; + this.varId = json['varId']; + this.group = json['group']; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if null, false if something changed. + */ +Blockly.Events.Abstract.prototype.isNull = function() { + return false; +}; + +/** + * Run an event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Abstract.prototype.run = function( + /* eslint-disable no-unused-vars */ forward + /* eslint-enable no-unused-vars */) { + // Defined by subclasses. +}; + +/** + * Get workspace the event belongs to. + * @return {Blockly.Workspace} The workspace the event belongs to. + * @throws {Error} if workspace is null. + * @private + */ +Blockly.Events.Abstract.prototype.getEventWorkspace_ = function() { + var workspace = Blockly.Workspace.getById(this.workspaceId); + if (!workspace) { + throw Error('Workspace is null. Event must have been generated from real' + + ' Blockly events.'); + } + return workspace; +}; + +/** + * Class for a block creation event. + * @param {Blockly.Block} block The created block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Create = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Create.superClass_.constructor.call(this, block); + + if (block.workspace.rendered) { + this.xml = Blockly.Xml.blockToDomWithXY(block); + } else { + this.xml = Blockly.Xml.blockToDom(block); + } + this.ids = Blockly.Events.getDescendantIds_(block); +}; +goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract); + +/** + * Class for a block creation event. + * @param {Blockly.Block} block The created block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockCreate = Blockly.Events.Create; + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Create.prototype.type = Blockly.Events.CREATE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Create.prototype.toJson = function() { + var json = Blockly.Events.Create.superClass_.toJson.call(this); + json['xml'] = Blockly.Xml.domToText(this.xml); + json['ids'] = this.ids; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Create.prototype.fromJson = function(json) { + Blockly.Events.Create.superClass_.fromJson.call(this, json); + this.xml = Blockly.Xml.textToDom('' + json['xml'] + '').firstChild; + this.ids = json['ids']; +}; + +/** + * Run a creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Create.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.xml); + Blockly.Xml.domToWorkspace(xml, workspace); + } else { + for (var i = 0, id; id = this.ids[i]; i++) { + var block = workspace.getBlockById(id); + if (block) { + block.dispose(false, false); + } else if (id == this.blockId) { + // Only complain about root-level block. + console.warn("Can't uncreate non-existent block: " + id); + } + } + } +}; + +/** + * Class for a block deletion event. + * @param {Blockly.Block} block The deleted block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Delete = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + if (block.getParent()) { + throw 'Connected blocks cannot be deleted.'; + } + Blockly.Events.Delete.superClass_.constructor.call(this, block); + + if (block.workspace.rendered) { + this.oldXml = Blockly.Xml.blockToDomWithXY(block); + } else { + this.oldXml = Blockly.Xml.blockToDom(block); + } + this.ids = Blockly.Events.getDescendantIds_(block); +}; +goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract); + +/** + * Class for a block deletion event. + * @param {Blockly.Block} block The deleted block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockDelete = Blockly.Events.Delete; + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Delete.prototype.toJson = function() { + var json = Blockly.Events.Delete.superClass_.toJson.call(this); + json['ids'] = this.ids; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Delete.prototype.fromJson = function(json) { + Blockly.Events.Delete.superClass_.fromJson.call(this, json); + this.ids = json['ids']; +}; + +/** + * Run a deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Delete.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + for (var i = 0, id; id = this.ids[i]; i++) { + var block = workspace.getBlockById(id); + if (block) { + block.dispose(false, false); + } else if (id == this.blockId) { + // Only complain about root-level block. + console.warn("Can't delete non-existent block: " + id); + } + } + } else { + var xml = goog.dom.createDom('xml'); + xml.appendChild(this.oldXml); + Blockly.Xml.domToWorkspace(xml, workspace); + } +}; + +/** + * Class for a block change event. + * @param {Blockly.Block} block The changed block. Null for a blank event. + * @param {string} element One of 'field', 'comment', 'disabled', etc. + * @param {?string} name Name of input or field affected, or null. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Change = function(block, element, name, oldValue, newValue) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Change.superClass_.constructor.call(this, block); + this.element = element; + this.name = name; + this.oldValue = oldValue; + this.newValue = newValue; +}; +goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract); + +/** + * Class for a block change event. + * @param {Blockly.Block} block The changed block. Null for a blank event. + * @param {string} element One of 'field', 'comment', 'disabled', etc. + * @param {?string} name Name of input or field affected, or null. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockChange = Blockly.Events.Change; + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Change.prototype.type = Blockly.Events.CHANGE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Change.prototype.toJson = function() { + var json = Blockly.Events.Change.superClass_.toJson.call(this); + json['element'] = this.element; + if (this.name) { + json['name'] = this.name; + } + json['newValue'] = this.newValue; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Change.prototype.fromJson = function(json) { + Blockly.Events.Change.superClass_.fromJson.call(this, json); + this.element = json['element']; + this.name = json['name']; + this.newValue = json['newValue']; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if something changed. + */ +Blockly.Events.Change.prototype.isNull = function() { + return this.oldValue == this.newValue; +}; + +/** + * Run a change event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Change.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + var block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn("Can't change non-existent block: " + this.blockId); + return; + } + if (block.mutator) { + // Close the mutator (if open) since we don't want to update it. + block.mutator.setVisible(false); + } + var value = forward ? this.newValue : this.oldValue; + switch (this.element) { + case 'field': + var field = block.getField(this.name); + if (field) { + // Run the validator for any side-effects it may have. + // The validator's opinion on validity is ignored. + field.callValidator(value); + field.setValue(value); + } else { + console.warn("Can't set non-existent field: " + this.name); + } + break; + case 'comment': + block.setCommentText(value || null); + break; + case 'collapsed': + block.setCollapsed(value); + break; + case 'disabled': + block.setDisabled(value); + break; + case 'inline': + block.setInputsInline(value); + break; + case 'mutation': + var oldMutation = ''; + if (block.mutationToDom) { + var oldMutationDom = block.mutationToDom(); + oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + } + if (block.domToMutation) { + value = value || ''; + var dom = Blockly.Xml.textToDom('' + value + ''); + block.domToMutation(dom.firstChild); + } + Blockly.Events.fire(new Blockly.Events.Change( + block, 'mutation', null, oldMutation, value)); + break; + default: + console.warn('Unknown change type: ' + this.element); + } +}; + +/** + * Class for a block move event. Created before the move. + * @param {Blockly.Block} block The moved block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Move = function(block) { + if (!block) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.Move.superClass_.constructor.call(this, block); + var location = this.currentLocation_(); + this.oldParentId = location.parentId; + this.oldInputName = location.inputName; + this.oldCoordinate = location.coordinate; +}; +goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract); + + +/** + * Class for a block move event. Created before the move. + * @param {Blockly.Block} block The moved block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockMove = Blockly.Events.Move; + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Move.prototype.type = Blockly.Events.MOVE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Move.prototype.toJson = function() { + var json = Blockly.Events.Move.superClass_.toJson.call(this); + if (this.newParentId) { + json['newParentId'] = this.newParentId; + } + if (this.newInputName) { + json['newInputName'] = this.newInputName; + } + if (this.newCoordinate) { + json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' + + Math.round(this.newCoordinate.y); + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Move.prototype.fromJson = function(json) { + Blockly.Events.Move.superClass_.fromJson.call(this, json); + this.newParentId = json['newParentId']; + this.newInputName = json['newInputName']; + if (json['newCoordinate']) { + var xy = json['newCoordinate'].split(','); + this.newCoordinate = + new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1])); + } +}; + +/** + * Record the block's new location. Called after the move. + */ +Blockly.Events.Move.prototype.recordNew = function() { + var location = this.currentLocation_(); + this.newParentId = location.parentId; + this.newInputName = location.inputName; + this.newCoordinate = location.coordinate; +}; + +/** + * Returns the parentId and input if the block is connected, + * or the XY location if disconnected. + * @return {!Object} Collection of location info. + * @private + */ +Blockly.Events.Move.prototype.currentLocation_ = function() { + var workspace = Blockly.Workspace.getById(this.workspaceId); + var block = workspace.getBlockById(this.blockId); + var location = {}; + var parent = block.getParent(); + if (parent) { + location.parentId = parent.id; + var input = parent.getInputWithBlock(block); + if (input) { + location.inputName = input.name; + } + } else { + location.coordinate = block.getRelativeToSurfaceXY(); + } + return location; +}; + +/** + * Does this event record any change of state? + * @return {boolean} True if something changed. + */ +Blockly.Events.Move.prototype.isNull = function() { + return this.oldParentId == this.newParentId && + this.oldInputName == this.newInputName && + goog.math.Coordinate.equals(this.oldCoordinate, this.newCoordinate); +}; + +/** + * Run a move event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.Move.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + var block = workspace.getBlockById(this.blockId); + if (!block) { + console.warn("Can't move non-existent block: " + this.blockId); + return; + } + var parentId = forward ? this.newParentId : this.oldParentId; + var inputName = forward ? this.newInputName : this.oldInputName; + var coordinate = forward ? this.newCoordinate : this.oldCoordinate; + var parentBlock = null; + if (parentId) { + parentBlock = workspace.getBlockById(parentId); + if (!parentBlock) { + console.warn("Can't connect to non-existent block: " + parentId); + return; + } + } + if (block.getParent()) { + block.unplug(); + } + if (coordinate) { + var xy = block.getRelativeToSurfaceXY(); + block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y); + } else { + var blockConnection = block.outputConnection || block.previousConnection; + var parentConnection; + if (inputName) { + var input = parentBlock.getInput(inputName); + if (input) { + parentConnection = input.connection; + } + } else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) { + parentConnection = parentBlock.nextConnection; + } + if (parentConnection) { + blockConnection.connect(parentConnection); + } else { + console.warn("Can't connect to non-existent input: " + inputName); + } + } +}; + +/** + * Class for a UI event. + * @param {Blockly.Block} block The affected block. + * @param {string} element One of 'selected', 'comment', 'mutator', etc. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.Ui = function(block, element, oldValue, newValue) { + Blockly.Events.Ui.superClass_.constructor.call(this, block); + this.element = element; + this.oldValue = oldValue; + this.newValue = newValue; + this.recordUndo = false; +}; +goog.inherits(Blockly.Events.Ui, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.Ui.prototype.type = Blockly.Events.UI; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.Ui.prototype.toJson = function() { + var json = Blockly.Events.Ui.superClass_.toJson.call(this); + json['element'] = this.element; + if (this.newValue !== undefined) { + json['newValue'] = this.newValue; + } + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.Ui.prototype.fromJson = function(json) { + Blockly.Events.Ui.superClass_.fromJson.call(this, json); + this.element = json['element']; + this.newValue = json['newValue']; +}; + +/** + * Class for a variable creation event. + * @param {Blockly.VariableModel} variable The created variable. + * Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarCreate = function(variable) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarCreate.superClass_.constructor.call(this, variable); + this.varType = variable.type; + this.varName = variable.name; +}; +goog.inherits(Blockly.Events.VarCreate, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarCreate.prototype.type = Blockly.Events.VAR_CREATE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarCreate.prototype.toJson = function() { + var json = Blockly.Events.VarCreate.superClass_.toJson.call(this); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarCreate.prototype.fromJson = function(json) { + Blockly.Events.VarCreate.superClass_.fromJson.call(this, json); + this.varType = json['varType']; + this.varName = json['varName']; +}; + +/** + * Run a variable creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarCreate.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.createVariable(this.varName, this.varType, this.varId); + } else { + workspace.deleteVariableById(this.varId); + } +}; + +/** + * Class for a variable deletion event. + * @param {Blockly.VariableModel} variable The deleted variable. + * Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarDelete = function(variable) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarDelete.superClass_.constructor.call(this, variable); + this.varType = variable.type; + this.varName = variable.name; +}; +goog.inherits(Blockly.Events.VarDelete, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarDelete.prototype.type = Blockly.Events.VAR_DELETE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarDelete.prototype.toJson = function() { + var json = Blockly.Events.VarDelete.superClass_.toJson.call(this); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarDelete.prototype.fromJson = function(json) { + Blockly.Events.VarDelete.superClass_.fromJson.call(this, json); + this.varType = json['varType']; + this.varName = json['varName']; +}; + +/** + * Run a variable deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarDelete.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.deleteVariableById(this.varId); + } else { + workspace.createVariable(this.varName, this.varType, this.varId); + } +}; + +/** + * Class for a variable rename event. + * @param {Blockly.VariableModel} variable The renamed variable. + * Null for a blank event. + * @param {string} newName The new name the variable will be changed to. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarRename = function(variable, newName) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarRename.superClass_.constructor.call(this, variable); + this.oldName = variable.name; + this.newName = newName; +}; +goog.inherits(Blockly.Events.VarRename, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarRename.prototype.type = Blockly.Events.VAR_RENAME; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarRename.prototype.toJson = function() { + var json = Blockly.Events.VarRename.superClass_.toJson.call(this); + json['oldName'] = this.oldName; + json['newName'] = this.newName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarRename.prototype.fromJson = function(json) { + Blockly.Events.VarRename.superClass_.fromJson.call(this, json); + this.oldName = json['oldName']; + this.newName = json['newName']; +}; + +/** + * Run a variable rename event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarRename.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.renameVariableById(this.varId, this.newName); + } else { + workspace.renameVariableById(this.varId, this.oldName); + } +}; + +/** + * Enable/disable a block depending on whether it is properly connected. + * Use this on applications where all blocks should be connected to a top block. + * Recommend setting the 'disable' option to 'false' in the config so that + * users don't try to reenable disabled orphan blocks. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.disableOrphans = function(event) { + if (event.type == Blockly.Events.MOVE || + event.type == Blockly.Events.CREATE) { + var workspace = Blockly.Workspace.getById(event.workspaceId); + var block = workspace.getBlockById(event.blockId); + if (block) { + if (block.getParent() && !block.getParent().disabled) { + var children = block.getDescendants(); + for (var i = 0, child; child = children[i]; i++) { + child.setDisabled(false); + } + } else if ((block.outputConnection || block.previousConnection) && + !workspace.isDragging()) { + do { + block.setDisabled(true); + block = block.getNextBlock(); + } while (block); + } + } + } +}; diff --git a/core/.svn/pristine/30/30449f2aeb021815259161cdc1b99434b3ec0e72.svn-base b/core/.svn/pristine/30/30449f2aeb021815259161cdc1b99434b3ec0e72.svn-base new file mode 100644 index 0000000..53ced0e --- /dev/null +++ b/core/.svn/pristine/30/30449f2aeb021815259161cdc1b99434b3ec0e72.svn-base @@ -0,0 +1,349 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2015 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 Date input field. + * @author pkendall64@gmail.com (Paul Kendall) + */ +'use strict'; + +goog.provide('Blockly.FieldDate'); + +goog.require('Blockly.Field'); +goog.require('Blockly.utils'); + +goog.require('goog.date'); +goog.require('goog.date.DateTime'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.i18n.DateTimeSymbols'); +goog.require('goog.i18n.DateTimeSymbols_he'); +goog.require('goog.style'); +goog.require('goog.ui.DatePicker'); + + +/** + * Class for a date input field. + * @param {string} date The initial date. + * @param {Function=} opt_validator A function that is executed when a new + * date is selected. Its sole argument is the new date value. Its + * return value becomes the selected date, unless it is undefined, in + * which case the new date stands, or it is null, in which case the change + * is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldDate = function(date, opt_validator) { + if (!date) { + date = new goog.date.Date().toIsoString(true); + } + Blockly.FieldDate.superClass_.constructor.call(this, date, opt_validator); + this.setValue(date); +}; +goog.inherits(Blockly.FieldDate, Blockly.Field); + +/** + * Construct a FieldDate from a JSON arg object. + * @param {!Object} options A JSON object with options (date). + * @returns {!Blockly.FieldDate} The new field instance. + * @package + */ +Blockly.FieldDate.fromJson = function(options) { + return new Blockly.FieldDate(options['date']); +}; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldDate.prototype.CURSOR = 'text'; + +/** + * Close the colour picker if this input is being deleted. + */ +Blockly.FieldDate.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldDate.superClass_.dispose.call(this); +}; + +/** + * Return the current date. + * @return {string} Current date. + */ +Blockly.FieldDate.prototype.getValue = function() { + return this.date_; +}; + +/** + * Set the date. + * @param {string} date The new date. + */ +Blockly.FieldDate.prototype.setValue = function(date) { + if (this.sourceBlock_) { + var validated = this.callValidator(date); + // If the new date is invalid, validation returns null. + // In this case we still want to display the illegal result. + if (validated !== null) { + date = validated; + } + } + this.date_ = date; + Blockly.Field.prototype.setText.call(this, date); +}; + +/** + * Create a date picker under the date field. + * @private + */ +Blockly.FieldDate.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, + Blockly.FieldDate.widgetDispose_); + + // Record viewport dimensions before adding the picker. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getScaledBBox_(); + + // Create and add the date picker, then record the size. + var picker = this.createWidget_(); + var pickerSize = goog.style.getSize(picker.getElement()); + + // Position the picker to line up with the field. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, pickerSize, + this.sourceBlock_.RTL); + + // Configure event handler. + var thisField = this; + Blockly.FieldDate.changeEventKey_ = goog.events.listen(picker, + goog.ui.DatePicker.Events.CHANGE, + function(event) { + var date = event.date ? event.date.toIsoString(true) : ''; + Blockly.WidgetDiv.hide(); + if (thisField.sourceBlock_) { + // Call any validation function, and allow it to override. + date = thisField.callValidator(date); + } + thisField.setValue(date); + }); +}; + +/** + * Create a date picker widget and render it inside the widget div. + * @return {!goog.ui.DatePicker} The newly created date picker. + * @private + */ +Blockly.FieldDate.prototype.createWidget_ = function() { + // Create the date picker using Closure. + Blockly.FieldDate.loadLanguage_(); + var picker = new goog.ui.DatePicker(); + picker.setAllowNone(false); + picker.setShowWeekNum(false); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setDate(goog.date.DateTime.fromIsoString(this.getValue())); + return picker; +}; + +/** + * Hide the date picker. + * @private + */ +Blockly.FieldDate.widgetDispose_ = function() { + if (Blockly.FieldDate.changeEventKey_) { + goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_); + } + Blockly.Events.setGroup(false); +}; + +/** + * Load the best language pack by scanning the Blockly.Msg object for a + * language that matches the available languages in Closure. + * @private + */ +Blockly.FieldDate.loadLanguage_ = function() { + var reg = /^DateTimeSymbols_(.+)$/; + for (var prop in goog.i18n) { + var m = prop.match(reg); + if (m) { + var lang = m[1].toLowerCase().replace('_', '.'); // E.g. 'pt.br' + if (goog.getObjectByName(lang, Blockly.Msg)) { + goog.i18n.DateTimeSymbols = goog.i18n[prop]; + } + } + } +}; + +/** + * CSS for date picker. See css.js for use. + */ +Blockly.FieldDate.CSS = [ + /* Copied from: goog/css/datepicker.css */ + /** + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for a goog.ui.DatePicker. + * + * @author arv@google.com (Erik Arvidsson) + */ + + '.blocklyWidgetDiv .goog-date-picker,', + '.blocklyWidgetDiv .goog-date-picker th,', + '.blocklyWidgetDiv .goog-date-picker td {', + ' font: 13px Arial, sans-serif;', + '}', + + '.blocklyWidgetDiv .goog-date-picker {', + ' -moz-user-focus: normal;', + ' -moz-user-select: none;', + ' position: relative;', + ' border: 1px solid #000;', + ' float: left;', + ' padding: 2px;', + ' color: #000;', + ' background: #c3d9ff;', + ' cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-date-picker th {', + ' text-align: center;', + '}', + + '.blocklyWidgetDiv .goog-date-picker td {', + ' text-align: center;', + ' vertical-align: middle;', + ' padding: 1px 3px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu {', + ' position: absolute;', + ' background: threedface;', + ' border: 1px solid gray;', + ' -moz-user-focus: normal;', + ' z-index: 1;', + ' outline: none;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu ul {', + ' list-style: none;', + ' margin: 0px;', + ' padding: 0px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu ul li {', + ' cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-menu-selected {', + ' background: #ccf;', + '}', + + '.blocklyWidgetDiv .goog-date-picker th {', + ' font-size: .9em;', + '}', + + '.blocklyWidgetDiv .goog-date-picker td div {', + ' float: left;', + '}', + + '.blocklyWidgetDiv .goog-date-picker button {', + ' padding: 0px;', + ' margin: 1px 0;', + ' border: 0;', + ' color: #20c;', + ' font-weight: bold;', + ' background: transparent;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-date {', + ' background: #fff;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-week,', + '.blocklyWidgetDiv .goog-date-picker-wday {', + ' padding: 1px 3px;', + ' border: 0;', + ' border-color: #a2bbdd;', + ' border-style: solid;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-week {', + ' border-right-width: 1px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-wday {', + ' border-bottom-width: 1px;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-head td {', + ' text-align: center;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-today-cont {', + ' text-align: center;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-none-cont {', + ' text-align: center;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-month {', + ' min-width: 11ex;', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-year {', + ' min-width: 6ex;', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-monthyear {', + ' white-space: nowrap;', + '}', + + '.blocklyWidgetDiv .goog-date-picker table {', + ' border-collapse: collapse;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-other-month {', + ' color: #888;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-wkend-start,', + '.blocklyWidgetDiv .goog-date-picker-wkend-end {', + ' background: #eee;', + '}', + + /** Use td.className instead of !important */ + '.blocklyWidgetDiv td.goog-date-picker-selected {', + ' background: #c3d9ff;', + '}', + + '.blocklyWidgetDiv .goog-date-picker-today {', + ' background: #9ab;', + ' font-weight: bold !important;', + ' border-color: #246 #9bd #9bd #246;', + ' color: #fff;', + '}' +]; diff --git a/core/.svn/pristine/32/3248bb7d2665720356b7ba689cf8987ecdf61c7e.svn-base b/core/.svn/pristine/32/3248bb7d2665720356b7ba689cf8987ecdf61c7e.svn-base new file mode 100644 index 0000000..a704489 --- /dev/null +++ b/core/.svn/pristine/32/3248bb7d2665720356b7ba689cf8987ecdf61c7e.svn-base @@ -0,0 +1,396 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object representing a map of variables and their types. + * @author marisaleung@google.com (Marisa Leung) + */ +'use strict'; + +goog.provide('Blockly.VariableMap'); + + +/** + * Class for a variable map. This contains a dictionary data structure with + * variable types as keys and lists of variables as values. The list of + * variables are the type indicated by the key. + * @param {!Blockly.Workspace} workspace The workspace this map belongs to. + * @constructor + */ +Blockly.VariableMap = function(workspace) { + /** + * A map from variable type to list of variable names. The lists contain all + * of the named variables in the workspace, including variables + * that are not currently in use. + * @type {!Object.>} + * @private + */ + this.variableMap_ = {}; + + /** + * The workspace this map belongs to. + * @type {!Blockly.Workspace} + */ + this.workspace = workspace; +}; + +/** + * Clear the variable map. + */ +Blockly.VariableMap.prototype.clear = function() { + this.variableMap_ = new Object(null); +}; + +/* Begin functions for renaming variables. */ + +/** + * Rename the given variable by updating its name in the variable map. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @package + */ +Blockly.VariableMap.prototype.renameVariable = function(variable, newName) { + var type = variable.type; + var conflictVar = this.getVariable(newName, type); + var blocks = this.workspace.getAllBlocks(); + Blockly.Events.setGroup(true); + try { + // The IDs may match if the rename is a simple case change (name1 -> Name1). + if (!conflictVar || conflictVar.getId() == variable.getId()) { + this.renameVariableAndUses_(variable, newName, blocks); + } else { + this.renameVariableWithConflict_(variable, newName, conflictVar, blocks); + } + } finally { + Blockly.Events.setGroup(false); + } +}; + +/** + * Rename a variable by updating its name in the variable map. Identify the + * variable to rename with the given ID. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + */ +Blockly.VariableMap.prototype.renameVariableById = function(id, newName) { + var variable = this.getVariableById(id); + if (!variable) { + throw new Error('Tried to rename a variable that didn\'t exist. ID: ' + id); + } + + this.renameVariable(variable, newName); +}; + +/** + * Update the name of the given variable and refresh all references to it. + * The new name must not conflict with any existing variable names. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableAndUses_ = function(variable, + newName, blocks) { + Blockly.Events.fire(new Blockly.Events.VarRename(variable, newName)); + variable.name = newName; + for (var i = 0; i < blocks.length; i++) { + blocks[i].updateVarName(variable); + } +}; + +/** + * Update the name of the given variable to the same name as an existing + * variable. The two variables are coalesced into a single variable with the ID + * of the existing variable that was already using newName. + * Refresh all references to the variable. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Blockly.VariableModel} conflictVar The variable that was already + * using newName. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableWithConflict_ = function(variable, + newName, conflictVar, blocks) { + var type = variable.type; + var oldCase = conflictVar.name; + + if (newName != oldCase) { + // Simple rename to change the case and update references. + this.renameVariableAndUses_(conflictVar, newName, blocks); + } + + // These blocks now refer to a different variable. + // These will fire change events. + for (var i = 0; i < blocks.length; i++) { + blocks[i].renameVarById(variable.getId(), conflictVar.getId()); + } + + // Finally delete the original variable, which is now unreferenced. + Blockly.Events.fire(new Blockly.Events.VarDelete(variable)); + // And remove it from the list. + var variableList = this.getVariablesOfType(type); + var variableIndex = variableList.indexOf(variable); + this.variableMap_[type].splice(variableIndex, 1); + +}; + +/* End functions for renaming variabless. */ + +/** + * Create a variable with a given name, optional type, and optional ID. + * @param {string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {Blockly.VariableModel} The newly created variable. + */ +Blockly.VariableMap.prototype.createVariable = function(name, + opt_type, opt_id) { + var variable = this.getVariable(name, opt_type); + if (variable) { + if (opt_id && variable.getId() != opt_id) { + throw Error('Variable "' + name + '" is already in use and its id is "' + + variable.getId() + '" which conflicts with the passed in ' + + 'id, "' + opt_id + '".'); + } + // The variable already exists and has the same ID. + return variable; + } + if (opt_id && this.getVariableById(opt_id)) { + throw Error('Variable id, "' + opt_id + '", is already in use.'); + } + opt_id = opt_id || Blockly.utils.genUid(); + opt_type = opt_type || ''; + + variable = new Blockly.VariableModel(this.workspace, name, opt_type, opt_id); + // If opt_type is not a key, create a new list. + if (!this.variableMap_[opt_type]) { + this.variableMap_[opt_type] = [variable]; + } else { + // Else append the variable to the preexisting list. + this.variableMap_[opt_type].push(variable); + } + return variable; +}; + +/* Begin functions for variable deletion. */ + +/** + * Delete a variable. + * @param {!Blockly.VariableModel} variable Variable to delete. + */ +Blockly.VariableMap.prototype.deleteVariable = function(variable) { + var variableList = this.variableMap_[variable.type]; + for (var i = 0, tempVar; tempVar = variableList[i]; i++) { + if (tempVar.getId() == variable.getId()) { + variableList.splice(i, 1); + Blockly.Events.fire(new Blockly.Events.VarDelete(variable)); + return; + } + } +}; + +/** + * Delete a variables by the passed in ID and all of its uses from this + * workspace. May prompt the user for confirmation. + * @param {string} id ID of variable to delete. + */ +Blockly.VariableMap.prototype.deleteVariableById = function(id) { + var variable = this.getVariableById(id); + if (variable) { + // Check whether this variable is a function parameter before deleting. + var variableName = variable.name; + var uses = this.getVariableUsesById(id); + for (var i = 0, block; block = uses[i]; i++) { + if (block.type == 'procedures_defnoreturn' || + block.type == 'procedures_defreturn') { + var procedureName = block.getFieldValue('NAME'); + var deleteText = Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE. + replace('%1', variableName). + replace('%2', procedureName); + Blockly.alert(deleteText); + return; + } + } + + var map = this; + if (uses.length > 1) { + // Confirm before deleting multiple blocks. + var confirmText = Blockly.Msg.DELETE_VARIABLE_CONFIRMATION. + replace('%1', String(uses.length)). + replace('%2', variableName); + Blockly.confirm(confirmText, + function(ok) { + if (ok) { + map.deleteVariableInternal_(variable, uses); + } + }); + } else { + // No confirmation necessary for a single block. + map.deleteVariableInternal_(variable, uses); + } + } else { + console.warn("Can't delete non-existent variable: " + id); + } +}; + +/** + * Deletes a variable and all of its uses from this workspace without asking the + * user for confirmation. + * @param {!Blockly.VariableModel} variable Variable to delete. + * @param {!Array.} uses An array of uses of the variable. + * @private + */ +Blockly.VariableMap.prototype.deleteVariableInternal_ = function(variable, + uses) { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + for (var i = 0; i < uses.length; i++) { + uses[i].dispose(true, false); + } + this.deleteVariable(variable); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } +}; + +/* End functions for variable deletion. */ + +/** + * Find the variable by the given name and type and return it. Return null if + * it is not found. + * @param {string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. + * @return {Blockly.VariableModel} The variable with the given name, or null if + * it was not found. + */ +Blockly.VariableMap.prototype.getVariable = function(name, opt_type) { + var type = opt_type || ''; + var list = this.variableMap_[type]; + if (list) { + for (var j = 0, variable; variable = list[j]; j++) { + if (Blockly.Names.equals(variable.name, name)) { + return variable; + } + } + } + return null; +}; + +/** + * Find the variable by the given ID and return it. Return null if it is not + * found. + * @param {string} id The ID to check for. + * @return {Blockly.VariableModel} The variable with the given ID. + */ +Blockly.VariableMap.prototype.getVariableById = function(id) { + var keys = Object.keys(this.variableMap_); + for (var i = 0; i < keys.length; i++ ) { + var key = keys[i]; + for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) { + if (variable.getId() == id) { + return variable; + } + } + } + return null; +}; + +/** + * Get a list containing all of the variables of a specified type. If type is + * null, return list of variables with empty string type. + * @param {?string} type Type of the variables to find. + * @return {!Array.} The sought after variables of the + * passed in type. An empty array if none are found. + */ +Blockly.VariableMap.prototype.getVariablesOfType = function(type) { + type = type || ''; + var variable_list = this.variableMap_[type]; + if (variable_list) { + return variable_list.slice(); + } + return []; +}; + +/** + * Return all variable types. This list always contains the empty string. + * @return {!Array.} List of variable types. + * @package + */ +Blockly.VariableMap.prototype.getVariableTypes = function() { + var types = Object.keys(this.variableMap_); + var hasEmpty = false; + for (var i = 0; i < types.length; i++) { + if (types[i] == '') { + hasEmpty = true; + } + } + if (!hasEmpty) { + types.push(''); + } + return types; +}; + +/** + * Return all variables of all types. + * @return {!Array.} List of variable models. + */ +Blockly.VariableMap.prototype.getAllVariables = function() { + var all_variables = []; + var keys = Object.keys(this.variableMap_); + for (var i = 0; i < keys.length; i++ ) { + all_variables = all_variables.concat(this.variableMap_[keys[i]]); + } + return all_variables; +}; + +/** + * Find all the uses of a named variable. + * @param {string} id ID of the variable to find. + * @return {!Array.} Array of block usages. + */ +Blockly.VariableMap.prototype.getVariableUsesById = function(id) { + var uses = []; + var blocks = this.workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + var blockVariables = blocks[i].getVarModels(); + if (blockVariables) { + for (var j = 0; j < blockVariables.length; j++) { + if (blockVariables[j].getId() == id) { + uses.push(blocks[i]); + } + } + } + } + return uses; +}; diff --git a/core/.svn/pristine/34/34e460171cb8b355a6a249561e713e7707670f7e.svn-base b/core/.svn/pristine/34/34e460171cb8b355a6a249561e713e7707670f7e.svn-base new file mode 100644 index 0000000..d845ae8 --- /dev/null +++ b/core/.svn/pristine/34/34e460171cb8b355a6a249561e713e7707670f7e.svn-base @@ -0,0 +1,804 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Flyout tray containing blocks which may be created. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Flyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.Gesture'); +goog.require('Blockly.Touch'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @constructor + */ +Blockly.Flyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.isFlyout = true; + + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = !!workspaceOptions.RTL; + + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + * @private + */ + this.toolboxPosition_ = workspaceOptions.toolboxPosition; + + /** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {!Array.} + * @private + */ + this.eventWrappers_ = []; + + /** + * List of background mats that lurk behind each block to catch clicks + * landing in the blocks' lakes and bays. + * @type {!Array.} + * @private + */ + this.mats_ = []; + + /** + * List of visible buttons. + * @type {!Array.} + * @private + */ + this.buttons_ = []; + + /** + * List of event listeners. + * @type {!Array.} + * @private + */ + this.listeners_ = []; + + /** + * List of blocks that should always be disabled. + * @type {!Array.} + * @private + */ + this.permanentlyDisabled_ = []; +}; + +/** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ +Blockly.Flyout.prototype.autoClose = true; + +/** + * Whether the flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.isVisible_ = false; + +/** + * Whether the workspace containing this flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.containerVisible_ = true; + +/** + * Corner radius of the flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.CORNER_RADIUS = 8; + +/** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS; + +// TODO: Move GAP_X and GAP_Y to their appropriate files. + +/** + * Gap between items in horizontal flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Gap between items in vertical flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Top/bottom padding between scrollbar and edge of flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2; + +/** + * Width of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.width_ = 0; + +/** + * Height of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.height_ = 0; + +/** + * Range of a drag angle from a flyout considered "dragging toward workspace". + * Drags that are within the bounds of this many degrees from the orthogonal + * line to the flyout edge are considered to be "drags toward the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @private +*/ +Blockly.Flyout.prototype.dragAngleRange_ = 70; + +/** + * Creates the flyout's DOM. Only needs to be called once. The flyout can + * either exist as its own svg element or be a g element nested inside a + * separate svg element. + * @param {string} tagName The type of tag to put the flyout in. This + * should be or . + * @return {!Element} The flyout's SVG group. + */ +Blockly.Flyout.prototype.createDom = function(tagName) { + /* + + + + + */ + // Setting style to display:none to start. The toolbox and flyout + // hide/show code will set up proper visibility and size later. + this.svgGroup_ = Blockly.utils.createSvgElement(tagName, + {'class': 'blocklyFlyout', 'style': 'display: none'}, null); + this.svgBackground_ = Blockly.utils.createSvgElement('path', + {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); + this.svgGroup_.appendChild(this.workspace_.createDom()); + return this.svgGroup_; +}; + +/** + * Initializes the flyout. + * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create + * new blocks. + */ +Blockly.Flyout.prototype.init = function(targetWorkspace) { + this.targetWorkspace_ = targetWorkspace; + this.workspace_.targetWorkspace = targetWorkspace; + // Add scrollbar. + this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, + this.horizontalLayout_, false, 'blocklyFlyoutScrollbar'); + + this.hide(); + + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, this.wheel_)); + if (!this.autoClose) { + this.filterWrapper_ = this.filterForCapacity_.bind(this); + this.targetWorkspace_.addChangeListener(this.filterWrapper_); + } + + // Dragging the flyout up and down. + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEventWithChecks_( + this.svgBackground_, 'mousedown', this, this.onMouseDown_)); + + // A flyout connected to a workspace doesn't have its own current gesture. + this.workspace_.getGesture = + this.targetWorkspace_.getGesture.bind(this.targetWorkspace_); + + // Get variables from the main workspace rather than the target workspace. + this.workspace_.variableMap_ = this.targetWorkspace_.getVariableMap(); + + this.workspace_.createPotentialVariableMap(); +}; + +/** + * Dispose of this flyout. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Flyout.prototype.dispose = function() { + this.hide(); + Blockly.unbindEvent_(this.eventWrappers_); + if (this.filterWrapper_) { + this.targetWorkspace_.removeChangeListener(this.filterWrapper_); + this.filterWrapper_ = null; + } + if (this.scrollbar_) { + this.scrollbar_.dispose(); + this.scrollbar_ = null; + } + if (this.workspace_) { + this.workspace_.targetWorkspace = null; + this.workspace_.dispose(); + this.workspace_ = null; + } + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBackground_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Get the width of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getWidth = function() { + return this.width_; +}; + +/** + * Get the height of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getHeight = function() { + return this.height_; +}; + +/** + * Get the workspace inside the flyout. + * @return {!Blockly.WorkspaceSvg} The workspace inside the flyout. + * @package + */ +Blockly.Flyout.prototype.getWorkspace = function() { + return this.workspace_; +}; + +/** + * Is the flyout visible? + * @return {boolean} True if visible. + */ +Blockly.Flyout.prototype.isVisible = function() { + return this.isVisible_; +}; + +/** + * Set whether the flyout is visible. A value of true does not necessarily mean + * that the flyout is shown. It could be hidden because its container is hidden. + * @param {boolean} visible True if visible. + */ +Blockly.Flyout.prototype.setVisible = function(visible) { + var visibilityChanged = (visible != this.isVisible()); + + this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Set whether this flyout's container is visible. + * @param {boolean} visible Whether the container is visible. + */ +Blockly.Flyout.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Update the display property of the flyout based whether it thinks it should + * be visible and whether its containing workspace is visible. + * @private + */ +Blockly.Flyout.prototype.updateDisplay_ = function() { + var show = true; + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + this.svgGroup_.style.display = show ? 'block' : 'none'; + // Update the scrollbar's visiblity too since it should mimic the + // flyout's visibility. + this.scrollbar_.setContainerVisible(show); +}; + +/** + * Update the view based on coordinates calculated in position(). + * @param {number} width The computed width of the flyout's SVG group + * @param {number} height The computed height of the flyout's SVG group. + * @param {number} x The computed x origin of the flyout's SVG group. + * @param {number} y The computed y origin of the flyout's SVG group. + * @private + */ +Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) { + this.svgGroup_.setAttribute("width", width); + this.svgGroup_.setAttribute("height", height); + var transform = 'translate(' + x + 'px,' + y + 'px)'; + Blockly.utils.setCssTransform(this.svgGroup_, transform); + + // Update the scrollbar (if one exists). + if (this.scrollbar_) { + // Set the scrollbars origin to be the top left of the flyout. + this.scrollbar_.setOrigin(x, y); + this.scrollbar_.resize(); + } +}; + +/** + * Hide and empty the flyout. + */ +Blockly.Flyout.prototype.hide = function() { + if (!this.isVisible()) { + return; + } + this.setVisible(false); + // Delete all the event listeners. + for (var x = 0, listen; listen = this.listeners_[x]; x++) { + Blockly.unbindEvent_(listen); + } + this.listeners_.length = 0; + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + this.reflowWrapper_ = null; + } + // Do NOT delete the blocks here. Wait until Flyout.show. + // https://neil.fraser.name/news/2014/08/09/ +}; + +/** + * Show and populate the flyout. + * @param {!Array|string} xmlList List of blocks to show. + * Variables and procedures have a custom set of blocks. + */ +Blockly.Flyout.prototype.show = function(xmlList) { + this.workspace_.setResizesEnabled(false); + this.hide(); + this.clearOldBlocks_(); + + // Handle dynamic categories, represented by a name instead of a list of XML. + // Look up the correct category generation function and call that to get a + // valid XML list. + if (typeof xmlList == 'string') { + var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback( + xmlList); + goog.asserts.assert(goog.isFunction(fnToApply), + 'Couldn\'t find a callback function when opening a toolbox category.'); + xmlList = fnToApply(this.workspace_.targetWorkspace); + goog.asserts.assert(goog.isArray(xmlList), + 'The result of a toolbox category callback must be an array.'); + } + + this.setVisible(true); + // Create the blocks to be shown in this flyout. + var contents = []; + var gaps = []; + this.permanentlyDisabled_.length = 0; + for (var i = 0, xml; xml = xmlList[i]; i++) { + if (xml.tagName) { + var tagName = xml.tagName.toUpperCase(); + var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y; + if (tagName == 'BLOCK') { + var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_); + if (curBlock.disabled) { + // Record blocks that were initially disabled. + // Do not enable these blocks as a result of capacity filtering. + this.permanentlyDisabled_.push(curBlock); + } + contents.push({type: 'block', block: curBlock}); + var gap = parseInt(xml.getAttribute('gap'), 10); + gaps.push(isNaN(gap) ? default_gap : gap); + } else if (xml.tagName.toUpperCase() == 'SEP') { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // This overwrites the gap attribute on the previous block. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseInt(xml.getAttribute('gap'), 10); + // Ignore gaps before the first block. + if (!isNaN(newGap) && gaps.length > 0) { + gaps[gaps.length - 1] = newGap; + } else { + gaps.push(default_gap); + } + } else if (tagName == 'BUTTON' || tagName == 'LABEL') { + // Labels behave the same as buttons, but are styled differently. + var isLabel = tagName == 'LABEL'; + var curButton = new Blockly.FlyoutButton(this.workspace_, + this.targetWorkspace_, xml, isLabel); + contents.push({type: 'button', button: curButton}); + gaps.push(default_gap); + } + } + } + + this.layout_(contents, gaps); + + // IE 11 is an incompetent browser that fails to fire mouseout events. + // When the mouse is over the background, deselect all blocks. + var deselectAll = function() { + var topBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = topBlocks[i]; i++) { + block.removeSelect(); + } + }; + + this.listeners_.push(Blockly.bindEventWithChecks_(this.svgBackground_, + 'mouseover', this, deselectAll)); + + if (this.horizontalLayout_) { + this.height_ = 0; + } else { + this.width_ = 0; + } + this.workspace_.setResizesEnabled(true); + this.reflow(); + + this.filterForCapacity_(); + + // Correctly position the flyout's scrollbar when it opens. + this.position(); + + this.reflowWrapper_ = this.reflow.bind(this); + this.workspace_.addChangeListener(this.reflowWrapper_); +}; + +/** + * Delete blocks, mats and buttons from a previous showing of the flyout. + * @private + */ +Blockly.Flyout.prototype.clearOldBlocks_ = function() { + // Delete any blocks from a previous showing. + var oldBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = oldBlocks[i]; i++) { + if (block.workspace == this.workspace_) { + block.dispose(false, false); + } + } + // Delete any mats from a previous showing. + for (var j = 0; j < this.mats_.length; j++) { + var rect = this.mats_[j]; + if (rect) { + goog.dom.removeNode(rect); + } + } + this.mats_.length = 0; + // Delete any buttons from a previous showing. + for (var i = 0, button; button = this.buttons_[i]; i++) { + button.dispose(); + } + this.buttons_.length = 0; + + // Clear potential variables from the previous showing. + this.workspace_.getPotentialVariableMap().clear(); +}; + +/** + * Add listeners to a block that has been added to the flyout. + * @param {!Element} root The root node of the SVG group the block is in. + * @param {!Blockly.Block} block The block to add listeners for. + * @param {!Element} rect The invisible rectangle under the block that acts as + * a mat for that block. + * @private + */ +Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { + this.listeners_.push(Blockly.bindEventWithChecks_(root, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEventWithChecks_(rect, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, + block.removeSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, + block.removeSelect)); +}; + +/** + * Handle a mouse-down on an SVG block in a non-closing flyout. + * @param {!Blockly.Block} block The flyout block to copy. + * @return {!Function} Function to call when block is clicked. + * @private + */ +Blockly.Flyout.prototype.blockMouseDown_ = function(block) { + var flyout = this; + return function(e) { + var gesture = flyout.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.setStartBlock(block); + gesture.handleFlyoutStart(e, flyout); + } + }; +}; + +/** + * Mouse down on the flyout background. Start a vertical scroll drag. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Flyout.prototype.onMouseDown_ = function(e) { + var gesture = this.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.handleFlyoutStart(e, this); + } +}; + +/** + * Create a copy of this block on the workspace. + * @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout. + * @return {Blockly.BlockSvg} The newly created block, or null if something + * went wrong with deserialization. + * @package + */ +Blockly.Flyout.prototype.createBlock = function(originalBlock) { + var newBlock = null; + Blockly.Events.disable(); + var variablesBeforeCreation = this.targetWorkspace_.getAllVariables(); + this.targetWorkspace_.setResizesEnabled(false); + try { + newBlock = this.placeNewBlock_(originalBlock); + // Close the flyout. + Blockly.hideChaff(); + } finally { + Blockly.Events.enable(); + } + + var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace_, + variablesBeforeCreation); + + if (Blockly.Events.isEnabled()) { + Blockly.Events.setGroup(true); + Blockly.Events.fire(new Blockly.Events.Create(newBlock)); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } + } + if (this.autoClose) { + this.hide(); + } else { + this.filterForCapacity_(); + } + return newBlock; +}; + +/** + * Initialize the given button: move it to the correct location, + * add listeners, etc. + * @param {!Blockly.FlyoutButton} button The button to initialize and place. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @private + */ +Blockly.Flyout.prototype.initFlyoutButton_ = function(button, x, y) { + var buttonSvg = button.createDom(); + button.moveTo(x, y); + button.show(); + // Clicking on a flyout button or label is a lot like clicking on the + // flyout background. + this.listeners_.push( + Blockly.bindEventWithChecks_( + buttonSvg, 'mousedown', this, this.onMouseDown_)); + + this.buttons_.push(button); +}; + +/** + * Create and place a rectangle corresponding to the given block. + * @param {!Blockly.Block} block The block to associate the rect to. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @param {!{height: number, width: number}} blockHW The height and width of the + * block. + * @param {number} index The index into the mats list where this rect should be + * placed. + * @return {!SVGElement} Newly created SVG element for the rectangle behind the + * block. + * @private + */ +Blockly.Flyout.prototype.createRect_ = function(block, x, y, blockHW, index) { + // Create an invisible rectangle under the block to act as a button. Just + // using the block as a button is poor, since blocks have holes in them. + var rect = Blockly.utils.createSvgElement('rect', + { + 'fill-opacity': 0, + 'x': x, + 'y': y, + 'height': blockHW.height, + 'width': blockHW.width + }, null); + rect.tooltip = block; + Blockly.Tooltip.bindMouseEvents(rect); + // Add the rectangles under the blocks, so that the blocks' tooltips work. + this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); + + block.flyoutRect_ = rect; + this.mats_[index] = rect; + return rect; +}; + +/** + * Move a rectangle to sit exactly behind a block, taking into account tabs, + * hats, and any other protrusions we invent. + * @param {!SVGElement} rect The rectangle to move directly behind the block. + * @param {!Blockly.BlockSvg} block The block the rectangle should be behind. + * @private + */ +Blockly.Flyout.prototype.moveRectToBlock_ = function(rect, block) { + var blockHW = block.getHeightWidth(); + rect.setAttribute('width', blockHW.width); + rect.setAttribute('height', blockHW.height); + + // For hat blocks we want to shift them down by the hat height + // since the y coordinate is the corner, not the top of the hat. + var hatOffset = + block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; + if (hatOffset) { + block.moveBy(0, hatOffset); + } + + // Blocks with output tabs are shifted a bit. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockXY = block.getRelativeToSurfaceXY(); + rect.setAttribute('y', blockXY.y); + rect.setAttribute('x', + this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); +}; + +/** + * Filter the blocks on the flyout to disable the ones that are above the + * capacity limit. For instance, if the user may only place two more blocks on + * the workspace, an "a + b" block that has two shadow blocks would be disabled. + * @private + */ +Blockly.Flyout.prototype.filterForCapacity_ = function() { + var remainingCapacity = this.targetWorkspace_.remainingCapacity(); + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (this.permanentlyDisabled_.indexOf(block) == -1) { + var allBlocks = block.getDescendants(); + block.setDisabled(allBlocks.length > remainingCapacity); + } + } +}; + +/** + * Reflow blocks and their mats. + */ +Blockly.Flyout.prototype.reflow = function() { + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + } + this.reflowInternal_(); + if (this.reflowWrapper_) { + this.workspace_.addChangeListener(this.reflowWrapper_); + } +}; + +/** + * @return {boolean} True if this flyout may be scrolled with a scrollbar or by + * dragging. + * @package + */ +Blockly.Flyout.prototype.isScrollable = function() { + return this.scrollbar_ ? this.scrollbar_.isVisible() : false; +}; + +/** + * Copy a block from the flyout to the workspace and position it correctly. + * @param {!Blockly.Block} oldBlock The flyout block to copy. + * @return {!Blockly.Block} The new block in the main workspace. + * @private + */ +Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) { + var targetWorkspace = this.targetWorkspace_; + var svgRootOld = oldBlock.getSvgRoot(); + if (!svgRootOld) { + throw 'oldBlock is not rendered.'; + } + + // Create the new block by cloning the block in the flyout (via XML). + var xml = Blockly.Xml.blockToDom(oldBlock); + // The target workspace would normally resize during domToBlock, which will + // lead to weird jumps. Save it for terminateDrag. + targetWorkspace.setResizesEnabled(false); + + // Using domToBlock instead of domToWorkspace means that the new block will be + // placed at position (0, 0) in main workspace units. + var block = Blockly.Xml.domToBlock(xml, targetWorkspace); + var svgRootNew = block.getSvgRoot(); + if (!svgRootNew) { + throw 'block is not rendered.'; + } + + // The offset in pixels between the main workspace's origin and the upper left + // corner of the injection div. + var mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels(); + + // The offset in pixels between the flyout workspace's origin and the upper + // left corner of the injection div. + var flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels(); + + // The position of the old block in flyout workspace coordinates. + var oldBlockPosWs = oldBlock.getRelativeToSurfaceXY(); + + // The position of the old block in pixels relative to the flyout + // workspace's origin. + var oldBlockPosPixels = oldBlockPosWs.scale(this.workspace_.scale); + + // The position of the old block in pixels relative to the upper left corner + // of the injection div. + var oldBlockOffsetPixels = goog.math.Coordinate.sum(flyoutOffsetPixels, + oldBlockPosPixels); + + // The position of the old block in pixels relative to the origin of the + // main workspace. + var finalOffsetPixels = goog.math.Coordinate.difference(oldBlockOffsetPixels, + mainOffsetPixels); + + // The position of the old block in main workspace coordinates. + var finalOffsetMainWs = finalOffsetPixels.scale(1 / targetWorkspace.scale); + + block.moveBy(finalOffsetMainWs.x, finalOffsetMainWs.y); + return block; +}; diff --git a/core/.svn/pristine/36/3624e11061620990177d3377b35540fe3740a94b.svn-base b/core/.svn/pristine/36/3624e11061620990177d3377b35540fe3740a94b.svn-base new file mode 100644 index 0000000..cfd0473 --- /dev/null +++ b/core/.svn/pristine/36/3624e11061620990177d3377b35540fe3740a94b.svn-base @@ -0,0 +1,423 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Components for creating connections between blocks. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.RenderedConnection'); + +goog.require('Blockly.Connection'); + + +/** + * Class for a connection between blocks that may be rendered on screen. + * @param {!Blockly.Block} source The block establishing this connection. + * @param {number} type The type of the connection. + * @extends {Blockly.Connection} + * @constructor + */ +Blockly.RenderedConnection = function(source, type) { + Blockly.RenderedConnection.superClass_.constructor.call(this, source, type); + + /** + * Workspace units, (0, 0) is top left of block. + * @type {!goog.math.Coordinate} + * @private + */ + this.offsetInBlock_ = new goog.math.Coordinate(0, 0); +}; +goog.inherits(Blockly.RenderedConnection, Blockly.Connection); + +/** + * Returns the distance between this connection and another connection in + * workspace units. + * @param {!Blockly.Connection} otherConnection The other connection to measure + * the distance to. + * @return {number} The distance between connections, in workspace units. + */ +Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) { + var xDiff = this.x_ - otherConnection.x_; + var yDiff = this.y_ - otherConnection.y_; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff); +}; + +/** + * Move the block(s) belonging to the connection to a point where they don't + * visually interfere with the specified connection. + * @param {!Blockly.Connection} staticConnection The connection to move away + * from. + * @private + */ +Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) { + if (this.sourceBlock_.workspace.isDragging()) { + // Don't move blocks around while the user is doing the same. + return; + } + // Move the root block. + var rootBlock = this.sourceBlock_.getRootBlock(); + if (rootBlock.isInFlyout) { + // Don't move blocks around in a flyout. + return; + } + var reverse = false; + if (!rootBlock.isMovable()) { + // Can't bump an uneditable block away. + // Check to see if the other block is movable. + rootBlock = staticConnection.getSourceBlock().getRootBlock(); + if (!rootBlock.isMovable()) { + return; + } + // Swap the connections and move the 'static' connection instead. + staticConnection = this; + reverse = true; + } + // Raise it to the top for extra visibility. + var selected = Blockly.selected == rootBlock; + selected || rootBlock.addSelect(); + var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_; + var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_; + if (reverse) { + // When reversing a bump due to an uneditable block, bump up. + dy = -dy; + } + if (rootBlock.RTL) { + dx = -dx; + } + rootBlock.moveBy(dx, dy); + selected || rootBlock.removeSelect(); +}; + +/** + * Change the connection's coordinates. + * @param {number} x New absolute x coordinate, in workspace coordinates. + * @param {number} y New absolute y coordinate, in workspace coordinates. + */ +Blockly.RenderedConnection.prototype.moveTo = function(x, y) { + // Remove it from its old location in the database (if already present) + if (this.inDB_) { + this.db_.removeConnection_(this); + } + this.x_ = x; + this.y_ = y; + // Insert it into its new location in the database. + if (!this.hidden_) { + this.db_.addConnection(this); + } +}; + +/** + * Change the connection's coordinates. + * @param {number} dx Change to x coordinate, in workspace units. + * @param {number} dy Change to y coordinate, in workspace units. + */ +Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) { + this.moveTo(this.x_ + dx, this.y_ + dy); +}; + +/** + * Move this connection to the location given by its offset within the block and + * the location of the block's top left corner. + * @param {!goog.math.Coordinate} blockTL The location of the top left corner + * of the block, in workspace coordinates. + */ +Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) { + this.moveTo(blockTL.x + this.offsetInBlock_.x, + blockTL.y + this.offsetInBlock_.y); +}; + +/** + * Set the offset of this connection relative to the top left of its block. + * @param {number} x The new relative x, in workspace units. + * @param {number} y The new relative y, in workspace units. + */ +Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) { + this.offsetInBlock_.x = x; + this.offsetInBlock_.y = y; +}; + +/** + * Move the blocks on either side of this connection right next to each other. + * @private + */ +Blockly.RenderedConnection.prototype.tighten_ = function() { + var dx = this.targetConnection.x_ - this.x_; + var dy = this.targetConnection.y_ - this.y_; + if (dx != 0 || dy != 0) { + var block = this.targetBlock(); + var svgRoot = block.getSvgRoot(); + if (!svgRoot) { + throw 'block is not rendered.'; + } + // Workspace coordinates. + var xy = Blockly.utils.getRelativeXY(svgRoot); + block.getSvgRoot().setAttribute('transform', + 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')'); + block.moveConnections_(-dx, -dy); + } +}; + +/** + * Find the closest compatible connection to this connection. + * All parameters are in workspace units. + * @param {number} maxLimit The maximum radius to another connection. + * @param {number} dx Horizontal offset between this connection's location + * in the database and the current location (as a result of dragging). + * @param {number} dy Vertical offset between this connection's location + * in the database and the current location (as a result of dragging). + * @return {!{connection: ?Blockly.Connection, radius: number}} Contains two + * properties: 'connection' which is either another connection or null, + * and 'radius' which is the distance. + */ +Blockly.RenderedConnection.prototype.closest = function(maxLimit, dx, dy) { + return this.dbOpposite_.searchForClosest(this, maxLimit, dx, dy); +}; + +/** + * Add highlighting around this connection. + */ +Blockly.RenderedConnection.prototype.highlight = function() { + var steps; + if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { + steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5'; + } else { + steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5'; + } + var xy = this.sourceBlock_.getRelativeToSurfaceXY(); + var x = this.x_ - xy.x; + var y = this.y_ - xy.y; + Blockly.Connection.highlightedPath_ = Blockly.utils.createSvgElement( + 'path', + { + 'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + transform: 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : '') + }, + this.sourceBlock_.getSvgRoot()); +}; + +/** + * Unhide this connection, as well as all down-stream connections on any block + * attached to this connection. This happens when a block is expanded. + * Also unhides down-stream comments. + * @return {!Array.} List of blocks to render. + */ +Blockly.RenderedConnection.prototype.unhideAll = function() { + this.setHidden(false); + // All blocks that need unhiding must be unhidden before any rendering takes + // place, since rendering requires knowing the dimensions of lower blocks. + // Also, since rendering a block renders all its parents, we only need to + // render the leaf nodes. + var renderList = []; + if (this.type != Blockly.INPUT_VALUE && this.type != Blockly.NEXT_STATEMENT) { + // Only spider down. + return renderList; + } + var block = this.targetBlock(); + if (block) { + var connections; + if (block.isCollapsed()) { + // This block should only be partially revealed since it is collapsed. + connections = []; + block.outputConnection && connections.push(block.outputConnection); + block.nextConnection && connections.push(block.nextConnection); + block.previousConnection && connections.push(block.previousConnection); + } else { + // Show all connections of this block. + connections = block.getConnections_(true); + } + for (var i = 0; i < connections.length; i++) { + renderList.push.apply(renderList, connections[i].unhideAll()); + } + if (!renderList.length) { + // Leaf block. + renderList[0] = block; + } + } + return renderList; +}; + +/** + * Remove the highlighting around this connection. + */ +Blockly.RenderedConnection.prototype.unhighlight = function() { + goog.dom.removeNode(Blockly.Connection.highlightedPath_); + delete Blockly.Connection.highlightedPath_; +}; + +/** + * Set whether this connections is hidden (not tracked in a database) or not. + * @param {boolean} hidden True if connection is hidden. + */ +Blockly.RenderedConnection.prototype.setHidden = function(hidden) { + this.hidden_ = hidden; + if (hidden && this.inDB_) { + this.db_.removeConnection_(this); + } else if (!hidden && !this.inDB_) { + this.db_.addConnection(this); + } +}; + +/** + * Hide this connection, as well as all down-stream connections on any block + * attached to this connection. This happens when a block is collapsed. + * Also hides down-stream comments. + */ +Blockly.RenderedConnection.prototype.hideAll = function() { + this.setHidden(true); + if (this.targetConnection) { + var blocks = this.targetBlock().getDescendants(); + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + // Hide all connections of all children. + var connections = block.getConnections_(true); + for (var j = 0; j < connections.length; j++) { + connections[j].setHidden(true); + } + // Close all bubbles of all children. + var icons = block.getIcons(); + for (var j = 0; j < icons.length; j++) { + icons[j].setVisible(false); + } + } + } +}; + +/** + * Check if the two connections can be dragged to connect to each other. + * @param {!Blockly.Connection} candidate A nearby connection to check. + * @param {number} maxRadius The maximum radius allowed for connections, in + * workspace units. + * @return {boolean} True if the connection is allowed, false otherwise. + */ +Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate, + maxRadius) { + if (this.distanceFrom(candidate) > maxRadius) { + return false; + } + + return Blockly.RenderedConnection.superClass_.isConnectionAllowed.call(this, + candidate); +}; + +/** + * Disconnect two blocks that are connected by this connection. + * @param {!Blockly.Block} parentBlock The superior block. + * @param {!Blockly.Block} childBlock The inferior block. + * @private + */ +Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock, + childBlock) { + Blockly.RenderedConnection.superClass_.disconnectInternal_.call(this, + parentBlock, childBlock); + // Rerender the parent so that it may reflow. + if (parentBlock.rendered) { + parentBlock.render(); + } + if (childBlock.rendered) { + childBlock.updateDisabled(); + childBlock.render(); + } +}; + +/** + * Respawn the shadow block if there was one connected to the this connection. + * Render/rerender blocks as needed. + * @private + */ +Blockly.RenderedConnection.prototype.respawnShadow_ = function() { + var parentBlock = this.getSourceBlock(); + // Respawn the shadow block if there is one. + var shadow = this.getShadowDom(); + if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { + Blockly.RenderedConnection.superClass_.respawnShadow_.call(this); + var blockShadow = this.targetBlock(); + if (!blockShadow) { + throw 'Couldn\'t respawn the shadow block that should exist here.'; + } + blockShadow.initSvg(); + blockShadow.render(false); + if (parentBlock.rendered) { + parentBlock.render(); + } + } +}; + +/** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * @param {number} maxLimit The maximum radius to another connection, in + * workspace units. + * @return {!Array.} List of connections. + * @private + */ +Blockly.RenderedConnection.prototype.neighbours_ = function(maxLimit) { + return this.dbOpposite_.getNeighbours(this, maxLimit); +}; + +/** + * Connect two connections together. This is the connection on the superior + * block. Rerender blocks as needed. + * @param {!Blockly.Connection} childConnection Connection on inferior block. + * @private + */ +Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { + Blockly.RenderedConnection.superClass_.connect_.call(this, childConnection); + + var parentConnection = this; + var parentBlock = parentConnection.getSourceBlock(); + var childBlock = childConnection.getSourceBlock(); + + if (parentBlock.rendered) { + parentBlock.updateDisabled(); + } + if (childBlock.rendered) { + childBlock.updateDisabled(); + } + if (parentBlock.rendered && childBlock.rendered) { + if (parentConnection.type == Blockly.NEXT_STATEMENT || + parentConnection.type == Blockly.PREVIOUS_STATEMENT) { + // Child block may need to square off its corners if it is in a stack. + // Rendering a child will render its parent. + childBlock.render(); + } else { + // Child block does not change shape. Rendering the parent node will + // move its connected children into position. + parentBlock.render(); + } + } +}; + +/** + * Function to be called when this connection's compatible types have changed. + * @private + */ +Blockly.RenderedConnection.prototype.onCheckChanged_ = function() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && !this.checkType_(this.targetConnection)) { + var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + // Bump away. + this.sourceBlock_.bumpNeighbours_(); + } +}; diff --git a/core/.svn/pristine/37/37cfdb035b0b8afbfe94b8cd1db30c8f9aa77a3f.svn-base b/core/.svn/pristine/37/37cfdb035b0b8afbfe94b8cd1db30c8f9aa77a3f.svn-base new file mode 100644 index 0000000..c51d31e --- /dev/null +++ b/core/.svn/pristine/37/37cfdb035b0b8afbfe94b8cd1db30c8f9aa77a3f.svn-base @@ -0,0 +1,246 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Class that controls updates to connections during drags. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.DraggedConnectionManager'); + +goog.require('Blockly.RenderedConnection'); + +goog.require('goog.math.Coordinate'); + + +/** + * Class that controls updates to connections during drags. It is primarily + * responsible for finding the closest eligible connection and highlighting or + * unhiglighting it as needed during a drag. + * @param {!Blockly.BlockSvg} block The top block in the stack being dragged. + * @constructor + */ +Blockly.DraggedConnectionManager = function(block) { + Blockly.selected = block; + + /** + * The top block in the stack being dragged. + * Does not change during a drag. + * @type {!Blockly.Block} + * @private + */ + this.topBlock_ = block; + + /** + * The workspace on which these connections are being dragged. + * Does not change during a drag. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = block.workspace; + + /** + * The connections on the dragging blocks that are available to connect to + * other blocks. This includes all open connections on the top block, as well + * as the last connection on the block stack. + * Does not change during a drag. + * @type {!Array.} + * @private + */ + this.availableConnections_ = this.initAvailableConnections_(); + + /** + * The connection that this block would connect to if released immediately. + * Updated on every mouse move. + * @type {Blockly.RenderedConnection} + * @private + */ + this.closestConnection_ = null; + + /** + * The connection that would connect to this.closestConnection_ if this block + * were released immediately. + * Updated on every mouse move. + * @type {Blockly.RenderedConnection} + * @private + */ + this.localConnection_ = null; + + /** + * The distance between this.closestConnection_ and this.localConnection_, + * in workspace units. + * Updated on every mouse move. + * @type {number} + * @private + */ + this.radiusConnection_ = 0; + + /** + * Whether the block would be deleted if it were dropped immediately. + * Updated on every mouse move. + * @type {boolean} + * @private + */ + this.wouldDeleteBlock_ = false; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.DraggedConnectionManager.prototype.dispose = function() { + this.topBlock_ = null; + this.workspace_ = null; + this.availableConnections_.length = 0; + this.closestConnection_ = null; + this.localConnection_ = null; +}; + +/** + * Return whether the block would be deleted if dropped immediately, based on + * information from the most recent move event. + * @return {boolean} true if the block would be deleted if dropped immediately. + * @package + */ +Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() { + return this.wouldDeleteBlock_; +}; + +/** + * Connect to the closest connection and render the results. + * This should be called at the end of a drag. + * @package + */ +Blockly.DraggedConnectionManager.prototype.applyConnections = function() { + if (this.closestConnection_) { + // Connect two blocks together. + this.localConnection_.connect(this.closestConnection_); + if (this.topBlock_.rendered) { + // Trigger a connection animation. + // Determine which connection is inferior (lower in the source stack). + var inferiorConnection = this.localConnection_.isSuperior() ? + this.closestConnection_ : this.localConnection_; + inferiorConnection.getSourceBlock().connectionUiEffect(); + // Bring the just-edited stack to the front. + var rootBlock = this.topBlock_.getRootBlock(); + rootBlock.bringToFront(); + } + this.removeHighlighting_(); + } +}; + +/** + * Update highlighted connections based on the most recent move location. + * @param {!goog.math.Coordinate} dxy Position relative to drag start, + * in workspace units. + * @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH}, + * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. + * @package + */ +Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) { + var oldClosestConnection = this.closestConnection_; + var closestConnectionChanged = this.updateClosest_(dxy); + + if (closestConnectionChanged && oldClosestConnection) { + oldClosestConnection.unhighlight(); + } + + // Prefer connecting over dropping into the trash can, but prefer dragging to + // the toolbox over connecting to other blocks. + var wouldConnect = !!this.closestConnection_ && + deleteArea != Blockly.DELETE_AREA_TOOLBOX; + var wouldDelete = !!deleteArea && !this.topBlock_.getParent() && + this.topBlock_.isDeletable(); + this.wouldDeleteBlock_ = wouldDelete && !wouldConnect; + + // Get rid of highlighting so we don't sent mixed messages. + if (wouldDelete && this.closestConnection_) { + this.closestConnection_.unhighlight(); + this.closestConnection_ = null; + } + + if (!this.wouldDeleteBlock_ && closestConnectionChanged && + this.closestConnection_) { + this.addHighlighting_(); + } +}; + +/** + * Remove highlighting from the currently highlighted connection, if it exists. + * @private + */ +Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() { + if (this.closestConnection_) { + this.closestConnection_.unhighlight(); + } +}; + +/** + * Add highlighting to the closest connection, if it exists. + * @private + */ +Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() { + if (this.closestConnection_) { + this.closestConnection_.highlight(); + } +}; + +/** + * Populate the list of available connections on this block stack. This should + * only be called once, at the beginning of a drag. + * @return {!Array.} a list of available + * connections. + * @private + */ +Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() { + var available = this.topBlock_.getConnections_(false); + // Also check the last connection on this stack + var lastOnStack = this.topBlock_.lastConnectionInStack_(); + if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) { + available.push(lastOnStack); + } + return available; +}; + +/** + * Find the new closest connection, and update internal state in response. + * @param {!goog.math.Coordinate} dxy Position relative to the drag start, + * in workspace units. + * @return {boolean} Whether the closest connection has changed. + * @private + */ +Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) { + var oldClosestConnection = this.closestConnection_; + + this.closestConnection_ = null; + this.localConnection_ = null; + this.radiusConnection_ = Blockly.SNAP_RADIUS; + for (var i = 0; i < this.availableConnections_.length; i++) { + var myConnection = this.availableConnections_[i]; + var neighbour = myConnection.closest(this.radiusConnection_, dxy); + if (neighbour.connection) { + this.closestConnection_ = neighbour.connection; + this.localConnection_ = myConnection; + this.radiusConnection_ = neighbour.radius; + } + } + return oldClosestConnection != this.closestConnection_; +}; diff --git a/core/.svn/pristine/3b/3b70f38a836ffd70bd092eef53e369b12df62f12.svn-base b/core/.svn/pristine/3b/3b70f38a836ffd70bd092eef53e369b12df62f12.svn-base new file mode 100644 index 0000000..e99a95a --- /dev/null +++ b/core/.svn/pristine/3b/3b70f38a836ffd70bd092eef53e369b12df62f12.svn-base @@ -0,0 +1,822 @@ +/** + * @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 XML reader and writer. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.Xml + * @namespace + **/ +goog.provide('Blockly.Xml'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); + + +/** + * Encode a block tree as XML. + * @param {!Blockly.Workspace} workspace The workspace containing blocks. + * @param {boolean=} opt_noId True if the encoder should skip the block IDs. + * @return {!Element} XML document. + */ +Blockly.Xml.workspaceToDom = function(workspace, opt_noId) { + var xml = goog.dom.createDom('xml'); + xml.appendChild(Blockly.Xml.variablesToDom( + Blockly.Variables.allUsedVarModels(workspace))); + var blocks = workspace.getTopBlocks(true); + for (var i = 0, block; block = blocks[i]; i++) { + xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId)); + } + return xml; +}; + +/** + * Encode a list of variables as XML. + * @param {!Array.} variableList List of all variable + * models. + * @return {!Element} List of XML elements. + */ +Blockly.Xml.variablesToDom = function(variableList) { + var variables = goog.dom.createDom('variables'); + for (var i = 0, variable; variable = variableList[i]; i++) { + var element = goog.dom.createDom('variable', null, variable.name); + element.setAttribute('type', variable.type); + element.setAttribute('id', variable.getId()); + variables.appendChild(element); + } + return variables; +}; + +/** + * Encode a block subtree as XML with XY coordinates. + * @param {!Blockly.Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. + * @return {!Element} Tree of XML elements. + */ +Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { + var width; // Not used in LTR. + if (block.workspace.RTL) { + width = block.workspace.getWidth(); + } + var element = Blockly.Xml.blockToDom(block, opt_noId); + var xy = block.getRelativeToSurfaceXY(); + element.setAttribute('x', + Math.round(block.workspace.RTL ? width - xy.x : xy.x)); + element.setAttribute('y', Math.round(xy.y)); + return element; +}; + +/** + * Encode a variable field as XML. + * @param {!Blockly.FieldVariable} field The field to encode. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDomVariable_ = function(field) { + var id = field.getValue(); + // The field had not been initialized fully before being serialized. + // This can happen if a block is created directly through a call to + // workspace.newBlock instead of from XML. + // The new block will be serialized for the first time when firing a block + // creation event. + if (id == null) { + field.initModel(); + id = field.getValue(); + } + // Get the variable directly from the field, instead of doing a lookup. This + // will work even if the variable has already been deleted. This can happen + // because the flyout defers deleting blocks until the next time the flyout is + // opened. + var variable = field.getVariable(); + + if (!variable) { + throw Error('Tried to serialize a variable field with no variable.'); + } + var container = goog.dom.createDom('field', null, variable.name); + container.setAttribute('name', field.name); + container.setAttribute('id', variable.getId()); + container.setAttribute('variabletype', variable.type); + return container; +}; + +/** + * Encode a field as XML. + * @param {!Blockly.Field} field The field to encode. + * @param {!Blockly.Workspace} workspace The workspace that the field is in. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDom_ = function(field) { + if (field.name && field.EDITABLE) { + if (field instanceof Blockly.FieldVariable) { + return Blockly.Xml.fieldToDomVariable_(field); + } else { + var container = goog.dom.createDom('field', null, field.getValue()); + container.setAttribute('name', field.name); + return container; + } + } + return null; +}; + +/** + * Encode all of a block's fields as XML and attach them to the given tree of + * XML elements. + * @param {!Blockly.Block} block A block with fields to be encoded. + * @param {!Element} element The XML element to which the field DOM should be + * attached. + * @private + */ +Blockly.Xml.allFieldsToDom_ = function(block, element) { + for (var i = 0, input; input = block.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + var fieldDom = Blockly.Xml.fieldToDom_(field); + if (fieldDom) { + element.appendChild(fieldDom); + } + } + } +}; + +/** + * Encode a block subtree as XML. + * @param {!Blockly.Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. + * @return {!Element} Tree of XML elements. + */ +Blockly.Xml.blockToDom = function(block, opt_noId) { + var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block'); + element.setAttribute('type', block.type); + if (!opt_noId) { + element.setAttribute('id', block.id); + } + if (block.mutationToDom) { + // Custom data for an advanced block. + var mutation = block.mutationToDom(); + if (mutation && (mutation.hasChildNodes() || mutation.hasAttributes())) { + element.appendChild(mutation); + } + } + + Blockly.Xml.allFieldsToDom_(block, element); + + var commentText = block.getCommentText(); + if (commentText) { + var commentElement = goog.dom.createDom('comment', null, commentText); + if (typeof block.comment == 'object') { + commentElement.setAttribute('pinned', block.comment.isVisible()); + var hw = block.comment.getBubbleSize(); + commentElement.setAttribute('h', hw.height); + commentElement.setAttribute('w', hw.width); + } + element.appendChild(commentElement); + } + + if (block.data) { + var dataElement = goog.dom.createDom('data', null, block.data); + element.appendChild(dataElement); + } + + for (var i = 0, input; input = block.inputList[i]; i++) { + var container; + var empty = true; + if (input.type == Blockly.DUMMY_INPUT) { + continue; + } else { + var childBlock = input.connection.targetBlock(); + if (input.type == Blockly.INPUT_VALUE) { + container = goog.dom.createDom('value'); + } else if (input.type == Blockly.NEXT_STATEMENT) { + container = goog.dom.createDom('statement'); + } + var shadow = input.connection.getShadowDom(); + if (shadow && (!childBlock || !childBlock.isShadow())) { + container.appendChild(Blockly.Xml.cloneShadow_(shadow)); + } + if (childBlock) { + container.appendChild(Blockly.Xml.blockToDom(childBlock, opt_noId)); + empty = false; + } + } + container.setAttribute('name', input.name); + if (!empty) { + element.appendChild(container); + } + } + if (block.inputsInlineDefault != block.inputsInline) { + element.setAttribute('inline', block.inputsInline); + } + if (block.isCollapsed()) { + element.setAttribute('collapsed', true); + } + if (block.disabled) { + element.setAttribute('disabled', true); + } + if (!block.isDeletable() && !block.isShadow()) { + element.setAttribute('deletable', false); + } + if (!block.isMovable() && !block.isShadow()) { + element.setAttribute('movable', false); + } + if (!block.isEditable()) { + element.setAttribute('editable', false); + } + + var nextBlock = block.getNextBlock(); + if (nextBlock) { + var container = goog.dom.createDom('next', null, + Blockly.Xml.blockToDom(nextBlock, opt_noId)); + element.appendChild(container); + } + var shadow = block.nextConnection && block.nextConnection.getShadowDom(); + if (shadow && (!nextBlock || !nextBlock.isShadow())) { + container.appendChild(Blockly.Xml.cloneShadow_(shadow)); + } + + return element; +}; + +/** + * Deeply clone the shadow's DOM so that changes don't back-wash to the block. + * @param {!Element} shadow A tree of XML elements. + * @return {!Element} A tree of XML elements. + * @private + */ +Blockly.Xml.cloneShadow_ = function(shadow) { + shadow = shadow.cloneNode(true); + // Walk the tree looking for whitespace. Don't prune whitespace in a tag. + var node = shadow; + var textNode; + while (node) { + if (node.firstChild) { + node = node.firstChild; + } else { + while (node && !node.nextSibling) { + textNode = node; + node = node.parentNode; + if (textNode.nodeType == 3 && textNode.data.trim() == '' && + node.firstChild != textNode) { + // Prune whitespace after a tag. + goog.dom.removeNode(textNode); + } + } + if (node) { + textNode = node; + node = node.nextSibling; + if (textNode.nodeType == 3 && textNode.data.trim() == '') { + // Prune whitespace before a tag. + goog.dom.removeNode(textNode); + } + } + } + } + return shadow; +}; + +/** + * Converts a DOM structure into plain text. + * Currently the text format is fairly ugly: all one line with no whitespace. + * @param {!Element} dom A tree of XML elements. + * @return {string} Text representation. + */ +Blockly.Xml.domToText = function(dom) { + var oSerializer = new XMLSerializer(); + return oSerializer.serializeToString(dom); +}; + +/** + * Converts a DOM structure into properly indented text. + * @param {!Element} dom A tree of XML elements. + * @return {string} Text representation. + */ +Blockly.Xml.domToPrettyText = function(dom) { + // This function is not guaranteed to be correct for all XML. + // But it handles the XML that Blockly generates. + var blob = Blockly.Xml.domToText(dom); + // Place every open and close tag on its own line. + var lines = blob.split('<'); + // Indent every line. + var indent = ''; + for (var i = 1; i < lines.length; i++) { + var line = lines[i]; + if (line[0] == '/') { + indent = indent.substring(2); + } + lines[i] = indent + '<' + line; + if (line[0] != '/' && line.slice(-2) != '/>') { + indent += ' '; + } + } + // Pull simple tags back together. + // E.g. + var text = lines.join('\n'); + text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1'); + // Trim leading blank line. + return text.replace(/^\n/, ''); +}; + +/** + * Converts plain text into a DOM structure. + * Throws an error if XML doesn't parse. + * @param {string} text Text representation. + * @return {!Element} A tree of XML elements. + */ +Blockly.Xml.textToDom = function(text) { + var oParser = new DOMParser(); + var dom = oParser.parseFromString(text, 'text/xml'); + // The DOM should have one and only one top-level node, an XML tag. + if (!dom || !dom.firstChild || + dom.firstChild.nodeName.toLowerCase() != 'xml' || + dom.firstChild !== dom.lastChild) { + // Whatever we got back from the parser is not XML. + goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.'); + } + return dom.firstChild; +}; + +/** + * Decode an XML DOM and create blocks on the workspace. + * @param {!Element} xml XML DOM. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {Array.} An array containing new block IDs. + */ +Blockly.Xml.domToWorkspace = function(xml, workspace) { + if (xml instanceof Blockly.Workspace) { + var swap = xml; + xml = workspace; + workspace = swap; + console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' + + 'swap the arguments.'); + } + var width; // Not used in LTR. + if (workspace.RTL) { + width = workspace.getWidth(); + } + var newBlockIds = []; // A list of block IDs added by this call. + Blockly.Field.startCache(); + // Safari 7.1.3 is known to provide node lists with extra references to + // children beyond the lists' length. Trust the length, do not use the + // looping pattern of checking the index for an object. + var childCount = xml.childNodes.length; + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + + // Disable workspace resizes as an optimization. + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(false); + } + var variablesFirst = true; + try { + for (var i = 0; i < childCount; i++) { + var xmlChild = xml.childNodes[i]; + var name = xmlChild.nodeName.toLowerCase(); + if (name == 'block' || + (name == 'shadow' && !Blockly.Events.recordUndo)) { + // Allow top-level shadow blocks if recordUndo is disabled since + // that means an undo is in progress. Such a block is expected + // to be moved to a nested destination in the next operation. + var block = Blockly.Xml.domToBlock(xmlChild, workspace); + newBlockIds.push(block.id); + var blockX = parseInt(xmlChild.getAttribute('x'), 10); + var blockY = parseInt(xmlChild.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); + } + variablesFirst = false; + } else if (name == 'shadow') { + goog.asserts.fail('Shadow block cannot be a top-level block.'); + variablesFirst = false; + } else if (name == 'variables') { + if (variablesFirst) { + Blockly.Xml.domToVariables(xmlChild, workspace); + } else { + throw Error('\'variables\' tag must exist once before block and ' + + 'shadow tag elements in the workspace XML, but it was found in ' + + 'another location.'); + } + variablesFirst = false; + } + } + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + Blockly.Field.stopCache(); + } + // Re-enable workspace resizing. + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(true); + } + return newBlockIds; +}; + +/** + * Decode an XML DOM and create blocks on the workspace. Position the new + * blocks immediately below prior blocks, aligned by their starting edge. + * @param {!Element} xml The XML DOM. + * @param {!Blockly.Workspace} workspace The workspace to add to. + * @return {Array.} An array containing new block IDs. + */ +Blockly.Xml.appendDomToWorkspace = function(xml, workspace) { + var bbox; // Bounding box of the current blocks. + // First check if we have a workspaceSvg, otherwise the blocks have no shape + // and the position does not matter. + if (workspace.hasOwnProperty('scale')) { + var savetab = Blockly.BlockSvg.TAB_WIDTH; + try { + Blockly.BlockSvg.TAB_WIDTH = 0; + bbox = workspace.getBlocksBoundingBox(); + } finally { + Blockly.BlockSvg.TAB_WIDTH = savetab; + } + } + // Load the new blocks into the workspace and get the IDs of the new blocks. + var newBlockIds = Blockly.Xml.domToWorkspace(xml,workspace); + if (bbox && bbox.height) { // check if any previous block + var offsetY = 0; // offset to add to y of the new block + var offsetX = 0; + var farY = bbox.y + bbox.height; // bottom position + var topX = bbox.x; // x of bounding box + // Check position of the new blocks. + var newX = Infinity; // x of top corner + var newY = Infinity; // y of top corner + for (var i = 0; i < newBlockIds.length; i++) { + var blockXY = workspace.getBlockById(newBlockIds[i]).getRelativeToSurfaceXY(); + if (blockXY.y < newY) { + newY = blockXY.y; + } + if (blockXY.x < newX) { // if we align also on x + newX = blockXY.x; + } + } + offsetY = farY - newY + Blockly.BlockSvg.SEP_SPACE_Y; + offsetX = topX - newX; + // move the new blocks to append them at the bottom + var width; // Not used in LTR. + if (workspace.RTL) { + width = workspace.getWidth(); + } + for (var i = 0; i < newBlockIds.length; i++) { + var block = workspace.getBlockById(newBlockIds[i]); + block.moveBy(workspace.RTL ? width - offsetX : offsetX, offsetY); + } + } + return newBlockIds; +}; + +/** + * Decode an XML block tag and create a block (and possibly sub blocks) on the + * workspace. + * @param {!Element} xmlBlock XML block element. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {!Blockly.Block} The root block created. + */ +Blockly.Xml.domToBlock = function(xmlBlock, workspace) { + if (xmlBlock instanceof Blockly.Workspace) { + var swap = xmlBlock; + xmlBlock = workspace; + workspace = swap; + console.warn('Deprecated call to Blockly.Xml.domToBlock, ' + + 'swap the arguments.'); + } + // Create top-level block. + Blockly.Events.disable(); + var variablesBeforeCreation = workspace.getAllVariables(); + try { + var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); + // Generate list of all blocks. + var blocks = topBlock.getDescendants(); + if (workspace.rendered) { + // Hide connections to speed up assembly. + topBlock.setConnectionsHidden(true); + // Render each block. + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initSvg(); + } + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].render(false); + } + // Populating the connection database may be deferred until after the + // blocks have rendered. + setTimeout(function() { + if (topBlock.workspace) { // Check that the block hasn't been deleted. + topBlock.setConnectionsHidden(false); + } + }, 1); + topBlock.updateDisabled(); + // Allow the scrollbars to resize and move based on the new contents. + // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. + workspace.resizeContents(); + } else { + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initModel(); + } + } + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled()) { + var newVariables = Blockly.Variables.getAddedVariables(workspace, + variablesBeforeCreation); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } + // Block events come after var events, in case they refer to newly created + // variables. + Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock)); + } + return topBlock; +}; + +/** + * Decode an XML list of variables and add the variables to the workspace. + * @param {!Element} xmlVariables List of XML variable elements. + * @param {!Blockly.Workspace} workspace The workspace to which the variable + * should be added. + */ +Blockly.Xml.domToVariables = function(xmlVariables, workspace) { + for (var i = 0, xmlChild; xmlChild = xmlVariables.children[i]; i++) { + var type = xmlChild.getAttribute('type'); + var id = xmlChild.getAttribute('id'); + var name = xmlChild.textContent; + + if (type == null) { + throw Error('Variable with id, ' + id + ' is without a type'); + } + workspace.createVariable(name, type, id); + } +}; + +/** + * Decode an XML block tag and create a block (and possibly sub blocks) on the + * workspace. + * @param {!Element} xmlBlock XML block element. + * @param {!Blockly.Workspace} workspace The workspace. + * @return {!Blockly.Block} The root block created. + * @private + */ +Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { + var block = null; + var prototypeName = xmlBlock.getAttribute('type'); + goog.asserts.assert( + prototypeName, 'Block type unspecified: %s', xmlBlock.outerHTML); + var id = xmlBlock.getAttribute('id'); + block = workspace.newBlock(prototypeName, id); + + var blockChild = null; + for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) { + if (xmlChild.nodeType == 3) { + // Ignore any text at the level. It's all whitespace anyway. + continue; + } + var input; + + // Find any enclosed blocks or shadows in this tag. + var childBlockElement = null; + var childShadowElement = null; + for (var j = 0, grandchild; grandchild = xmlChild.childNodes[j]; j++) { + if (grandchild.nodeType == 1) { + if (grandchild.nodeName.toLowerCase() == 'block') { + childBlockElement = /** @type {!Element} */ (grandchild); + } else if (grandchild.nodeName.toLowerCase() == 'shadow') { + childShadowElement = /** @type {!Element} */ (grandchild); + } + } + } + // Use the shadow block if there is no child block. + if (!childBlockElement && childShadowElement) { + childBlockElement = childShadowElement; + } + + var name = xmlChild.getAttribute('name'); + switch (xmlChild.nodeName.toLowerCase()) { + case 'mutation': + // Custom data for an advanced block. + if (block.domToMutation) { + block.domToMutation(xmlChild); + if (block.initSvg) { + // Mutation may have added some elements that need initializing. + block.initSvg(); + } + } + break; + case 'comment': + block.setCommentText(xmlChild.textContent); + var visible = xmlChild.getAttribute('pinned'); + if (visible && !block.isInFlyout) { + // Give the renderer a millisecond to render and position the block + // before positioning the comment bubble. + setTimeout(function() { + if (block.comment && block.comment.setVisible) { + block.comment.setVisible(visible == 'true'); + } + }, 1); + } + var bubbleW = parseInt(xmlChild.getAttribute('w'), 10); + var bubbleH = parseInt(xmlChild.getAttribute('h'), 10); + if (!isNaN(bubbleW) && !isNaN(bubbleH) && + block.comment && block.comment.setVisible) { + block.comment.setBubbleSize(bubbleW, bubbleH); + } + break; + case 'data': + block.data = xmlChild.textContent; + break; + case 'title': + // Titles were renamed to field in December 2013. + // Fall through. + case 'field': + Blockly.Xml.domToField_(block, name, xmlChild); + break; + case 'value': + case 'statement': + input = block.getInput(name); + if (!input) { + console.warn('Ignoring non-existent input ' + name + ' in block ' + + prototypeName); + break; + } + if (childShadowElement) { + input.connection.setShadowDom(childShadowElement); + } + if (childBlockElement) { + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockElement, + workspace); + if (blockChild.outputConnection) { + input.connection.connect(blockChild.outputConnection); + } else if (blockChild.previousConnection) { + input.connection.connect(blockChild.previousConnection); + } else { + goog.asserts.fail( + 'Child block does not have output or previous statement.'); + } + } + break; + case 'next': + if (childShadowElement && block.nextConnection) { + block.nextConnection.setShadowDom(childShadowElement); + } + if (childBlockElement) { + goog.asserts.assert(block.nextConnection, + 'Next statement does not exist.'); + // If there is more than one XML 'next' tag. + goog.asserts.assert(!block.nextConnection.isConnected(), + 'Next statement is already connected.'); + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockElement, + workspace); + goog.asserts.assert(blockChild.previousConnection, + 'Next block does not have previous statement.'); + block.nextConnection.connect(blockChild.previousConnection); + } + break; + default: + // Unknown tag; ignore. Same principle as HTML parsers. + console.warn('Ignoring unknown tag: ' + xmlChild.nodeName); + } + } + + var inline = xmlBlock.getAttribute('inline'); + if (inline) { + block.setInputsInline(inline == 'true'); + } + var disabled = xmlBlock.getAttribute('disabled'); + if (disabled) { + block.setDisabled(disabled == 'true'); + } + var deletable = xmlBlock.getAttribute('deletable'); + if (deletable) { + block.setDeletable(deletable == 'true'); + } + var movable = xmlBlock.getAttribute('movable'); + if (movable) { + block.setMovable(movable == 'true'); + } + var editable = xmlBlock.getAttribute('editable'); + if (editable) { + block.setEditable(editable == 'true'); + } + var collapsed = xmlBlock.getAttribute('collapsed'); + if (collapsed) { + block.setCollapsed(collapsed == 'true'); + } + if (xmlBlock.nodeName.toLowerCase() == 'shadow') { + // Ensure all children are also shadows. + var children = block.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + goog.asserts.assert( + child.isShadow(), 'Shadow block not allowed non-shadow child.'); + } + // Ensure this block doesn't have any variable inputs. + goog.asserts.assert(block.getVarModels().length == 0, + 'Shadow blocks cannot have variable references.'); + block.setShadow(true); + } + return block; +}; + +/** + * Decode an XML variable field tag and set the value of that field. + * @param {!Blockly.Workspace} workspace The workspace that is currently being + * deserialized. + * @param {!Element} xml The field tag to decode. + * @param {string} text The text content of the XML tag. + * @param {!Blockly.FieldVariable} field The field on which the value will be + * set. + * @private + */ +Blockly.Xml.domToFieldVariable_ = function(workspace, xml, text, field) { + var type = xml.getAttribute('variabletype') || ''; + // TODO (fenichel): Does this need to be explicit or not? + if (type == '\'\'') { + type = ''; + } + + var variable = Blockly.Variables.getOrCreateVariablePackage(workspace, xml.id, + text, type); + + // This should never happen :) + if (type != null && type !== variable.type) { + throw Error('Serialized variable type with id \'' + + variable.getId() + '\' had type ' + variable.type + ', and ' + + 'does not match variable field that references it: ' + + Blockly.Xml.domToText(xml) + '.'); + } + + field.setValue(variable.getId()); +}; + +/** + * Decode an XML field tag and set the value of that field on the given block. + * @param {!Blockly.Block} block The block that is currently being deserialized. + * @param {string} fieldName The name of the field on the block. + * @param {!Element} xml The field tag to decode. + * @private + */ +Blockly.Xml.domToField_ = function(block, fieldName, xml) { + var field = block.getField(fieldName); + if (!field) { + console.warn('Ignoring non-existent field ' + fieldName + ' in block ' + + block.type); + return; + } + + var workspace = block.workspace; + var text = xml.textContent; + if (field instanceof Blockly.FieldVariable) { + Blockly.Xml.domToFieldVariable_(workspace, xml, text, field); + } else { + field.setValue(text); + } +}; + +/** + * Remove any 'next' block (statements in a stack). + * @param {!Element} xmlBlock XML block element. + */ +Blockly.Xml.deleteNext = function(xmlBlock) { + for (var i = 0, child; child = xmlBlock.childNodes[i]; i++) { + if (child.nodeName.toLowerCase() == 'next') { + xmlBlock.removeChild(child); + break; + } + } +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +if (!goog.global['Blockly']['Xml']) { + goog.global['Blockly']['Xml'] = {}; +} +goog.global['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText; +goog.global['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace; +goog.global['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom; +goog.global['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom; diff --git a/core/.svn/pristine/3c/3ccb821378ede49a0243a2470f44b152c1c7ca95.svn-base b/core/.svn/pristine/3c/3ccb821378ede49a0243a2470f44b152c1c7ca95.svn-base new file mode 100644 index 0000000..ba25701 --- /dev/null +++ b/core/.svn/pristine/3c/3ccb821378ede49a0243a2470f44b152c1c7ca95.svn-base @@ -0,0 +1,299 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Functionality for the right-click context menus. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.ContextMenu + * @namespace + */ +goog.provide('Blockly.ContextMenu'); + +goog.require('Blockly.utils'); +goog.require('Blockly.utils.uiMenu'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.Menu'); +goog.require('goog.ui.MenuItem'); + + +/** + * Which block is the context menu attached to? + * @type {Blockly.Block} + */ +Blockly.ContextMenu.currentBlock = null; + +/** + * Opaque data that can be passed to unbindEvent_. + * @type {Array.} + * @private + */ +Blockly.ContextMenu.eventWrapper_ = null; + +/** + * Construct the menu based on the list of options and show the menu. + * @param {!Event} e Mouse event. + * @param {!Array.} options Array of menu options. + * @param {boolean} rtl True if RTL, false if LTR. + */ +Blockly.ContextMenu.show = function(e, options, rtl) { + Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null); + if (!options.length) { + Blockly.ContextMenu.hide(); + return; + } + var menu = Blockly.ContextMenu.populate_(options, rtl); + + goog.events.listen( + menu, goog.ui.Component.EventType.ACTION, Blockly.ContextMenu.hide); + + Blockly.ContextMenu.position_(menu, e, rtl); + // 1ms delay is required for focusing on context menus because some other + // mouse event is still waiting in the queue and clears focus. + setTimeout(function() {menu.getElement().focus();}, 1); + Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block. +}; + +/** + * Create the context menu object and populate it with the given options. + * @param {!Array.} options Array of menu options. + * @param {boolean} rtl True if RTL, false if LTR. + * @return {!goog.ui.Menu} The menu that will be shown on right click. + * @private + */ +Blockly.ContextMenu.populate_ = function(options, rtl) { + /* Here's what one option object looks like: + {text: 'Make It So', + enabled: true, + callback: Blockly.MakeItSo} + */ + var menu = new goog.ui.Menu(); + menu.setRightToLeft(rtl); + for (var i = 0, option; option = options[i]; i++) { + var menuItem = new goog.ui.MenuItem(option.text); + menuItem.setRightToLeft(rtl); + menu.addChild(menuItem, true); + menuItem.setEnabled(option.enabled); + if (option.enabled) { + goog.events.listen( + menuItem, goog.ui.Component.EventType.ACTION, option.callback); + menuItem.handleContextMenu = function(/* e */) { + // Right-clicking on menu option should count as a click. + goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION); + }; + } + } + return menu; +}; + +/** + * Add the menu to the page and position it correctly. + * @param {!goog.ui.Menu} menu The menu to add and position. + * @param {!Event} e Mouse event for the right click that is making the context + * menu appear. + * @param {boolean} rtl True if RTL, false if LTR. + * @private + */ +Blockly.ContextMenu.position_ = function(menu, e, rtl) { + // Record windowSize and scrollOffset before adding menu. + var viewportBBox = Blockly.utils.getViewportBBox(); + // This one is just a point, but we'll pretend that it's a rect so we can use + // some helper functions. + var anchorBBox = { + top: e.clientY + viewportBBox.top, + bottom: e.clientY + viewportBBox.top, + left: e.clientX + viewportBBox.left, + right: e.clientX + viewportBBox.left + }; + + Blockly.ContextMenu.createWidget_(menu); + var menuSize = Blockly.utils.uiMenu.getSize(menu); + + if (rtl) { + Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); + } + + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, rtl); + // Calling menuDom.focus() has to wait until after the menu has been placed + // correctly. Otherwise it will cause a page scroll to get the misplaced menu + // in view. See issue #1329. + menu.getElement().focus(); +}; + +/** + * Create and render the menu widget inside Blockly's widget div. + * @param {!goog.ui.Menu} menu The menu to add to the widget div. + * @private + */ +Blockly.ContextMenu.createWidget_ = function(menu) { + var div = Blockly.WidgetDiv.DIV; + menu.render(div); + var menuDom = menu.getElement(); + Blockly.utils.addClass(menuDom, 'blocklyContextMenu'); + // Prevent system context menu when right-clicking a Blockly context menu. + Blockly.bindEventWithChecks_( + menuDom, 'contextmenu', null, Blockly.utils.noEvent); + // Enable autofocus after the initial render to avoid issue #1329. + menu.setAllowAutoFocus(true); +}; + +/** + * Hide the context menu. + */ +Blockly.ContextMenu.hide = function() { + Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu); + Blockly.ContextMenu.currentBlock = null; + if (Blockly.ContextMenu.eventWrapper_) { + Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_); + } +}; + +/** + * Create a callback function that creates and configures a block, + * then places the new block next to the original. + * @param {!Blockly.Block} block Original block. + * @param {!Element} xml XML representation of new block. + * @return {!Function} Function that creates a block. + */ +Blockly.ContextMenu.callbackFactory = function(block, xml) { + return function() { + Blockly.Events.disable(); + try { + var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); + // Move the new block next to the old block. + var xy = block.getRelativeToSurfaceXY(); + if (block.RTL) { + xy.x -= Blockly.SNAP_RADIUS; + } else { + xy.x += Blockly.SNAP_RADIUS; + } + xy.y += Blockly.SNAP_RADIUS * 2; + newBlock.moveBy(xy.x, xy.y); + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled() && !newBlock.isShadow()) { + Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); + } + newBlock.select(); + }; +}; + +// Helper functions for creating context menu options. + +/** + * Make a context menu option for deleting the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockDeleteOption = function(block) { + // Option to delete this block but not blocks lower in the stack. + // Count the number of blocks that are nested in this block. + var descendantCount = block.getDescendants(true).length; + var nextBlock = block.getNextBlock(); + if (nextBlock) { + // Blocks in the current stack would survive this block's deletion. + descendantCount -= nextBlock.getDescendants(true).length; + } + var deleteOption = { + text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)), + enabled: true, + callback: function() { + Blockly.Events.setGroup(true); + block.dispose(true, true); + Blockly.Events.setGroup(false); + } + }; + return deleteOption; +}; + +/** + * Make a context menu option for showing help for the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockHelpOption = function(block) { + var url = goog.isFunction(block.helpUrl) ? block.helpUrl() : block.helpUrl; + var helpOption = { + enabled: !!url, + text: Blockly.Msg.HELP, + callback: function() { + block.showHelp_(); + } + }; + return helpOption; +}; + +/** + * Make a context menu option for duplicating the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockDuplicateOption = function(block) { + var enabled = true; + if (block.getDescendants().length > block.workspace.remainingCapacity()) { + enabled = false; + } + var duplicateOption = { + text: Blockly.Msg.DUPLICATE_BLOCK, + enabled: enabled, + callback: function() { + Blockly.duplicate_(block); + } + }; + return duplicateOption; +}; + +/** + * Make a context menu option for adding or removing comments on the current + * block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockCommentOption = function(block) { + var commentOption = { + enabled: !goog.userAgent.IE + }; + // If there's already a comment, add an option to delete it. + if (block.comment) { + commentOption.text = Blockly.Msg.REMOVE_COMMENT; + commentOption.callback = function() { + block.setCommentText(null); + }; + } else { + // If there's no comment, add an option to create a comment. + commentOption.text = Blockly.Msg.ADD_COMMENT; + commentOption.callback = function() { + block.setCommentText(''); + }; + } + return commentOption; +}; diff --git a/core/.svn/pristine/43/43cb348ede500104b53693704bcc08fe4358a525.svn-base b/core/.svn/pristine/43/43cb348ede500104b53693704bcc08fe4358a525.svn-base new file mode 100644 index 0000000..f6e5205 --- /dev/null +++ b/core/.svn/pristine/43/43cb348ede500104b53693704bcc08fe4358a525.svn-base @@ -0,0 +1,62 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 Empty name space for the Message singleton. + * @author scr@google.com (Sheridan Rawlins) + */ +'use strict'; + +/** + * Name space for the Msg singleton. + * Msg gets populated in the message files. + */ +goog.provide('Blockly.Msg'); + + +/** + * Back up original getMsg function. + * @type {!Function} + */ +goog.getMsgOrig = goog.getMsg; + +/** + * Gets a localized message. + * Overrides the default Closure function to check for a Blockly.Msg first. + * Used infrequently, only known case is TODAY button in date picker. + * @param {string} str Translatable string, places holders in the form {$foo}. + * @param {Object.=} opt_values Maps place holder name to value. + * @return {string} message with placeholders filled. + * @suppress {duplicate} + */ +goog.getMsg = function(str, opt_values) { + var key = goog.getMsg.blocklyMsgMap[str]; + if (key) { + str = Blockly.Msg[key]; + } + return goog.getMsgOrig(str, opt_values); +}; + +/** + * Mapping of Closure messages to Blockly.Msg names. + */ +goog.getMsg.blocklyMsgMap = { + 'Today': 'TODAY' +}; diff --git a/core/.svn/pristine/49/496aec23c523d2aae19a68455f350c6373787365.svn-base b/core/.svn/pristine/49/496aec23c523d2aae19a68455f350c6373787365.svn-base new file mode 100644 index 0000000..816e8d6 --- /dev/null +++ b/core/.svn/pristine/49/496aec23c523d2aae19a68455f350c6373787365.svn-base @@ -0,0 +1,243 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Touch handling for Blockly. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +/** + * @name Blockly.Touch + * @namespace + **/ +goog.provide('Blockly.Touch'); + +goog.require('goog.events'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.string'); + + +/** + * Which touch events are we currently paying attention to? + * @type {?string} + * @private + */ +Blockly.Touch.touchIdentifier_ = null; + +/** + * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, + * in conjunction with mouse events. + * @type {Object} + */ +Blockly.Touch.TOUCH_MAP = {}; +if (goog.events.BrowserFeature.TOUCH_ENABLED) { + Blockly.Touch.TOUCH_MAP = { + 'mousedown': ['touchstart'], + 'mousemove': ['touchmove'], + 'mouseup': ['touchend', 'touchcancel'] + }; +} + +/** + * PID of queued long-press task. + * @private + */ +Blockly.longPid_ = 0; + +/** + * Context menus on touch devices are activated using a long-press. + * Unfortunately the contextmenu touch event is currently (2015) only supported + * by Chrome. This function is fired on any touchstart event, queues a task, + * which after about a second opens the context menu. The tasks is killed + * if the touch event terminates early. + * @param {!Event} e Touch start event. + * @param {Blockly.Gesture} gesture The gesture that triggered this longStart. + * @private + */ +Blockly.longStart_ = function(e, gesture) { + Blockly.longStop_(); + // Punt on multitouch events. + if (e.changedTouches && e.changedTouches.length != 1) { + return; + } + Blockly.longPid_ = setTimeout(function() { + // Additional check to distinguish between touch events and pointer events + if (e.changedTouches) { + // TouchEvent + e.button = 2; // Simulate a right button click. + // e was a touch event. It needs to pretend to be a mouse event. + e.clientX = e.changedTouches[0].clientX; + e.clientY = e.changedTouches[0].clientY; + } + + // Let the gesture route the right-click correctly. + if (gesture) { + gesture.handleRightClick(e); + } + + }, Blockly.LONGPRESS); +}; + +/** + * Nope, that's not a long-press. Either touchend or touchcancel was fired, + * or a drag hath begun. Kill the queued long-press task. + * @private + */ +Blockly.longStop_ = function() { + if (Blockly.longPid_) { + clearTimeout(Blockly.longPid_); + Blockly.longPid_ = 0; + } +}; + +/** + * Clear the touch identifier that tracks which touch stream to pay attention + * to. This ends the current drag/gesture and allows other pointers to be + * captured. + */ +Blockly.Touch.clearTouchIdentifier = function() { + Blockly.Touch.touchIdentifier_ = null; +}; + +/** + * Decide whether Blockly should handle or ignore this event. + * Mouse and touch events require special checks because we only want to deal + * with one touch stream at a time. All other events should always be handled. + * @param {!Event} e The event to check. + * @return {boolean} True if this event should be passed through to the + * registered handler; false if it should be blocked. + */ +Blockly.Touch.shouldHandleEvent = function(e) { + return !Blockly.Touch.isMouseOrTouchEvent(e) || + Blockly.Touch.checkTouchIdentifier(e); +}; + +/** + * Get the touch identifier from the given event. If it was a mouse event, the + * identifier is the string 'mouse'. + * @param {!Event} e Mouse event or touch event. + * @return {string} The touch identifier from the first changed touch, if + * defined. Otherwise 'mouse'. + */ +Blockly.Touch.getTouchIdentifierFromEvent = function(e) { + return e.pointerId != undefined ? e.pointerId : + (e.changedTouches && e.changedTouches[0] && + e.changedTouches[0].identifier != undefined && + e.changedTouches[0].identifier != null) ? + e.changedTouches[0].identifier : 'mouse'; +}; + +/** + * Check whether the touch identifier on the event matches the current saved + * identifier. If there is no identifier, that means it's a mouse event and + * we'll use the identifier "mouse". This means we won't deal well with + * multiple mice being used at the same time. That seems okay. + * If the current identifier was unset, save the identifier from the + * event. This starts a drag/gesture, during which touch events with other + * identifiers will be silently ignored. + * @param {!Event} e Mouse event or touch event. + * @return {boolean} Whether the identifier on the event matches the current + * saved identifier. + */ +Blockly.Touch.checkTouchIdentifier = function(e) { + var identifier = Blockly.Touch.getTouchIdentifierFromEvent(e); + + // if (Blockly.touchIdentifier_ )is insufficient because Android touch + // identifiers may be zero. + if (Blockly.Touch.touchIdentifier_ != undefined && + Blockly.Touch.touchIdentifier_ != null) { + // We're already tracking some touch/mouse event. Is this from the same + // source? + return Blockly.Touch.touchIdentifier_ == identifier; + } + if (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown') { + // No identifier set yet, and this is the start of a drag. Set it and + // return. + Blockly.Touch.touchIdentifier_ = identifier; + return true; + } + // There was no identifier yet, but this wasn't a start event so we're going + // to ignore it. This probably means that another drag finished while this + // pointer was down. + return false; +}; + +/** + * Set an event's clientX and clientY from its first changed touch. Use this to + * make a touch event work in a mouse event handler. + * @param {!Event} e A touch event. + */ +Blockly.Touch.setClientFromTouch = function(e) { + if (goog.string.startsWith(e.type, 'touch')) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } +}; + +/** + * Check whether a given event is a mouse or touch event. + * @param {!Event} e An event. + * @return {boolean} true if it is a mouse or touch event; false otherwise. + */ +Blockly.Touch.isMouseOrTouchEvent = function(e) { + return goog.string.startsWith(e.type, 'touch') || + goog.string.startsWith(e.type, 'mouse') || + goog.string.startsWith(e.type, 'pointer'); +}; + +/** + * Check whether a given event is a touch event or a pointer event. + * @param {!Event} e An event. + * @return {boolean} true if it is a touch event; false otherwise. + */ +Blockly.Touch.isTouchEvent = function(e) { + return goog.string.startsWith(e.type, 'touch') || + goog.string.startsWith(e.type, 'pointer'); +}; + +/** + * Split an event into an array of events, one per changed touch or mouse + * point. + * @param {!Event} e A mouse event or a touch event with one or more changed + * touches. + * @return {!Array.} An array of mouse or touch events. Each touch + * event will have exactly one changed touch. + */ +Blockly.Touch.splitEventByTouches = function(e) { + var events = []; + if (e.changedTouches) { + for (var i = 0; i < e.changedTouches.length; i++) { + var newEvent = { + type: e.type, + changedTouches: [e.changedTouches[i]], + target: e.target, + stopPropagation: function(){ e.stopPropagation(); }, + preventDefault: function(){ e.preventDefault(); } + }; + events[i] = newEvent; + } + } else { + events.push(e); + } + return events; +}; diff --git a/core/.svn/pristine/4c/4c47fe8b01f61b79ff9a2fdbb7721993bf017b10.svn-base b/core/.svn/pristine/4c/4c47fe8b01f61b79ff9a2fdbb7721993bf017b10.svn-base new file mode 100644 index 0000000..e0dc03e --- /dev/null +++ b/core/.svn/pristine/4c/4c47fe8b01f61b79ff9a2fdbb7721993bf017b10.svn-base @@ -0,0 +1,188 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 Methods for dragging a bubble visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BubbleDragger'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a bubble dragger. It moves bubbles around the workspace when they + * are being dragged by a mouse or touch. + * @param {!Blockly.Bubble} bubble The bubble to drag. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on. + * @constructor + */ +Blockly.BubbleDragger = function(bubble, workspace) { + /** + * The bubble that is being dragged. + * @type {!Blockly.Bubble} + * @private + */ + this.draggingBubble_ = bubble; + + /** + * The workspace on which the bubble is being dragged. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The location of the top left corner of the dragging bubble's body at the + * beginning of the drag, in workspace coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY(); + + /** + * The drag surface to move bubbles to during a drag, or null if none should + * be used. Block dragging and bubble dragging use the same surface. + * @type {?Blockly.BlockDragSurfaceSvg} + * @private + */ + this.dragSurface_ = + Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface() ? + workspace.getBlockDragSurface() : null; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.BubbleDragger.prototype.dispose = function() { + this.draggingBubble_ = null; + this.workspace_ = null; + this.dragSurface_ = null; +}; + +/** + * Start dragging a bubble. This includes moving it to the drag surface. + * @package + */ +Blockly.BubbleDragger.prototype.startBubbleDrag = function() { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + + this.workspace_.setResizesEnabled(false); + this.draggingBubble_.setAutoLayout(false); + if (this.dragSurface_) { + this.moveToDragSurface_(); + } +}; + +/** + * Execute a step of bubble dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) { + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); + // TODO (fenichel): Possibly update the cursor if dragging to the trash can + // is allowed. +}; + +/** + * Finish a bubble drag and put the bubble back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BubbleDragger.prototype.endBubbleDrag = function( + e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBubble(e, currentDragDeltaXY); + + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + // Move the bubble to its final location. + this.draggingBubble_.moveTo(newLoc.x, newLoc.y); + // Put everything back onto the bubble canvas. + if (this.dragSurface_) { + this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); + } + + this.fireMoveEvent_(); + this.workspace_.setResizesEnabled(true); + + Blockly.Events.setGroup(false); +}; + +/** + * Fire a move event at the end of a bubble drag. + * @private + */ +Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() { + // TODO (fenichel): move events for comments. + return; +}; + +/** + * Convert a coordinate object from pixels to workspace units, including a + * correction for mutator workspaces. + * This function does not consider differing origins. It simply scales the + * input's x and y values. + * @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values + * in css pixel units. + * @return {!goog.math.Coordinate} The input coordinate divided by the workspace + * scale. + * @private + */ +Blockly.BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { + var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale, + pixelCoord.y / this.workspace_.scale); + if (this.workspace_.isMutator) { + // If we're in a mutator, its scale is always 1, purely because of some + // oddities in our rendering optimizations. The actual scale is the same as + // the scale on the parent workspace. + // Fix that for dragging. + var mainScale = this.workspace_.options.parentWorkspace.scale; + result = result.scale(1 / mainScale); + } + return result; +}; +/** + * Move the bubble onto the drag surface at the beginning of a drag. Move the + * drag surface to preserve the apparent location of the bubble. + * @private + */ +Blockly.BubbleDragger.prototype.moveToDragSurface_ = function() { + this.draggingBubble_.moveTo(0, 0); + this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y); + // Execute the move on the top-level SVG component. + this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot()); +}; diff --git a/core/.svn/pristine/4f/4f89a64584596c516df4c4fe831402478334ba45.svn-base b/core/.svn/pristine/4f/4f89a64584596c516df4c4fe831402478334ba45.svn-base new file mode 100644 index 0000000..eaa8e99 --- /dev/null +++ b/core/.svn/pristine/4f/4f89a64584596c516df4c4fe831402478334ba45.svn-base @@ -0,0 +1,218 @@ +/** + * @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 Image field. Used for pictures, icons, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldImage'); + +goog.require('Blockly.Field'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); +goog.require('goog.userAgent'); + + +/** + * Class for an image on a block. + * @param {string} src The URL of the image. + * @param {number} width Width of the image. + * @param {number} height Height of the image. + * @param {string=} opt_alt Optional alt text for when block is collapsed. + * @param {Function=} opt_onClick Optional function to be called when the image + * is clicked. If opt_onClick is defined, opt_alt must also be defined. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldImage = function(src, width, height, opt_alt, opt_onClick) { + this.sourceBlock_ = null; + + // Ensure height and width are numbers. Strings are bad at math. + this.height_ = Number(height); + this.width_ = Number(width); + this.size_ = new goog.math.Size(this.width_, + this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y); + this.text_ = opt_alt || ''; + this.setValue(src); + + if (typeof opt_onClick == 'function') { + this.clickHandler_ = opt_onClick; + } +}; +goog.inherits(Blockly.FieldImage, Blockly.Field); + +/** + * Construct a FieldImage from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (src, width, height, and + * alt). + * @returns {!Blockly.FieldImage} The new field instance. + * @package + */ +Blockly.FieldImage.fromJson = function(options) { + var src = Blockly.utils.replaceMessageReferences(options['src']); + var width = Number(Blockly.utils.replaceMessageReferences(options['width'])); + var height = + Number(Blockly.utils.replaceMessageReferences(options['height'])); + var alt = Blockly.utils.replaceMessageReferences(options['alt']); + return new Blockly.FieldImage(src, width, height, alt); +}; + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.FieldImage.prototype.EDITABLE = false; + +/** + * Install this image on a block. + */ +Blockly.FieldImage.prototype.init = function() { + if (this.fieldGroup_) { + // Image has already been initialized once. + return; + } + // Build the DOM. + /** @type {SVGElement} */ + this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null); + if (!this.visible_) { + this.fieldGroup_.style.display = 'none'; + } + /** @type {SVGElement} */ + this.imageElement_ = Blockly.utils.createSvgElement( + 'image', + { + 'height': this.height_ + 'px', + 'width': this.width_ + 'px' + }, + this.fieldGroup_); + this.setValue(this.src_); + this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + + // Configure the field to be transparent with respect to tooltips. + this.setTooltip(this.sourceBlock_); + Blockly.Tooltip.bindMouseEvents(this.imageElement_); + + this.maybeAddClickHandler_(); +}; + +/** + * Dispose of all DOM objects belonging to this text. + */ +Blockly.FieldImage.prototype.dispose = function() { + goog.dom.removeNode(this.fieldGroup_); + this.fieldGroup_ = null; + this.imageElement_ = null; +}; + +/** + * Bind events for a mouse down on the image, but only if a click handler has + * been defined. + * @private + */ +Blockly.FieldImage.prototype.maybeAddClickHandler_ = function() { + if (this.clickHandler_) { + this.mouseDownWrapper_ = + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); + } +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.FieldImage.prototype.setTooltip = function(newTip) { + this.imageElement_.tooltip = newTip; +}; + +/** + * Get the source URL of this image. + * @return {string} Current text. + * @override + */ +Blockly.FieldImage.prototype.getValue = function() { + return this.src_; +}; + +/** + * Set the source URL of this image. + * @param {?string} src New source. + * @override + */ +Blockly.FieldImage.prototype.setValue = function(src) { + if (src === null) { + // No change if null. + return; + } + this.src_ = src; + if (this.imageElement_) { + this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', + 'xlink:href', src || ''); + } +}; + +/** + * Set the alt text of this image. + * @param {?string} alt New alt text. + * @override + */ +Blockly.FieldImage.prototype.setText = function(alt) { + if (alt === null) { + // No change if null. + return; + } + this.text_ = alt; +}; + +/** + * Images are fixed width, no need to render. + * @private + */ +Blockly.FieldImage.prototype.render_ = function() { + // NOP +}; + +/** + * Images are fixed width, no need to render even if forced. + */ +Blockly.FieldImage.prototype.forceRerender = function() { + // NOP +}; + +/** + * Images are fixed width, no need to update. + * @private + */ +Blockly.FieldImage.prototype.updateWidth = function() { + // NOP +}; + +/** + * If field click is called, and click handler defined, + * call the handler. + */ +Blockly.FieldImage.prototype.showEditor_ = function() { + if (this.clickHandler_) { + this.clickHandler_(this); + } +}; diff --git a/core/.svn/pristine/5c/5c69ce12f974507caaa203ad0dcd12b337e59696.svn-base b/core/.svn/pristine/5c/5c69ce12f974507caaa203ad0dcd12b337e59696.svn-base new file mode 100644 index 0000000..039f73a --- /dev/null +++ b/core/.svn/pristine/5c/5c69ce12f974507caaa203ad0dcd12b337e59696.svn-base @@ -0,0 +1,116 @@ +/** + * @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 Non-editable text field. Used for titles, labels, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldLabel'); + +goog.require('Blockly.Field'); +goog.require('Blockly.Tooltip'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); + + +/** + * Class for a non-editable field. + * @param {string} text The initial content of the field. + * @param {string=} opt_class Optional CSS class for the field's text. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldLabel = function(text, opt_class) { + this.size_ = new goog.math.Size(0, 17.5); + this.class_ = opt_class; + this.setValue(text); +}; +goog.inherits(Blockly.FieldLabel, Blockly.Field); + +/** + * Construct a FieldLabel from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and class). + * @returns {!Blockly.FieldLabel} The new field instance. + * @package + */ +Blockly.FieldLabel.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + return new Blockly.FieldLabel(text, options['class']); +}; + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.FieldLabel.prototype.EDITABLE = false; + +/** + * Install this text on a block. + */ +Blockly.FieldLabel.prototype.init = function() { + if (this.textElement_) { + // Text has already been initialized once. + return; + } + // Build the DOM. + this.textElement_ = Blockly.utils.createSvgElement('text', + {'class': 'blocklyText', 'y': this.size_.height - 5}, null); + if (this.class_) { + Blockly.utils.addClass(this.textElement_, this.class_); + } + if (!this.visible_) { + this.textElement_.style.display = 'none'; + } + this.sourceBlock_.getSvgRoot().appendChild(this.textElement_); + + // Configure the field to be transparent with respect to tooltips. + this.textElement_.tooltip = this.sourceBlock_; + Blockly.Tooltip.bindMouseEvents(this.textElement_); + // Force a render. + this.render_(); +}; + +/** + * Dispose of all DOM objects belonging to this text. + */ +Blockly.FieldLabel.prototype.dispose = function() { + goog.dom.removeNode(this.textElement_); + this.textElement_ = null; +}; + +/** + * Gets the group element for this field. + * Used for measuring the size and for positioning. + * @return {!Element} The group element. + */ +Blockly.FieldLabel.prototype.getSvgRoot = function() { + return /** @type {!Element} */ (this.textElement_); +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.FieldLabel.prototype.setTooltip = function(newTip) { + this.textElement_.tooltip = newTip; +}; diff --git a/core/.svn/pristine/5d/5ddca6e86bdf18ea449511ad2b2ffb1b4c88dd07.svn-base b/core/.svn/pristine/5d/5ddca6e86bdf18ea449511ad2b2ffb1b4c88dd07.svn-base new file mode 100644 index 0000000..1a88e5e --- /dev/null +++ b/core/.svn/pristine/5d/5ddca6e86bdf18ea449511ad2b2ffb1b4c88dd07.svn-base @@ -0,0 +1,116 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 dynamic. + * + * @author duzc2dtw@gmail.com (Du Tian Wei) + */ +'use strict'; + +goog.provide('Blockly.VariablesDynamic'); + +goog.require('Blockly.Variables'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.VariableModel'); +// TODO Fix circular dependencies +// goog.require('Blockly.Workspace'); +goog.require('goog.string'); + + +Blockly.VariablesDynamic.onCreateVariableButtonClick_String = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'String'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Number = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Number'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Colour'); +}; +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!Blockly.Workspace} workspace The workspace containing variables. + * @return {!Array.} Array of XML elements. + */ +Blockly.VariablesDynamic.flyoutCategory = function(workspace) { + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_STRING_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_STRING'); + xmlList.push(button); + button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_NUMBER_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_NUMBER'); + xmlList.push(button);button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_COLOUR_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_COLOUR'); + xmlList.push(button); + + workspace.registerButtonCallback('CREATE_VARIABLE_STRING', + Blockly.VariablesDynamic.onCreateVariableButtonClick_String); + workspace.registerButtonCallback('CREATE_VARIABLE_NUMBER', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Number); + workspace.registerButtonCallback('CREATE_VARIABLE_COLOUR', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour); + + + var blockList = Blockly.VariablesDynamic.flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; +}; + +/** + * Construct the blocks required by the flyout for the variable category. + * @param {!Blockly.Workspace} workspace The workspace containing variables. + * @return {!Array.} Array of XML block elements. + */ +Blockly.VariablesDynamic.flyoutCategoryBlocks = function(workspace) { + var variableModelList = workspace.getAllVariables(); + variableModelList.sort(Blockly.VariableModel.compareByName); + + var xmlList = []; + if (variableModelList.length > 0) { + if (Blockly.Blocks['variables_set_dynamic']) { + var firstVariable = variableModelList[0]; + var gap = 24; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + if (Blockly.Blocks['variables_get_dynamic']) { + for (var i = 0, variable; variable = variableModelList[i]; i++) { + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(variable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + } + } + return xmlList; +}; diff --git a/core/.svn/pristine/69/6951b1904f63636fde8a3a564268abb5c3d88b63.svn-base b/core/.svn/pristine/69/6951b1904f63636fde8a3a564268abb5c3d88b63.svn-base new file mode 100644 index 0000000..09cb9b8 --- /dev/null +++ b/core/.svn/pristine/69/6951b1904f63636fde8a3a564268abb5c3d88b63.svn-base @@ -0,0 +1,132 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a workspace visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceDragger'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a workspace dragger. It moves the workspace around when it is + * being dragged by a mouse or touch. + * Note that the workspace itself manages whether or not it has a drag surface + * and how to do translations based on that. This simply passes the right + * commands based on events. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag. + * @constructor + */ +Blockly.WorkspaceDragger = function(workspace) { + /** + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The workspace's metrics object at the beginning of the drag. Contains size + * and position metrics of a workspace. + * Coordinate system: pixel coordinates. + * @type {!Object} + * @private + */ + this.startDragMetrics_ = workspace.getMetrics(); + + /** + * The scroll position of the workspace at the beginning of the drag. + * Coordinate system: pixel coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startScrollXY_ = new goog.math.Coordinate( + workspace.scrollX, workspace.scrollY); +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.WorkspaceDragger.prototype.dispose = function() { + this.workspace_ = null; +}; + +/** + * Start dragging the workspace. + * @package + */ +Blockly.WorkspaceDragger.prototype.startDrag = function() { + if (Blockly.selected) { + Blockly.selected.unselect(); + } + this.workspace_.setupDragSurface(); +}; + +/** + * Finish dragging the workspace and put everything back where it belongs. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ +Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) { + // Make sure everything is up to date. + this.drag(currentDragDeltaXY); + this.workspace_.resetDragSurface(); +}; + +/** + * Move the workspace based on the most recent mouse movements. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ +Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) { + var metrics = this.startDragMetrics_; + var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY); + + // Bound the new XY based on workspace bounds. + var x = Math.min(newXY.x, -metrics.contentLeft); + var y = Math.min(newXY.y, -metrics.contentTop); + x = Math.max(x, metrics.viewWidth - metrics.contentLeft - + metrics.contentWidth); + y = Math.max(y, metrics.viewHeight - metrics.contentTop - + metrics.contentHeight); + + x = -x - metrics.contentLeft; + y = -y - metrics.contentTop; + + this.updateScroll_(x, y); +}; + +/** + * Move the scrollbars to drag the workspace. + * x and y are in pixels. + * @param {number} x The new x position to move the scrollbar to. + * @param {number} y The new y position to move the scrollbar to. + * @private + */ +Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) { + this.workspace_.scrollbar.set(x, y); +}; diff --git a/core/.svn/pristine/6b/6bf6a29e7b1d6e62a74048e3e9b49d2cb6c57c72.svn-base b/core/.svn/pristine/6b/6bf6a29e7b1d6e62a74048e3e9b49d2cb6c57c72.svn-base new file mode 100644 index 0000000..c8b2e25 --- /dev/null +++ b/core/.svn/pristine/6b/6bf6a29e7b1d6e62a74048e3e9b49d2cb6c57c72.svn-base @@ -0,0 +1,224 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object for configuring and updating a workspace grid in + * Blockly. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Grid'); + +goog.require('Blockly.utils'); + +goog.require('goog.userAgent'); + + +/** + * Class for a workspace's grid. + * @param {!SVGElement} pattern The grid's SVG pattern, created during injection. + * @param {!Object} options A dictionary of normalized options for the grid. + * See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @constructor + */ +Blockly.Grid = function(pattern, options) { + /** + * The grid's SVG pattern, created during injection. + * @type {!SVGElement} + * @private + */ + this.gridPattern_ = pattern; + + /** + * The spacing of the grid lines (in px). + * @type {number} + * @private + */ + this.spacing_ = options['spacing']; + + /** + * How long the grid lines should be (in px). + * @type {number} + * @private + */ + this.length_ = options['length']; + + /** + * The horizontal grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line1_ = pattern.firstChild; + + /** + * The vertical grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line2_ = this.line1_ && this.line1_.nextSibling; + + /** + * Whether blocks should snap to the grid. + * @type {boolean} + * @private + */ + this.snapToGrid_ = options['snap']; +}; + +/** + * The scale of the grid, used to set stroke width on grid lines. + * This should always be the same as the workspace scale. + * @type {number} + * @private + */ +Blockly.Grid.prototype.scale_ = 1; + +/** + * Dispose of this grid and unlink from the DOM. + * @package + */ +Blockly.Grid.prototype.dispose = function() { + this.gridPattern_ = null; +}; + +/** + * Whether blocks should snap to the grid, based on the initial configuration. + * @return {boolean} True if blocks should snap, false otherwise. + * @package + */ +Blockly.Grid.prototype.shouldSnap = function() { + return this.snapToGrid_; +}; + +/** + * Get the spacing of the grid points (in px). + * @return {number} The spacing of the grid points. + * @package + */ +Blockly.Grid.prototype.getSpacing = function() { + return this.spacing_; +}; + +/** + * Get the id of the pattern element, which should be randomized to avoid + * conflicts with other Blockly instances on the page. + * @return {string} The pattern ID. + * @package + */ +Blockly.Grid.prototype.getPatternId = function() { + return this.gridPattern_.id; +}; + +/** + * Update the grid with a new scale. + * @param {number} scale The new workspace scale. + * @package + */ +Blockly.Grid.prototype.update = function(scale) { + this.scale_ = scale; + // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. + var safeSpacing = (this.spacing_ * scale) || 100; + + this.gridPattern_.setAttribute('width', safeSpacing); + this.gridPattern_.setAttribute('height', safeSpacing); + + var half = Math.floor(this.spacing_ / 2) + 0.5; + var start = half - this.length_ / 2; + var end = half + this.length_ / 2; + + half *= scale; + start *= scale; + end *= scale; + + this.setLineAttributes_(this.line1_, scale, start, end, half, half); + this.setLineAttributes_(this.line2_, scale, half, half, start, end); +}; + +/** + * Set the attributes on one of the lines in the grid. Use this to update the + * length and stroke width of the grid lines. + * @param {!SVGElement} line Which line to update. + * @param {number} width The new stroke size (in px). + * @param {number} x1 The new x start position of the line (in px). + * @param {number} x2 The new x end position of the line (in px). + * @param {number} y1 The new y start position of the line (in px). + * @param {number} y2 The new y end position of the line (in px). + * @private + */ +Blockly.Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) { + if (line) { + line.setAttribute('stroke-width', width); + line.setAttribute('x1', x1); + line.setAttribute('y1', y1); + line.setAttribute('x2', x2); + line.setAttribute('y2', y2); + } +}; + +/** + * Move the grid to a new x and y position, and make sure that change is visible. + * @param {number} x The new x position of the grid (in px). + * @param {number} y The new y position ofthe grid (in px). + * @package + */ +Blockly.Grid.prototype.moveTo = function(x, y) { + this.gridPattern_.setAttribute('x', x); + this.gridPattern_.setAttribute('y', y); + + if (goog.userAgent.IE || goog.userAgent.EDGE) { + // IE/Edge doesn't notice that the x/y offsets have changed. + // Force an update. + this.update(this.scale_); + } +}; + +/** + * Create the DOM for the grid described by options. + * @param {string} rnd A random ID to append to the pattern's ID. + * @param {!Object} gridOptions The object containing grid configuration. + * @param {!SVGElement} defs The root SVG element for this workspace's defs. + * @return {!SVGElement} The SVG element for the grid pattern. + * @package + */ +Blockly.Grid.createDom = function(rnd, gridOptions, defs) { + /* + + + + + */ + var gridPattern = Blockly.utils.createSvgElement('pattern', + { + 'id': 'blocklyGridPattern' + rnd, + 'patternUnits': 'userSpaceOnUse' + }, defs); + if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + if (gridOptions['length'] > 1) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + } + // x1, y1, x1, x2 properties will be set later in update. + } + return gridPattern; +}; diff --git a/core/.svn/pristine/6f/6f07fc0777ec1a5edab6bf5fbc66d01c26286c37.svn-base b/core/.svn/pristine/6f/6f07fc0777ec1a5edab6bf5fbc66d01c26286c37.svn-base new file mode 100644 index 0000000..77632c1 --- /dev/null +++ b/core/.svn/pristine/6f/6f07fc0777ec1a5edab6bf5fbc66d01c26286c37.svn-base @@ -0,0 +1,236 @@ +/** + * @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 Colour input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldColour'); + +goog.require('Blockly.Field'); +goog.require('Blockly.utils'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.ColorPicker'); + + +/** + * Class for a colour input field. + * @param {string} colour The initial colour in '#rrggbb' format. + * @param {Function=} opt_validator A function that is executed when a new + * colour is selected. Its sole argument is the new colour value. Its + * return value becomes the selected colour, unless it is undefined, in + * which case the new colour stands, or it is null, in which case the change + * is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldColour = function(colour, opt_validator) { + Blockly.FieldColour.superClass_.constructor.call(this, colour, opt_validator); + this.setText(Blockly.Field.NBSP + Blockly.Field.NBSP + Blockly.Field.NBSP); +}; +goog.inherits(Blockly.FieldColour, Blockly.Field); + +/** + * Construct a FieldColour from a JSON arg object. + * @param {!Object} options A JSON object with options (colour). + * @returns {!Blockly.FieldColour} The new field instance. + * @package + */ +Blockly.FieldColour.fromJson = function(options) { + return new Blockly.FieldColour(options['colour']); +}; + +/** + * By default use the global constants for colours. + * @type {Array.} + * @private + */ +Blockly.FieldColour.prototype.colours_ = null; + +/** + * By default use the global constants for columns. + * @type {number} + * @private + */ +Blockly.FieldColour.prototype.columns_ = 0; + +/** + * Install this field on a block. + */ +Blockly.FieldColour.prototype.init = function() { + Blockly.FieldColour.superClass_.init.call(this); + this.borderRect_.style['fillOpacity'] = 1; + this.setValue(this.getValue()); +}; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldColour.prototype.CURSOR = 'default'; + +/** + * Close the colour picker if this input is being deleted. + */ +Blockly.FieldColour.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldColour.superClass_.dispose.call(this); +}; + +/** + * Return the current colour. + * @return {string} Current colour in '#rrggbb' format. + */ +Blockly.FieldColour.prototype.getValue = function() { + return this.colour_; +}; + +/** + * Set the colour. + * @param {string} colour The new colour in '#rrggbb' format. + */ +Blockly.FieldColour.prototype.setValue = function(colour) { + if (this.sourceBlock_ && Blockly.Events.isEnabled() && + this.colour_ != colour) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.colour_, colour)); + } + this.colour_ = colour; + if (this.borderRect_) { + this.borderRect_.style.fill = colour; + } +}; + +/** + * Get the text from this field. Used when the block is collapsed. + * @return {string} Current text. + */ +Blockly.FieldColour.prototype.getText = function() { + var colour = this.colour_; + // Try to use #rgb format if possible, rather than #rrggbb. + var m = colour.match(/^#(.)\1(.)\2(.)\3$/); + if (m) { + colour = '#' + m[1] + m[2] + m[3]; + } + return colour; +}; + +/** + * An array of colour strings for the palette. + * See bottom of this page for the default: + * http://docs.closure-library.googlecode.com/git/closure_goog_ui_colorpicker.js.source.html + * @type {!Array.} + */ +Blockly.FieldColour.COLOURS = goog.ui.ColorPicker.SIMPLE_GRID_COLORS; + +/** + * Number of columns in the palette. + */ +Blockly.FieldColour.COLUMNS = 7; + +/** + * Set a custom colour grid for this field. + * @param {Array.} colours Array of colours for this block, + * or null to use default (Blockly.FieldColour.COLOURS). + * @return {!Blockly.FieldColour} Returns itself (for method chaining). + */ +Blockly.FieldColour.prototype.setColours = function(colours) { + this.colours_ = colours; + return this; +}; + +/** + * Set a custom grid size for this field. + * @param {number} columns Number of columns for this block, + * or 0 to use default (Blockly.FieldColour.COLUMNS). + * @return {!Blockly.FieldColour} Returns itself (for method chaining). + */ +Blockly.FieldColour.prototype.setColumns = function(columns) { + this.columns_ = columns; + return this; +}; + +/** + * Create a palette under the colour field. + * @private + */ +Blockly.FieldColour.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, + Blockly.FieldColour.widgetDispose_); + + // Record viewport dimensions before adding the widget. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getScaledBBox_(); + + // Create and add the colour picker, then record the size. + var picker = this.createWidget_(); + var paletteSize = goog.style.getSize(picker.getElement()); + + // Position the picker to line up with the field. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, paletteSize, + this.sourceBlock_.RTL); + + // Configure event handler. + var thisField = this; + Blockly.FieldColour.changeEventKey_ = goog.events.listen(picker, + goog.ui.ColorPicker.EventType.CHANGE, + function(event) { + var colour = event.target.getSelectedColor() || '#000000'; + Blockly.WidgetDiv.hide(); + if (thisField.sourceBlock_) { + // Call any validation function, and allow it to override. + colour = thisField.callValidator(colour); + } + if (colour !== null) { + thisField.setValue(colour); + } + }); +}; + +/** + * Create a color picker widget and render it inside the widget div. + * @return {!goog.ui.ColorPicker} The newly created color picker. + * @private + */ +Blockly.FieldColour.prototype.createWidget_ = function() { + // Create the palette using Closure. + var picker = new goog.ui.ColorPicker(); + picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS); + picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setSelectedColor(this.getValue()); + return picker; +}; + +/** + * Hide the colour palette. + * @private + */ +Blockly.FieldColour.widgetDispose_ = function() { + if (Blockly.FieldColour.changeEventKey_) { + goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_); + } + Blockly.Events.setGroup(false); +}; diff --git a/core/.svn/pristine/70/702327f0a464802a9839277c2ee622fd90b3a8e7.svn-base b/core/.svn/pristine/70/702327f0a464802a9839277c2ee622fd90b3a8e7.svn-base new file mode 100644 index 0000000..2367066 --- /dev/null +++ b/core/.svn/pristine/70/702327f0a464802a9839277c2ee622fd90b3a8e7.svn-base @@ -0,0 +1,1551 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 The class representing one block. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Block'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.Comment'); +goog.require('Blockly.Connection'); +goog.require('Blockly.Extensions'); +goog.require('Blockly.Input'); +goog.require('Blockly.Mutator'); +goog.require('Blockly.Warning'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.Xml'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); +goog.require('goog.string'); + + +/** + * Class for one block. + * Not normally called directly, workspace.newBlock() is preferred. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. + * @constructor + */ +Blockly.Block = function(workspace, prototypeName, opt_id) { + if (typeof Blockly.Generator.prototype[prototypeName] !== 'undefined') { + console.warn('FUTURE ERROR: Block prototypeName "' + prototypeName + + '" conflicts with Blockly.Generator members. Registering Generators ' + + 'for this block type will incur errors.' + + '\nThis name will be DISALLOWED (throwing an error) in future ' + + 'versions of Blockly.'); + } + + /** @type {string} */ + this.id = (opt_id && !workspace.getBlockById(opt_id)) ? + opt_id : Blockly.utils.genUid(); + workspace.blockDB_[this.id] = this; + /** @type {Blockly.Connection} */ + this.outputConnection = null; + /** @type {Blockly.Connection} */ + this.nextConnection = null; + /** @type {Blockly.Connection} */ + this.previousConnection = null; + /** @type {!Array.} */ + this.inputList = []; + /** @type {boolean|undefined} */ + this.inputsInline = undefined; + /** @type {boolean} */ + this.disabled = false; + /** @type {string|!Function} */ + this.tooltip = ''; + /** @type {boolean} */ + this.contextMenu = true; + + /** + * @type {Blockly.Block} + * @private + */ + this.parentBlock_ = null; + + /** + * @type {!Array.} + * @private + */ + this.childBlocks_ = []; + + /** + * @type {boolean} + * @private + */ + this.deletable_ = true; + + /** + * @type {boolean} + * @private + */ + this.movable_ = true; + + /** + * @type {boolean} + * @private + */ + this.editable_ = true; + + /** + * @type {boolean} + * @private + */ + this.isShadow_ = false; + + /** + * @type {boolean} + * @private + */ + this.collapsed_ = false; + + /** @type {string|Blockly.Comment} */ + this.comment = null; + + /** + * The block's position in workspace units. (0, 0) is at the workspace's + * origin; scale does not change this value. + * @type {!goog.math.Coordinate} + * @private + */ + this.xy_ = new goog.math.Coordinate(0, 0); + + /** @type {!Blockly.Workspace} */ + this.workspace = workspace; + /** @type {boolean} */ + this.isInFlyout = workspace.isFlyout; + /** @type {boolean} */ + this.isInMutator = workspace.isMutator; + + /** @type {boolean} */ + this.RTL = workspace.RTL; + + // Copy the type-specific functions and data from the prototype. + if (prototypeName) { + /** @type {string} */ + this.type = prototypeName; + var prototype = Blockly.Blocks[prototypeName]; + goog.asserts.assertObject(prototype, + 'Error: Unknown block type "%s".', prototypeName); + goog.mixin(this, prototype); + } + + workspace.addTopBlock(this); + + // Call an initialization function, if it exists. + if (goog.isFunction(this.init)) { + this.init(); + } + // Record initial inline state. + /** @type {boolean|undefined} */ + this.inputsInlineDefault = this.inputsInline; + + // Fire a create event. + if (Blockly.Events.isEnabled()) { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + Blockly.Events.fire(new Blockly.Events.BlockCreate(this)); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } + + } + // Bind an onchange function, if it exists. + if (goog.isFunction(this.onchange)) { + this.setOnChange(this.onchange); + } +}; + +/** + * Obtain a newly created block. + * @param {!Blockly.Workspace} workspace The block's workspace. + * @param {?string} prototypeName Name of the language object containing + * type-specific functions for this block. + * @return {!Blockly.Block} The created block. + * @deprecated December 2015 + */ +Blockly.Block.obtain = function(workspace, prototypeName) { + console.warn('Deprecated call to Blockly.Block.obtain, ' + + 'use workspace.newBlock instead.'); + return workspace.newBlock(prototypeName); +}; + +/** + * Optional text data that round-trips beween blocks and XML. + * Has no effect. May be used by 3rd parties for meta information. + * @type {?string} + */ +Blockly.Block.prototype.data = null; + +/** + * Colour of the block in '#RRGGBB' format. + * @type {string} + * @private + */ +Blockly.Block.prototype.colour_ = '#000000'; + +/** + * Colour of the block as HSV hue value (0-360) + * @type {?number} + * @private + */ +Blockly.Block.prototype.hue_ = null; + +/** + * Dispose of this block. + * @param {boolean} healStack If true, then try to heal any gap by connecting + * the next statement with the previous statement. Otherwise, dispose of + * all children of this block. + */ +Blockly.Block.prototype.dispose = function(healStack) { + if (!this.workspace) { + // Already deleted. + return; + } + // Terminate onchange event calls. + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + this.unplug(healStack); + if (Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockDelete(this)); + } + Blockly.Events.disable(); + + try { + // This block is now at the top of the workspace. + // Remove this block from the workspace's list of top-most blocks. + if (this.workspace) { + this.workspace.removeTopBlock(this); + // Remove from block database. + delete this.workspace.blockDB_[this.id]; + this.workspace = null; + } + + // Just deleting this block from the DOM would result in a memory leak as + // well as corruption of the connection database. Therefore we must + // methodically step through the blocks and carefully disassemble them. + + // First, dispose of all my children. + for (var i = this.childBlocks_.length - 1; i >= 0; i--) { + this.childBlocks_[i].dispose(false); + } + // Then dispose of myself. + // Dispose of all inputs and their fields. + for (var i = 0, input; input = this.inputList[i]; i++) { + input.dispose(); + } + this.inputList.length = 0; + // Dispose of any remaining connections (next/previous/output). + var connections = this.getConnections_(true); + for (var i = 0; i < connections.length; i++) { + var connection = connections[i]; + if (connection.isConnected()) { + connection.disconnect(); + } + connections[i].dispose(); + } + } finally { + Blockly.Events.enable(); + } +}; + +/** + * Call initModel on all fields on the block. + * May be called more than once. + * Either initModel or initSvg must be called after creating a block and before + * the first interaction with it. Interactions include UI actions + * (e.g. clicking and dragging) and firing events (e.g. create, delete, and + * change). + * @public + */ +Blockly.Block.prototype.initModel = function() { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field.initModel) { + field.initModel(); + } + } + } +}; + +/** + * Unplug this block from its superior block. If this block is a statement, + * optionally reconnect the block underneath with the block on top. + * @param {boolean=} opt_healStack Disconnect child statement and reconnect + * stack. Defaults to false. + */ +Blockly.Block.prototype.unplug = function(opt_healStack) { + if (this.outputConnection) { + if (this.outputConnection.isConnected()) { + // Disconnect from any superior block. + this.outputConnection.disconnect(); + } + } else if (this.previousConnection) { + var previousTarget = null; + if (this.previousConnection.isConnected()) { + // Remember the connection that any next statements need to connect to. + previousTarget = this.previousConnection.targetConnection; + // Detach this block from the parent's tree. + this.previousConnection.disconnect(); + } + var nextBlock = this.getNextBlock(); + if (opt_healStack && nextBlock) { + // Disconnect the next statement. + var nextTarget = this.nextConnection.targetConnection; + nextTarget.disconnect(); + if (previousTarget && previousTarget.checkType_(nextTarget)) { + // Attach the next statement to the previous statement. + previousTarget.connect(nextTarget); + } + } + } +}; + +/** + * Returns all connections originating from this block. + * @param {boolean} all If true, return all connections even hidden ones. + * @return {!Array.} Array of connections. + * @private + */ +Blockly.Block.prototype.getConnections_ = function( + /* eslint-disable no-unused-vars */ all + /* eslint-enable no-unused-vars */) { + var myConnections = []; + if (this.outputConnection) { + myConnections.push(this.outputConnection); + } + if (this.previousConnection) { + myConnections.push(this.previousConnection); + } + if (this.nextConnection) { + myConnections.push(this.nextConnection); + } + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection) { + myConnections.push(input.connection); + } + } + return myConnections; +}; + +/** + * Walks down a stack of blocks and finds the last next connection on the stack. + * @return {Blockly.Connection} The last next connection on the stack, or null. + * @package + */ +Blockly.Block.prototype.lastConnectionInStack_ = function() { + var nextConnection = this.nextConnection; + while (nextConnection) { + var nextBlock = nextConnection.targetBlock(); + if (!nextBlock) { + // Found a next connection with nothing on the other side. + return nextConnection; + } + nextConnection = nextBlock.nextConnection; + } + // Ran out of next connections. + return null; +}; + +/** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + * @private + */ +Blockly.Block.prototype.bumpNeighbours_ = function() { + console.warn('Not expected to reach this bumpNeighbours_ function. The ' + + 'BlockSvg function for bumpNeighbours_ was expected to be called instead.'); +}; + +/** + * Return the parent block or null if this block is at the top level. + * @return {Blockly.Block} The block that holds the current block. + */ +Blockly.Block.prototype.getParent = function() { + // Look at the DOM to see if we are nested in another block. + return this.parentBlock_; +}; + +/** + * Return the input that connects to the specified block. + * @param {!Blockly.Block} block A block connected to an input on this block. + * @return {Blockly.Input} The input that connects to the specified block. + */ +Blockly.Block.prototype.getInputWithBlock = function(block) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.connection && input.connection.targetBlock() == block) { + return input; + } + } + return null; +}; + +/** + * Return the parent block that surrounds the current block, or null if this + * block has no surrounding block. A parent block might just be the previous + * statement, whereas the surrounding block is an if statement, while loop, etc. + * @return {Blockly.Block} The block that surrounds the current block. + */ +Blockly.Block.prototype.getSurroundParent = function() { + var block = this; + do { + var prevBlock = block; + block = block.getParent(); + if (!block) { + // Ran off the top. + return null; + } + } while (block.getNextBlock() == prevBlock); + // This block is an enclosing parent, not just a statement in a stack. + return block; +}; + +/** + * Return the next statement block directly connected to this block. + * @return {Blockly.Block} The next statement block or null. + */ +Blockly.Block.prototype.getNextBlock = function() { + return this.nextConnection && this.nextConnection.targetBlock(); +}; + +/** + * Return the top-most block in this block's tree. + * This will return itself if this block is at the top level. + * @return {!Blockly.Block} The root block. + */ +Blockly.Block.prototype.getRootBlock = function() { + var rootBlock; + var block = this; + do { + rootBlock = block; + block = rootBlock.parentBlock_; + } while (block); + return rootBlock; +}; + +/** + * Find all the blocks that are directly nested inside this one. + * Includes value and block inputs, as well as any following statement. + * Excludes any connection on an output tab or any preceding statement. + * @return {!Array.} Array of blocks. + */ +Blockly.Block.prototype.getChildren = function() { + return this.childBlocks_; +}; + +/** + * Set parent of this block to be a new block or null. + * @param {Blockly.Block} newParent New parent block. + */ +Blockly.Block.prototype.setParent = function(newParent) { + if (newParent == this.parentBlock_) { + return; + } + if (this.parentBlock_) { + // Remove this block from the old parent's child list. + goog.array.remove(this.parentBlock_.childBlocks_, this); + + // Disconnect from superior blocks. + if (this.previousConnection && this.previousConnection.isConnected()) { + throw 'Still connected to previous block.'; + } + if (this.outputConnection && this.outputConnection.isConnected()) { + throw 'Still connected to parent block.'; + } + this.parentBlock_ = null; + // This block hasn't actually moved on-screen, so there's no need to update + // its connection locations. + } else { + // Remove this block from the workspace's list of top-most blocks. + this.workspace.removeTopBlock(this); + } + + this.parentBlock_ = newParent; + if (newParent) { + // Add this block to the new parent's child list. + newParent.childBlocks_.push(this); + } else { + this.workspace.addTopBlock(this); + } +}; + +/** + * Find all the blocks that are directly or indirectly nested inside this one. + * Includes this block in the list. + * Includes value and block inputs, as well as any following statements. + * Excludes any connection on an output tab or any preceding statements. + * @return {!Array.} Flattened array of blocks. + */ +Blockly.Block.prototype.getDescendants = function() { + var blocks = [this]; + for (var child, x = 0; child = this.childBlocks_[x]; x++) { + blocks.push.apply(blocks, child.getDescendants()); + } + return blocks; +}; + +/** + * Get whether this block is deletable or not. + * @return {boolean} True if deletable. + */ +Blockly.Block.prototype.isDeletable = function() { + return this.deletable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is deletable or not. + * @param {boolean} deletable True if deletable. + */ +Blockly.Block.prototype.setDeletable = function(deletable) { + this.deletable_ = deletable; +}; + +/** + * Get whether this block is movable or not. + * @return {boolean} True if movable. + */ +Blockly.Block.prototype.isMovable = function() { + return this.movable_ && !this.isShadow_ && + !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is movable or not. + * @param {boolean} movable True if movable. + */ +Blockly.Block.prototype.setMovable = function(movable) { + this.movable_ = movable; +}; + +/** + * Get whether this block is a shadow block or not. + * @return {boolean} True if a shadow. + */ +Blockly.Block.prototype.isShadow = function() { + return this.isShadow_; +}; + +/** + * Set whether this block is a shadow block or not. + * @param {boolean} shadow True if a shadow. + */ +Blockly.Block.prototype.setShadow = function(shadow) { + this.isShadow_ = shadow; +}; + +/** + * Get whether this block is editable or not. + * @return {boolean} True if editable. + */ +Blockly.Block.prototype.isEditable = function() { + return this.editable_ && !(this.workspace && this.workspace.options.readOnly); +}; + +/** + * Set whether this block is editable or not. + * @param {boolean} editable True if editable. + */ +Blockly.Block.prototype.setEditable = function(editable) { + this.editable_ = editable; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + field.updateEditable(); + } + } +}; + +/** + * Set whether the connections are hidden (not tracked in a database) or not. + * Recursively walk down all child blocks (except collapsed blocks). + * @param {boolean} hidden True if connections are hidden. + */ +Blockly.Block.prototype.setConnectionsHidden = function(hidden) { + if (!hidden && this.isCollapsed()) { + if (this.outputConnection) { + this.outputConnection.setHidden(hidden); + } + if (this.previousConnection) { + this.previousConnection.setHidden(hidden); + } + if (this.nextConnection) { + this.nextConnection.setHidden(hidden); + var child = this.nextConnection.targetBlock(); + if (child) { + child.setConnectionsHidden(hidden); + } + } + } else { + var myConnections = this.getConnections_(true); + for (var i = 0, connection; connection = myConnections[i]; i++) { + connection.setHidden(hidden); + if (connection.isSuperior()) { + var child = connection.targetBlock(); + if (child) { + child.setConnectionsHidden(hidden); + } + } + } + } +}; + +/** + * Set the URL of this block's help page. + * @param {string|Function} url URL string for block help, or function that + * returns a URL. Null for no help. + */ +Blockly.Block.prototype.setHelpUrl = function(url) { + this.helpUrl = url; +}; + +/** + * Change the tooltip text for a block. + * @param {string|!Function} newTip Text for tooltip or a parent element to + * link to for its tooltip. May be a function that returns a string. + */ +Blockly.Block.prototype.setTooltip = function(newTip) { + this.tooltip = newTip; +}; + +/** + * Get the colour of a block. + * @return {string} #RRGGBB string. + */ +Blockly.Block.prototype.getColour = function() { + return this.colour_; +}; + +/** + * Get the HSV hue value of a block. Null if hue not set. + * @return {?number} Hue value (0-360) + */ +Blockly.Block.prototype.getHue = function() { + return this.hue_; +}; + +/** + * Change the colour of a block. + * @param {number|string} colour HSV hue value, or #RRGGBB string. + */ +Blockly.Block.prototype.setColour = function(colour) { + var hue = Number(colour); + if (!isNaN(hue) && 0 <= hue && hue <= 360) { + this.hue_ = hue; + this.colour_ = Blockly.hueToRgb(hue); + } else if (goog.isString(colour) && /^#[0-9a-fA-F]{6}$/.test(colour)) { + this.colour_ = colour; + // Only store hue if colour is set as a hue. + this.hue_ = null; + } else { + throw 'Invalid colour: ' + colour; + } +}; + +/** + * Sets a callback function to use whenever the block's parent workspace + * changes, replacing any prior onchange handler. This is usually only called + * from the constructor, the block type initializer function, or an extension + * initializer function. + * @param {function(Blockly.Events.Abstract)} onchangeFn The callback to call + * when the block's workspace changes. + * @throws {Error} if onchangeFn is not falsey or a function. + */ +Blockly.Block.prototype.setOnChange = function(onchangeFn) { + if (onchangeFn && !goog.isFunction(onchangeFn)) { + throw new Error("onchange must be a function."); + } + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + this.onchange = onchangeFn; + if (this.onchange) { + this.onchangeWrapper_ = onchangeFn.bind(this); + this.workspace.addChangeListener(this.onchangeWrapper_); + } +}; + +/** + * Returns the named field from a block. + * @param {string} name The name of the field. + * @return {Blockly.Field} Named field, or null if field does not exist. + */ +Blockly.Block.prototype.getField = function(name) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field.name === name) { + return field; + } + } + } + return null; +}; + +/** + * Return all variables referenced by this block. + * @return {!Array.} List of variable names. + * @package + */ +Blockly.Block.prototype.getVars = function() { + var vars = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable) { + vars.push(field.getValue()); + } + } + } + return vars; +}; + +/** + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. + * @package + */ +Blockly.Block.prototype.getVarModels = function() { + var vars = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable) { + var model = this.workspace.getVariableById(field.getValue()); + // Check if the variable actually exists (and isn't just a potential + // variable). + if (model) { + vars.push(model); + } + } + } + } + return vars; +}; + +/** + * Notification that a variable is renaming but keeping the same ID. If the + * variable is in use on this block, rerender to show the new name. + * @param {!Blockly.VariableModel} variable The variable being renamed. + * @package + */ +Blockly.Block.prototype.updateVarName = function(variable) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable && + variable.getId() == field.getValue()) { + field.setText(variable.name); + } + } + } +}; + +/** + * Notification that a variable is renaming. + * If the ID matches one of this block's variables, rename it. + * @param {string} oldId ID of variable to rename. + * @param {string} newId ID of new variable. May be the same as oldId, but with + * an updated name. + */ +Blockly.Block.prototype.renameVarById = function(oldId, newId) { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldVariable && + oldId == field.getValue()) { + field.setValue(newId); + } + } + } +}; + +/** + * Returns the language-neutral value from the field of a block. + * @param {string} name The name of the field. + * @return {?string} Value from the field or null if field does not exist. + */ +Blockly.Block.prototype.getFieldValue = function(name) { + var field = this.getField(name); + if (field) { + return field.getValue(); + } + return null; +}; + +/** + * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). + * @param {string} newValue Value to be the new field. + * @param {string} name The name of the field. + */ +Blockly.Block.prototype.setFieldValue = function(newValue, name) { + var field = this.getField(name); + goog.asserts.assertObject(field, 'Field "%s" not found.', name); + field.setValue(newValue); +}; + +/** + * Set whether this block can chain onto the bottom of another block. + * @param {boolean} newBoolean True if there can be a previous statement. + * @param {(string|Array.|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.previousConnection) { + goog.asserts.assert(!this.outputConnection, + 'Remove output connection prior to adding previous connection.'); + this.previousConnection = + this.makeConnection_(Blockly.PREVIOUS_STATEMENT); + } + this.previousConnection.setCheck(opt_check); + } else { + if (this.previousConnection) { + goog.asserts.assert(!this.previousConnection.isConnected(), + 'Must disconnect previous statement before removing connection.'); + this.previousConnection.dispose(); + this.previousConnection = null; + } + } +}; + +/** + * Set whether another block can chain onto the bottom of this block. + * @param {boolean} newBoolean True if there can be a next statement. + * @param {(string|Array.|null)=} opt_check Statement type or + * list of statement types. Null/undefined if any type could be connected. + */ +Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.nextConnection) { + this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT); + } + this.nextConnection.setCheck(opt_check); + } else { + if (this.nextConnection) { + goog.asserts.assert(!this.nextConnection.isConnected(), + 'Must disconnect next statement before removing connection.'); + this.nextConnection.dispose(); + this.nextConnection = null; + } + } +}; + +/** + * Set whether this block returns a value. + * @param {boolean} newBoolean True if there is an output. + * @param {(string|Array.|null)=} opt_check Returned type or list + * of returned types. Null or undefined if any type could be returned + * (e.g. variable get). + */ +Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) { + if (newBoolean) { + if (opt_check === undefined) { + opt_check = null; + } + if (!this.outputConnection) { + goog.asserts.assert(!this.previousConnection, + 'Remove previous connection prior to adding output connection.'); + this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE); + } + this.outputConnection.setCheck(opt_check); + } else { + if (this.outputConnection) { + goog.asserts.assert(!this.outputConnection.isConnected(), + 'Must disconnect output value before removing connection.'); + this.outputConnection.dispose(); + this.outputConnection = null; + } + } +}; + +/** + * Set whether value inputs are arranged horizontally or vertically. + * @param {boolean} newBoolean True if inputs are horizontal. + */ +Blockly.Block.prototype.setInputsInline = function(newBoolean) { + if (this.inputsInline != newBoolean) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this, 'inline', null, this.inputsInline, newBoolean)); + this.inputsInline = newBoolean; + } +}; + +/** + * Get whether value inputs are arranged horizontally or vertically. + * @return {boolean} True if inputs are horizontal. + */ +Blockly.Block.prototype.getInputsInline = function() { + if (this.inputsInline != undefined) { + // Set explicitly. + return this.inputsInline; + } + // Not defined explicitly. Figure out what would look best. + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT && + this.inputList[i].type == Blockly.DUMMY_INPUT) { + // Two dummy inputs in a row. Don't inline them. + return false; + } + } + for (var i = 1; i < this.inputList.length; i++) { + if (this.inputList[i - 1].type == Blockly.INPUT_VALUE && + this.inputList[i].type == Blockly.DUMMY_INPUT) { + // Dummy input after a value input. Inline them. + return true; + } + } + return false; +}; + +/** + * Set whether the block is disabled or not. + * @param {boolean} disabled True if disabled. + */ +Blockly.Block.prototype.setDisabled = function(disabled) { + if (this.disabled != disabled) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this, 'disabled', null, this.disabled, disabled)); + this.disabled = disabled; + } +}; + +/** + * Get whether the block is disabled or not due to parents. + * The block's own disabled property is not considered. + * @return {boolean} True if disabled. + */ +Blockly.Block.prototype.getInheritedDisabled = function() { + var ancestor = this.getSurroundParent(); + while (ancestor) { + if (ancestor.disabled) { + return true; + } + ancestor = ancestor.getSurroundParent(); + } + // Ran off the top. + return false; +}; + +/** + * Get whether the block is collapsed or not. + * @return {boolean} True if collapsed. + */ +Blockly.Block.prototype.isCollapsed = function() { + return this.collapsed_; +}; + +/** + * Set whether the block is collapsed or not. + * @param {boolean} collapsed True if collapsed. + */ +Blockly.Block.prototype.setCollapsed = function(collapsed) { + if (this.collapsed_ != collapsed) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this, 'collapsed', null, this.collapsed_, collapsed)); + this.collapsed_ = collapsed; + } +}; + +/** + * Create a human-readable text representation of this block and any children. + * @param {number=} opt_maxLength Truncate the string to this length. + * @param {string=} opt_emptyToken The placeholder string used to denote an + * empty field. If not specified, '?' is used. + * @return {string} Text of block. + */ +Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { + var text = []; + var emptyFieldPlaceholder = opt_emptyToken || '?'; + if (this.collapsed_) { + text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_); + } else { + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldDropdown && !field.getValue()) { + text.push(emptyFieldPlaceholder); + } else { + text.push(field.getText()); + } + } + if (input.connection) { + var child = input.connection.targetBlock(); + if (child) { + text.push(child.toString(undefined, opt_emptyToken)); + } else { + text.push(emptyFieldPlaceholder); + } + } + } + } + text = goog.string.trim(text.join(' ')) || '???'; + if (opt_maxLength) { + // TODO: Improve truncation so that text from this block is given priority. + // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...". + // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...". + text = goog.string.truncate(text, opt_maxLength); + } + return text; +}; + +/** + * Shortcut for appending a value input row. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendValueInput = function(name) { + return this.appendInput_(Blockly.INPUT_VALUE, name); +}; + +/** + * Shortcut for appending a statement input row. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendStatementInput = function(name) { + return this.appendInput_(Blockly.NEXT_STATEMENT, name); +}; + +/** + * Shortcut for appending a dummy input row. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + */ +Blockly.Block.prototype.appendDummyInput = function(opt_name) { + return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || ''); +}; + +/** + * Initialize this block using a cross-platform, internationalization-friendly + * JSON description. + * @param {!Object} json Structured data describing the block. + */ +Blockly.Block.prototype.jsonInit = function(json) { + + // Validate inputs. + goog.asserts.assert( + json['output'] == undefined || json['previousStatement'] == undefined, + 'Must not have both an output and a previousStatement.'); + + // Set basic properties of block. + if (json['colour'] !== undefined) { + var rawValue = json['colour']; + var colour = goog.isString(rawValue) ? + Blockly.utils.replaceMessageReferences(rawValue) : rawValue; + this.setColour(colour); + } + + // Interpolate the message blocks. + var i = 0; + while (json['message' + i] !== undefined) { + this.interpolate_(json['message' + i], json['args' + i] || [], + json['lastDummyAlign' + i]); + i++; + } + + if (json['inputsInline'] !== undefined) { + this.setInputsInline(json['inputsInline']); + } + // Set output and previous/next connections. + if (json['output'] !== undefined) { + this.setOutput(true, json['output']); + } + if (json['previousStatement'] !== undefined) { + this.setPreviousStatement(true, json['previousStatement']); + } + if (json['nextStatement'] !== undefined) { + this.setNextStatement(true, json['nextStatement']); + } + if (json['tooltip'] !== undefined) { + var rawValue = json['tooltip']; + var localizedText = Blockly.utils.replaceMessageReferences(rawValue); + this.setTooltip(localizedText); + } + if (json['enableContextMenu'] !== undefined) { + var rawValue = json['enableContextMenu']; + this.contextMenu = !!rawValue; + } + if (json['helpUrl'] !== undefined) { + var rawValue = json['helpUrl']; + var localizedValue = Blockly.utils.replaceMessageReferences(rawValue); + this.setHelpUrl(localizedValue); + } + if (goog.isString(json['extensions'])) { + console.warn('JSON attribute \'extensions\' should be an array of ' + + 'strings. Found raw string in JSON for \'' + json['type'] + '\' block.'); + json['extensions'] = [json['extensions']]; // Correct and continue. + } + + // Add the mutator to the block + if (json['mutator'] !== undefined) { + Blockly.Extensions.apply(json['mutator'], this, true); + } + + if (Array.isArray(json['extensions'])) { + var extensionNames = json['extensions']; + for (var i = 0; i < extensionNames.length; ++i) { + var extensionName = extensionNames[i]; + Blockly.Extensions.apply(extensionName, this, false); + } + } +}; + +/** + * Add key/values from mixinObj to this block object. By default, this method + * will check that the keys in mixinObj will not overwrite existing values in + * the block, including prototype values. This provides some insurance against + * mixin / extension incompatibilities with future block features. This check + * can be disabled by passing true as the second argument. + * @param {!Object} mixinObj The key/values pairs to add to this block object. + * @param {boolean=} opt_disableCheck Option flag to disable overwrite checks. + */ +Blockly.Block.prototype.mixin = function(mixinObj, opt_disableCheck) { + if (goog.isDef(opt_disableCheck) && !goog.isBoolean(opt_disableCheck)) { + throw new Error("opt_disableCheck must be a boolean if provided"); + } + if (!opt_disableCheck) { + var overwrites = []; + for (var key in mixinObj) { + if (this[key] !== undefined) { + overwrites.push(key); + } + } + if (overwrites.length) { + throw new Error('Mixin will overwrite block members: ' + + JSON.stringify(overwrites)); + } + } + goog.mixin(this, mixinObj); +}; + +/** + * Interpolate a message description onto the block. + * @param {string} message Text contains interpolation tokens (%1, %2, ...) + * that match with fields or inputs defined in the args array. + * @param {!Array} args Array of arguments to be interpolated. + * @param {string=} lastDummyAlign If a dummy input is added at the end, + * how should it be aligned? + * @private + */ +Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { + var tokens = Blockly.utils.tokenizeInterpolation(message); + // Interpolate the arguments. Build a list of elements. + var indexDup = []; + var indexCount = 0; + var elements = []; + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + if (typeof token == 'number') { + if (token <= 0 || token > args.length) { + throw new Error('Block "' + this.type + '": ' + + 'Message index %' + token + ' out of range.'); + } + if (indexDup[token]) { + throw new Error('Block "' + this.type + '": ' + + 'Message index %' + token + ' duplicated.'); + } + indexDup[token] = true; + indexCount++; + elements.push(args[token - 1]); + } else { + token = token.trim(); + if (token) { + elements.push(token); + } + } + } + if(indexCount != args.length) { + throw new Error('Block "' + this.type + '": ' + + 'Message does not reference all ' + args.length + ' arg(s).'); + } + // Add last dummy input if needed. + if (elements.length && (typeof elements[elements.length - 1] == 'string' || + goog.string.startsWith( + elements[elements.length - 1]['type'], 'field_'))) { + var dummyInput = {type: 'input_dummy'}; + if (lastDummyAlign) { + dummyInput['align'] = lastDummyAlign; + } + elements.push(dummyInput); + } + // Lookup of alignment constants. + var alignmentLookup = { + 'LEFT': Blockly.ALIGN_LEFT, + 'RIGHT': Blockly.ALIGN_RIGHT, + 'CENTRE': Blockly.ALIGN_CENTRE + }; + // Populate block with inputs and fields. + var fieldStack = []; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (typeof element == 'string') { + fieldStack.push([element, undefined]); + } else { + var field = null; + var input = null; + do { + var altRepeat = false; + if (typeof element == 'string') { + field = new Blockly.FieldLabel(element); + } else { + switch (element['type']) { + case 'input_value': + input = this.appendValueInput(element['name']); + break; + case 'input_statement': + input = this.appendStatementInput(element['name']); + break; + case 'input_dummy': + input = this.appendDummyInput(element['name']); + break; + case 'field_label': + field = Blockly.FieldLabel.fromJson(element); + break; + case 'field_input': + field = Blockly.FieldTextInput.fromJson(element); + break; + case 'field_angle': + field = Blockly.FieldAngle.fromJson(element); + break; + case 'field_checkbox': + field = Blockly.FieldCheckbox.fromJson(element); + break; + case 'field_colour': + field = Blockly.FieldColour.fromJson(element); + break; + case 'field_variable': + field = Blockly.FieldVariable.fromJson(element); + break; + case 'field_dropdown': + field = Blockly.FieldDropdown.fromJson(element); + break; + case 'field_image': + field = Blockly.FieldImage.fromJson(element); + break; + case 'field_number': + field = Blockly.FieldNumber.fromJson(element); + break; + case 'field_date': + if (Blockly.FieldDate) { + field = Blockly.FieldDate.fromJson(element); + break; + } + // Fall through if FieldDate is not compiled in. + default: + // Unknown field. + if (element['alt']) { + element = element['alt']; + altRepeat = true; + } + } + } + } while (altRepeat); + if (field) { + fieldStack.push([field, element['name']]); + } else if (input) { + if (element['check']) { + input.setCheck(element['check']); + } + if (element['align']) { + input.setAlign(alignmentLookup[element['align']]); + } + for (var j = 0; j < fieldStack.length; j++) { + input.appendField(fieldStack[j][0], fieldStack[j][1]); + } + fieldStack.length = 0; + } + } + } +}; + +/** + * Add a value input, statement input or local variable to this block. + * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or + * Blockly.DUMMY_INPUT. + * @param {string} name Language-neutral identifier which may used to find this + * input again. Should be unique to this block. + * @return {!Blockly.Input} The input object created. + * @private + */ +Blockly.Block.prototype.appendInput_ = function(type, name) { + var connection = null; + if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) { + connection = this.makeConnection_(type); + } + var input = new Blockly.Input(type, name, this, connection); + // Append input to list. + this.inputList.push(input); + return input; +}; + +/** + * Move a named input to a different location on this block. + * @param {string} name The name of the input to move. + * @param {?string} refName Name of input that should be after the moved input, + * or null to be the input at the end. + */ +Blockly.Block.prototype.moveInputBefore = function(name, refName) { + if (name == refName) { + return; + } + // Find both inputs. + var inputIndex = -1; + var refIndex = refName ? -1 : this.inputList.length; + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + inputIndex = i; + if (refIndex != -1) { + break; + } + } else if (refName && input.name == refName) { + refIndex = i; + if (inputIndex != -1) { + break; + } + } + } + goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name); + goog.asserts.assert( + refIndex != -1, 'Reference input "%s" not found.', refName); + this.moveNumberedInputBefore(inputIndex, refIndex); +}; + +/** + * Move a numbered input to a different location on this block. + * @param {number} inputIndex Index of the input to move. + * @param {number} refIndex Index of input that should be after the moved input. + */ +Blockly.Block.prototype.moveNumberedInputBefore = function( + inputIndex, refIndex) { + // Validate arguments. + goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); + goog.asserts.assert(inputIndex < this.inputList.length, + 'Input index ' + inputIndex + ' out of bounds.'); + goog.asserts.assert(refIndex <= this.inputList.length, + 'Reference input ' + refIndex + ' out of bounds.'); + // Remove input. + var input = this.inputList[inputIndex]; + this.inputList.splice(inputIndex, 1); + if (inputIndex < refIndex) { + refIndex--; + } + // Reinsert input. + this.inputList.splice(refIndex, 0, input); +}; + +/** + * Remove an input from this block. + * @param {string} name The name of the input. + * @param {boolean=} opt_quiet True to prevent error if input is not present. + * @throws {goog.asserts.AssertionError} if the input is not present and + * opt_quiet is not true. + */ +Blockly.Block.prototype.removeInput = function(name, opt_quiet) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + if (input.connection && input.connection.isConnected()) { + input.connection.setShadowDom(null); + var block = input.connection.targetBlock(); + if (block.isShadow()) { + // Destroy any attached shadow block. + block.dispose(); + } else { + // Disconnect any attached normal block. + block.unplug(); + } + } + input.dispose(); + this.inputList.splice(i, 1); + return; + } + } + if (!opt_quiet) { + goog.asserts.fail('Input "%s" not found.', name); + } +}; + +/** + * Fetches the named input object. + * @param {string} name The name of the input. + * @return {Blockly.Input} The input object, or null if input does not exist. + */ +Blockly.Block.prototype.getInput = function(name) { + for (var i = 0, input; input = this.inputList[i]; i++) { + if (input.name == name) { + return input; + } + } + // This input does not exist. + return null; +}; + +/** + * Fetches the block attached to the named input. + * @param {string} name The name of the input. + * @return {Blockly.Block} The attached value block, or null if the input is + * either disconnected or if the input does not exist. + */ +Blockly.Block.prototype.getInputTargetBlock = function(name) { + var input = this.getInput(name); + return input && input.connection && input.connection.targetBlock(); +}; + +/** + * Returns the comment on this block (or '' if none). + * @return {string} Block's comment. + */ +Blockly.Block.prototype.getCommentText = function() { + return this.comment || ''; +}; + +/** + * Set this block's comment text. + * @param {?string} text The text, or null to delete. + */ +Blockly.Block.prototype.setCommentText = function(text) { + if (this.comment != text) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this, 'comment', null, this.comment, text || '')); + this.comment = text; + } +}; + +/** + * Set this block's warning text. + * @param {?string} text The text, or null to delete. + * @param {string=} opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. + */ +Blockly.Block.prototype.setWarningText = function(text, + /* eslint-disable no-unused-vars */ opt_id + /* eslint-enable no-unused-vars */) { + // NOP. +}; + +/** + * Give this block a mutator dialog. + * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. + */ +Blockly.Block.prototype.setMutator = function( + /* eslint-disable no-unused-vars */ mutator + /* eslint-enable no-unused-vars */) { + // NOP. +}; + +/** + * Return the coordinates of the top-left corner of this block relative to the + * drawing surface's origin (0,0), in workspace units. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.Block.prototype.getRelativeToSurfaceXY = function() { + return this.xy_; +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. + */ +Blockly.Block.prototype.moveBy = function(dx, dy) { + goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); + var event = new Blockly.Events.BlockMove(this); + this.xy_.translate(dx, dy); + event.recordNew(); + Blockly.Events.fire(event); +}; + +/** + * Create a connection of the specified type. + * @param {number} type The type of the connection to create. + * @return {!Blockly.Connection} A new connection of the specified type. + * @private + */ +Blockly.Block.prototype.makeConnection_ = function(type) { + return new Blockly.Connection(this, type); +}; + +/** + * Recursively checks whether all statement and value inputs are filled with + * blocks. Also checks all following statement blocks in this stack. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling + * whether shadow blocks are counted as filled. Defaults to true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ +Blockly.Block.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) { + // Account for the shadow block filledness toggle. + if (opt_shadowBlocksAreFilled === undefined) { + opt_shadowBlocksAreFilled = true; + } + if (!opt_shadowBlocksAreFilled && this.isShadow()) { + return false; + } + + // Recursively check each input block of the current block. + for (var i = 0, input; input = this.inputList[i]; i++) { + if (!input.connection) { + continue; + } + var target = input.connection.targetBlock(); + if (!target || !target.allInputsFilled(opt_shadowBlocksAreFilled)) { + return false; + } + } + + // Recursively check the next block after the current block. + var next = this.getNextBlock(); + if (next) { + return next.allInputsFilled(opt_shadowBlocksAreFilled); + } + + return true; +}; + +/** + * This method returns a string describing this Block in developer terms (type + * name and ID; English only). + * + * Intended to on be used in console logs and errors. If you need a string that + * uses the user's native language (including block text, field values, and + * child blocks), use [toString()]{@link Blockly.Block#toString}. + * @return {string} The description. + */ +Blockly.Block.prototype.toDevString = function() { + var msg = this.type ? '"' + this.type + '" block' : 'Block'; + if (this.id) { + msg += ' (id="' + this.id + '")'; + } + return msg; +}; diff --git a/core/.svn/pristine/72/72a206fa7c2af71c582cd4a1463bdbb5339611f9.svn-base b/core/.svn/pristine/72/72a206fa7c2af71c582cd4a1463bdbb5339611f9.svn-base new file mode 100644 index 0000000..54ed1fb --- /dev/null +++ b/core/.svn/pristine/72/72a206fa7c2af71c582cd4a1463bdbb5339611f9.svn-base @@ -0,0 +1,274 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Blockly constants. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.constants'); + + +/** + * Number of pixels the mouse must move before a drag starts. + */ +Blockly.DRAG_RADIUS = 5; + +/** + * Number of pixels the mouse must move before a drag/scroll starts from the + * flyout. Because the drag-intention is determined when this is reached, it is + * larger than Blockly.DRAG_RADIUS so that the drag-direction is clearer. + */ +Blockly.FLYOUT_DRAG_RADIUS = 10; + +/** + * Maximum misalignment between connections for them to snap together. + */ +Blockly.SNAP_RADIUS = 20; + +/** + * Delay in ms between trigger and bumping unconnected block out of alignment. + */ +Blockly.BUMP_DELAY = 250; + +/** + * Number of characters to truncate a collapsed block to. + */ +Blockly.COLLAPSE_CHARS = 30; + +/** + * Length in ms for a touch to become a long press. + */ +Blockly.LONGPRESS = 750; + +/** + * Prevent a sound from playing if another sound preceded it within this many + * milliseconds. + */ +Blockly.SOUND_LIMIT = 100; + +/** + * When dragging a block out of a stack, split the stack in two (true), or drag + * out the block healing the stack (false). + */ +Blockly.DRAG_STACK = true; + +/** + * The richness of block colours, regardless of the hue. + * Must be in the range of 0 (inclusive) to 1 (exclusive). + */ +Blockly.HSV_SATURATION = 0.45; + +/** + * The intensity of block colours, regardless of the hue. + * Must be in the range of 0 (inclusive) to 1 (exclusive). + */ +Blockly.HSV_VALUE = 0.65; + +/** + * Sprited icons and images. + */ +Blockly.SPRITE = { + width: 96, + height: 124, + url: 'sprites.png' +}; + +// Constants below this point are not intended to be changed. + +/** + * Required name space for SVG elements. + * @const + */ +Blockly.SVG_NS = 'http://www.w3.org/2000/svg'; + +/** + * Required name space for HTML elements. + * @const + */ +Blockly.HTML_NS = 'http://www.w3.org/1999/xhtml'; + +/** + * ENUM for a right-facing value input. E.g. 'set item to' or 'return'. + * @const + */ +Blockly.INPUT_VALUE = 1; + +/** + * ENUM for a left-facing value output. E.g. 'random fraction'. + * @const + */ +Blockly.OUTPUT_VALUE = 2; + +/** + * ENUM for a down-facing block stack. E.g. 'if-do' or 'else'. + * @const + */ +Blockly.NEXT_STATEMENT = 3; + +/** + * ENUM for an up-facing block stack. E.g. 'break out of loop'. + * @const + */ +Blockly.PREVIOUS_STATEMENT = 4; + +/** + * ENUM for an dummy input. Used to add field(s) with no input. + * @const + */ +Blockly.DUMMY_INPUT = 5; + +/** + * ENUM for left alignment. + * @const + */ +Blockly.ALIGN_LEFT = -1; + +/** + * ENUM for centre alignment. + * @const + */ +Blockly.ALIGN_CENTRE = 0; + +/** + * ENUM for right alignment. + * @const + */ +Blockly.ALIGN_RIGHT = 1; + +/** + * ENUM for no drag operation. + * @const + */ +Blockly.DRAG_NONE = 0; + +/** + * ENUM for inside the sticky DRAG_RADIUS. + * @const + */ +Blockly.DRAG_STICKY = 1; + +/** + * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between + * clicks and drags. + * @const + */ +Blockly.DRAG_BEGIN = 1; + +/** + * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies). + * @const + */ +Blockly.DRAG_FREE = 2; + +/** + * Lookup table for determining the opposite type of a connection. + * @const + */ +Blockly.OPPOSITE_TYPE = []; +Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE; +Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE; +Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT; +Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT; + + +/** + * ENUM for toolbox and flyout at top of screen. + * @const + */ +Blockly.TOOLBOX_AT_TOP = 0; + +/** + * ENUM for toolbox and flyout at bottom of screen. + * @const + */ +Blockly.TOOLBOX_AT_BOTTOM = 1; + +/** + * ENUM for toolbox and flyout at left of screen. + * @const + */ +Blockly.TOOLBOX_AT_LEFT = 2; + +/** + * ENUM for toolbox and flyout at right of screen. + * @const + */ +Blockly.TOOLBOX_AT_RIGHT = 3; + +/** + * ENUM representing that an event is not in any delete areas. + * Null for backwards compatibility reasons. + * @const + */ +Blockly.DELETE_AREA_NONE = null; + +/** + * ENUM representing that an event is in the delete area of the trash can. + * @const + */ +Blockly.DELETE_AREA_TRASH = 1; + +/** + * ENUM representing that an event is in the delete area of the toolbox or + * flyout. + * @const + */ +Blockly.DELETE_AREA_TOOLBOX = 2; + +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * variable blocks. + * @const {string} + */ +Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE'; +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * variable blocks. + * @const {string} + */ +Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME = 'VARIABLE_DYNAMIC'; + +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * procedure blocks. + * @const {string} + */ +Blockly.PROCEDURE_CATEGORY_NAME = 'PROCEDURE'; + +/** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Rename + * variable...' and if selected, should trigger the prompt to rename a variable. + * @const {string} + */ +Blockly.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID'; + +/** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Delete the "%1" + * variable' and if selected, should trigger the prompt to delete a variable. + * @const {string} + */ +Blockly.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID'; diff --git a/core/.svn/pristine/7f/7f204e5324d99ab91f4435557adefa46cb44dc6a.svn-base b/core/.svn/pristine/7f/7f204e5324d99ab91f4435557adefa46cb44dc6a.svn-base new file mode 100644 index 0000000..7268e9b --- /dev/null +++ b/core/.svn/pristine/7f/7f204e5324d99ab91f4435557adefa46cb44dc6a.svn-base @@ -0,0 +1,381 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Horizontal flyout tray containing blocks which may be created. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.HorizontalFlyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.Flyout'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @extends {Blockly.Flyout} + * @constructor + */ +Blockly.HorizontalFlyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions); + /** + * Flyout should be laid out horizontally. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = true; +}; +goog.inherits(Blockly.HorizontalFlyout, Blockly.Flyout); + +/** + * Return an object with all the metrics required to size scrollbars for the + * flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the contents, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .absoluteTop: Top-edge of view. + * .viewLeft: Offset of the left edge of visible rectangle from parent, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteLeft: Left-edge of view. + * @return {Object} Contains size and position metrics of the flyout. + * @private + */ +Blockly.HorizontalFlyout.prototype.getMetrics_ = function() { + if (!this.isVisible()) { + // Flyout is hidden. + return null; + } + + try { + var optionBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + var optionBox = {height: 0, y: 0, width: 0, x: 0}; + } + + var absoluteTop = this.SCROLLBAR_PADDING; + var absoluteLeft = this.SCROLLBAR_PADDING; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + absoluteTop = 0; + } + var viewHeight = this.height_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + viewHeight -= this.SCROLLBAR_PADDING; + } + var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; + + var metrics = { + viewHeight: viewHeight, + viewWidth: viewWidth, + contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, + contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, + viewTop: -this.workspace_.scrollY, + viewLeft: -this.workspace_.scrollX, + contentTop: optionBox.y, + contentLeft: optionBox.x, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft + }; + return metrics; +}; + +/** + * Sets the translation of the flyout to match the scrollbars. + * @param {!Object} xyRatio Contains a y property which is a float + * between 0 and 1 specifying the degree of scrolling and a + * similar x property. + * @private + */ +Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) { + var metrics = this.getMetrics_(); + // This is a fix to an apparent race condition. + if (!metrics) { + return; + } + + if (goog.isNumber(xyRatio.x)) { + this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x; + } + + this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, + this.workspace_.scrollY + metrics.absoluteTop); +}; + +/** + * Move the flyout to the edge of the workspace. + */ +Blockly.HorizontalFlyout.prototype.position = function() { + if (!this.isVisible()) { + return; + } + var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return; + } + // Record the width for Blockly.Flyout.getMetrics_. + this.width_ = targetWorkspaceMetrics.viewWidth; + + var edgeWidth = targetWorkspaceMetrics.viewWidth - 2 * this.CORNER_RADIUS; + var edgeHeight = this.height_ - this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + var x = targetWorkspaceMetrics.absoluteLeft; + var y = targetWorkspaceMetrics.absoluteTop; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + y += (targetWorkspaceMetrics.viewHeight - this.height_); + } + this.positionAt_(this.width_, this.height_, x, y); +}; + +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width, + height) { + var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; + // Start at top left. + var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; + + if (atTop) { + // Top. + path.push('h', width + 2 * this.CORNER_RADIUS); + // Right. + path.push('v', height); + // Bottom. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('h', -1 * width); + // Left. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('z'); + } else { + // Top. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('h', width); + // Right. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('v', height); + // Bottom. + path.push('h', -width - 2 * this.CORNER_RADIUS); + // Left. + path.push('z'); + } + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Scroll the flyout to the top. + */ +Blockly.HorizontalFlyout.prototype.scrollToStart = function() { + this.scrollbar_.set(this.RTL ? Infinity : 0); +}; + +/** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @private + */ +Blockly.HorizontalFlyout.prototype.wheel_ = function(e) { + var delta = e.deltaX; + + if (delta) { + // Firefox's mouse wheel deltas are a tenth that of Chrome/Safari. + // DeltaMode is 1 for a mouse wheel, but not for a trackpad scroll event + if (goog.userAgent.GECKO && (e.deltaMode === 1)) { + delta *= 10; + } + // TODO: #1093 + var metrics = this.getMetrics_(); + var pos = metrics.viewLeft + delta; + var limit = metrics.contentWidth - metrics.viewWidth; + pos = Math.min(pos, limit); + pos = Math.max(pos, 0); + this.scrollbar_.set(pos); + // When the flyout moves from a wheel event, hide WidgetDiv. + Blockly.WidgetDiv.hide(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array.} contents The blocks and buttons to lay out. + * @param {!Array.} gaps The visible gaps between blocks. + * @private + */ +Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) { + this.workspace_.scale = this.targetWorkspace_.scale; + var margin = this.MARGIN; + var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; + var cursorY = margin; + if (this.RTL) { + contents = contents.reverse(); + } + + for (var i = 0, item; item = contents[i]; i++) { + if (item.type == 'block') { + var block = item.block; + var allBlocks = block.getDescendants(); + for (var j = 0, child; child = allBlocks[j]; j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such a + // block. + child.isInFlyout = true; + } + block.render(); + var root = block.getSvgRoot(); + var blockHW = block.getHeightWidth(); + + // Figure out where to place the block. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + if (this.RTL) { + var moveX = cursorX + blockHW.width; + } else { + var moveX = cursorX + tab; + } + block.moveBy(moveX, cursorY); + + var rect = this.createRect_(block, moveX, cursorY, blockHW, i); + cursorX += (blockHW.width + gaps[i]); + + this.addBlockListeners_(root, block, rect); + } else if (item.type == 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorX += (item.button.width + gaps[i]); + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} true if the drag is toward the workspace. + * @package + */ +Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function( + currentDragDeltaXY) { + var dx = currentDragDeltaXY.x; + var dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var range = this.dragAngleRange_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + // Horizontal at top. + if (dragDirection < 90 + range && dragDirection > 90 - range) { + return true; + } + } else { + // Horizontal at bottom. + if (dragDirection > -90 - range && dragDirection < -90 + range) { + return true; + } + } + return false; +}; + +/** + * Return the deletion rectangle for this flyout in viewport coordinates. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.HorizontalFlyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout + // area are still deleted. Must be larger than the largest screen size, + // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). + var BIG_NUM = 1000000000; + var y = flyoutRect.top; + var height = flyoutRect.height; + + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2, + BIG_NUM + height); + } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2, + BIG_NUM + height); + } + // TODO: Else throw error (should never happen). +}; + +/** + * Compute height of flyout. Position mat under each block. + * For RTL: Lay out the blocks right-aligned. + * @private + */ +Blockly.HorizontalFlyout.prototype.reflowInternal_ = function() { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutHeight = 0; + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); + } + flyoutHeight += this.MARGIN * 1.5; + flyoutHeight *= this.workspace_.scale; + flyoutHeight += Blockly.Scrollbar.scrollbarThickness; + + if (this.height_ != flyoutHeight) { + for (var i = 0, block; block = blocks[i]; i++) { + if (block.flyoutRect_) { + this.moveRectToBlock_(block.flyoutRect_, block); + } + } + // Record the height for .getMetrics_ and .position. + this.height_ = flyoutHeight; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; diff --git a/core/.svn/pristine/85/85973fcd13c213a54d38641632f960622904efe4.svn-base b/core/.svn/pristine/85/85973fcd13c213a54d38641632f960622904efe4.svn-base new file mode 100644 index 0000000..be8a0e1 --- /dev/null +++ b/core/.svn/pristine/85/85973fcd13c213a54d38641632f960622904efe4.svn-base @@ -0,0 +1,252 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 A div that floats on top of Blockly. This singleton contains + * temporary HTML UI widgets that the user is currently interacting with. + * E.g. text input areas, colour pickers, context menus. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.WidgetDiv + * @namespace + **/ +goog.provide('Blockly.WidgetDiv'); + +goog.require('Blockly.Css'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.style'); + + +/** + * The HTML container. Set once by Blockly.WidgetDiv.createDom. + * @type {Element} + */ +Blockly.WidgetDiv.DIV = null; + +/** + * The object currently using this container. + * @type {Object} + * @private + */ +Blockly.WidgetDiv.owner_ = null; + +/** + * Optional cleanup function set by whichever object uses the widget. + * @type {Function} + * @private + */ +Blockly.WidgetDiv.dispose_ = null; + +/** + * Create the widget div and inject it onto the page. + */ +Blockly.WidgetDiv.createDom = function() { + if (Blockly.WidgetDiv.DIV) { + return; // Already created. + } + // Create an HTML container for popup overlays (e.g. editor widgets). + Blockly.WidgetDiv.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyWidgetDiv'); + document.body.appendChild(Blockly.WidgetDiv.DIV); +}; + +/** + * Initialize and display the widget div. Close the old one if needed. + * @param {!Object} newOwner The object that will be using this container. + * @param {boolean} rtl Right-to-left (true) or left-to-right (false). + * @param {Function} dispose Optional cleanup function to be run when the widget + * is closed. + */ +Blockly.WidgetDiv.show = function(newOwner, rtl, dispose) { + Blockly.WidgetDiv.hide(); + Blockly.WidgetDiv.owner_ = newOwner; + Blockly.WidgetDiv.dispose_ = dispose; + // Temporarily move the widget to the top of the screen so that it does not + // cause a scrollbar jump in Firefox when displayed. + var xy = goog.style.getViewportPageOffset(document); + Blockly.WidgetDiv.DIV.style.top = xy.y + 'px'; + Blockly.WidgetDiv.DIV.style.direction = rtl ? 'rtl' : 'ltr'; + Blockly.WidgetDiv.DIV.style.display = 'block'; +}; + +/** + * Destroy the widget and hide the div. + */ +Blockly.WidgetDiv.hide = function() { + if (Blockly.WidgetDiv.owner_) { + Blockly.WidgetDiv.owner_ = null; + Blockly.WidgetDiv.DIV.style.display = 'none'; + Blockly.WidgetDiv.DIV.style.left = ''; + Blockly.WidgetDiv.DIV.style.top = ''; + Blockly.WidgetDiv.dispose_ && Blockly.WidgetDiv.dispose_(); + Blockly.WidgetDiv.dispose_ = null; + goog.dom.removeChildren(Blockly.WidgetDiv.DIV); + } +}; + +/** + * Is the container visible? + * @return {boolean} True if visible. + */ +Blockly.WidgetDiv.isVisible = function() { + return !!Blockly.WidgetDiv.owner_; +}; + +/** + * Destroy the widget and hide the div if it is being used by the specified + * object. + * @param {!Object} oldOwner The object that was using this container. + */ +Blockly.WidgetDiv.hideIfOwner = function(oldOwner) { + if (Blockly.WidgetDiv.owner_ == oldOwner) { + Blockly.WidgetDiv.hide(); + } +}; + +/** + * Position the widget at a given location. Prevent the widget from going + * offscreen top or left (right in RTL). + * @param {number} anchorX Horizontal location (window coordinates, not body). + * @param {number} anchorY Vertical location (window coordinates, not body). + * @param {!goog.math.Size} windowSize Height/width of window. + * @param {!goog.math.Coordinate} scrollOffset X/y of window scrollbars. + * @param {boolean} rtl True if RTL, false if LTR. + */ +Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize, + scrollOffset, rtl) { + // Don't let the widget go above the top edge of the window. + if (anchorY < scrollOffset.y) { + anchorY = scrollOffset.y; + } + if (rtl) { + // Don't let the widget go right of the right edge of the window. + if (anchorX > windowSize.width + scrollOffset.x) { + anchorX = windowSize.width + scrollOffset.x; + } + } else { + // Don't let the widget go left of the left edge of the window. + if (anchorX < scrollOffset.x) { + anchorX = scrollOffset.x; + } + } + Blockly.WidgetDiv.positionInternal_(anchorX, anchorY, windowSize.height); +}; + +/** + * Set the widget div's position and height. This function does nothing clever: + * it will not ensure that your widget div ends up in the visible window. + * @param {number} x Horizontal location (window coordinates, not body). + * @param {number} y Vertical location (window coordinates, not body). + * @param {number} height The height of the widget div (pixels). + * @private + */ +Blockly.WidgetDiv.positionInternal_ = function(x, y, height) { + Blockly.WidgetDiv.DIV.style.left = x + 'px'; + Blockly.WidgetDiv.DIV.style.top = y + 'px'; + Blockly.WidgetDiv.DIV.style.height = height + 'px'; +}; + +/** + * Position the widget div based on an anchor rectangle. + * The widget should be placed adjacent to but not overlapping the anchor + * rectangle. The preferred position is directly below and aligned to the left + * (ltr) or right (rtl) side of the anchor. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {!goog.math.Size} widgetSize The size of the widget that is inside the + * widget div, in window coordinates. + * @param {boolean} rtl Whether the workspace is in RTL mode. This determines + * horizontal alignment. + * @package + */ +Blockly.WidgetDiv.positionWithAnchor = function(viewportBBox, anchorBBox, + widgetSize, rtl) { + var y = Blockly.WidgetDiv.calculateY_(viewportBBox, anchorBBox, widgetSize); + var x = Blockly.WidgetDiv.calculateX_(viewportBBox, anchorBBox, widgetSize, + rtl); + + Blockly.WidgetDiv.positionInternal_(x, y, widgetSize.height); +}; + +/** + * Calculate an x position (in window coordinates) such that the widget will not + * be offscreen on the right or left. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {goog.math.Size} widgetSize The dimensions of the widget inside the + * widget div. + * @param {boolean} rtl Whether the Blockly workspace is in RTL mode. + * @return {number} A valid x-coordinate for the top left corner of the widget + * div, in window coordinates. + * @private + */ +Blockly.WidgetDiv.calculateX_ = function(viewportBBox, anchorBBox, widgetSize, + rtl) { + if (rtl) { + // Try to align the right side of the field and the right side of the widget. + var widgetLeft = anchorBBox.right - widgetSize.width; + // Don't go offscreen left. + var x = Math.max(widgetLeft, viewportBBox.left); + // But really don't go offscreen right: + return Math.min(x, viewportBBox.right - widgetSize.width); + } else { + // Try to align the left side of the field and the left side of the widget. + // Don't go offscreen right. + var x = Math.min(anchorBBox.left, + viewportBBox.right - widgetSize.width); + // But left is more important, because that's where the text is. + return Math.max(x, viewportBBox.left); + } +}; + +/** + * Calculate a y position (in window coordinates) such that the widget will not + * be offscreen on the top or bottom. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {goog.math.Size} widgetSize The dimensions of the widget inside the + * widget div. + * @return {number} A valid y-coordinate for the top left corner of the widget + * div, in window coordinates. + * @private + */ +Blockly.WidgetDiv.calculateY_ = function(viewportBBox, anchorBBox, widgetSize) { + // Flip the widget vertically if off the bottom. + if (anchorBBox.bottom + widgetSize.height >= + viewportBBox.bottom) { + // The bottom of the widget is at the top of the field. + return anchorBBox.top - widgetSize.height; + // The widget could go off the top of the window, but it would also go off + // the bottom. The window is just too small. + } else { + // The top of the widget is at the bottom of the field. + return anchorBBox.bottom; + } +}; diff --git a/core/.svn/pristine/86/86b4ec9bf879f9bfee7d54bd8e2ea287742665ef.svn-base b/core/.svn/pristine/86/86b4ec9bf879f9bfee7d54bd8e2ea287742665ef.svn-base new file mode 100644 index 0000000..5559c43 --- /dev/null +++ b/core/.svn/pristine/86/86b4ec9bf879f9bfee7d54bd8e2ea287742665ef.svn-base @@ -0,0 +1,379 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Layout code for a vertical variant of the flyout. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.VerticalFlyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.Flyout'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.utils'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @extends {Blockly.Flyout} + * @constructor + */ +Blockly.VerticalFlyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions); + /** + * Flyout should be laid out vertically. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = false; +}; +goog.inherits(Blockly.VerticalFlyout, Blockly.Flyout); + +/** + * Return an object with all the metrics required to size scrollbars for the + * flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the contents, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .absoluteTop: Top-edge of view. + * .viewLeft: Offset of the left edge of visible rectangle from parent, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteLeft: Left-edge of view. + * @return {Object} Contains size and position metrics of the flyout. + * @private + */ +Blockly.VerticalFlyout.prototype.getMetrics_ = function() { + if (!this.isVisible()) { + // Flyout is hidden. + return null; + } + + try { + var optionBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + var optionBox = {height: 0, y: 0, width: 0, x: 0}; + } + + // Padding for the end of the scrollbar. + var absoluteTop = this.SCROLLBAR_PADDING; + var absoluteLeft = 0; + + var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; + var viewWidth = this.width_; + if (!this.RTL) { + viewWidth -= this.SCROLLBAR_PADDING; + } + + var metrics = { + viewHeight: viewHeight, + viewWidth: viewWidth, + contentHeight: optionBox.height * this.workspace_.scale + 2 * this.MARGIN, + contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN, + viewTop: -this.workspace_.scrollY + optionBox.y, + viewLeft: -this.workspace_.scrollX, + contentTop: optionBox.y, + contentLeft: optionBox.x, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft + }; + return metrics; +}; + +/** + * Sets the translation of the flyout to match the scrollbars. + * @param {!Object} xyRatio Contains a y property which is a float + * between 0 and 1 specifying the degree of scrolling and a + * similar x property. + * @private + */ +Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { + var metrics = this.getMetrics_(); + // This is a fix to an apparent race condition. + if (!metrics) { + return; + } + if (goog.isNumber(xyRatio.y)) { + this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y; + } + this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, + this.workspace_.scrollY + metrics.absoluteTop); +}; + +/** + * Move the flyout to the edge of the workspace. + */ +Blockly.VerticalFlyout.prototype.position = function() { + if (!this.isVisible()) { + return; + } + var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return; + } + // Record the height for Blockly.Flyout.getMetrics_ + this.height_ = targetWorkspaceMetrics.viewHeight; + + var edgeWidth = this.width_ - this.CORNER_RADIUS; + var edgeHeight = targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + var y = targetWorkspaceMetrics.absoluteTop; + var x = targetWorkspaceMetrics.absoluteLeft; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { + x += (targetWorkspaceMetrics.viewWidth - this.width_); + } + this.positionAt_(this.width_, this.height_, x, y); +}; + +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) { + var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT; + var totalWidth = width + this.CORNER_RADIUS; + + // Decide whether to start on the left or right. + var path = ['M ' + (atRight ? totalWidth : 0) + ',0']; + // Top. + path.push('h', atRight ? -width : width); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Side closest to workspace. + path.push('v', Math.max(0, height)); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Bottom. + path.push('h', atRight ? width : -width); + path.push('z'); + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Scroll the flyout to the top. + */ +Blockly.VerticalFlyout.prototype.scrollToStart = function() { + this.scrollbar_.set(0); +}; + +/** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @private + */ +Blockly.VerticalFlyout.prototype.wheel_ = function(e) { + var delta = e.deltaY; + + if (delta) { + if (goog.userAgent.GECKO) { + // Firefox's deltas are a tenth that of Chrome/Safari. + delta *= 10; + } + var metrics = this.getMetrics_(); + var pos = (metrics.viewTop - metrics.contentTop) + delta; + var limit = metrics.contentHeight - metrics.viewHeight; + pos = Math.min(pos, limit); + pos = Math.max(pos, 0); + this.scrollbar_.set(pos); + // When the flyout moves from a wheel event, hide WidgetDiv. + Blockly.WidgetDiv.hide(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array.} contents The blocks and buttons to lay out. + * @param {!Array.} gaps The visible gaps between blocks. + * @private + */ +Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) { + this.workspace_.scale = this.targetWorkspace_.scale; + var margin = this.MARGIN; + var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; + var cursorY = margin; + + for (var i = 0, item; item = contents[i]; i++) { + if (item.type == 'block') { + var block = item.block; + var allBlocks = block.getDescendants(); + for (var j = 0, child; child = allBlocks[j]; j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such a + // block. + child.isInFlyout = true; + } + block.render(); + var root = block.getSvgRoot(); + var blockHW = block.getHeightWidth(); + block.moveBy(cursorX, cursorY); + + var rect = this.createRect_(block, + this.RTL ? cursorX - blockHW.width : cursorX, cursorY, blockHW, i); + + this.addBlockListeners_(root, block, rect); + + cursorY += blockHW.height + gaps[i]; + } else if (item.type == 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorY += item.button.height + gaps[i]; + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} true if the drag is toward the workspace. + * @package + */ +Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function( + currentDragDeltaXY) { + var dx = currentDragDeltaXY.x; + var dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var range = this.dragAngleRange_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + // Vertical at left. + if (dragDirection < range && dragDirection > -range) { + return true; + } + } else { + // Vertical at right. + if (dragDirection < -180 + range || dragDirection > 180 - range) { + return true; + } + } + return false; +}; + +/** + * Return the deletion rectangle for this flyout in viewport coordinates. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.VerticalFlyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout + // area are still deleted. Must be larger than the largest screen size, + // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). + var BIG_NUM = 1000000000; + var x = flyoutRect.left; + var width = flyoutRect.width; + + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width, + BIG_NUM * 2); + } else { // Right + return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2); + } +}; + +/** + * Compute width of flyout. Position mat under each block. + * For RTL: Lay out the blocks and buttons to be right-aligned. + * @private + */ +Blockly.VerticalFlyout.prototype.reflowInternal_ = function() { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutWidth = 0; + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + var width = block.getHeightWidth().width; + if (block.outputConnection) { + width -= Blockly.BlockSvg.TAB_WIDTH; + } + flyoutWidth = Math.max(flyoutWidth, width); + } + for (var i = 0, button; button = this.buttons_[i]; i++) { + flyoutWidth = Math.max(flyoutWidth, button.width); + } + flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH; + flyoutWidth *= this.workspace_.scale; + flyoutWidth += Blockly.Scrollbar.scrollbarThickness; + + if (this.width_ != flyoutWidth) { + for (var i = 0, block; block = blocks[i]; i++) { + if (this.RTL) { + // With the flyoutWidth known, right-align the blocks. + var oldX = block.getRelativeToSurfaceXY().x; + var newX = flyoutWidth / this.workspace_.scale - this.MARGIN - + Blockly.BlockSvg.TAB_WIDTH; + block.moveBy(newX - oldX, 0); + } + if (block.flyoutRect_) { + this.moveRectToBlock_(block.flyoutRect_, block); + } + } + if (this.RTL) { + // With the flyoutWidth known, right-align the buttons. + for (var i = 0, button; button = this.buttons_[i]; i++) { + var y = button.getPosition().y; + var x = flyoutWidth / this.workspace_.scale - button.width - this.MARGIN - + Blockly.BlockSvg.TAB_WIDTH; + button.moveTo(x, y); + } + } + // Record the width for .getMetrics_ and .position. + this.width_ = flyoutWidth; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; diff --git a/core/.svn/pristine/8d/8d534e3b253efe6d9c74f265ae07973d9bbcfadb.svn-base b/core/.svn/pristine/8d/8d534e3b253efe6d9c74f265ae07973d9bbcfadb.svn-base new file mode 100644 index 0000000..d723bab --- /dev/null +++ b/core/.svn/pristine/8d/8d534e3b253efe6d9c74f265ae07973d9bbcfadb.svn-base @@ -0,0 +1,129 @@ +/** + * @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 Checkbox field. Checked or not checked. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldCheckbox'); + +goog.require('Blockly.Field'); + + +/** + * Class for a checkbox field. + * @param {string} state The initial state of the field ('TRUE' or 'FALSE'). + * @param {Function=} opt_validator A function that is executed when a new + * option is selected. Its sole argument is the new checkbox state. If + * it returns a value, this becomes the new checkbox state, unless the + * value is null, in which case the change is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldCheckbox = function(state, opt_validator) { + Blockly.FieldCheckbox.superClass_.constructor.call(this, '', opt_validator); + // Set the initial state. + this.setValue(state); +}; +goog.inherits(Blockly.FieldCheckbox, Blockly.Field); + +/** + * Construct a FieldCheckbox from a JSON arg object. + * @param {!Object} options A JSON object with options (checked). + * @returns {!Blockly.FieldCheckbox} The new field instance. + * @package + */ +Blockly.FieldCheckbox.fromJson = function(options) { + return new Blockly.FieldCheckbox(options['checked'] ? 'TRUE' : 'FALSE'); +}; + +/** + * Character for the checkmark. + */ +Blockly.FieldCheckbox.CHECK_CHAR = '\u2713'; + +/** + * Mouse cursor style when over the hotspot that initiates editability. + */ +Blockly.FieldCheckbox.prototype.CURSOR = 'default'; + +/** + * Install this checkbox on a block. + */ +Blockly.FieldCheckbox.prototype.init = function() { + if (this.fieldGroup_) { + // Checkbox has already been initialized once. + return; + } + Blockly.FieldCheckbox.superClass_.init.call(this); + // The checkbox doesn't use the inherited text element. + // Instead it uses a custom checkmark element that is either visible or not. + this.checkElement_ = Blockly.utils.createSvgElement('text', + {'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14}, + this.fieldGroup_); + var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR); + this.checkElement_.appendChild(textNode); + this.checkElement_.style.display = this.state_ ? 'block' : 'none'; +}; + +/** + * Return 'TRUE' if the checkbox is checked, 'FALSE' otherwise. + * @return {string} Current state. + */ +Blockly.FieldCheckbox.prototype.getValue = function() { + return String(this.state_).toUpperCase(); +}; + +/** + * Set the checkbox to be checked if newBool is 'TRUE' or true, + * unchecks otherwise. + * @param {string|boolean} newBool New state. + */ +Blockly.FieldCheckbox.prototype.setValue = function(newBool) { + var newState = (typeof newBool == 'string') ? + (newBool.toUpperCase() == 'TRUE') : !!newBool; + if (this.state_ !== newState) { + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.state_, newState)); + } + this.state_ = newState; + if (this.checkElement_) { + this.checkElement_.style.display = newState ? 'block' : 'none'; + } + } +}; + +/** + * Toggle the state of the checkbox. + * @private + */ +Blockly.FieldCheckbox.prototype.showEditor_ = function() { + var newState = !this.state_; + if (this.sourceBlock_) { + // Call any validation function, and allow it to override. + newState = this.callValidator(newState); + } + if (newState !== null) { + this.setValue(String(newState).toUpperCase()); + } +}; diff --git a/core/.svn/pristine/8e/8e1fc7dbbe82a36328bd13dfd32988028c0bf94b.svn-base b/core/.svn/pristine/8e/8e1fc7dbbe82a36328bd13dfd32988028c0bf94b.svn-base new file mode 100644 index 0000000..be1edb3 --- /dev/null +++ b/core/.svn/pristine/8e/8e1fc7dbbe82a36328bd13dfd32988028c0bf94b.svn-base @@ -0,0 +1,220 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 A class that manages a surface for dragging blocks. When a + * block drag is started, we move the block (and children) to a separate DOM + * element that we move around using translate3d. At the end of the drag, the + * blocks are put back in into the SVG they came from. This helps performance by + * avoiding repainting the entire SVG on every mouse move while dragging blocks. + * @author picklesrus + */ + +'use strict'; + +goog.provide('Blockly.BlockDragSurfaceSvg'); +goog.require('Blockly.utils'); +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a drag surface for the currently dragged block. This is a separate + * SVG that contains only the currently moving block, or nothing. + * @param {!Element} container Containing element. + * @constructor + */ +Blockly.BlockDragSurfaceSvg = function(container) { + /** + * @type {!Element} + * @private + */ + this.container_ = container; + this.createDom(); +}; + +/** + * The SVG drag surface. Set once by Blockly.BlockDragSurfaceSvg.createDom. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.SVG_ = null; + +/** + * This is where blocks live while they are being dragged if the drag surface + * is enabled. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.dragGroup_ = null; + +/** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.container_ = null; + +/** + * Cached value for the scale of the drag surface. + * Used to set/get the correct translation during and after a drag. + * @type {number} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1; + +/** + * Cached value for the translation of the drag surface. + * This translation is in pixel units, because the scale is applied to the + * drag group rather than the top-level SVG. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null; + +/** + * Create the drag surface and inject it into the container. + */ +Blockly.BlockDragSurfaceSvg.prototype.createDom = function() { + if (this.SVG_) { + return; // Already created. + } + this.SVG_ = Blockly.utils.createSvgElement('svg', { + 'xmlns': Blockly.SVG_NS, + 'xmlns:html': Blockly.HTML_NS, + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklyBlockDragSurface' + }, this.container_); + this.dragGroup_ = Blockly.utils.createSvgElement('g', {}, this.SVG_); +}; + +/** + * Set the SVG blocks on the drag surface's group and show the surface. + * Only one block group should be on the drag surface at a time. + * @param {!Element} blocks Block or group of blocks to place on the drag + * surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) { + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Already dragging a block.'); + // appendChild removes the blocks from the previous parent + this.dragGroup_.appendChild(blocks); + this.SVG_.style.display = 'block'; + this.surfaceXY_ = new goog.math.Coordinate(0, 0); +}; + +/** + * Translate and scale the entire drag surface group to the given position, to + * keep in sync with the workspace. + * @param {number} x X translation in workspace coordinates. + * @param {number} y Y translation in workspace coordinates. + * @param {number} scale Scale of the group. + */ +Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) { + this.scale_ = scale; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + this.dragGroup_.setAttribute('transform', 'translate('+ x + ','+ y + ')' + + ' scale(' + scale + ')'); +}; + +/** + * Translate the drag surface's SVG based on its internal state. + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() { + var x = this.surfaceXY_.x; + var y = this.surfaceXY_.y; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + this.SVG_.style.display = 'block'; + + Blockly.utils.setCssTransform(this.SVG_, + 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); +}; + +/** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface. + * @param {number} y Y translation for the entire surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) { + this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_); + this.translateSurfaceInternal_(); +}; + +/** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!goog.math.Coordinate} Current translation of the surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() { + var xy = Blockly.utils.getRelativeXY(this.SVG_); + return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_); +}; + +/** + * Provide a reference to the drag group (primarily for + * BlockSvg.getRelativeToSurfaceXY). + * @return {Element} Drag surface group element. + */ +Blockly.BlockDragSurfaceSvg.prototype.getGroup = function() { + return this.dragGroup_; +}; + +/** + * Get the current blocks on the drag surface, if any (primarily + * for BlockSvg.getRelativeToSurfaceXY). + * @return {!Element|undefined} Drag surface block DOM element, or undefined + * if no blocks exist. + */ +Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() { + return this.dragGroup_.firstChild; +}; + +/** + * Clear the group and hide the surface; move the blocks off onto the provided + * element. + * If the block is being deleted it doesn't need to go back to the original + * surface, since it would be removed immediately during dispose. + * @param {Element=} opt_newSurface Surface the dragging blocks should be moved + * to, or null if the blocks should be removed from this surface without + * being moved to a different surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) { + if (opt_newSurface) { + // appendChild removes the node from this.dragGroup_ + opt_newSurface.appendChild(this.getCurrentBlock()); + } else { + this.dragGroup_.removeChild(this.getCurrentBlock()); + } + this.SVG_.style.display = 'none'; + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Drag group was not cleared.'); + this.surfaceXY_ = null; +}; diff --git a/core/.svn/pristine/92/92d7b8fc4d423e52d7f9e9f18b8a17365d8d1750.svn-base b/core/.svn/pristine/92/92d7b8fc4d423e52d7f9e9f18b8a17365d8d1750.svn-base new file mode 100644 index 0000000..2372e2b --- /dev/null +++ b/core/.svn/pristine/92/92d7b8fc4d423e52d7f9e9f18b8a17365d8d1750.svn-base @@ -0,0 +1,289 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Object representing a code comment. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Comment'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); +goog.require('goog.userAgent'); + + +/** + * Class for a comment. + * @param {!Blockly.Block} block The block associated with this comment. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Comment = function(block) { + Blockly.Comment.superClass_.constructor.call(this, block); + this.createIcon(); +}; +goog.inherits(Blockly.Comment, Blockly.Icon); + +/** + * Comment text (if bubble is not visible). + * @private + */ +Blockly.Comment.prototype.text_ = ''; + +/** + * Width of bubble. + * @private + */ +Blockly.Comment.prototype.width_ = 160; + +/** + * Height of bubble. + * @private + */ +Blockly.Comment.prototype.height_ = 80; + +/** + * Draw the comment icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Comment.prototype.drawIcon_ = function(group) { + // Circle. + Blockly.utils.createSvgElement('circle', + {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, + group); + // Can't use a real '?' text character since different browsers and operating + // systems render it differently. + // Body of question mark. + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + + '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + + '-1.201,0.998 -1.201,1.528 -1.204,2.19z'}, + group); + // Dot of question mark. + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconSymbol', + 'x': '6.8', + 'y': '10.78', + 'height': '2', + 'width': '2' + }, + group); +}; + +/** + * Create the editor for the comment's bubble. + * @return {!Element} The top-level node of the editor. + * @private + */ +Blockly.Comment.prototype.createEditor_ = function() { + /* Create the editor. Here's the markup that will be generated: + + + + + + */ + this.foreignObject_ = Blockly.utils.createSvgElement('foreignObject', + {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, + null); + var body = document.createElementNS(Blockly.HTML_NS, 'body'); + body.setAttribute('xmlns', Blockly.HTML_NS); + body.className = 'blocklyMinimalBody'; + var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea'); + textarea.className = 'blocklyCommentTextarea'; + textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR'); + body.appendChild(textarea); + this.textarea_ = textarea; + this.foreignObject_.appendChild(body); + Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_); + // Don't zoom with mousewheel. + Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) { + e.stopPropagation(); + }); + Blockly.bindEventWithChecks_(textarea, 'change', this, function( + /* eslint-disable no-unused-vars */ e + /* eslint-enable no-unused-vars */) { + if (this.text_ != textarea.value) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.block_, 'comment', null, this.text_, textarea.value)); + this.text_ = textarea.value; + } + }); + setTimeout(function() { + textarea.focus(); + }, 0); + return this.foreignObject_; +}; + +/** + * Add or remove editability of the comment. + * @override + */ +Blockly.Comment.prototype.updateEditable = function() { + if (this.isVisible()) { + // Toggling visibility will force a rerendering. + this.setVisible(false); + this.setVisible(true); + } + // Allow the icon to update. + Blockly.Icon.prototype.updateEditable.call(this); +}; + +/** + * Callback function triggered when the bubble has resized. + * Resize the text area accordingly. + * @private + */ +Blockly.Comment.prototype.resizeBubble_ = function() { + if (this.isVisible()) { + var size = this.bubble_.getBubbleSize(); + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth); + this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth); + this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px'; + this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px'; + } +}; + +/** + * Show or hide the comment bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Comment.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible)); + if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) { + // Steal the code from warnings to make an uneditable text bubble. + // MSIE does not support foreignobject; textareas are impossible. + // http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx + // Always treat comments in IE as uneditable. + Blockly.Warning.prototype.setVisible.call(this, visible); + return; + } + // Save the bubble stats before the visibility switch. + var text = this.getText(); + var size = this.getBubbleSize(); + if (visible) { + // Create the bubble. + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.svgPath_, + this.iconXY_, this.width_, this.height_); + this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this)); + this.updateColour(); + } else { + // Dispose of the bubble. + this.bubble_.dispose(); + this.bubble_ = null; + this.textarea_ = null; + this.foreignObject_ = null; + } + // Restore the bubble stats after the visibility switch. + this.setText(text); + this.setBubbleSize(size.width, size.height); +}; + +/** + * Bring the comment to the top of the stack when clicked on. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Comment.prototype.textareaFocus_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + // Ideally this would be hooked to the focus event for the comment. + // However doing so in Firefox swallows the cursor for unknown reasons. + // So this is hooked to mouseup instead. No big deal. + this.bubble_.promote_(); + // Since the act of moving this node within the DOM causes a loss of focus, + // we need to reapply the focus. + this.textarea_.focus(); +}; + +/** + * Get the dimensions of this comment's bubble. + * @return {!Object} Object with width and height properties. + */ +Blockly.Comment.prototype.getBubbleSize = function() { + if (this.isVisible()) { + return this.bubble_.getBubbleSize(); + } else { + return {width: this.width_, height: this.height_}; + } +}; + +/** + * Size this comment's bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ +Blockly.Comment.prototype.setBubbleSize = function(width, height) { + if (this.textarea_) { + this.bubble_.setBubbleSize(width, height); + } else { + this.width_ = width; + this.height_ = height; + } +}; + +/** + * Returns this comment's text. + * @return {string} Comment text. + */ +Blockly.Comment.prototype.getText = function() { + return this.textarea_ ? this.textarea_.value : this.text_; +}; + +/** + * Set this comment's text. + * @param {string} text Comment text. + */ +Blockly.Comment.prototype.setText = function(text) { + if (this.text_ != text) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.block_, 'comment', null, this.text_, text)); + this.text_ = text; + } + if (this.textarea_) { + this.textarea_.value = text; + } +}; + +/** + * Dispose of this comment. + */ +Blockly.Comment.prototype.dispose = function() { + if (Blockly.Events.isEnabled()) { + this.setText(''); // Fire event to delete comment. + } + this.block_.comment = null; + Blockly.Icon.prototype.dispose.call(this); +}; diff --git a/core/.svn/pristine/93/93271c1be95e0a40a16c7060c9eb596614bfa85a.svn-base b/core/.svn/pristine/93/93271c1be95e0a40a16c7060c9eb596614bfa85a.svn-base new file mode 100644 index 0000000..11f3efa --- /dev/null +++ b/core/.svn/pristine/93/93271c1be95e0a40a16c7060c9eb596614bfa85a.svn-base @@ -0,0 +1,419 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Functions for injecting Blockly into a web page. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.inject'); + +goog.require('Blockly.BlockDragSurfaceSvg'); +goog.require('Blockly.Css'); +goog.require('Blockly.Grid'); +goog.require('Blockly.Options'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('Blockly.WorkspaceDragSurfaceSvg'); +goog.require('goog.dom'); +goog.require('goog.ui.Component'); +goog.require('goog.userAgent'); + + +/** + * Inject a Blockly editor into the specified container element (usually a div). + * @param {!Element|string} container Containing element, or its ID, + * or a CSS selector. + * @param {Object=} opt_options Optional dictionary of options. + * @return {!Blockly.Workspace} Newly created main workspace. + */ +Blockly.inject = function(container, opt_options) { + if (goog.isString(container)) { + container = document.getElementById(container) || + document.querySelector(container); + } + // Verify that the container is in document. + if (!goog.dom.contains(document, container)) { + throw 'Error: container is not in current document.'; + } + var options = new Blockly.Options(opt_options || {}); + var subContainer = goog.dom.createDom(goog.dom.TagName.DIV, 'injectionDiv'); + container.appendChild(subContainer); + var svg = Blockly.createDom_(subContainer, options); + + // Create surfaces for dragging things. These are optimizations + // so that the broowser does not repaint during the drag. + var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer); + var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer); + + var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface, + workspaceDragSurface); + Blockly.init_(workspace); + Blockly.mainWorkspace = workspace; + + Blockly.svgResize(workspace); + return workspace; +}; + +/** + * Create the SVG image. + * @param {!Element} container Containing element. + * @param {!Blockly.Options} options Dictionary of options. + * @return {!Element} Newly created SVG image. + * @private + */ +Blockly.createDom_ = function(container, options) { + // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying + // out content in RTL mode. Therefore Blockly forces the use of LTR, + // then manually positions content in RTL as needed. + container.setAttribute('dir', 'LTR'); + // Closure can be trusted to create HTML widgets with the proper direction. + goog.ui.Component.setDefaultRightToLeft(options.RTL); + + // Load CSS. + Blockly.Css.inject(options.hasCss, options.pathToMedia); + + // Build the SVG DOM. + /* + + ... + + */ + var svg = Blockly.utils.createSvgElement('svg', { + 'xmlns': 'http://www.w3.org/2000/svg', + 'xmlns:html': 'http://www.w3.org/1999/xhtml', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklySvg' + }, container); + /* + + ... filters go here ... + + */ + var defs = Blockly.utils.createSvgElement('defs', {}, svg); + // Each filter/pattern needs a unique ID for the case of multiple Blockly + // instances on a page. Browser behaviour becomes undefined otherwise. + // https://neil.fraser.name/news/2015/11/01/ + var rnd = String(Math.random()).substring(2); + /* + + + + + + + + + */ + var embossFilter = Blockly.utils.createSvgElement('filter', + {'id': 'blocklyEmbossFilter' + rnd}, defs); + Blockly.utils.createSvgElement('feGaussianBlur', + {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); + var feSpecularLighting = Blockly.utils.createSvgElement('feSpecularLighting', + { + 'in': 'blur', + 'surfaceScale': 1, + 'specularConstant': 0.5, + 'specularExponent': 10, + 'lighting-color': 'white', + 'result': 'specOut' + }, + embossFilter); + Blockly.utils.createSvgElement('fePointLight', + {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); + Blockly.utils.createSvgElement('feComposite', + { + 'in': 'specOut', + 'in2': 'SourceAlpha', + 'operator': 'in', + 'result': 'specOut' + }, embossFilter); + Blockly.utils.createSvgElement('feComposite', + { + 'in': 'SourceGraphic', + 'in2': 'specOut', + 'operator': 'arithmetic', + 'k1': 0, + 'k2': 1, + 'k3': 1, + 'k4': 0 + }, embossFilter); + options.embossFilterId = embossFilter.id; + /* + + + + + */ + var disabledPattern = Blockly.utils.createSvgElement('pattern', + { + 'id': 'blocklyDisabledPattern' + rnd, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, + 'height': 10 + }, defs); + Blockly.utils.createSvgElement('rect', + {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); + Blockly.utils.createSvgElement('path', + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); + options.disabledPatternId = disabledPattern.id; + + options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs); + return svg; +}; + +/** + * Create a main workspace and add it to the SVG. + * @param {!Element} svg SVG element with pattern defined. + * @param {!Blockly.Options} options Dictionary of options. + * @param {!Blockly.BlockDragSurfaceSvg} blockDragSurface Drag surface SVG + * for the blocks. + * @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface + * SVG for the workspace. + * @return {!Blockly.Workspace} Newly created main workspace. + * @private + */ +Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspaceDragSurface) { + options.parentWorkspace = null; + var mainWorkspace = new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface); + mainWorkspace.scale = options.zoomOptions.startScale; + svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); + + if (!options.hasCategories && options.languageTree) { + // Add flyout as an that is a sibling of the workspace svg. + var flyout = mainWorkspace.addFlyout_('svg'); + Blockly.utils.insertAfter_(flyout, svg); + } + + // A null translation will also apply the correct initial scale. + mainWorkspace.translate(0, 0); + Blockly.mainWorkspace = mainWorkspace; + + if (!options.readOnly && !options.hasScrollbars) { + var workspaceChanged = function() { + if (!mainWorkspace.isDragging()) { + var metrics = mainWorkspace.getMetrics(); + var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; + var edgeTop = metrics.viewTop + metrics.absoluteTop; + if (metrics.contentTop < edgeTop || + metrics.contentTop + metrics.contentHeight > + metrics.viewHeight + edgeTop || + metrics.contentLeft < + (options.RTL ? metrics.viewLeft : edgeLeft) || + metrics.contentLeft + metrics.contentWidth > (options.RTL ? + metrics.viewWidth : metrics.viewWidth + edgeLeft)) { + // One or more blocks may be out of bounds. Bump them back in. + var MARGIN = 25; + var blocks = mainWorkspace.getTopBlocks(false); + for (var b = 0, block; block = blocks[b]; b++) { + var blockXY = block.getRelativeToSurfaceXY(); + var blockHW = block.getHeightWidth(); + // Bump any block that's above the top back inside. + var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y; + if (overflowTop > 0) { + block.moveBy(0, overflowTop); + } + // Bump any block that's below the bottom back inside. + var overflowBottom = + edgeTop + metrics.viewHeight - MARGIN - blockXY.y; + if (overflowBottom < 0) { + block.moveBy(0, overflowBottom); + } + // Bump any block that's off the left back inside. + var overflowLeft = MARGIN + edgeLeft - + blockXY.x - (options.RTL ? 0 : blockHW.width); + if (overflowLeft > 0) { + block.moveBy(overflowLeft, 0); + } + // Bump any block that's off the right back inside. + var overflowRight = edgeLeft + metrics.viewWidth - MARGIN - + blockXY.x + (options.RTL ? blockHW.width : 0); + if (overflowRight < 0) { + block.moveBy(overflowRight, 0); + } + } + } + } + }; + mainWorkspace.addChangeListener(workspaceChanged); + } + // The SVG is now fully assembled. + Blockly.svgResize(mainWorkspace); + Blockly.WidgetDiv.createDom(); + Blockly.Tooltip.createDom(); + return mainWorkspace; +}; + +/** + * Initialize Blockly with various handlers. + * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace. + * @private + */ +Blockly.init_ = function(mainWorkspace) { + var options = mainWorkspace.options; + var svg = mainWorkspace.getParentSvg(); + + // Suppress the browser's context menu. + Blockly.bindEventWithChecks_(svg.parentNode, 'contextmenu', null, + function(e) { + if (!Blockly.utils.isTargetInput(e)) { + e.preventDefault(); + } + }); + + var workspaceResizeHandler = Blockly.bindEventWithChecks_(window, 'resize', + null, + function() { + Blockly.hideChaff(true); + Blockly.svgResize(mainWorkspace); + }); + mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler); + + Blockly.inject.bindDocumentEvents_(); + + if (options.languageTree) { + if (mainWorkspace.toolbox_) { + mainWorkspace.toolbox_.init(mainWorkspace); + } else if (mainWorkspace.flyout_) { + // Build a fixed flyout with the root blocks. + mainWorkspace.flyout_.init(mainWorkspace); + mainWorkspace.flyout_.show(options.languageTree.childNodes); + mainWorkspace.flyout_.scrollToStart(); + // Translate the workspace sideways to avoid the fixed flyout. + mainWorkspace.scrollX = mainWorkspace.flyout_.width_; + if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + mainWorkspace.scrollX *= -1; + } + mainWorkspace.translate(mainWorkspace.scrollX, 0); + } + } + + if (options.hasScrollbars) { + mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); + mainWorkspace.scrollbar.resize(); + } + + // Load the sounds. + if (options.hasSounds) { + Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace); + } +}; + +/** + * Bind document events, but only once. Destroying and reinjecting Blockly + * should not bind again. + * Bind events for scrolling the workspace. + * Most of these events should be bound to the SVG's surface. + * However, 'mouseup' has to be on the whole document so that a block dragged + * out of bounds and released will know that it has been released. + * Also, 'keydown' has to be on the whole document since the browser doesn't + * understand a concept of focus on the SVG image. + * @private + */ +Blockly.inject.bindDocumentEvents_ = function() { + if (!Blockly.documentEventsBound_) { + Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown_); + // longStop needs to run to stop the context menu from showing up. It + // should run regardless of what other touch event handlers have run. + Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); + Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); + // Some iPad versions don't fire resize after portrait to landscape change. + if (goog.userAgent.IPAD) { + Blockly.bindEventWithChecks_(window, 'orientationchange', document, + function() { + // TODO(#397): Fix for multiple blockly workspaces. + Blockly.svgResize(Blockly.getMainWorkspace()); + }); + } + } + Blockly.documentEventsBound_ = true; +}; + +/** + * Load sounds for the given workspace. + * @param {string} pathToMedia The path to the media directory. + * @param {!Blockly.Workspace} workspace The workspace to load sounds for. + * @private + */ +Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { + var audioMgr = workspace.getAudioManager(); + audioMgr.load( + [ + pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg' + ], 'click'); + audioMgr.load( + [ + pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg' + ], 'disconnect'); + audioMgr.load( + [ + pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav' + ], 'delete'); + + // Bind temporary hooks that preload the sounds. + var soundBinds = []; + var unbindSounds = function() { + while (soundBinds.length) { + Blockly.unbindEvent_(soundBinds.pop()); + } + audioMgr.preload(); + }; + + // These are bound on mouse/touch events with Blockly.bindEventWithChecks_, so + // they restrict the touch identifier that will be recognized. But this is + // really something that happens on a click, not a drag, so that's not + // necessary. + + // Android ignores any sound not loaded as a result of a user action. + soundBinds.push( + Blockly.bindEventWithChecks_(document, 'mousemove', null, unbindSounds, + true)); + soundBinds.push( + Blockly.bindEventWithChecks_(document, 'touchstart', null, unbindSounds, + true)); +}; + +/** + * Modify the block tree on the existing toolbox. + * @param {Node|string} tree DOM tree of blocks, or text representation of same. + * @deprecated April 2015 + */ +Blockly.updateToolbox = function(tree) { + console.warn('Deprecated call to Blockly.updateToolbox, ' + + 'use workspace.updateToolbox instead.'); + Blockly.getMainWorkspace().updateToolbox(tree); +}; diff --git a/core/.svn/pristine/94/944828b08dda5a16dc7e5c6398f0e44fc0e506fd.svn-base b/core/.svn/pristine/94/944828b08dda5a16dc7e5c6398f0e44fc0e506fd.svn-base new file mode 100644 index 0000000..030079e --- /dev/null +++ b/core/.svn/pristine/94/944828b08dda5a16dc7e5c6398f0e44fc0e506fd.svn-base @@ -0,0 +1,945 @@ +/** + * @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 methods. + * These methods are not specific to Blockly, and could be factored out into + * a JavaScript framework such as Closure. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.utils + * @namespace + **/ +goog.provide('Blockly.utils'); + +goog.require('Blockly.Touch'); +goog.require('goog.dom'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * To allow ADVANCED_OPTIMIZATIONS, combining variable.name and variable['name'] + * is not possible. To access the exported Blockly.Msg.Something it needs to be + * accessed through the exact name that was exported. Note, that all the exports + * are happening as the last thing in the generated js files, so they won't be + * accessible before JavaScript loads! + * @return {!Object.} The message array. + * @private + */ +Blockly.utils.getMessageArray_ = function() { + return goog.global['Blockly']['Msg']; +}; + +/** + * Remove an attribute from a element even if it's in IE 10. + * Similar to Element.removeAttribute() but it works on SVG elements in IE 10. + * Sets the attribute to null in IE 10, which treats removeAttribute as a no-op + * if it's called on an SVG element. + * @param {!Element} element DOM element to remove attribute from. + * @param {string} attributeName Name of attribute to remove. + */ +Blockly.utils.removeAttribute = function(element, attributeName) { + // goog.userAgent.isVersion is deprecated, but the replacement is + // goog.userAgent.isVersionOrHigher. + if (goog.userAgent.IE && goog.userAgent.isVersion('10.0')) { + element.setAttribute(attributeName, null); + } else { + element.removeAttribute(attributeName); + } +}; + +/** + * Add a CSS class to a element. + * Similar to Closure's goog.dom.classes.add, except it handles SVG elements. + * @param {!Element} element DOM element to add class to. + * @param {string} className Name of class to add. + * @return {boolean} True if class was added, false if already present. + */ +Blockly.utils.addClass = function(element, className) { + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) { + return false; + } + if (classes) { + classes += ' '; + } + element.setAttribute('class', classes + className); + return true; +}; + +/** + * Remove a CSS class from a element. + * Similar to Closure's goog.dom.classes.remove, except it handles SVG elements. + * @param {!Element} element DOM element to remove class from. + * @param {string} className Name of class to remove. + * @return {boolean} True if class was removed, false if never present. + */ +Blockly.utils.removeClass = function(element, className) { + var classes = element.getAttribute('class'); + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) { + return false; + } + var classList = classes.split(/\s+/); + for (var i = 0; i < classList.length; i++) { + if (!classList[i] || classList[i] == className) { + classList.splice(i, 1); + i--; + } + } + if (classList.length) { + element.setAttribute('class', classList.join(' ')); + } else { + Blockly.utils.removeAttribute(element, 'class'); + } + return true; +}; + +/** + * Checks if an element has the specified CSS class. + * Similar to Closure's goog.dom.classes.has, except it handles SVG elements. + * @param {!Element} element DOM element to check. + * @param {string} className Name of class to check. + * @return {boolean} True if class exists, false otherwise. + * @private + */ +Blockly.utils.hasClass = function(element, className) { + var classes = element.getAttribute('class'); + return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1; +}; + +/** + * Don't do anything for this event, just halt propagation. + * @param {!Event} e An event. + */ +Blockly.utils.noEvent = function(e) { + // This event has been handled. No need to bubble up to the document. + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Is this event targeting a text input widget? + * @param {!Event} e An event. + * @return {boolean} True if text input. + */ +Blockly.utils.isTargetInput = function(e) { + return e.target.type == 'textarea' || e.target.type == 'text' || + e.target.type == 'number' || e.target.type == 'email' || + e.target.type == 'password' || e.target.type == 'search' || + e.target.type == 'tel' || e.target.type == 'url' || + e.target.isContentEditable; +}; + +/** + * Return the coordinates of the top-left corner of this element relative to + * its parent. Only for SVG elements and children (e.g. rect, g, path). + * @param {!Element} element SVG element to find the coordinates of. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.utils.getRelativeXY = function(element) { + var xy = new goog.math.Coordinate(0, 0); + // First, check for x and y attributes. + var x = element.getAttribute('x'); + if (x) { + xy.x = parseInt(x, 10); + } + var y = element.getAttribute('y'); + if (y) { + xy.y = parseInt(y, 10); + } + // Second, check for transform="translate(...)" attribute. + var transform = element.getAttribute('transform'); + var r = transform && transform.match(Blockly.utils.getRelativeXY.XY_REGEX_); + if (r) { + xy.x += parseFloat(r[1]); + if (r[3]) { + xy.y += parseFloat(r[3]); + } + } + + // Then check for style = transform: translate(...) or translate3d(...) + var style = element.getAttribute('style'); + if (style && style.indexOf('translate') > -1) { + var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_2D_REGEX_); + // Try transform3d if 2d transform wasn't there. + if (!styleComponents) { + styleComponents = style.match(Blockly.utils.getRelativeXY.XY_3D_REGEX_); + } + if (styleComponents) { + xy.x += parseFloat(styleComponents[1]); + if (styleComponents[3]) { + xy.y += parseFloat(styleComponents[3]); + } + } + } + return xy; +}; + +/** + * Return the coordinates of the top-left corner of this element relative to + * the div blockly was injected into. + * @param {!Element} element SVG element to find the coordinates of. If this is + * not a child of the div blockly was injected into, the behaviour is + * undefined. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.utils.getInjectionDivXY_ = function(element) { + var x = 0; + var y = 0; + while (element) { + var xy = Blockly.utils.getRelativeXY(element); + var scale = Blockly.utils.getScale_(element); + x = (x * scale) + xy.x; + y = (y * scale) + xy.y; + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) { + break; + } + element = element.parentNode; + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Return the scale of this element. + * @param {!Element} element The element to find the coordinates of. + * @return {!number} number represending the scale applied to the element. + * @private + */ +Blockly.utils.getScale_ = function(element) { + var scale = 1; + var transform = element.getAttribute('transform'); + if (transform) { + var transformComponents = + transform.match(Blockly.utils.getScale_.REGEXP_); + if (transformComponents && transformComponents[0]) { + scale = parseFloat(transformComponents[0]); + } + } + return scale; +}; + +/** + * Static regex to pull the x,y values out of an SVG translate() directive. + * Note that Firefox and IE (9,10) return 'translate(12)' instead of + * 'translate(12, 0)'. + * Note that IE (9,10) returns 'translate(16 8)' instead of 'translate(16, 8)'. + * Note that IE has been reported to return scientific notation (0.123456e-42). + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_REGEX_ = + /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/; + + +/** + * Static regex to pull the scale values out of a transform style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getScale_REGEXP_ = /scale\(\s*([-+\d.e]+)\s*\)/; + +/** + * Static regex to pull the x,y,z values out of a translate3d() style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_3D_REGEX_ = + /transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + +/** + * Static regex to pull the x,y,z values out of a translate3d() style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_2D_REGEX_ = + /transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + +/** + * Helper method for creating SVG elements. + * @param {string} name Element's tag name. + * @param {!Object} attrs Dictionary of attribute names and values. + * @param {Element} parent Optional parent on which to append the element. + * @return {!SVGElement} Newly created SVG element. + */ +Blockly.utils.createSvgElement = function(name, attrs, parent) { + var e = /** @type {!SVGElement} */ + (document.createElementNS(Blockly.SVG_NS, name)); + for (var key in attrs) { + e.setAttribute(key, attrs[key]); + } + // IE defines a unique attribute "runtimeStyle", it is NOT applied to + // elements created with createElementNS. However, Closure checks for IE + // and assumes the presence of the attribute and crashes. + if (document.body.runtimeStyle) { // Indicates presence of IE-only attr. + e.runtimeStyle = e.currentStyle = e.style; + } + if (parent) { + parent.appendChild(e); + } + return e; +}; + +/** + * Is this event a right-click? + * @param {!Event} e Mouse event. + * @return {boolean} True if right-click. + */ +Blockly.utils.isRightButton = function(e) { + if (e.ctrlKey && goog.userAgent.MAC) { + // Control-clicking on Mac OS X is treated as a right-click. + // WebKit on Mac OS X fails to change button to 2 (but Gecko does). + return true; + } + return e.button == 2; +}; + +/** + * Return the converted coordinates of the given mouse event. + * The origin (0,0) is the top-left corner of the Blockly SVG. + * @param {!Event} e Mouse event. + * @param {!Element} svg SVG element. + * @param {SVGMatrix} matrix Inverted screen CTM to use. + * @return {!SVGPoint} Object with .x and .y properties. + */ +Blockly.utils.mouseToSvg = function(e, svg, matrix) { + var svgPoint = svg.createSVGPoint(); + svgPoint.x = e.clientX; + svgPoint.y = e.clientY; + + if (!matrix) { + matrix = svg.getScreenCTM().inverse(); + } + return svgPoint.matrixTransform(matrix); +}; + +/** + * Given an array of strings, return the length of the shortest one. + * @param {!Array.} array Array of strings. + * @return {number} Length of shortest string. + */ +Blockly.utils.shortestStringLength = function(array) { + if (!array.length) { + return 0; + } + return array.reduce(function(a, b) { + return a.length < b.length ? a : b; + }).length; +}; + +/** + * Given an array of strings, return the length of the common prefix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array.} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common prefix. + */ +Blockly.utils.commonWordPrefix = function(array, opt_shortest) { + if (!array.length) { + return 0; + } else if (array.length == 1) { + return array[0].length; + } + var wordPrefix = 0; + var max = opt_shortest || Blockly.utils.shortestStringLength(array); + for (var len = 0; len < max; len++) { + var letter = array[0][len]; + for (var i = 1; i < array.length; i++) { + if (letter != array[i][len]) { + return wordPrefix; + } + } + if (letter == ' ') { + wordPrefix = len + 1; + } + } + for (var i = 1; i < array.length; i++) { + var letter = array[i][len]; + if (letter && letter != ' ') { + return wordPrefix; + } + } + return max; +}; + +/** + * Given an array of strings, return the length of the common suffix. + * Words may not be split. Any space after a word is included in the length. + * @param {!Array.} array Array of strings. + * @param {number=} opt_shortest Length of shortest string. + * @return {number} Length of common suffix. + */ +Blockly.utils.commonWordSuffix = function(array, opt_shortest) { + if (!array.length) { + return 0; + } else if (array.length == 1) { + return array[0].length; + } + var wordPrefix = 0; + var max = opt_shortest || Blockly.utils.shortestStringLength(array); + for (var len = 0; len < max; len++) { + var letter = array[0].substr(-len - 1, 1); + for (var i = 1; i < array.length; i++) { + if (letter != array[i].substr(-len - 1, 1)) { + return wordPrefix; + } + } + if (letter == ' ') { + wordPrefix = len + 1; + } + } + for (var i = 1; i < array.length; i++) { + var letter = array[i].charAt(array[i].length - len - 1); + if (letter && letter != ' ') { + return wordPrefix; + } + } + return max; +}; + +/** + * Parse a string with any number of interpolation tokens (%1, %2, ...). + * It will also replace string table references (e.g., %{bky_my_msg} and + * %{BKY_MY_MSG} will both be replaced with the value in + * Blockly.Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped + * (e.g., '%%'). + * @param {string} message Text which might contain string table references and + * interpolation tokens. + * @return {!Array.} Array of strings and numbers. + */ +Blockly.utils.tokenizeInterpolation = function(message) { + return Blockly.utils.tokenizeInterpolation_(message, true); +}; + +/** + * Replaces string table references in a message, if the message is a string. + * For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with + * the value in Blockly.Msg['MY_MSG']. + * @param {string|?} message Message, which may be a string that contains + * string table references. + * @return {!string} String with message references replaced. + */ +Blockly.utils.replaceMessageReferences = function(message) { + if (!goog.isString(message)) { + return message; + } + var interpolatedResult = Blockly.utils.tokenizeInterpolation_(message, false); + // When parseInterpolationTokens == false, interpolatedResult should be at + // most length 1. + return interpolatedResult.length ? interpolatedResult[0] : ''; +}; + +/** + * Validates that any %{MSG_KEY} references in the message refer to keys of + * the Blockly.Msg string table. + * @param {string} message Text which might contain string table references. + * @return {boolean} True if all message references have matching values. + * Otherwise, false. + */ +Blockly.utils.checkMessageReferences = function(message) { + var validSoFar = true; + + var msgTable = Blockly.utils.getMessageArray_(); + + // TODO(#1169): Implement support for other string tables, prefixes other than BKY_. + var regex = /%{(BKY_[A-Z][A-Z0-9_]*)}/gi; + var match = regex.exec(message); + while (match) { + var msgKey = match[1]; + msgKey = msgKey.toUpperCase(); + if (msgKey.substr(0, 4) != 'BKY_') { + console.log('WARNING: Unsupported message table prefix in %{' + match[1] + '}.'); + validSoFar = false; // Continue to report other errors. + } else if (msgTable[msgKey.substr(4)] == undefined) { + console.log('WARNING: No message string for %{' + match[1] + '}.'); + validSoFar = false; // Continue to report other errors. + } + + // Re-run on remainder of string. + message = message.substring(match.index + msgKey.length + 1); + match = regex.exec(message); + } + + return validSoFar; +}; + +/** + * Internal implementation of the message reference and interpolation token + * parsing used by tokenizeInterpolation() and replaceMessageReferences(). + * @param {string} message Text which might contain string table references and + * interpolation tokens. + * @param {boolean} parseInterpolationTokens Option to parse numeric + * interpolation tokens (%1, %2, ...) when true. + * @return {!Array.} Array of strings and numbers. + * @private + */ +Blockly.utils.tokenizeInterpolation_ = function(message, + parseInterpolationTokens) { + var tokens = []; + var chars = message.split(''); + chars.push(''); // End marker. + // Parse the message with a finite state machine. + // 0 - Base case. + // 1 - % found. + // 2 - Digit found. + // 3 - Message ref found. + var state = 0; + var buffer = []; + var number = null; + for (var i = 0; i < chars.length; i++) { + var c = chars[i]; + if (state == 0) { + if (c == '%') { + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + buffer.length = 0; + state = 1; // Start escape. + } else { + buffer.push(c); // Regular char. + } + } else if (state == 1) { + if (c == '%') { + buffer.push(c); // Escaped %: %% + state = 0; + } else if (parseInterpolationTokens && '0' <= c && c <= '9') { + state = 2; + number = c; + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + buffer.length = 0; + } else if (c == '{') { + state = 3; + } else { + buffer.push('%', c); // Not recognized. Return as literal. + state = 0; + } + } else if (state == 2) { + if ('0' <= c && c <= '9') { + number += c; // Multi-digit number. + } else { + tokens.push(parseInt(number, 10)); + i--; // Parse this char again. + state = 0; + } + } else if (state == 3) { // String table reference + if (c == '') { + // Premature end before closing '}' + buffer.splice(0, 0, '%{'); // Re-insert leading delimiter + i--; // Parse this char again. + state = 0; // and parse as string literal. + } else if (c != '}') { + buffer.push(c); + } else { + var rawKey = buffer.join(''); + if (/[a-zA-Z][a-zA-Z0-9_]*/.test(rawKey)) { // Strict matching + // Found a valid string key. Attempt case insensitive match. + var keyUpper = rawKey.toUpperCase(); + + // BKY_ is the prefix used to namespace the strings used in Blockly + // core files and the predefined blocks in ../blocks/. These strings + // are defined in ../msgs/ files. + var bklyKey = goog.string.startsWith(keyUpper, 'BKY_') ? + keyUpper.substring(4) : null; + if (bklyKey && bklyKey in Blockly.utils.getMessageArray_()) { + var rawValue = Blockly.utils.getMessageArray_()[bklyKey]; + if (goog.isString(rawValue)) { + // Attempt to dereference substrings, too, appending to the end. + Array.prototype.push.apply(tokens, + Blockly.utils.tokenizeInterpolation_( + rawValue, parseInterpolationTokens)); + } else if (parseInterpolationTokens) { + // When parsing interpolation tokens, numbers are special + // placeholders (%1, %2, etc). Make sure all other values are + // strings. + tokens.push(String(rawValue)); + } else { + tokens.push(rawValue); + } + } else { + // No entry found in the string table. Pass reference as string. + tokens.push('%{' + rawKey + '}'); + } + buffer.length = 0; // Clear the array + state = 0; + } else { + tokens.push('%{' + rawKey + '}'); + buffer.length = 0; + state = 0; // and parse as string literal. + } + } + } + } + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + + // Merge adjacent text tokens into a single string. + var mergedTokens = []; + buffer.length = 0; + for (var i = 0; i < tokens.length; ++i) { + if (typeof tokens[i] == 'string') { + buffer.push(tokens[i]); + } else { + text = buffer.join(''); + if (text) { + mergedTokens.push(text); + } + buffer.length = 0; + mergedTokens.push(tokens[i]); + } + } + text = buffer.join(''); + if (text) { + mergedTokens.push(text); + } + buffer.length = 0; + + return mergedTokens; +}; + +/** + * Generate a unique ID. This should be globally unique. + * 87 characters ^ 20 length > 128 bits (better than a UUID). + * @return {string} A globally unique ID string. + */ +Blockly.utils.genUid = function() { + var length = 20; + var soupLength = Blockly.utils.genUid.soup_.length; + var id = []; + for (var i = 0; i < length; i++) { + id[i] = Blockly.utils.genUid.soup_.charAt(Math.random() * soupLength); + } + return id.join(''); +}; + +/** + * Legal characters for the unique ID. Should be all on a US keyboard. + * No characters that conflict with XML or JSON. Requests to remove additional + * 'problematic' characters from this soup will be denied. That's your failure + * to properly escape in your own environment. Issues #251, #625, #682, #1304. + * @private + */ +Blockly.utils.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' + + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + +/** + * Wrap text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + */ +Blockly.utils.wrap = function(text, limit) { + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) { + lines[i] = Blockly.utils.wrapLine_(lines[i], limit); + } + return lines.join('\n'); +}; + +/** + * Wrap single line of text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + * @private + */ +Blockly.utils.wrapLine_ = function(text, limit) { + if (text.length <= limit) { + // Short text, no need to wrap. + return text; + } + // Split the text into words. + var words = text.trim().split(/\s+/); + // Set limit to be the length of the largest word. + for (var i = 0; i < words.length; i++) { + if (words[i].length > limit) { + limit = words[i].length; + } + } + + var lastScore; + var score = -Infinity; + var lastText; + var lineCount = 1; + do { + lastScore = score; + lastText = text; + // Create a list of booleans representing if a space (false) or + // a break (true) appears after each word. + var wordBreaks = []; + // Seed the list with evenly spaced linebreaks. + var steps = words.length / lineCount; + var insertedBreaks = 1; + for (var i = 0; i < words.length - 1; i++) { + if (insertedBreaks < (i + 1.5) / steps) { + insertedBreaks++; + wordBreaks[i] = true; + } else { + wordBreaks[i] = false; + } + } + wordBreaks = Blockly.utils.wrapMutate_(words, wordBreaks, limit); + score = Blockly.utils.wrapScore_(words, wordBreaks, limit); + text = Blockly.utils.wrapToText_(words, wordBreaks); + lineCount++; + } while (score > lastScore); + return lastText; +}; + +/** + * Compute a score for how good the wrapping is. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {number} Larger the better. + * @private + */ +Blockly.utils.wrapScore_ = function(words, wordBreaks, limit) { + // If this function becomes a performance liability, add caching. + // Compute the length of each line. + var lineLengths = [0]; + var linePunctuation = []; + for (var i = 0; i < words.length; i++) { + lineLengths[lineLengths.length - 1] += words[i].length; + if (wordBreaks[i] === true) { + lineLengths.push(0); + linePunctuation.push(words[i].charAt(words[i].length - 1)); + } else if (wordBreaks[i] === false) { + lineLengths[lineLengths.length - 1]++; + } + } + var maxLength = Math.max.apply(Math, lineLengths); + + var score = 0; + for (var i = 0; i < lineLengths.length; i++) { + // Optimize for width. + // -2 points per char over limit (scaled to the power of 1.5). + score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2; + // Optimize for even lines. + // -1 point per char smaller than max (scaled to the power of 1.5). + score -= Math.pow(maxLength - lineLengths[i], 1.5); + // Optimize for structure. + // Add score to line endings after punctuation. + if ('.?!'.indexOf(linePunctuation[i]) != -1) { + score += limit / 3; + } else if (',;)]}'.indexOf(linePunctuation[i]) != -1) { + score += limit / 4; + } + } + // All else being equal, the last line should not be longer than the + // previous line. For example, this looks wrong: + // aaa bbb + // ccc ddd eee + if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <= + lineLengths[lineLengths.length - 2]) { + score += 0.5; + } + return score; +}; + +/** + * Mutate the array of line break locations until an optimal solution is found. + * No line breaks are added or deleted, they are simply moved around. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {!Array.} New array of optimal line breaks. + * @private + */ +Blockly.utils.wrapMutate_ = function(words, wordBreaks, limit) { + var bestScore = Blockly.utils.wrapScore_(words, wordBreaks, limit); + var bestBreaks; + // Try shifting every line break forward or backward. + for (var i = 0; i < wordBreaks.length - 1; i++) { + if (wordBreaks[i] == wordBreaks[i + 1]) { + continue; + } + var mutatedWordBreaks = [].concat(wordBreaks); + mutatedWordBreaks[i] = !mutatedWordBreaks[i]; + mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1]; + var mutatedScore = + Blockly.utils.wrapScore_(words, mutatedWordBreaks, limit); + if (mutatedScore > bestScore) { + bestScore = mutatedScore; + bestBreaks = mutatedWordBreaks; + } + } + if (bestBreaks) { + // Found an improvement. See if it may be improved further. + return Blockly.utils.wrapMutate_(words, bestBreaks, limit); + } + // No improvements found. Done. + return wordBreaks; +}; + +/** + * Reassemble the array of words into text, with the specified line breaks. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @return {string} Plain text. + * @private + */ +Blockly.utils.wrapToText_ = function(words, wordBreaks) { + var text = []; + for (var i = 0; i < words.length; i++) { + text.push(words[i]); + if (wordBreaks[i] !== undefined) { + text.push(wordBreaks[i] ? '\n' : ' '); + } + } + return text.join(''); +}; + +/** + * Check if 3D transforms are supported by adding an element + * and attempting to set the property. + * @return {boolean} true if 3D transforms are supported. + */ +Blockly.utils.is3dSupported = function() { + if (Blockly.utils.is3dSupported.cached_ !== undefined) { + return Blockly.utils.is3dSupported.cached_; + } + // CC-BY-SA Lorenzo Polidori + // stackoverflow.com/questions/5661671/detecting-transform-translate3d-support + if (!goog.global.getComputedStyle) { + return false; + } + + var el = document.createElement('p'); + var has3d = 'none'; + var transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + + // Add it to the body to get the computed style. + document.body.insertBefore(el, null); + + for (var t in transforms) { + if (el.style[t] !== undefined) { + el.style[t] = 'translate3d(1px,1px,1px)'; + var computedStyle = goog.global.getComputedStyle(el); + if (!computedStyle) { + // getComputedStyle in Firefox returns null when blockly is loaded + // inside an iframe with display: none. Returning false and not + // caching is3dSupported means we try again later. This is most likely + // when users are interacting with blocks which should mean blockly is + // visible again. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + document.body.removeChild(el); + return false; + } + has3d = computedStyle.getPropertyValue(transforms[t]); + } + } + document.body.removeChild(el); + Blockly.utils.is3dSupported.cached_ = has3d !== 'none'; + return Blockly.utils.is3dSupported.cached_; +}; + +/** + * Insert a node after a reference node. + * Contrast with node.insertBefore function. + * @param {!Element} newNode New element to insert. + * @param {!Element} refNode Existing element to precede new node. + * @private + */ +Blockly.utils.insertAfter_ = function(newNode, refNode) { + var siblingNode = refNode.nextSibling; + var parentNode = refNode.parentNode; + if (!parentNode) { + throw 'Reference node has no parent.'; + } + if (siblingNode) { + parentNode.insertBefore(newNode, siblingNode); + } else { + parentNode.appendChild(newNode); + } +}; + +/** + * Calls a function after the page has loaded, possibly immediately. + * @param {function()} fn Function to run. + * @throws Error Will throw if no global document can be found (e.g., Node.js). + */ +Blockly.utils.runAfterPageLoad = function(fn) { + if (typeof document != 'object') { + throw new Error('Blockly.utils.runAfterPageLoad() requires browser document.'); + } + if (document.readyState === 'complete') { + fn(); // Page has already loaded. Call immediately. + } else { + // Poll readyState. + var readyStateCheckInterval = setInterval(function() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + fn(); + } + }, 10); + } +}; + +/** + * Sets the CSS transform property on an element. This function sets the + * non-vendor-prefixed and vendor-prefixed versions for backwards compatibility + * with older browsers. See http://caniuse.com/#feat=transforms2d + * @param {!Element} node The node which the CSS transform should be applied. + * @param {string} transform The value of the CSS `transform` property. + */ +Blockly.utils.setCssTransform = function(node, transform) { + node.style['transform'] = transform; + node.style['-webkit-transform'] = transform; +}; + +/** + * Get the position of the current viewport in window coordinates. This takes + * scroll into account. + * @return {!Object} an object containing window width, height, and scroll + * position in window coordinates. + * @package + */ +Blockly.utils.getViewportBBox = function() { + // Pixels. + var windowSize = goog.dom.getViewportSize(); + // Pixels, in window coordinates. + var scrollOffset = goog.style.getViewportPageOffset(document); + return { + right: windowSize.width + scrollOffset.x, + bottom: windowSize.height + scrollOffset.y, + top: scrollOffset.y, + left: scrollOffset.x + }; +}; diff --git a/core/.svn/pristine/95/95f2ce0d719a4fafe9746a7edd9e3963ec1d152b.svn-base b/core/.svn/pristine/95/95f2ce0d719a4fafe9746a7edd9e3963ec1d152b.svn-base new file mode 100644 index 0000000..3227d93 --- /dev/null +++ b/core/.svn/pristine/95/95f2ce0d719a4fafe9746a7edd9e3963ec1d152b.svn-base @@ -0,0 +1,192 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 An SVG that floats on top of the workspace. + * Blocks are moved into this SVG during a drag, improving performance. + * The entire SVG is translated using css translation instead of SVG so the + * blocks are never repainted during drag improving performance. + * @author katelyn@google.com (Katelyn Mann) + */ + +'use strict'; + +goog.provide('Blockly.WorkspaceDragSurfaceSvg'); + +goog.require('Blockly.utils'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Blocks are moved into this SVG during a drag, improving performance. + * The entire SVG is translated using css transforms instead of SVG so the + * blocks are never repainted during drag improving performance. + * @param {!Element} container Containing element. + * @constructor + */ +Blockly.WorkspaceDragSurfaceSvg = function(container) { + this.container_ = container; + this.createDom(); +}; + +/** + * The SVG drag surface. Set once by Blockly.WorkspaceDragSurfaceSvg.createDom. + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.SVG_ = null; + +/** + * SVG group inside the drag surface that holds blocks while a drag is in + * progress. Blocks are moved here by the workspace at start of a drag and moved + * back into the main SVG at the end of a drag. + * + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.dragGroup_ = null; + +/** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.container_ = null; + +/** + * Create the drag surface and inject it into the container. + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.createDom = function() { + if (this.SVG_) { + return; // Already created. + } + + /** + * Dom structure when the workspace is being dragged. If there is no drag in + * progress, the SVG is empty and display: none. + * + * + * /g> + * + */ + this.SVG_ = Blockly.utils.createSvgElement('svg', + { + 'xmlns': Blockly.SVG_NS, + 'xmlns:html': Blockly.HTML_NS, + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklyWsDragSurface blocklyOverflowVisible' + }, null); + this.container_.appendChild(this.SVG_); +}; + +/** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface + * @param {number} y Y translation for the entire surface + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) { + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being moved on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + + this.SVG_.style.display = 'block'; + Blockly.utils.setCssTransform( + this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); +}; + +/** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!goog.math.Coordinate} Current translation of the surface + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() { + return Blockly.utils.getRelativeXY(this.SVG_); +}; + +/** + * Move the blockCanvas and bubbleCanvas out of the surface SVG and on to + * newSurface. + * @param {!SVGElement} newSurface The element to put the drag surface contents + * into. + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) { + var blockCanvas = this.SVG_.childNodes[0]; + var bubbleCanvas = this.SVG_.childNodes[1]; + if (!blockCanvas || !bubbleCanvas || + !Blockly.utils.hasClass(blockCanvas, 'blocklyBlockCanvas') || + !Blockly.utils.hasClass(bubbleCanvas, 'blocklyBubbleCanvas')) { + throw 'Couldn\'t clear and hide the drag surface. A node was missing.'; + } + + // If there is a previous sibling, put the blockCanvas back right afterwards, + // otherwise insert it as the first child node in newSurface. + if (this.previousSibling_ != null) { + Blockly.utils.insertAfter_(blockCanvas, this.previousSibling_); + } else { + newSurface.insertBefore(blockCanvas, newSurface.firstChild); + } + + // Reattach the bubble canvas after the blockCanvas. + Blockly.utils.insertAfter_(bubbleCanvas, blockCanvas); + // Hide the drag surface. + this.SVG_.style.display = 'none'; + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Drag surface was not cleared.'); + Blockly.utils.setCssTransform(this.SVG_, ''); + this.previousSibling_ = null; +}; + +/** + * Set the SVG to have the block canvas and bubble canvas in it and then + * show the surface. + * @param {!Element} blockCanvas The block canvas element from the workspace. + * @param {!Element} bubbleCanvas The element that contains the bubbles. + * @param {?Element} previousSibling The element to insert the block canvas & + bubble canvas after when it goes back in the DOM at the end of a drag. + * @param {number} width The width of the workspace SVG element. + * @param {number} height The height of the workspace SVG element. + * @param {number} scale The scale of the workspace being dragged. + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.setContentsAndShow = function( + blockCanvas, bubbleCanvas, previousSibling, width, height, scale) { + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Already dragging a block.'); + this.previousSibling_ = previousSibling; + // Make sure the blocks and bubble canvas are scaled appropriately. + blockCanvas.setAttribute('transform', 'translate(0, 0) scale(' + scale + ')'); + bubbleCanvas.setAttribute( + 'transform', 'translate(0, 0) scale(' + scale + ')'); + this.SVG_.setAttribute('width', width); + this.SVG_.setAttribute('height', height); + this.SVG_.appendChild(blockCanvas); + this.SVG_.appendChild(bubbleCanvas); + this.SVG_.style.display = 'block'; +}; diff --git a/core/.svn/pristine/98/985947b7cbbc40f965aba1043a17f6af60f6c573.svn-base b/core/.svn/pristine/98/985947b7cbbc40f965aba1043a17f6af60f6c573.svn-base new file mode 100644 index 0000000..d210d00 --- /dev/null +++ b/core/.svn/pristine/98/985947b7cbbc40f965aba1043a17f6af60f6c573.svn-base @@ -0,0 +1,338 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Library to create tooltips for Blockly. + * First, call Blockly.Tooltip.init() after onload. + * Second, set the 'tooltip' property on any SVG element that needs a tooltip. + * If the tooltip is a string, then that message will be displayed. + * If the tooltip is an SVG element, then that object's tooltip will be used. + * Third, call Blockly.Tooltip.bindMouseEvents(e) passing the SVG element. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.Tooltip + * @namespace + **/ +goog.provide('Blockly.Tooltip'); + +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); + + +/** + * Is a tooltip currently showing? + */ +Blockly.Tooltip.visible = false; + +/** + * Is someone else blocking the tooltip from being shown? + * @type {boolean} + * @private + */ +Blockly.Tooltip.blocked_ = false; + +/** + * Maximum width (in characters) of a tooltip. + */ +Blockly.Tooltip.LIMIT = 50; + +/** + * PID of suspended thread to clear tooltip on mouse out. + * @private + */ +Blockly.Tooltip.mouseOutPid_ = 0; + +/** + * PID of suspended thread to show the tooltip. + * @private + */ +Blockly.Tooltip.showPid_ = 0; + +/** + * Last observed X location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastX_ = 0; + +/** + * Last observed Y location of the mouse pointer (freezes when tooltip appears). + * @private + */ +Blockly.Tooltip.lastY_ = 0; + +/** + * Current element being pointed at. + * @private + */ +Blockly.Tooltip.element_ = null; + +/** + * Once a tooltip has opened for an element, that element is 'poisoned' and + * cannot respawn a tooltip until the pointer moves over a different element. + * @private + */ +Blockly.Tooltip.poisonedElement_ = null; + +/** + * Horizontal offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_X = 0; + +/** + * Vertical offset between mouse cursor and tooltip. + */ +Blockly.Tooltip.OFFSET_Y = 10; + +/** + * Radius mouse can move before killing tooltip. + */ +Blockly.Tooltip.RADIUS_OK = 10; + +/** + * Delay before tooltip appears. + */ +Blockly.Tooltip.HOVER_MS = 750; + +/** + * Horizontal padding between tooltip and screen edge. + */ +Blockly.Tooltip.MARGINS = 5; + +/** + * The HTML container. Set once by Blockly.Tooltip.createDom. + * @type {Element} + */ +Blockly.Tooltip.DIV = null; + +/** + * Create the tooltip div and inject it onto the page. + */ +Blockly.Tooltip.createDom = function() { + if (Blockly.Tooltip.DIV) { + return; // Already created. + } + // Create an HTML container for popup overlays (e.g. editor widgets). + Blockly.Tooltip.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyTooltipDiv'); + document.body.appendChild(Blockly.Tooltip.DIV); +}; + +/** + * Binds the required mouse events onto an SVG element. + * @param {!Element} element SVG element onto which tooltip is to be bound. + */ +Blockly.Tooltip.bindMouseEvents = function(element) { + Blockly.bindEvent_(element, 'mouseover', null, + Blockly.Tooltip.onMouseOver_); + Blockly.bindEvent_(element, 'mouseout', null, + Blockly.Tooltip.onMouseOut_); + + // Don't use bindEvent_ for mousemove since that would create a + // corresponding touch handler, even though this only makes sense in the + // context of a mouseover/mouseout. + element.addEventListener('mousemove', Blockly.Tooltip.onMouseMove_, false); +}; + +/** + * Hide the tooltip if the mouse is over a different object. + * Initialize the tooltip to potentially appear for this object. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOver_ = function(e) { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } + // If the tooltip is an object, treat it as a pointer to the next object in + // the chain to look at. Terminate when a string or function is found. + var element = e.target; + while (!goog.isString(element.tooltip) && !goog.isFunction(element.tooltip)) { + element = element.tooltip; + } + if (Blockly.Tooltip.element_ != element) { + Blockly.Tooltip.hide(); + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.element_ = element; + } + // Forget about any immediately preceding mouseOut event. + clearTimeout(Blockly.Tooltip.mouseOutPid_); +}; + +/** + * Hide the tooltip if the mouse leaves the object and enters the workspace. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseOut_ = function(/* eslint-disable no-unused-vars */e + /* eslint-enable no-unused-vars */) { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } + // Moving from one element to another (overlapping or with no gap) generates + // a mouseOut followed instantly by a mouseOver. Fork off the mouseOut + // event and kill it if a mouseOver is received immediately. + // This way the task only fully executes if mousing into the void. + Blockly.Tooltip.mouseOutPid_ = setTimeout(function() { + Blockly.Tooltip.element_ = null; + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.hide(); + }, 1); + clearTimeout(Blockly.Tooltip.showPid_); +}; + +/** + * When hovering over an element, schedule a tooltip to be shown. If a tooltip + * is already visible, hide it if the mouse strays out of a certain radius. + * @param {!Event} e Mouse event. + * @private + */ +Blockly.Tooltip.onMouseMove_ = function(e) { + if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) { + // No tooltip here to show. + return; + } else if (Blockly.WidgetDiv.isVisible()) { + // Don't display a tooltip if a widget is open (tooltip would be under it). + return; + } else if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. We are probably handling a + // user gesture, such as a click or drag. + return; + } + if (Blockly.Tooltip.visible) { + // Compute the distance between the mouse position when the tooltip was + // shown and the current mouse position. Pythagorean theorem. + var dx = Blockly.Tooltip.lastX_ - e.pageX; + var dy = Blockly.Tooltip.lastY_ - e.pageY; + if (Math.sqrt(dx * dx + dy * dy) > Blockly.Tooltip.RADIUS_OK) { + Blockly.Tooltip.hide(); + } + } else if (Blockly.Tooltip.poisonedElement_ != Blockly.Tooltip.element_) { + // The mouse moved, clear any previously scheduled tooltip. + clearTimeout(Blockly.Tooltip.showPid_); + // Maybe this time the mouse will stay put. Schedule showing of tooltip. + Blockly.Tooltip.lastX_ = e.pageX; + Blockly.Tooltip.lastY_ = e.pageY; + Blockly.Tooltip.showPid_ = + setTimeout(Blockly.Tooltip.show_, Blockly.Tooltip.HOVER_MS); + } +}; + +/** + * Hide the tooltip. + */ +Blockly.Tooltip.hide = function() { + if (Blockly.Tooltip.visible) { + Blockly.Tooltip.visible = false; + if (Blockly.Tooltip.DIV) { + Blockly.Tooltip.DIV.style.display = 'none'; + } + } + if (Blockly.Tooltip.showPid_) { + clearTimeout(Blockly.Tooltip.showPid_); + } +}; + +/** + * Hide any in-progress tooltips and block showing new tooltips until the next + * call to unblock(). + * @package + */ +Blockly.Tooltip.block = function() { + Blockly.Tooltip.hide(); + Blockly.Tooltip.blocked_ = true; +}; + +/** + * Unblock tooltips: allow them to be scheduled and shown according to their own + * logic. + * @package + */ +Blockly.Tooltip.unblock = function() { + Blockly.Tooltip.blocked_ = false; +}; + +/** + * Create the tooltip and show it. + * @private + */ +Blockly.Tooltip.show_ = function() { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } + Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_; + if (!Blockly.Tooltip.DIV) { + return; + } + // Erase all existing text. + goog.dom.removeChildren(/** @type {!Element} */ (Blockly.Tooltip.DIV)); + // Get the new text. + var tip = Blockly.Tooltip.element_.tooltip; + while (goog.isFunction(tip)) { + tip = tip(); + } + tip = Blockly.utils.wrap(tip, Blockly.Tooltip.LIMIT); + // Create new text, line by line. + var lines = tip.split('\n'); + for (var i = 0; i < lines.length; i++) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(lines[i])); + Blockly.Tooltip.DIV.appendChild(div); + } + var rtl = Blockly.Tooltip.element_.RTL; + var windowSize = goog.dom.getViewportSize(); + // Display the tooltip. + Blockly.Tooltip.DIV.style.direction = rtl ? 'rtl' : 'ltr'; + Blockly.Tooltip.DIV.style.display = 'block'; + Blockly.Tooltip.visible = true; + // Move the tooltip to just below the cursor. + var anchorX = Blockly.Tooltip.lastX_; + if (rtl) { + anchorX -= Blockly.Tooltip.OFFSET_X + Blockly.Tooltip.DIV.offsetWidth; + } else { + anchorX += Blockly.Tooltip.OFFSET_X; + } + var anchorY = Blockly.Tooltip.lastY_ + Blockly.Tooltip.OFFSET_Y; + + if (anchorY + Blockly.Tooltip.DIV.offsetHeight > + windowSize.height + window.scrollY) { + // Falling off the bottom of the screen; shift the tooltip up. + anchorY -= Blockly.Tooltip.DIV.offsetHeight + 2 * Blockly.Tooltip.OFFSET_Y; + } + if (rtl) { + // Prevent falling off left edge in RTL mode. + anchorX = Math.max(Blockly.Tooltip.MARGINS - window.scrollX, anchorX); + } else { + if (anchorX + Blockly.Tooltip.DIV.offsetWidth > + windowSize.width + window.scrollX - 2 * Blockly.Tooltip.MARGINS) { + // Falling off the right edge of the screen; + // clamp the tooltip on the edge. + anchorX = windowSize.width - Blockly.Tooltip.DIV.offsetWidth - + 2 * Blockly.Tooltip.MARGINS; + } + } + Blockly.Tooltip.DIV.style.top = anchorY + 'px'; + Blockly.Tooltip.DIV.style.left = anchorX + 'px'; +}; diff --git a/core/.svn/pristine/9e/9e42ae49d35d4d884337d6c37f2deeafc5689cad.svn-base b/core/.svn/pristine/9e/9e42ae49d35d4d884337d6c37f2deeafc5689cad.svn-base new file mode 100644 index 0000000..92dc288 --- /dev/null +++ b/core/.svn/pristine/9e/9e42ae49d35d4d884337d6c37f2deeafc5689cad.svn-base @@ -0,0 +1,630 @@ +/** + * @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 Object representing a UI bubble. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Bubble'); + +goog.require('Blockly.Touch'); +goog.require('Blockly.Workspace'); +goog.require('goog.dom'); +goog.require('goog.math'); +goog.require('goog.math.Coordinate'); +goog.require('goog.userAgent'); + + +/** + * Class for UI bubble. + * @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the + * bubble. + * @param {!Element} content SVG content for the bubble. + * @param {Element} shape SVG element to avoid eclipsing. + * @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor + * point. + * @param {?number} bubbleWidth Width of bubble, or null if not resizable. + * @param {?number} bubbleHeight Height of bubble, or null if not resizable. + * @constructor + */ +Blockly.Bubble = function(workspace, content, shape, anchorXY, + bubbleWidth, bubbleHeight) { + this.workspace_ = workspace; + this.content_ = content; + this.shape_ = shape; + + var angle = Blockly.Bubble.ARROW_ANGLE; + if (this.workspace_.RTL) { + angle = -angle; + } + this.arrow_radians_ = goog.math.toRadians(angle); + + var canvas = workspace.getBubbleCanvas(); + canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight))); + + this.setAnchorLocation(anchorXY); + if (!bubbleWidth || !bubbleHeight) { + var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox(); + bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH; + bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH; + } + this.setBubbleSize(bubbleWidth, bubbleHeight); + + // Render the bubble. + this.positionBubble_(); + this.renderArrow_(); + this.rendered_ = true; + + if (!workspace.options.readOnly) { + Blockly.bindEventWithChecks_( + this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); + if (this.resizeGroup_) { + Blockly.bindEventWithChecks_( + this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); + } + } +}; + +/** + * Width of the border around the bubble. + */ +Blockly.Bubble.BORDER_WIDTH = 6; + +/** + * Determines the thickness of the base of the arrow in relation to the size + * of the bubble. Higher numbers result in thinner arrows. + */ +Blockly.Bubble.ARROW_THICKNESS = 5; + +/** + * The number of degrees that the arrow bends counter-clockwise. + */ +Blockly.Bubble.ARROW_ANGLE = 20; + +/** + * The sharpness of the arrow's bend. Higher numbers result in smoother arrows. + */ +Blockly.Bubble.ARROW_BEND = 4; + +/** + * Distance between arrow point and anchor point. + */ +Blockly.Bubble.ANCHOR_RADIUS = 8; + +/** + * Wrapper function called when a mouseUp occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.Bubble.onMouseUpWrapper_ = null; + +/** + * Wrapper function called when a mouseMove occurs during a drag operation. + * @type {Array.} + * @private + */ +Blockly.Bubble.onMouseMoveWrapper_ = null; + +/** + * Function to call on resize of bubble. + * @type {Function} + */ +Blockly.Bubble.prototype.resizeCallback_ = null; + +/** + * Stop binding to the global mouseup and mousemove events. + * @private + */ +Blockly.Bubble.unbindDragEvents_ = function() { + if (Blockly.Bubble.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_); + Blockly.Bubble.onMouseUpWrapper_ = null; + } + if (Blockly.Bubble.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_); + Blockly.Bubble.onMouseMoveWrapper_ = null; + } +}; + +/* + * Handle a mouse-up event while dragging a bubble's border or resize handle. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) { + Blockly.Touch.clearTouchIdentifier(); + Blockly.Bubble.unbindDragEvents_(); +}; + +/** + * Flag to stop incremental rendering during construction. + * @private + */ +Blockly.Bubble.prototype.rendered_ = false; + +/** + * Absolute coordinate of anchor point, in workspace coordinates. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Bubble.prototype.anchorXY_ = null; + +/** + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. + * In RTL mode the initial value is negated. + * @private + */ +Blockly.Bubble.prototype.relativeLeft_ = 0; + +/** + * Relative Y coordinate of bubble with respect to the anchor's centre. + * @private + */ +Blockly.Bubble.prototype.relativeTop_ = 0; + +/** + * Width of bubble. + * @private + */ +Blockly.Bubble.prototype.width_ = 0; + +/** + * Height of bubble. + * @private + */ +Blockly.Bubble.prototype.height_ = 0; + +/** + * Automatically position and reposition the bubble. + * @private + */ +Blockly.Bubble.prototype.autoLayout_ = true; + +/** + * Create the bubble's DOM. + * @param {!Element} content SVG content for the bubble. + * @param {boolean} hasResize Add diagonal resize gripper if true. + * @return {!Element} The bubble's SVG group. + * @private + */ +Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { + /* Create the bubble. Here's the markup that will be generated: + + + + + + + + + + + [...content goes here...] + + */ + this.bubbleGroup_ = Blockly.utils.createSvgElement('g', {}, null); + var filter = + {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'}; + if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) { + // Multiple reports that JavaFX can't handle filters. UserAgent: + // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 + // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 + // https://github.com/google/blockly/issues/99 + filter = {}; + } + var bubbleEmboss = Blockly.utils.createSvgElement('g', + filter, this.bubbleGroup_); + this.bubbleArrow_ = Blockly.utils.createSvgElement('path', {}, bubbleEmboss); + this.bubbleBack_ = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyDraggable', + 'x': 0, + 'y': 0, + 'rx': Blockly.Bubble.BORDER_WIDTH, + 'ry': Blockly.Bubble.BORDER_WIDTH + }, + bubbleEmboss); + if (hasResize) { + this.resizeGroup_ = Blockly.utils.createSvgElement('g', + {'class': this.workspace_.RTL ? + 'blocklyResizeSW' : 'blocklyResizeSE'}, + this.bubbleGroup_); + var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; + Blockly.utils.createSvgElement('polygon', + {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, + this.resizeGroup_); + Blockly.utils.createSvgElement('line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize / 3 + }, this.resizeGroup_); + Blockly.utils.createSvgElement('line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, + 'y1': resizeSize - 1, + 'x2': resizeSize - 1, + 'y2': resizeSize * 2 / 3 + }, this.resizeGroup_); + } else { + this.resizeGroup_ = null; + } + this.bubbleGroup_.appendChild(content); + return this.bubbleGroup_; +}; + +/** + * Return the root node of the bubble's SVG group. + * @return {Element} The root SVG node of the bubble's group. + */ +Blockly.Bubble.prototype.getSvgRoot = function() { + return this.bubbleGroup_; +}; + +/** + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { + var gesture = this.workspace_.getGesture(e); + if (gesture) { + gesture.handleBubbleStart(e, this); + } +}; + +/** + * Handle a mouse-down on bubble's resize corner. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Bubble.prototype.resizeMouseDown_ = function(e) { + this.promote_(); + Blockly.Bubble.unbindDragEvents_(); + if (Blockly.utils.isRightButton(e)) { + // No right-click. + e.stopPropagation(); + return; + } + // Left-click (or middle click) + this.workspace_.startDrag(e, new goog.math.Coordinate( + this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); + + Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document, + 'mouseup', this, Blockly.Bubble.bubbleMouseUp_); + Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document, + 'mousemove', this, this.resizeMouseMove_); + Blockly.hideChaff(); + // This event has been handled. No need to bubble up to the document. + e.stopPropagation(); +}; + +/** + * Resize this bubble to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.Bubble.prototype.resizeMouseMove_ = function(e) { + this.autoLayout_ = false; + var newXY = this.workspace_.moveDrag(e); + this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); + if (this.workspace_.RTL) { + // RTL requires the bubble to move its left edge. + this.positionBubble_(); + } +}; + +/** + * Register a function as a callback event for when the bubble is resized. + * @param {!Function} callback The function to call on resize. + */ +Blockly.Bubble.prototype.registerResizeEvent = function(callback) { + this.resizeCallback_ = callback; +}; + +/** + * Move this bubble to the top of the stack. + * @private + */ +Blockly.Bubble.prototype.promote_ = function() { + var svgGroup = this.bubbleGroup_.parentNode; + svgGroup.appendChild(this.bubbleGroup_); +}; + +/** + * Notification that the anchor has moved. + * Update the arrow and bubble accordingly. + * @param {!goog.math.Coordinate} xy Absolute location. + */ +Blockly.Bubble.prototype.setAnchorLocation = function(xy) { + this.anchorXY_ = xy; + if (this.rendered_) { + this.positionBubble_(); + } +}; + +/** + * Position the bubble so that it does not fall off-screen. + * @private + */ +Blockly.Bubble.prototype.layoutBubble_ = function() { + // Compute the preferred bubble location. + var relativeLeft = -this.width_ / 4; + var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y; + // Prevent the bubble from being off-screen. + var metrics = this.workspace_.getMetrics(); + metrics.viewWidth /= this.workspace_.scale; + metrics.viewLeft /= this.workspace_.scale; + var anchorX = this.anchorXY_.x; + if (this.workspace_.RTL) { + if (anchorX - metrics.viewLeft - relativeLeft - this.width_ < + Blockly.Scrollbar.scrollbarThickness) { + // Slide the bubble right until it is onscreen. + relativeLeft = anchorX - metrics.viewLeft - this.width_ - + Blockly.Scrollbar.scrollbarThickness; + } else if (anchorX - metrics.viewLeft - relativeLeft > + metrics.viewWidth) { + // Slide the bubble left until it is onscreen. + relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth; + } + } else { + if (anchorX + relativeLeft < metrics.viewLeft) { + // Slide the bubble right until it is onscreen. + relativeLeft = metrics.viewLeft - anchorX; + } else if (metrics.viewLeft + metrics.viewWidth < + anchorX + relativeLeft + this.width_ + + Blockly.BlockSvg.SEP_SPACE_X + + Blockly.Scrollbar.scrollbarThickness) { + // Slide the bubble left until it is onscreen. + relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX - + this.width_ - Blockly.Scrollbar.scrollbarThickness; + } + } + if (this.anchorXY_.y + relativeTop < metrics.viewTop) { + // Slide the bubble below the block. + var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox(); + relativeTop = bBox.height; + } + this.relativeLeft_ = relativeLeft; + this.relativeTop_ = relativeTop; +}; + +/** + * Move the bubble to a location relative to the anchor's centre. + * @private + */ +Blockly.Bubble.prototype.positionBubble_ = function() { + var left = this.anchorXY_.x; + if (this.workspace_.RTL) { + left -= this.relativeLeft_ + this.width_; + } else { + left += this.relativeLeft_; + } + var top = this.relativeTop_ + this.anchorXY_.y; + this.moveTo(left, top); +}; + +/** + * Move the bubble group to the specified location in workspace coordinates. + * @param {number} x The x position to move to. + * @param {number} y The y position to move to. + * @package + */ +Blockly.Bubble.prototype.moveTo = function(x, y) { + this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); +}; + +/** + * Get the dimensions of this bubble. + * @return {!Object} Object with width and height properties. + */ +Blockly.Bubble.prototype.getBubbleSize = function() { + return {width: this.width_, height: this.height_}; +}; + +/** + * Size this bubble. + * @param {number} width Width of the bubble. + * @param {number} height Height of the bubble. + */ +Blockly.Bubble.prototype.setBubbleSize = function(width, height) { + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + // Minimum size of a bubble. + width = Math.max(width, doubleBorderWidth + 45); + height = Math.max(height, doubleBorderWidth + 20); + this.width_ = width; + this.height_ = height; + this.bubbleBack_.setAttribute('width', width); + this.bubbleBack_.setAttribute('height', height); + if (this.resizeGroup_) { + if (this.workspace_.RTL) { + // Mirror the resize group. + var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; + this.resizeGroup_.setAttribute('transform', 'translate(' + + resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)'); + } else { + this.resizeGroup_.setAttribute('transform', 'translate(' + + (width - doubleBorderWidth) + ',' + + (height - doubleBorderWidth) + ')'); + } + } + if (this.rendered_) { + if (this.autoLayout_) { + this.layoutBubble_(); + } + this.positionBubble_(); + this.renderArrow_(); + } + // Allow the contents to resize. + if (this.resizeCallback_) { + this.resizeCallback_(); + } +}; + +/** + * Draw the arrow between the bubble and the origin. + * @private + */ +Blockly.Bubble.prototype.renderArrow_ = function() { + var steps = []; + // Find the relative coordinates of the center of the bubble. + var relBubbleX = this.width_ / 2; + var relBubbleY = this.height_ / 2; + // Find the relative coordinates of the center of the anchor. + var relAnchorX = -this.relativeLeft_; + var relAnchorY = -this.relativeTop_; + if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) { + // Null case. Bubble is directly on top of the anchor. + // Short circuit this rather than wade through divide by zeros. + steps.push('M ' + relBubbleX + ',' + relBubbleY); + } else { + // Compute the angle of the arrow's line. + var rise = relAnchorY - relBubbleY; + var run = relAnchorX - relBubbleX; + if (this.workspace_.RTL) { + run *= -1; + } + var hypotenuse = Math.sqrt(rise * rise + run * run); + var angle = Math.acos(run / hypotenuse); + if (rise < 0) { + angle = 2 * Math.PI - angle; + } + // Compute a line perpendicular to the arrow. + var rightAngle = angle + Math.PI / 2; + if (rightAngle > Math.PI * 2) { + rightAngle -= Math.PI * 2; + } + var rightRise = Math.sin(rightAngle); + var rightRun = Math.cos(rightAngle); + + // Calculate the thickness of the base of the arrow. + var bubbleSize = this.getBubbleSize(); + var thickness = (bubbleSize.width + bubbleSize.height) / + Blockly.Bubble.ARROW_THICKNESS; + thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4; + + // Back the tip of the arrow off of the anchor. + var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse; + relAnchorX = relBubbleX + backoffRatio * run; + relAnchorY = relBubbleY + backoffRatio * rise; + + // Coordinates for the base of the arrow. + var baseX1 = relBubbleX + thickness * rightRun; + var baseY1 = relBubbleY + thickness * rightRise; + var baseX2 = relBubbleX - thickness * rightRun; + var baseY2 = relBubbleY - thickness * rightRise; + + // Distortion to curve the arrow. + var swirlAngle = angle + this.arrow_radians_; + if (swirlAngle > Math.PI * 2) { + swirlAngle -= Math.PI * 2; + } + var swirlRise = Math.sin(swirlAngle) * + hypotenuse / Blockly.Bubble.ARROW_BEND; + var swirlRun = Math.cos(swirlAngle) * + hypotenuse / Blockly.Bubble.ARROW_BEND; + + steps.push('M' + baseX1 + ',' + baseY1); + steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + + ' ' + relAnchorX + ',' + relAnchorY + + ' ' + relAnchorX + ',' + relAnchorY); + steps.push('C' + relAnchorX + ',' + relAnchorY + + ' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) + + ' ' + baseX2 + ',' + baseY2); + } + steps.push('z'); + this.bubbleArrow_.setAttribute('d', steps.join(' ')); +}; + +/** + * Change the colour of a bubble. + * @param {string} hexColour Hex code of colour. + */ +Blockly.Bubble.prototype.setColour = function(hexColour) { + this.bubbleBack_.setAttribute('fill', hexColour); + this.bubbleArrow_.setAttribute('fill', hexColour); +}; + +/** + * Dispose of this bubble. + */ +Blockly.Bubble.prototype.dispose = function() { + Blockly.Bubble.unbindDragEvents_(); + // Dispose of and unlink the bubble. + goog.dom.removeNode(this.bubbleGroup_); + this.bubbleGroup_ = null; + this.bubbleArrow_ = null; + this.bubbleBack_ = null; + this.resizeGroup_ = null; + this.workspace_ = null; + this.content_ = null; + this.shape_ = null; +}; + +/** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) { + if (dragSurface) { + dragSurface.translateSurface(newLoc.x, newLoc.y); + } else { + this.moveTo(newLoc.x, newLoc.y); + } + if (this.workspace_.RTL) { + this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_; + } else { + this.relativeLeft_ = newLoc.x - this.anchorXY_.x; + } + this.relativeTop_ = newLoc.y - this.anchorXY_.y; + this.renderArrow_(); +}; + +/** + * Return the coordinates of the top-left corner of this bubble's body relative + * to the drawing surface's origin (0,0), in workspace units. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() { + return new goog.math.Coordinate( + this.anchorXY_.x + this.relativeLeft_, + this.anchorXY_.y + this.relativeTop_); +}; + +/** + * Set whether auto-layout of this bubble is enabled. The first time a bubble + * is shown it positions itself to not cover any blocks. Once a user has + * dragged it to reposition, it renders where the user put it. + * @param {boolean} enable True if auto-layout should be enabled, false + * otherwise. + * @package + */ +Blockly.Bubble.prototype.setAutoLayout = function(enable) { + this.autoLayout_ = enable; +}; diff --git a/core/.svn/pristine/a9/a90d3f612f2edc785095f7a8ffc7b91fcf9f9a88.svn-base b/core/.svn/pristine/a9/a90d3f612f2edc785095f7a8ffc7b91fcf9f9a88.svn-base new file mode 100644 index 0000000..26adc0f --- /dev/null +++ b/core/.svn/pristine/a9/a90d3f612f2edc785095f7a8ffc7b91fcf9f9a88.svn-base @@ -0,0 +1,309 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 The class extends Blockly.Gesture to support pinch to zoom + * for both pointer and touch events. + * @author samelh@microsoft.com (Sam El-Husseini) + */ +'use strict'; + +goog.provide('Blockly.TouchGesture'); + +goog.require('Blockly.Gesture'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/* + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ + +/** + * Class for one gesture. + * @param {!Event} e The event that kicked off this gesture. + * @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. + * @extends {Blockly.Gesture} + * @constructor + */ +Blockly.TouchGesture = function(e, creatorWorkspace) { + Blockly.TouchGesture.superClass_.constructor.call(this, e, creatorWorkspace); + + /** + * Boolean for whether or not this gesture is a multi-touch gesture. + * @type {boolean} + * @private + */ + this.isMultiTouch_ = false; + + /** + * A map of cached points used for tracking multi-touch gestures. + * @type {Object} + * @private + */ + this.cachedPoints_ = {}; + + /** + * This is the ratio between the starting distance between the touch points + * and the most recent distance between the touch points. + * Scales between 0 and 1 mean the most recent zoom was a zoom out. + * Scales above 1.0 mean the most recent zoom was a zoom in. + * @type {number} + * @private + */ + this.previousScale_ = 0; + + /** + * The starting distance between two touch points. + * @type {number} + * @private + */ + this.startDistance_ = 0; + + /** + * A handle to use to unbind the second touch start or pointer down listener + * at the end of a drag. Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onStartWrapper_ = null; +}; +goog.inherits(Blockly.TouchGesture, Blockly.Gesture); + +/** + * A multiplier used to convert the gesture scale to a zoom in delta. + * @const + */ +Blockly.TouchGesture.ZOOM_IN_MULTIPLIER = 5; + +/** + * A multiplier used to convert the gesture scale to a zoom out delta. + * @const + */ +Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER = 6; + +/** + * Start a gesture: update the workspace to indicate that a gesture is in + * progress and bind mousemove and mouseup handlers. + * @param {!Event} e A mouse down, touch start or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.doStart = function(e) { + Blockly.TouchGesture.superClass_.doStart.call(this, e); + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + } +}; + +/** + * Bind gesture events. + * Overriding the gesture definition of this function, binding the same + * functions for onMoveWrapper_ and onUpWrapper_ but passing opt_noCaptureIdentifier. + * In addition, binding a second mouse down event to detect multi-touch events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.TouchGesture.prototype.bindMouseEvents = function(e) { + this.onStartWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousedown', null, this.handleStart.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', null, this.handleMove.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', null, this.handleUp.bind(this), + /*opt_noCaptureIdentifier*/ true); + + e.preventDefault(); +}; + +/** + * Handle a mouse down, touch start, or pointer down event. + * @param {!Event} e A mouse down, touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleStart = function(e) { + if (!this.isDragging) { + // A drag has already started, so this can no longer be a pinch-zoom. + return; + } + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + + if (this.isMultiTouch()) { + Blockly.longStop_(); + } + } +}; + +/** + * Handle a mouse move, touch move, or pointer move event. + * @param {!Event} e A mouse move, touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleMove = function(e) { + if (this.isDragging()) { + // We are in the middle of a drag, only handle the relevant events + if (Blockly.Touch.shouldHandleEvent(e)) { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } + return; + } + if (this.isMultiTouch()) { + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchMove(e); + } + Blockly.longStop_(); + } else { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } +}; + +/** + * Handle a mouse up, touch end, or pointer up event. + * @param {!Event} e A mouse up, touch end, or pointer up event. + * @package + */ +Blockly.TouchGesture.prototype.handleUp = function(e) { + if (Blockly.Touch.isTouchEvent(e) && !this.isDragging()) { + this.handleTouchEnd(e); + } + if (!this.isMultiTouch() || this.isDragging()) { + if (!Blockly.Touch.shouldHandleEvent(e)) { + return; + } + Blockly.TouchGesture.superClass_.handleUp.call(this, e); + } else { + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); + } +}; + +/** + * Whether this gesture is part of a multi-touch gesture. + * @return {boolean} whether this gesture is part of a multi-touch gesture. + * @package + */ +Blockly.TouchGesture.prototype.isMultiTouch = function() { + return this.isMultiTouch_; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.TouchGesture.prototype.dispose = function() { + Blockly.TouchGesture.superClass_.dispose.call(this); + + if (this.onStartWrapper_) { + Blockly.unbindEvent_(this.onStartWrapper_); + } +}; + +/** + * Handle a touch start or pointer down event and keep track of current pointers. + * @param {!Event} e A touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchStart = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // store the pointerId in the current list of pointers + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + this.startDistance_ = goog.math.Coordinate.distance(point0, point1); + this.isMultiTouch_ = true; + } + e.preventDefault(); +}; + +/** + * Handle a touch move or pointer move event and zoom in/out if two pointers are on the screen. + * @param {!Event} e A touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchMove = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // Update the cache + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + // Calculate the distance between the two pointers + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + var moveDistance = goog.math.Coordinate.distance(point0, point1); + var startDistance = this.startDistance_; + var scale = this.touchScale_ = moveDistance / startDistance; + + if (this.previousScale_ > 0 && this.previousScale_ < Infinity) { + var gestureScale = scale - this.previousScale_; + var delta = gestureScale > 0 ? + gestureScale * Blockly.TouchGesture.ZOOM_IN_MULTIPLIER : + gestureScale * Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER; + var workspace = this.startWorkspace_; + var position = Blockly.utils.mouseToSvg(e, workspace.getParentSvg(), workspace.getInverseScreenCTM()); + workspace.zoom(position.x, position.y, delta); + } + this.previousScale_ = scale; + } + e.preventDefault(); +}; + +/** + * Handle a touch end or pointer end event and end the gesture. + * @param {!Event} e A touch end, or pointer end event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchEnd = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + if (this.cachedPoints_[pointerId]) { + delete this.cachedPoints_[pointerId]; + } + if (Object.keys(this.cachedPoints_).length < 2) { + this.cachedPoints_ = {}; + this.previousScale_ = 0; + } +}; + +/** + * Helper function returning the current touch point coordinate. + * @param {!Event} e A touch or pointer event. + * @return {goog.math.Coordinate} the current touch point coordinate + * @package + */ +Blockly.TouchGesture.prototype.getTouchPoint = function(e) { + if (!this.startWorkspace_) { + return null; + } + return new goog.math.Coordinate( + (e.pageX ? e.pageX : e.changedTouches[0].pageX), + (e.pageY ? e.pageY : e.changedTouches[0].pageY) + ); +}; diff --git a/core/.svn/pristine/ac/ac99b528616dcc471e3e4fa7ce2bd7a6b7c428db.svn-base b/core/.svn/pristine/ac/ac99b528616dcc471e3e4fa7ce2bd7a6b7c428db.svn-base new file mode 100644 index 0000000..88b7fb6 --- /dev/null +++ b/core/.svn/pristine/ac/ac99b528616dcc471e3e4fa7ce2bd7a6b7c428db.svn-base @@ -0,0 +1,205 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 Object representing an icon on a block. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Icon'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for an icon. + * @param {Blockly.Block} block The block associated with this icon. + * @constructor + */ +Blockly.Icon = function(block) { + this.block_ = block; +}; + +/** + * Does this icon get hidden when the block is collapsed. + */ +Blockly.Icon.prototype.collapseHidden = true; + +/** + * Height and width of icons. + */ +Blockly.Icon.prototype.SIZE = 17; + +/** + * Bubble UI (if visible). + * @type {Blockly.Bubble} + * @private + */ +Blockly.Icon.prototype.bubble_ = null; + +/** + * Absolute coordinate of icon's center. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Icon.prototype.iconXY_ = null; + +/** + * Create the icon on the block. + */ +Blockly.Icon.prototype.createIcon = function() { + if (this.iconGroup_) { + // Icon already exists. + return; + } + /* Here's the markup that will be generated: + + ... + + */ + this.iconGroup_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyIconGroup'}, null); + if (this.block_.isInFlyout) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), 'blocklyIconGroupReadonly'); + } + this.drawIcon_(this.iconGroup_); + + this.block_.getSvgRoot().appendChild(this.iconGroup_); + Blockly.bindEventWithChecks_( + this.iconGroup_, 'mouseup', this, this.iconClick_); + this.updateEditable(); +}; + +/** + * Dispose of this icon. + */ +Blockly.Icon.prototype.dispose = function() { + // Dispose of and unlink the icon. + goog.dom.removeNode(this.iconGroup_); + this.iconGroup_ = null; + // Dispose of and unlink the bubble. + this.setVisible(false); + this.block_ = null; +}; + +/** + * Add or remove the UI indicating if this icon may be clicked or not. + */ +Blockly.Icon.prototype.updateEditable = function() { +}; + +/** + * Is the associated bubble visible? + * @return {boolean} True if the bubble is visible. + */ +Blockly.Icon.prototype.isVisible = function() { + return !!this.bubble_; +}; + +/** + * Clicking on the icon toggles if the bubble is visible. + * @param {!Event} e Mouse click event. + * @private + */ +Blockly.Icon.prototype.iconClick_ = function(e) { + if (this.block_.workspace.isDragging()) { + // Drag operation is concluding. Don't open the editor. + return; + } + if (!this.block_.isInFlyout && !Blockly.utils.isRightButton(e)) { + this.setVisible(!this.isVisible()); + } +}; + +/** + * Change the colour of the associated bubble to match its block. + */ +Blockly.Icon.prototype.updateColour = function() { + if (this.isVisible()) { + this.bubble_.setColour(this.block_.getColour()); + } +}; + +/** + * Render the icon. + * @param {number} cursorX Horizontal offset at which to position the icon. + * @return {number} Horizontal offset for next item to draw. + */ +Blockly.Icon.prototype.renderIcon = function(cursorX) { + if (this.collapseHidden && this.block_.isCollapsed()) { + this.iconGroup_.setAttribute('display', 'none'); + return cursorX; + } + this.iconGroup_.setAttribute('display', 'block'); + + var TOP_MARGIN = 5; + var width = this.SIZE; + if (this.block_.RTL) { + cursorX -= width; + } + this.iconGroup_.setAttribute('transform', + 'translate(' + cursorX + ',' + TOP_MARGIN + ')'); + this.computeIconLocation(); + if (this.block_.RTL) { + cursorX -= Blockly.BlockSvg.SEP_SPACE_X; + } else { + cursorX += width + Blockly.BlockSvg.SEP_SPACE_X; + } + return cursorX; +}; + +/** + * Notification that the icon has moved. Update the arrow accordingly. + * @param {!goog.math.Coordinate} xy Absolute location in workspace coordinates. + */ +Blockly.Icon.prototype.setIconLocation = function(xy) { + this.iconXY_ = xy; + if (this.isVisible()) { + this.bubble_.setAnchorLocation(xy); + } +}; + +/** + * Notification that the icon has moved, but we don't really know where. + * Recompute the icon's location from scratch. + */ +Blockly.Icon.prototype.computeIconLocation = function() { + // Find coordinates for the centre of the icon and update the arrow. + var blockXY = this.block_.getRelativeToSurfaceXY(); + var iconXY = Blockly.utils.getRelativeXY(this.iconGroup_); + var newXY = new goog.math.Coordinate( + blockXY.x + iconXY.x + this.SIZE / 2, + blockXY.y + iconXY.y + this.SIZE / 2); + if (!goog.math.Coordinate.equals(this.getIconLocation(), newXY)) { + this.setIconLocation(newXY); + } +}; + +/** + * Returns the center of the block's icon relative to the surface. + * @return {!goog.math.Coordinate} Object with x and y properties in workspace + * coordinates. + */ +Blockly.Icon.prototype.getIconLocation = function() { + return this.iconXY_; +}; diff --git a/core/.svn/pristine/b0/b00b0d78bffc6b7f1cccc7d14b9e43b4716a3208.svn-base b/core/.svn/pristine/b0/b00b0d78bffc6b7f1cccc7d14b9e43b4716a3208.svn-base new file mode 100644 index 0000000..333fee2 --- /dev/null +++ b/core/.svn/pristine/b0/b00b0d78bffc6b7f1cccc7d14b9e43b4716a3208.svn-base @@ -0,0 +1,354 @@ +/** + * @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 Variable input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldVariable'); + +goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.Msg'); +goog.require('Blockly.VariableModel'); +goog.require('Blockly.Variables'); +goog.require('goog.asserts'); +goog.require('goog.string'); + + +/** + * Class for a variable's dropdown field. + * @param {?string} varname The default name for the variable. If null, + * a unique variable name will be generated. + * @param {Function=} opt_validator A function that is executed when a new + * option is selected. Its sole argument is the new option value. + * @param {Array.=} opt_variableTypes A list of the types of variables + * to include in the dropdown. + * @param {string=} opt_defaultType The type of variable to create if this + * field's value is not explicitly set. Defaults to ''. + * @extends {Blockly.FieldDropdown} + * @constructor + */ +Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes, + opt_defaultType) { + // The FieldDropdown constructor would call setValue, which might create a + // spurious variable. Just do the relevant parts of the constructor. + this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate; + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); + this.setValidator(opt_validator); + this.defaultVariableName = (varname || ''); + + this.setTypes_(opt_variableTypes, opt_defaultType); + this.value_ = null; +}; +goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); + +/** + * Construct a FieldVariable from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (variable, + * variableTypes, and defaultType). + * @returns {!Blockly.FieldVariable} The new field instance. + * @package + */ +Blockly.FieldVariable.fromJson = function(options) { + var varname = Blockly.utils.replaceMessageReferences(options['variable']); + var variableTypes = options['variableTypes']; + var defaultType = options['defaultType']; + return new Blockly.FieldVariable(varname, null, variableTypes, defaultType); +}; + +/** + * Initialize everything needed to render this field. This includes making sure + * that the field's value is valid. + * @public + */ +Blockly.FieldVariable.prototype.init = function() { + if (this.fieldGroup_) { + // Dropdown has already been initialized once. + return; + } + Blockly.FieldVariable.superClass_.init.call(this); + + // TODO (1010): Change from init/initModel to initView/initModel + this.initModel(); +}; + +/** + * Initialize the model for this field if it has not already been initialized. + * If the value has not been set to a variable by the first render, we make up a + * variable rather than let the value be invalid. + * @package + */ +Blockly.FieldVariable.prototype.initModel = function() { + if (this.variable_) { + return; // Initialization already happened. + } + this.workspace_ = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace_, null, this.defaultVariableName, this.defaultType_); + + // Don't fire a change event for this setValue. It would have null as the + // old value, which is not valid. + Blockly.Events.disable(); + try { + this.setValue(variable.getId()); + } finally { + Blockly.Events.enable(); + } +}; + +/** + * Dispose of this field. + * @public + */ +Blockly.FieldVariable.dispose = function() { + Blockly.FieldVariable.superClass_.dispose.call(this); + this.workspace_ = null; + this.variableMap_ = null; +}; + +/** + * Attach this field to a block. + * @param {!Blockly.Block} block The block containing this field. + */ +Blockly.FieldVariable.prototype.setSourceBlock = function(block) { + goog.asserts.assert(!block.isShadow(), + 'Variable fields are not allowed to exist on shadow blocks.'); + Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block); +}; + +/** + * Get the variable's ID. + * @return {string} Current variable's ID. + */ +Blockly.FieldVariable.prototype.getValue = function() { + return this.variable_ ? this.variable_.getId() : null; +}; + +/** + * Get the text from this field, which is the selected variable's name. + * @return {string} The selected variable's name, or the empty string if no + * variable is selected. + */ +Blockly.FieldVariable.prototype.getText = function() { + return this.variable_ ? this.variable_.name : ''; +}; + +/** + * Get the variable model for the selected variable. + * Not guaranteed to be in the variable map on the workspace (e.g. if accessed + * after the variable has been deleted). + * @return {?Blockly.VariableModel} the selected variable, or null if none was + * selected. + * @package + */ +Blockly.FieldVariable.prototype.getVariable = function() { + return this.variable_; +}; + +/** + * Set the variable ID. + * @param {string} id New variable ID, which must reference an existing + * variable. + */ +Blockly.FieldVariable.prototype.setValue = function(id) { + var workspace = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getVariable(workspace, id); + + if (!variable) { + throw new Error('Variable id doesn\'t point to a real variable! ID was ' + + id); + } + // Type checks! + var type = variable.type; + if (!this.typeIsAllowed_(type)) { + throw new Error('Variable type doesn\'t match this field! Type was ' + + type); + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + var oldValue = this.variable_ ? this.variable_.getId() : null; + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, id)); + } + this.variable_ = variable; + this.value_ = id; + this.setText(variable.name); +}; + +/** + * Check whether the given variable type is allowed on this field. + * @param {string} type The type to check. + * @return {boolean} True if the type is in the list of allowed types. + * @private + */ +Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) { + var typeList = this.getVariableTypes_(); + if (!typeList) { + return true; // If it's null, all types are valid. + } + for (var i = 0; i < typeList.length; i++) { + if (type == typeList[i]) { + return true; + } + } + return false; +}; + +/** + * Return a list of variable types to include in the dropdown. + * @return {!Array.} Array of variable types. + * @throws {Error} if variableTypes is an empty array. + * @private + */ +Blockly.FieldVariable.prototype.getVariableTypes_ = function() { + // TODO (#1513): Try to avoid calling this every time the field is edited. + var variableTypes = this.variableTypes; + if (variableTypes === null) { + // If variableTypes is null, return all variable types. + if (this.sourceBlock_) { + var workspace = this.sourceBlock_.workspace; + return workspace.getVariableTypes(); + } + } + variableTypes = variableTypes || ['']; + if (variableTypes.length == 0) { + // Throw an error if variableTypes is an empty list. + var name = this.getText(); + throw new Error('\'variableTypes\' of field variable ' + + name + ' was an empty list'); + } + return variableTypes; +}; + +/** + * Parse the optional arguments representing the allowed variable types and the + * default variable type. + * @param {Array.=} opt_variableTypes A list of the types of variables + * to include in the dropdown. If null or undefined, variables of all types + * will be displayed in the dropdown. + * @param {string=} opt_defaultType The type of the variable to create if this + * field's value is not explicitly set. Defaults to ''. + * @private + */ +Blockly.FieldVariable.prototype.setTypes_ = function(opt_variableTypes, + opt_defaultType) { + // If you expected that the default type would be the same as the only entry + // in the variable types array, tell the Blockly team by commenting on #1499. + var defaultType = opt_defaultType || ''; + // Set the allowable variable types. Null means all types on the workspace. + if (opt_variableTypes == null || opt_variableTypes == undefined) { + var variableTypes = null; + } else if (Array.isArray(opt_variableTypes)) { + var variableTypes = opt_variableTypes; + // Make sure the default type is valid. + var isInArray = false; + for (var i = 0; i < variableTypes.length; i++) { + if (variableTypes[i] == defaultType) { + isInArray = true; + } + } + if (!isInArray) { + throw new Error('Invalid default type \'' + defaultType + '\' in ' + + 'the definition of a FieldVariable'); + } + } else { + throw new Error('\'variableTypes\' was not an array in the definition of ' + + 'a FieldVariable'); + } + // Only update the field once all checks pass. + this.defaultType_ = defaultType; + this.variableTypes = variableTypes; +}; + +/** + * Return a sorted list of variable names for variable dropdown menus. + * Include a special option at the end for creating a new variable name. + * @return {!Array.} Array of variable names. + * @this {Blockly.FieldVariable} + */ +Blockly.FieldVariable.dropdownCreate = function() { + if (!this.variable_) { + throw new Error('Tried to call dropdownCreate on a variable field with no' + + ' variable selected.'); + } + var variableModelList = []; + var name = this.getText(); + var workspace = null; + if (this.sourceBlock_) { + workspace = this.sourceBlock_.workspace; + } + if (workspace) { + var variableTypes = this.getVariableTypes_(); + var variableModelList = []; + // Get a copy of the list, so that adding rename and new variable options + // doesn't modify the workspace's list. + for (var i = 0; i < variableTypes.length; i++) { + var variableType = variableTypes[i]; + var variables = workspace.getVariablesOfType(variableType); + variableModelList = variableModelList.concat(variables); + } + } + variableModelList.sort(Blockly.VariableModel.compareByName); + + var options = []; + for (var i = 0; i < variableModelList.length; i++) { + // Set the UUID as the internal representation of the variable. + options[i] = [variableModelList[i].name, variableModelList[i].getId()]; + } + options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]); + if (Blockly.Msg.DELETE_VARIABLE) { + options.push( + [ + Blockly.Msg.DELETE_VARIABLE.replace('%1', name), + Blockly.DELETE_VARIABLE_ID + ] + ); + } + + return options; +}; + +/** + * Handle the selection of an item in the variable dropdown menu. + * Special case the 'Rename variable...' and 'Delete variable...' options. + * In the rename case, prompt the user for a new name. + * @param {!goog.ui.Menu} menu The Menu component clicked. + * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu. + */ +Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) { + var id = menuItem.getValue(); + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + var workspace = this.sourceBlock_.workspace; + if (id == Blockly.RENAME_VARIABLE_ID) { + // Rename variable. + Blockly.Variables.renameVariable(workspace, this.variable_); + return; + } else if (id == Blockly.DELETE_VARIABLE_ID) { + // Delete variable. + workspace.deleteVariableById(this.variable_.getId()); + return; + } + + // TODO (#1529): Call any validation function, and allow it to override. + } + this.setValue(id); +}; diff --git a/core/.svn/pristine/b1/b122fa948189635f895dd38d5dd48b28670897dd.svn-base b/core/.svn/pristine/b1/b122fa948189635f895dd38d5dd48b28670897dd.svn-base new file mode 100644 index 0000000..6805d42 --- /dev/null +++ b/core/.svn/pristine/b1/b122fa948189635f895dd38d5dd48b28670897dd.svn-base @@ -0,0 +1,197 @@ +/** + * @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 Object representing a warning. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Warning'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); + + +/** + * Class for a warning. + * @param {!Blockly.Block} block The block associated with this warning. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Warning = function(block) { + Blockly.Warning.superClass_.constructor.call(this, block); + this.createIcon(); + // The text_ object can contain multiple warnings. + this.text_ = {}; +}; +goog.inherits(Blockly.Warning, Blockly.Icon); + +/** + * Does this icon get hidden when the block is collapsed. + */ +Blockly.Warning.prototype.collapseHidden = false; + +/** + * Draw the warning icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Warning.prototype.drawIcon_ = function(group) { + // Triangle with rounded corners. + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconShape', + 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z' + }, + group); + // Can't use a real '!' text character since different browsers and operating + // systems render it differently. + // Body of exclamation point. + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z' + }, + group); + // Dot of exclamation point. + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconSymbol', + 'x': '7', 'y': '11', 'height': '2', 'width': '2' + }, + group); +}; + +/** + * Create the text for the warning's bubble. + * @param {string} text The text to display. + * @return {!SVGTextElement} The top-level node of the text. + * @private + */ +Blockly.Warning.textToDom_ = function(text) { + var paragraph = /** @type {!SVGTextElement} */ + (Blockly.utils.createSvgElement( + 'text', + { + 'class': 'blocklyText blocklyBubbleText', + 'y': Blockly.Bubble.BORDER_WIDTH + }, + null) + ); + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) { + var tspanElement = Blockly.utils.createSvgElement('tspan', + {'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph); + var textNode = document.createTextNode(lines[i]); + tspanElement.appendChild(textNode); + } + return paragraph; +}; + +/** + * Show or hide the warning bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Warning.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible)); + if (visible) { + // Create the bubble to display all warnings. + var paragraph = Blockly.Warning.textToDom_(this.getText()); + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + paragraph, this.block_.svgPath_, this.iconXY_, null, null); + if (this.block_.RTL) { + // Right-align the paragraph. + // This cannot be done until the bubble is rendered on screen. + var maxWidth = paragraph.getBBox().width; + for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) { + textElement.setAttribute('text-anchor', 'end'); + textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH); + } + } + this.updateColour(); + // Bump the warning into the right location. + var size = this.bubble_.getBubbleSize(); + this.bubble_.setBubbleSize(size.width, size.height); + } else { + // Dispose of the bubble. + this.bubble_.dispose(); + this.bubble_ = null; + this.body_ = null; + } +}; + +/** + * Bring the warning to the top of the stack when clicked on. + * @param {!Event} e Mouse up event. + * @private + */ + +Blockly.Warning.prototype.bodyFocus_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + this.bubble_.promote_(); +}; + +/** + * Set this warning's text. + * @param {string} text Warning text (or '' to delete). + * @param {string} id An ID for this text entry to be able to maintain + * multiple warnings. + */ +Blockly.Warning.prototype.setText = function(text, id) { + if (this.text_[id] == text) { + return; + } + if (text) { + this.text_[id] = text; + } else { + delete this.text_[id]; + } + if (this.isVisible()) { + this.setVisible(false); + this.setVisible(true); + } +}; + +/** + * Get this warning's texts. + * @return {string} All texts concatenated into one string. + */ +Blockly.Warning.prototype.getText = function() { + var allWarnings = []; + for (var id in this.text_) { + allWarnings.push(this.text_[id]); + } + return allWarnings.join('\n'); +}; + +/** + * Dispose of this warning. + */ +Blockly.Warning.prototype.dispose = function() { + this.block_.warning = null; + Blockly.Icon.prototype.dispose.call(this); +}; diff --git a/core/.svn/pristine/b1/b14ab7c6d3ff137f38ebaa112252f648f6963563.svn-base b/core/.svn/pristine/b1/b14ab7c6d3ff137f38ebaa112252f648f6963563.svn-base new file mode 100644 index 0000000..2166860 --- /dev/null +++ b/core/.svn/pristine/b1/b14ab7c6d3ff137f38ebaa112252f648f6963563.svn-base @@ -0,0 +1,991 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Methods for graphically rendering a block as SVG. + * @author fenichel@google.com (Rachel Fenichel) + */ + +'use strict'; + +goog.provide('Blockly.BlockSvg.render'); + +goog.require('Blockly.BlockSvg'); + +goog.require('goog.userAgent'); + + +// UI constants for rendering blocks. +/** + * Horizontal space between elements. + * @const + */ +Blockly.BlockSvg.SEP_SPACE_X = 10; +/** + * Vertical space between elements. + * @const + */ +Blockly.BlockSvg.SEP_SPACE_Y = 10; +/** + * Vertical padding around inline elements. + * @const + */ +Blockly.BlockSvg.INLINE_PADDING_Y = 5; +/** + * Minimum height of a block. + * @const + */ +Blockly.BlockSvg.MIN_BLOCK_Y = 25; +/** + * Height of horizontal puzzle tab. + * @const + */ +Blockly.BlockSvg.TAB_HEIGHT = 20; +/** + * Width of horizontal puzzle tab. + * @const + */ +Blockly.BlockSvg.TAB_WIDTH = 8; +/** + * Width of vertical tab (inc left margin). + * @const + */ +Blockly.BlockSvg.NOTCH_WIDTH = 30; +/** + * Rounded corner radius. + * @const + */ +Blockly.BlockSvg.CORNER_RADIUS = 8; +/** + * Do blocks with no previous or output connections have a 'hat' on top? + * @const + */ +Blockly.BlockSvg.START_HAT = false; +/** + * Height of the top hat. + * @const + */ +Blockly.BlockSvg.START_HAT_HEIGHT = 15; +/** + * Path of the top hat's curve. + * @const + */ +Blockly.BlockSvg.START_HAT_PATH = 'c 30,-' + + Blockly.BlockSvg.START_HAT_HEIGHT + ' 70,-' + + Blockly.BlockSvg.START_HAT_HEIGHT + ' 100,0'; +/** + * Path of the top hat's curve's highlight in LTR. + * @const + */ +Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR = + 'c 17.8,-9.2 45.3,-14.9 75,-8.7 M 100.5,0.5'; +/** + * Path of the top hat's curve's highlight in RTL. + * @const + */ +Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL = + 'm 25,-8.7 c 29.7,-6.2 57.2,-0.5 75,8.7'; +/** + * Distance from shape edge to intersect with a curved corner at 45 degrees. + * Applies to highlighting on around the inside of a curve. + * @const + */ +Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) * + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + 0.5; +/** + * Distance from shape edge to intersect with a curved corner at 45 degrees. + * Applies to highlighting on around the outside of a curve. + * @const + */ +Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) * + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) - 0.5; +/** + * SVG path for drawing next/previous notch from left to right. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4'; +/** + * SVG path for drawing next/previous notch from left to right with + * highlighting. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6,4 3,0 6,-4'; +/** + * SVG path for drawing next/previous notch from right to left. + * @const + */ +Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4'; +/** + * SVG path for drawing jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4'; +/** + * Height of SVG path for jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20; +/** + * Width of SVG path for jagged teeth at the end of collapsed blocks. + * @const + */ +Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15; +/** + * SVG path for drawing a horizontal puzzle tab from top to bottom. + * @const + */ +Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH + + ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' + + Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5'; +/** + * SVG path for drawing a horizontal puzzle tab from top to bottom with + * highlighting from the upper-right. + * @const + */ +Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' + + (Blockly.BlockSvg.TAB_WIDTH * 0.97) + ',3 q -' + + (Blockly.BlockSvg.TAB_WIDTH * 0.05) + ',10 ' + + (Blockly.BlockSvg.TAB_WIDTH * 0.3) + ',9.5 m ' + + (Blockly.BlockSvg.TAB_WIDTH * 0.67) + ',-1.9 v 1.4'; + +/** + * SVG start point for drawing the top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START = + 'm 0,' + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG start point for drawing the top-left corner's highlight in RTL. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL = + 'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' + + Blockly.BlockSvg.DISTANCE_45_INSIDE; +/** + * SVG start point for drawing the top-left corner's highlight in LTR. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR = + 'm 0.5,' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5); +/** + * SVG path for drawing the rounded top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER = + 'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',0'; +/** + * SVG path for drawing the highlight on the rounded top-left corner. + * @const + */ +Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT = + 'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',0.5'; +/** + * SVG path for drawing the top-left corner of a statement input. + * Includes the top notch, a horizontal space, and the rounded inside corner. + * @const + */ +Blockly.BlockSvg.INNER_TOP_LEFT_CORNER = + Blockly.BlockSvg.NOTCH_PATH_RIGHT + ' h -' + + (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) + + ' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' + + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG path for drawing the bottom-left corner of a statement input. + * Includes the rounded inside corner. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER = + 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' + + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS; +/** + * SVG path for drawing highlight on the top-left corner of a statement + * input in RTL. + * @const + */ +Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL = + 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' + + (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - + Blockly.BlockSvg.DISTANCE_45_OUTSIDE); +/** + * SVG path for drawing highlight on the bottom-left corner of a statement + * input in RTL. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL = + 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5); +/** + * SVG path for drawing highlight on the bottom-left corner of a statement + * input in LTR. + * @const + */ +Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR = + 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS + 0.5) + ' 0 0,0 ' + + (Blockly.BlockSvg.CORNER_RADIUS - + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' + + (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5); + +/** + * Returns a bounding box describing the dimensions of this block + * and any blocks stacked below it. + * @return {!{height: number, width: number}} Object with height and width + * properties in workspace units. + */ +Blockly.BlockSvg.prototype.getHeightWidth = function() { + var height = this.height; + var width = this.width; + // Recursively add size of subsequent blocks. + var nextBlock = this.getNextBlock(); + if (nextBlock) { + var nextHeightWidth = nextBlock.getHeightWidth(); + height += nextHeightWidth.height - 4; // Height of tab. + width = Math.max(width, nextHeightWidth.width); + } else if (!this.nextConnection && !this.outputConnection) { + // Add a bit of margin under blocks with no bottom tab. + height += 2; + } + return {height: height, width: width}; +}; + +/** + * Render the block. + * Lays out and reflows a block based on its contents and settings. + * @param {boolean=} opt_bubble If false, just render this block. + * If true, also render block's parent, grandparent, etc. Defaults to true. + */ +Blockly.BlockSvg.prototype.render = function(opt_bubble) { + Blockly.Field.startCache(); + this.rendered = true; + + var cursorX = Blockly.BlockSvg.SEP_SPACE_X; + if (this.RTL) { + cursorX = -cursorX; + } + // Move the icons into position. + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + cursorX = icons[i].renderIcon(cursorX); + } + cursorX += this.RTL ? + Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X; + // If there are no icons, cursorX will be 0, otherwise it will be the + // width that the first label needs to move over by. + + var inputRows = this.renderCompute_(cursorX); + this.renderDraw_(cursorX, inputRows); + this.renderMoveConnections_(); + + if (opt_bubble !== false) { + // Render all blocks above this one (propagate a reflow). + var parentBlock = this.getParent(); + if (parentBlock) { + parentBlock.render(true); + } else { + // Top-most block. Fire an event to allow scrollbars to resize. + this.workspace.resizeContents(); + } + } + Blockly.Field.stopCache(); +}; + +/** + * Render a list of fields starting at the specified location. + * @param {!Array.} fieldList List of fields. + * @param {number} cursorX X-coordinate to start the fields. + * @param {number} cursorY Y-coordinate to start the fields. + * @return {number} X-coordinate of the end of the field row (plus a gap). + * @private + */ +Blockly.BlockSvg.prototype.renderFields_ = function(fieldList, + cursorX, cursorY) { + cursorY += Blockly.BlockSvg.INLINE_PADDING_Y; + if (this.RTL) { + cursorX = -cursorX; + } + for (var t = 0, field; field = fieldList[t]; t++) { + var root = field.getSvgRoot(); + if (!root) { + continue; + } + + if (this.RTL) { + cursorX -= field.renderSep + field.renderWidth; + root.setAttribute('transform', + 'translate(' + cursorX + ',' + cursorY + ')'); + if (field.renderWidth) { + cursorX -= Blockly.BlockSvg.SEP_SPACE_X; + } + } else { + root.setAttribute('transform', + 'translate(' + (cursorX + field.renderSep) + ',' + cursorY + ')'); + if (field.renderWidth) { + cursorX += field.renderSep + field.renderWidth + + Blockly.BlockSvg.SEP_SPACE_X; + } + } + } + return this.RTL ? -cursorX : cursorX; +}; + +/** + * Computes the height and widths for each row and field. + * @param {number} iconWidth Offset of first row due to icons. + * @return {!Array.>} 2D array of objects, each containing + * position information. + * @private + */ +Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { + var inputList = this.inputList; + var inputRows = []; + inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2; + if (this.previousConnection || this.nextConnection) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, + Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X); + } + var fieldValueWidth = 0; // Width of longest external value field. + var fieldStatementWidth = 0; // Width of longest statement field. + var hasValue = false; + var hasStatement = false; + var hasDummy = false; + var lastType = undefined; + var isInline = this.getInputsInline() && !this.isCollapsed(); + for (var i = 0, input; input = inputList[i]; i++) { + if (!input.isVisible()) { + continue; + } + var row; + if (!isInline || !lastType || + lastType == Blockly.NEXT_STATEMENT || + input.type == Blockly.NEXT_STATEMENT) { + // Create new row. + lastType = input.type; + row = []; + if (isInline && input.type != Blockly.NEXT_STATEMENT) { + row.type = Blockly.BlockSvg.INLINE; + } else { + row.type = input.type; + } + row.height = 0; + inputRows.push(row); + } else { + row = inputRows[inputRows.length - 1]; + } + row.push(input); + + // Compute minimum input size. + input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y; + // The width is currently only needed for inline value inputs. + if (isInline && input.type == Blockly.INPUT_VALUE) { + input.renderWidth = Blockly.BlockSvg.TAB_WIDTH + + Blockly.BlockSvg.SEP_SPACE_X * 1.25; + } else { + input.renderWidth = 0; + } + // Expand input size if there is a connection. + if (input.connection && input.connection.isConnected()) { + var linkedBlock = input.connection.targetBlock(); + var bBox = linkedBlock.getHeightWidth(); + input.renderHeight = Math.max(input.renderHeight, bBox.height); + input.renderWidth = Math.max(input.renderWidth, bBox.width); + } + // Blocks have a one pixel shadow that should sometimes overhang. + if (!isInline && i == inputList.length - 1) { + // Last value input should overhang. + input.renderHeight--; + } else if (!isInline && input.type == Blockly.INPUT_VALUE && + inputList[i + 1] && inputList[i + 1].type == Blockly.NEXT_STATEMENT) { + // Value input above statement input should overhang. + input.renderHeight--; + } + + row.height = Math.max(row.height, input.renderHeight); + input.fieldWidth = 0; + if (inputRows.length == 1) { + // The first row gets shifted to accommodate any icons. + input.fieldWidth += this.RTL ? -iconWidth : iconWidth; + } + var previousFieldEditable = false; + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (j != 0) { + input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X; + } + // Get the dimensions of the field. + var fieldSize = field.getSize(); + field.renderWidth = fieldSize.width; + field.renderSep = (previousFieldEditable && field.EDITABLE) ? + Blockly.BlockSvg.SEP_SPACE_X : 0; + input.fieldWidth += field.renderWidth + field.renderSep; + row.height = Math.max(row.height, fieldSize.height); + previousFieldEditable = field.EDITABLE; + } + + if (row.type != Blockly.BlockSvg.INLINE) { + if (row.type == Blockly.NEXT_STATEMENT) { + hasStatement = true; + fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth); + } else { + if (row.type == Blockly.INPUT_VALUE) { + hasValue = true; + } else if (row.type == Blockly.DUMMY_INPUT) { + hasDummy = true; + } + fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth); + } + } + } + + // Make inline rows a bit thicker in order to enclose the values. + for (var y = 0, row; row = inputRows[y]; y++) { + row.thicker = false; + if (row.type == Blockly.BlockSvg.INLINE) { + for (var z = 0, input; input = row[z]; z++) { + if (input.type == Blockly.INPUT_VALUE) { + row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y; + row.thicker = true; + break; + } + } + } + } + + // Compute the statement edge. + // This is the width of a block where statements are nested. + inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X + + fieldStatementWidth; + // Compute the preferred right edge. Inline blocks may extend beyond. + // This is the width of the block where external inputs connect. + if (hasStatement) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, + inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH); + } + if (hasValue) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth + + Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH); + } else if (hasDummy) { + inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth + + Blockly.BlockSvg.SEP_SPACE_X * 2); + } + + inputRows.hasValue = hasValue; + inputRows.hasStatement = hasStatement; + inputRows.hasDummy = hasDummy; + return inputRows; +}; + + +/** + * Draw the path of the block. + * Move the fields to the correct locations. + * @param {number} iconWidth Offset of first row due to icons. + * @param {!Array.>} inputRows 2D array of objects, each + * containing position information. + * @private + */ +Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { + this.startHat_ = false; + // Reset the height to zero and let the rendering process add in + // portions of the block height as it goes. (e.g. hats, inputs, etc.) + this.height = 0; + // Should the top and bottom left corners be rounded or square? + if (this.outputConnection) { + this.squareTopLeftCorner_ = true; + this.squareBottomLeftCorner_ = true; + } else { + this.squareTopLeftCorner_ = false; + this.squareBottomLeftCorner_ = false; + // If this block is in the middle of a stack, square the corners. + if (this.previousConnection) { + var prevBlock = this.previousConnection.targetBlock(); + if (prevBlock && prevBlock.getNextBlock() == this) { + this.squareTopLeftCorner_ = true; + } + } else if (Blockly.BlockSvg.START_HAT) { + // No output or previous connection. + this.squareTopLeftCorner_ = true; + this.startHat_ = true; + this.height += Blockly.BlockSvg.START_HAT_HEIGHT; + inputRows.rightEdge = Math.max(inputRows.rightEdge, 100); + } + var nextBlock = this.getNextBlock(); + if (nextBlock) { + this.squareBottomLeftCorner_ = true; + } + } + + // Assemble the block's path. + var steps = []; + var inlineSteps = []; + // The highlighting applies to edges facing the upper-left corner. + // Since highlighting is a two-pixel wide border, it would normally overhang + // the edge of the block by a pixel. So undersize all measurements by a pixel. + var highlightSteps = []; + var highlightInlineSteps = []; + + this.renderDrawTop_(steps, highlightSteps, inputRows.rightEdge); + var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps, + highlightInlineSteps, inputRows, iconWidth); + this.renderDrawBottom_(steps, highlightSteps, cursorY); + this.renderDrawLeft_(steps, highlightSteps); + + var pathString = steps.join(' ') + '\n' + inlineSteps.join(' '); + this.svgPath_.setAttribute('d', pathString); + this.svgPathDark_.setAttribute('d', pathString); + pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' '); + this.svgPathLight_.setAttribute('d', pathString); + if (this.RTL) { + // Mirror the block's path. + this.svgPath_.setAttribute('transform', 'scale(-1 1)'); + this.svgPathLight_.setAttribute('transform', 'scale(-1 1)'); + this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)'); + } +}; + +/** + * Update all of the connections on this block with the new locations calculated + * in renderCompute. Also move all of the connected blocks based on the new + * connection locations. + * @private + */ +Blockly.BlockSvg.prototype.renderMoveConnections_ = function() { + var blockTL = this.getRelativeToSurfaceXY(); + // Don't tighten previous or output connections because they are inferior + // connections. + if (this.previousConnection) { + this.previousConnection.moveToOffset(blockTL); + } + if (this.outputConnection) { + this.outputConnection.moveToOffset(blockTL); + } + + for (var i = 0; i < this.inputList.length; i++) { + var conn = this.inputList[i].connection; + if (conn) { + conn.moveToOffset(blockTL); + if (conn.isConnected()) { + conn.tighten_(); + } + } + } + + if (this.nextConnection) { + this.nextConnection.moveToOffset(blockTL); + if (this.nextConnection.isConnected()) { + this.nextConnection.tighten_(); + } + } + +}; + +/** + * Render the top edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {number} rightEdge Minimum width of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawTop_ = function(steps, + highlightSteps, rightEdge) { + // Position the cursor at the top-left starting point. + if (this.squareTopLeftCorner_) { + steps.push('m 0,0'); + highlightSteps.push('m 0.5,0.5'); + if (this.startHat_) { + steps.push(Blockly.BlockSvg.START_HAT_PATH); + highlightSteps.push(this.RTL ? + Blockly.BlockSvg.START_HAT_HIGHLIGHT_RTL : + Blockly.BlockSvg.START_HAT_HIGHLIGHT_LTR); + } + } else { + steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START); + highlightSteps.push(this.RTL ? + Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL : + Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR); + // Top-left rounded corner. + steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER); + highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT); + } + + // Top edge. + if (this.previousConnection) { + steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15); + highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15); + steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT); + highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT); + + var connectionX = (this.RTL ? + -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH); + this.previousConnection.setOffsetInBlock(connectionX, 0); + } + steps.push('H', rightEdge); + highlightSteps.push('H', rightEdge - 0.5); + this.width = rightEdge; +}; + +/** + * Render the right edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {!Array.} inlineSteps Inline block outlines. + * @param {!Array.} highlightInlineSteps Inline block highlights. + * @param {!Array.>} inputRows 2D array of objects, each + * containing position information. + * @param {number} iconWidth Offset of first row due to icons. + * @return {number} Height of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, + inlineSteps, highlightInlineSteps, inputRows, iconWidth) { + var cursorX; + var cursorY = 0; + var connectionX, connectionY; + for (var y = 0, row; row = inputRows[y]; y++) { + cursorX = Blockly.BlockSvg.SEP_SPACE_X; + if (y == 0) { + cursorX += this.RTL ? -iconWidth : iconWidth; + } + highlightSteps.push('M', (inputRows.rightEdge - 0.5) + ',' + + (cursorY + 0.5)); + if (this.isCollapsed()) { + // Jagged right edge. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push(Blockly.BlockSvg.JAGGED_TEETH); + highlightSteps.push('h 8'); + var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT; + steps.push('v', remainder); + if (this.RTL) { + highlightSteps.push('v 3.9 l 7.2,3.4 m -14.5,8.9 l 7.3,3.5'); + highlightSteps.push('v', remainder - 0.7); + } + this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH; + } else if (row.type == Blockly.BlockSvg.INLINE) { + // Inline inputs. + for (var x = 0, input; input = row[x]; x++) { + var fieldX = cursorX; + var fieldY = cursorY; + if (row.thicker) { + // Lower the field slightly. + fieldY += Blockly.BlockSvg.INLINE_PADDING_Y; + } + // TODO: Align inline field rows (left/right/centre). + cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY); + if (input.type != Blockly.DUMMY_INPUT) { + cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X; + } + if (input.type == Blockly.INPUT_VALUE) { + inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) + + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y)); + inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 - + input.renderWidth); + inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + inlineSteps.push('v', input.renderHeight + 1 - + Blockly.BlockSvg.TAB_HEIGHT); + inlineSteps.push('h', input.renderWidth + 2 - + Blockly.BlockSvg.TAB_WIDTH); + inlineSteps.push('z'); + if (this.RTL) { + // Highlight right edge, around back of tab, and bottom. + highlightInlineSteps.push('M', + (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 2.5 + + Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' + + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5)); + highlightInlineSteps.push( + Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); + highlightInlineSteps.push('v', + input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 2.5); + highlightInlineSteps.push('h', + input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 2); + } else { + // Highlight right edge, bottom. + highlightInlineSteps.push('M', + (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 0.5) + ',' + + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 0.5)); + highlightInlineSteps.push('v', input.renderHeight + 1); + highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 - + input.renderWidth); + // Short highlight glint at bottom of tab. + highlightInlineSteps.push('M', + (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X + + 0.9) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + + Blockly.BlockSvg.TAB_HEIGHT - 0.7)); + highlightInlineSteps.push('l', + (Blockly.BlockSvg.TAB_WIDTH * 0.46) + ',-2.1'); + } + // Create inline input connection. + if (this.RTL) { + connectionX = -cursorX - + Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X + + input.renderWidth + 1; + } else { + connectionX = cursorX + + Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X - + input.renderWidth - 1; + } + connectionY = cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1; + input.connection.setOffsetInBlock(connectionX, connectionY); + } + } + + cursorX = Math.max(cursorX, inputRows.rightEdge); + this.width = Math.max(this.width, cursorX); + steps.push('H', cursorX); + highlightSteps.push('H', cursorX - 0.5); + steps.push('v', row.height); + if (this.RTL) { + highlightSteps.push('v', row.height - 1); + } + } else if (row.type == Blockly.INPUT_VALUE) { + // External input. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.rightEdge - input.fieldWidth - + Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + var v = row.height - Blockly.BlockSvg.TAB_HEIGHT; + steps.push('v', v); + if (this.RTL) { + // Highlight around back of tab. + highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); + highlightSteps.push('v', v + 0.5); + } else { + // Short highlight glint at bottom of tab. + highlightSteps.push('M', (inputRows.rightEdge - 5) + ',' + + (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.7)); + highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.46) + + ',-2.1'); + } + // Create external input connection. + connectionX = this.RTL ? -inputRows.rightEdge - 1 : + inputRows.rightEdge + 1; + input.connection.setOffsetInBlock(connectionX, cursorY); + if (input.connection.isConnected()) { + this.width = Math.max(this.width, inputRows.rightEdge + + input.connection.targetBlock().getHeightWidth().width - + Blockly.BlockSvg.TAB_WIDTH + 1); + } + } else if (row.type == Blockly.DUMMY_INPUT) { + // External naked field. + var input = row[0]; + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.rightEdge - input.fieldWidth - + 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (inputRows.hasValue) { + fieldRightX -= Blockly.BlockSvg.TAB_WIDTH; + } + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + steps.push('v', row.height); + if (this.RTL) { + highlightSteps.push('v', row.height - 1); + } + } else if (row.type == Blockly.NEXT_STATEMENT) { + // Nested statement. + var input = row[0]; + if (y == 0) { + // If the first input is a statement stack, add a small row on top. + steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y); + if (this.RTL) { + highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1); + } + cursorY += Blockly.BlockSvg.SEP_SPACE_Y; + } + var fieldX = cursorX; + var fieldY = cursorY; + if (input.align != Blockly.ALIGN_LEFT) { + var fieldRightX = inputRows.statementEdge - input.fieldWidth - + 2 * Blockly.BlockSvg.SEP_SPACE_X; + if (input.align == Blockly.ALIGN_RIGHT) { + fieldX += fieldRightX; + } else if (input.align == Blockly.ALIGN_CENTRE) { + fieldX += fieldRightX / 2; + } + } + this.renderFields_(input.fieldRow, fieldX, fieldY); + cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH; + steps.push('H', cursorX); + steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER); + steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); + steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER); + steps.push('H', inputRows.rightEdge); + if (this.RTL) { + highlightSteps.push('M', + (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + + ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); + highlightSteps.push( + Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('v', + row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); + highlightSteps.push( + Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('H', inputRows.rightEdge - 0.5); + } else { + highlightSteps.push('M', + (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' + + (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); + highlightSteps.push( + Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR); + highlightSteps.push('H', inputRows.rightEdge - 0.5); + } + // Create statement connection. + connectionX = this.RTL ? -cursorX : cursorX + 1; + input.connection.setOffsetInBlock(connectionX, cursorY + 1); + + if (input.connection.isConnected()) { + this.width = Math.max(this.width, inputRows.statementEdge + + input.connection.targetBlock().getHeightWidth().width); + } + if (y == inputRows.length - 1 || + inputRows[y + 1].type == Blockly.NEXT_STATEMENT) { + // If the final input is a statement stack, add a small row underneath. + // Consecutive statement stacks are also separated by a small divider. + steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y); + if (this.RTL) { + highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1); + } + cursorY += Blockly.BlockSvg.SEP_SPACE_Y; + } + } + cursorY += row.height; + } + if (!inputRows.length) { + cursorY = Blockly.BlockSvg.MIN_BLOCK_Y; + steps.push('V', cursorY); + if (this.RTL) { + highlightSteps.push('V', cursorY - 1); + } + } + return cursorY; +}; + +/** + * Render the bottom edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @param {number} cursorY Height of block. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, + highlightSteps, cursorY) { + this.height += cursorY + 1; // Add one for the shadow. + if (this.nextConnection) { + steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) + + ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT); + // Create next block connection. + var connectionX; + if (this.RTL) { + connectionX = -Blockly.BlockSvg.NOTCH_WIDTH; + } else { + connectionX = Blockly.BlockSvg.NOTCH_WIDTH; + } + this.nextConnection.setOffsetInBlock(connectionX, cursorY + 1); + this.height += 4; // Height of tab. + } + + // Should the bottom-left corner be rounded or square? + if (this.squareBottomLeftCorner_) { + steps.push('H 0'); + if (!this.RTL) { + highlightSteps.push('M', '0.5,' + (cursorY - 0.5)); + } + } else { + steps.push('H', Blockly.BlockSvg.CORNER_RADIUS); + steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' + + Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' + + Blockly.BlockSvg.CORNER_RADIUS + ',-' + + Blockly.BlockSvg.CORNER_RADIUS); + if (!this.RTL) { + highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' + + (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE)); + highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ',' + + (Blockly.BlockSvg.CORNER_RADIUS - 0.5) + ' 0 0,1 ' + + '0.5,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS)); + } + } +}; + +/** + * Render the left edge of the block. + * @param {!Array.} steps Path of block outline. + * @param {!Array.} highlightSteps Path of block highlights. + * @private + */ +Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, highlightSteps) { + if (this.outputConnection) { + // Create output connection. + this.outputConnection.setOffsetInBlock(0, 0); + steps.push('V', Blockly.BlockSvg.TAB_HEIGHT); + steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' + + Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH + + ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5'); + if (this.RTL) { + highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.25) + ',8.4'); + highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1'); + } else { + highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1.5); + highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) + + ',-0.5 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) + + ',-5.5 0,-11'); + highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) + + ',1 V 0.5 H 1'); + } + this.width += Blockly.BlockSvg.TAB_WIDTH; + } else if (!this.RTL) { + if (this.squareTopLeftCorner_) { + // Statement block in a stack. + highlightSteps.push('V', 0.5); + } else { + highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS); + } + } + steps.push('z'); +}; diff --git a/core/.svn/pristine/ba/ba57fa40001bcebf23cacb74df4a2534bf10a335.svn-base b/core/.svn/pristine/ba/ba57fa40001bcebf23cacb74df4a2534bf10a335.svn-base new file mode 100644 index 0000000..75209df --- /dev/null +++ b/core/.svn/pristine/ba/ba57fa40001bcebf23cacb74df4a2534bf10a335.svn-base @@ -0,0 +1,572 @@ +/** + * @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) + */ +'use strict'; + +/** + * @name Blockly.Variables + * @namespace + **/ +goog.provide('Blockly.Variables'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.VariableModel'); +goog.require('Blockly.Workspace'); +goog.require('goog.string'); + + +/** + * Constant to separate variable names from procedures and generated functions + * when running generators. + * @deprecated Use Blockly.VARIABLE_CATEGORY_NAME + */ +Blockly.Variables.NAME_TYPE = Blockly.VARIABLE_CATEGORY_NAME; + +/** + * Find all user-created variables that are in use in the workspace. + * For use by generators. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @param {!Blockly.Workspace} ws The workspace to search for variables. + * @return {!Array.} Array of variable models. + */ +Blockly.Variables.allUsedVarModels = function(ws) { + var blocks = ws.getAllBlocks(); + var variableHash = Object.create(null); + // Iterate through every block and add each variable to the hash. + for (var x = 0; x < blocks.length; x++) { + var blockVariables = blocks[x].getVarModels(); + if (blockVariables) { + for (var y = 0; y < blockVariables.length; y++) { + var variable = blockVariables[y]; + if (variable.getId()) { + variableHash[variable.getId()] = variable; + } + } + } + } + // Flatten the hash into a list. + var variableList = []; + for (var id in variableHash) { + variableList.push(variableHash[id]); + } + return variableList; +}; + +/** + * Find all user-created variables that are in use in the workspace and return + * only their names. + * For use by generators. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @deprecated January 2018 + */ +Blockly.Variables.allUsedVariables = function() { + console.warn('Deprecated call to Blockly.Variables.allUsedVariables. ' + + 'Use Blockly.Variables.allUsedVarModels instead.\nIf this is a major ' + + 'issue please file a bug on GitHub.'); +}; + +/** + * Find all developer variables used by blocks in the workspace. + * Developer variables are never shown to the user, but are declared as global + * variables in the generated code. + * To declare developer variables, define the getDeveloperVariables function on + * your block and return a list of variable names. + * For use by generators. + * @param {!Blockly.Workspace} workspace The workspace to search. + * @return {!Array.} A list of non-duplicated variable names. + */ +Blockly.Variables.allDeveloperVariables = function(workspace) { + var blocks = workspace.getAllBlocks(); + var hash = {}; + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + if (block.getDeveloperVars) { + var devVars = block.getDeveloperVars(); + for (var j = 0; j < devVars.length; j++) { + hash[devVars[j]] = devVars[j]; + } + } + } + + // Flatten the hash into a list. + var list = []; + for (var name in hash) { + list.push(hash[name]); + } + return list; +}; + +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!Blockly.Workspace} workspace The workspace contianing variables. + * @return {!Array.} Array of XML elements. + */ +Blockly.Variables.flyoutCategory = function(workspace) { + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE'); + + workspace.registerButtonCallback('CREATE_VARIABLE', function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace()); + }); + + xmlList.push(button); + + var blockList = Blockly.Variables.flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; +}; + +/** + * Construct the blocks required by the flyout for the variable category. + * @param {!Blockly.Workspace} workspace The workspace contianing variables. + * @return {!Array.} Array of XML block elements. + */ +Blockly.Variables.flyoutCategoryBlocks = function(workspace) { + var variableModelList = workspace.getVariablesOfType(''); + variableModelList.sort(Blockly.VariableModel.compareByName); + + var xmlList = []; + if (variableModelList.length > 0) { + var firstVariable = variableModelList[0]; + if (Blockly.Blocks['variables_set']) { + var gap = Blockly.Blocks['math_change'] ? 8 : 24; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + if (Blockly.Blocks['math_change']) { + var gap = Blockly.Blocks['variables_get'] ? 20 : 8; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + '' + + '1' + + '' + + '' + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + + for (var i = 0, variable; variable = variableModelList[i]; i++) { + if (Blockly.Blocks['variables_get']) { + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(variable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + 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'. + * @param {!Blockly.Workspace} workspace The workspace to be unique in. + * @return {string} New variable name. + */ +Blockly.Variables.generateUniqueName = function(workspace) { + var variableList = workspace.getAllVariables(); + var newName = ''; + if (variableList.length) { + var nameSuffix = 1; + var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'. + var letterIndex = 0; + var potName = letters.charAt(letterIndex); + while (!newName) { + var inUse = false; + for (var i = 0; i < variableList.length; i++) { + if (variableList[i].name.toLowerCase() == 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; +}; + +/** + * Handles "Create Variable" button in the default variables toolbox category. + * It will prompt the user for a varibale name, including re-prompts if a name + * is already in use among the workspace's variables. + * + * Custom button handlers can delegate to this function, allowing variables + * types and after-creation processing. More complex customization (e.g., + * prompting for variable type) is beyond the scope of this function. + * + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @param {function(?string=)=} opt_callback A callback. It will be passed an + * acceptable new variable name, or null if change is to be aborted (cancel + * button), or undefined if an existing variable was chosen. + * @param {string=} opt_type The type of the variable like 'int', 'string', or + * ''. This will default to '', which is a specific type. + */ +Blockly.Variables.createVariableButtonHandler = function( + workspace, opt_callback, opt_type) { + var type = opt_type || ''; + // This function needs to be named so it can be called recursively. + var promptAndCheckWithAlert = function(defaultName) { + Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName, + function(text) { + if (text) { + var existing = + Blockly.Variables.nameUsedWithAnyType_(text, workspace); + if (existing) { + var lowerCase = text.toLowerCase(); + if (existing.type == type) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace( + '%1', lowerCase); + } else { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE; + msg = msg.replace('%1', lowerCase).replace('%2', existing.type); + } + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(text); // Recurse + }); + } else { + // No conflict + workspace.createVariable(text, type); + if (opt_callback) { + opt_callback(text); + } + } + } else { + // User canceled prompt. + if (opt_callback) { + opt_callback(null); + } + } + }); + }; + promptAndCheckWithAlert(''); +}; +goog.exportSymbol('Blockly.Variables.createVariableButtonHandler', + Blockly.Variables.createVariableButtonHandler); + +/** + * Original name of Blockly.Variables.createVariableButtonHandler(..). + * @deprecated Use Blockly.Variables.createVariableButtonHandler(..). + * + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @param {function(?string=)=} opt_callback A callback. It will be passed an + * acceptable new variable name, or null if change is to be aborted (cancel + * button), or undefined if an existing variable was chosen. + * @param {string=} opt_type The type of the variable like 'int', 'string', or + * ''. This will default to '', which is a specific type. + */ +Blockly.Variables.createVariable = + Blockly.Variables.createVariableButtonHandler; +goog.exportSymbol('Blockly.Variables.createVariable', + Blockly.Variables.createVariable); + +/** + * Rename a variable with the given workspace, variableType, and oldName. + * @param {!Blockly.Workspace} workspace The workspace on which to rename the + * variable. + * @param {Blockly.VariableModel} variable Variable to rename. + * @param {function(?string=)=} opt_callback A callback. It will + * be passed an acceptable new variable name, or null if change is to be + * aborted (cancel button), or undefined if an existing variable was chosen. + */ +Blockly.Variables.renameVariable = function(workspace, variable, + opt_callback) { + // This function needs to be named so it can be called recursively. + var promptAndCheckWithAlert = function(defaultName) { + var promptText = + Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name); + Blockly.Variables.promptName(promptText, defaultName, + function(newName) { + if (newName) { + var existing = Blockly.Variables.nameUsedWithOtherType_(newName, + variable.type, workspace); + if (existing) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE + .replace('%1', newName.toLowerCase()) + .replace('%2', existing.type); + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(newName); // Recurse + }); + } else { + workspace.renameVariableById(variable.getId(), newName); + if (opt_callback) { + opt_callback(newName); + } + } + } else { + // User canceled prompt. + if (opt_callback) { + opt_callback(null); + } + } + }); + }; + promptAndCheckWithAlert(''); +}; + +/** + * Prompt the user for a new variable name. + * @param {string} promptText The string of the prompt. + * @param {string} defaultText The default value to show in the prompt's field. + * @param {function(?string)} callback A callback. It will return the new + * variable name, or null if the user picked something illegal. + */ +Blockly.Variables.promptName = function(promptText, defaultText, callback) { + Blockly.prompt(promptText, defaultText, function(newVar) { + // Merge runs of whitespace. Strip leading and trailing whitespace. + // Beyond this, all names are legal. + if (newVar) { + newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (newVar == Blockly.Msg.RENAME_VARIABLE || + newVar == Blockly.Msg.NEW_VARIABLE) { + // Ok, not ALL names are legal... + newVar = null; + } + } + callback(newVar); + }); +}; + +/** + * Check whether there exists a variable with the given name but a different + * type. + * @param {string} name The name to search for. + * @param {string} type The type to exclude from the search. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name and a + * different type, or null if none was found. + * @private + */ +Blockly.Variables.nameUsedWithOtherType_ = function(name, type, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name && variable.type != type) { + return variable; + } + } + return null; +}; + +/** + * Check whether there exists a variable with the given name of any type. + * @param {string} name The name to search for. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name, or null if + * none was found. + * @private + */ +Blockly.Variables.nameUsedWithAnyType_ = function(name, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name) { + return variable; + } + } + return null; +}; + +/** + * Generate XML string for variable field. + * @param {!Blockly.VariableModel} variableModel The variable model to generate + * an XML string from. + * @return {string} The generated XML. + * @private + */ +Blockly.Variables.generateVariableFieldXml_ = function(variableModel) { + // The variable name may be user input, so it may contain characters that need + // to be escaped to create valid XML. + var typeString = variableModel.type; + if (typeString == '') { + typeString = '\'\''; + } + var text = '' + goog.string.htmlEscape(variableModel.name) + ''; + return text; +}; + +/** + * Helper function to look up or create a variable on the given workspace. + * If no variable exists, creates and returns it. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up or create the variable, or null. + * @param {string=} opt_name The string to use to look up or create the + * variable. + * @param {string=} opt_type The type to use to look up or create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + */ +Blockly.Variables.getOrCreateVariablePackage = function(workspace, id, opt_name, + opt_type) { + var variable = Blockly.Variables.getVariable(workspace, id, opt_name, + opt_type); + if (!variable) { + variable = Blockly.Variables.createVariable_(workspace, id, opt_name, + opt_type); + } + return variable; +}; + +/** + * Look up a variable on the given workspace. + * Always looks in the main workspace before looking in the flyout workspace. + * Always prefers lookup by ID to lookup by name + type. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up the variable, or null. + * @param {string=} opt_name The string to use to look up the variable. Only + * used if lookup by ID fails. + * @param {string=} opt_type The type to use to look up the variable. Only used + * if lookup by ID fails. + * @return {?Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination, or null if not found. + * @package + */ +Blockly.Variables.getVariable = function(workspace, id, opt_name, opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Try to just get the variable, by ID if possible. + if (id) { + // Look in the real variable map before checking the potential variable map. + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + } else if (opt_name) { + if (opt_type == undefined) { + throw new Error('Tried to look up a variable by name without a type'); + } + // Otherwise look up by name and type. + var variable = workspace.getVariable(opt_name, opt_type); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariable(opt_name, opt_type); + } + } + return variable; +}; + +/** + * Helper function to create a variable on the given workspace. + * @param {!Blockly.Workspace} workspace The workspace in which to create the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to create the variable, or null. + * @param {string=} opt_name The string to use to create the variable. + * @param {string=} opt_type The type to use to create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + * @private + */ +Blockly.Variables.createVariable_ = function(workspace, id, opt_name, + opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Variables without names get uniquely named for this workspace. + if (!opt_name) { + var ws = workspace.isFlyout ? workspace.targetWorkspace : workspace; + opt_name = Blockly.Variables.generateUniqueName(ws); + } + + // Create a potential variable if in the flyout. + if (potentialVariableMap) { + var variable = potentialVariableMap.createVariable(opt_name, opt_type, id); + } else { // In the main workspace, create a real variable. + var variable = workspace.createVariable(opt_name, opt_type, id); + } + return variable; +}; + +/** + * Helper function to get the list of variables that have been added to the + * workspace after adding a new block, using the given list of variables that + * were in the workspace before the new block was added. + * @param {!Blockly.Workspace} workspace The workspace to inspect. + * @param {!Array.} originalVariables The array of + * variables that existed in the workspace before adding the new block. + * @return {!Array.} The new array of variables that were + * freshly added to the workspace after creating the new block, or [] if no + * new variables were added to the workspace. + * @package + */ +Blockly.Variables.getAddedVariables = function(workspace, originalVariables) { + var allCurrentVariables = workspace.getAllVariables(); + var addedVariables = []; + if (originalVariables.length != allCurrentVariables.length) { + for (var i = 0; i < allCurrentVariables.length; i++) { + var variable = allCurrentVariables[i]; + // For any variable that is present in allCurrentVariables but not + // present in originalVariables, add the variable to addedVariables. + if (!originalVariables.includes(variable)) { + addedVariables.push(variable); + } + } + } + return addedVariables; +}; diff --git a/core/.svn/pristine/be/bea91056f9413a640928d9feaf7591443fb0f40a.svn-base b/core/.svn/pristine/be/bea91056f9413a640928d9feaf7591443fb0f40a.svn-base new file mode 100644 index 0000000..c27be1d --- /dev/null +++ b/core/.svn/pristine/be/bea91056f9413a640928d9feaf7591443fb0f40a.svn-base @@ -0,0 +1,37 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 A mapping of block type names to block prototype objects. + * @author spertus@google.com (Ellen Spertus) + */ +'use strict'; + +/** + * A mapping of block type names to block prototype objects. + * @name Blockly.Blocks + */ +goog.provide('Blockly.Blocks'); + +/* + * A mapping of block type names to block prototype objects. + * @type {!Object.} + */ +Blockly.Blocks = new Object(null); diff --git a/core/.svn/pristine/c3/c3eb610686fd7c5ddbb44298812e2f9dbeaf6e46.svn-base b/core/.svn/pristine/c3/c3eb610686fd7c5ddbb44298812e2f9dbeaf6e46.svn-base new file mode 100644 index 0000000..8d8b085 --- /dev/null +++ b/core/.svn/pristine/c3/c3eb610686fd7c5ddbb44298812e2f9dbeaf6e46.svn-base @@ -0,0 +1,253 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2015 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 Object representing a zoom icons. + * @author carloslfu@gmail.com (Carlos Galarza) + */ +'use strict'; + +goog.provide('Blockly.ZoomControls'); + +goog.require('Blockly.Touch'); +goog.require('goog.dom'); + + +/** + * Class for a zoom controls. + * @param {!Blockly.Workspace} workspace The workspace to sit in. + * @constructor + */ +Blockly.ZoomControls = function(workspace) { + this.workspace_ = workspace; +}; + +/** + * Width of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.WIDTH_ = 32; + +/** + * Height of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.HEIGHT_ = 110; + +/** + * Distance between zoom controls and bottom edge of workspace. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.MARGIN_BOTTOM_ = 20; + +/** + * Distance between zoom controls and right edge of workspace. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.MARGIN_SIDE_ = 20; + +/** + * The SVG group containing the zoom controls. + * @type {Element} + * @private + */ +Blockly.ZoomControls.prototype.svgGroup_ = null; + +/** + * Left coordinate of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.left_ = 0; + +/** + * Top coordinate of the zoom controls. + * @type {number} + * @private + */ +Blockly.ZoomControls.prototype.top_ = 0; + +/** + * Create the zoom controls. + * @return {!Element} The zoom controls SVG group. + */ +Blockly.ZoomControls.prototype.createDom = function() { + var workspace = this.workspace_; + /* Here's the markup that will be generated: + + + + + + + + + + + + + + + */ + this.svgGroup_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyZoom'}, null); + var clip; + var rnd = String(Math.random()).substring(2); + + clip = Blockly.utils.createSvgElement('clipPath', + {'id': 'blocklyZoomoutClipPath' + rnd}, + this.svgGroup_); + Blockly.utils.createSvgElement('rect', + {'width': 32, 'height': 32, 'y': 77}, + clip); + var zoomoutSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'x': -64, + 'y': -15, + 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')' + }, + this.svgGroup_); + zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + clip = Blockly.utils.createSvgElement('clipPath', + {'id': 'blocklyZoominClipPath' + rnd}, + this.svgGroup_); + Blockly.utils.createSvgElement('rect', + {'width': 32, 'height': 32, 'y': 43}, + clip); + var zoominSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, + 'x': -32, + 'y': -49, + 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')' + }, + this.svgGroup_); + zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + clip = Blockly.utils.createSvgElement('clipPath', + {'id': 'blocklyZoomresetClipPath' + rnd}, + this.svgGroup_); + Blockly.utils.createSvgElement('rect', + {'width': 32, 'height': 32}, + clip); + var zoomresetSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'y': -92, + 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')' + }, + this.svgGroup_); + zoomresetSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + workspace.options.pathToMedia + Blockly.SPRITE.url); + + // Attach event listeners. + Blockly.bindEventWithChecks_(zoomresetSvg, 'mousedown', null, function(e) { + workspace.markFocused(); + workspace.setScale(workspace.options.zoomOptions.startScale); + workspace.scrollCenter(); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + Blockly.bindEventWithChecks_(zoominSvg, 'mousedown', null, function(e) { + workspace.markFocused(); + workspace.zoomCenter(1); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + Blockly.bindEventWithChecks_(zoomoutSvg, 'mousedown', null, function(e) { + workspace.markFocused(); + workspace.zoomCenter(-1); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. + e.stopPropagation(); // Don't start a workspace scroll. + e.preventDefault(); // Stop double-clicking from selecting text. + }); + + return this.svgGroup_; +}; + +/** + * Initialize the zoom controls. + * @param {number} bottom Distance from workspace bottom to bottom of controls. + * @return {number} Distance from workspace bottom to the top of controls. + */ +Blockly.ZoomControls.prototype.init = function(bottom) { + this.bottom_ = this.MARGIN_BOTTOM_ + bottom; + return this.bottom_ + this.HEIGHT_; +}; + +/** + * Dispose of this zoom controls. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.ZoomControls.prototype.dispose = function() { + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.workspace_ = null; +}; + +/** + * Move the zoom controls to the bottom-right corner. + */ +Blockly.ZoomControls.prototype.position = function() { + var metrics = this.workspace_.getMetrics(); + if (!metrics) { + // There are no metrics available (workspace is probably not visible). + return; + } + if (this.workspace_.RTL) { + this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + this.left_ += metrics.flyoutWidth; + if (this.workspace_.toolbox_) { + this.left_ += metrics.absoluteLeft; + } + } + } else { + this.left_ = metrics.viewWidth + metrics.absoluteLeft - + this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + this.left_ -= metrics.flyoutWidth; + } + } + this.top_ = metrics.viewHeight + metrics.absoluteTop - + this.HEIGHT_ - this.bottom_; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + this.top_ -= metrics.flyoutHeight; + } + this.svgGroup_.setAttribute('transform', + 'translate(' + this.left_ + ',' + this.top_ + ')'); +}; diff --git a/core/.svn/pristine/c6/c62a4d1ae0d939e3bc1367561b0b75ad463c32e1.svn-base b/core/.svn/pristine/c6/c62a4d1ae0d939e3bc1367561b0b75ad463c32e1.svn-base new file mode 100644 index 0000000..6c222dd --- /dev/null +++ b/core/.svn/pristine/c6/c62a4d1ae0d939e3bc1367561b0b75ad463c32e1.svn-base @@ -0,0 +1,918 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 The class representing an in-progress gesture, usually a drag + * or a tap. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Gesture'); + +goog.require('Blockly.BlockDragger'); +goog.require('Blockly.BubbleDragger'); +goog.require('Blockly.constants'); +goog.require('Blockly.FlyoutDragger'); +goog.require('Blockly.Tooltip'); +goog.require('Blockly.Touch'); +goog.require('Blockly.WorkspaceDragger'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/* + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ +// TODO: Consider touchcancel/pointercancel. + +/** + * Class for one gesture. + * @param {!Event} e The event that kicked off this gesture. + * @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. + * @constructor + */ +Blockly.Gesture = function(e, creatorWorkspace) { + + /** + * The position of the mouse when the gesture started. Units are css pixels, + * with (0, 0) at the top left of the browser window (mouseEvent clientX/Y). + * @type {goog.math.Coordinate} + */ + this.mouseDownXY_ = null; + + /** + * How far the mouse has moved during this drag, in pixel units. + * (0, 0) is at this.mouseDownXY_. + * @type {goog.math.Coordinate} + * private + */ + this.currentDragDeltaXY_ = 0; + + /** + * The bubble that the gesture started on, or null if it did not start on a + * bubble. + * @type {Blockly.Bubble} + * @private + */ + this.startBubble_ = null; + + /** + * The field that the gesture started on, or null if it did not start on a + * field. + * @type {Blockly.Field} + * @private + */ + this.startField_ = null; + + /** + * The block that the gesture started on, or null if it did not start on a + * block. + * @type {Blockly.BlockSvg} + * @private + */ + this.startBlock_ = null; + + /** + * The block that this gesture targets. If the gesture started on a + * shadow block, this is the first non-shadow parent of the block. If the + * gesture started in the flyout, this is the root block of the block group + * that was clicked or dragged. + * @type {Blockly.BlockSvg} + * @private + */ + this.targetBlock_ = null; + + /** + * The workspace that the gesture started on. There may be multiple + * workspaces on a page; this is more accurate than using + * Blockly.getMainWorkspace(). + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.startWorkspace_ = null; + + /** + * The workspace that created this gesture. This workspace keeps a reference + * to the gesture, which will need to be cleared at deletion. + * This may be different from the start workspace. For instance, a flyout is + * a workspace, but its parent workspace manages gestures for it. + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.creatorWorkspace_ = creatorWorkspace; + + /** + * Whether the pointer has at any point moved out of the drag radius. + * A gesture that exceeds the drag radius is a drag even if it ends exactly at + * its start point. + * @type {boolean} + * @private + */ + this.hasExceededDragRadius_ = false; + + /** + * Whether the workspace is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingWorkspace_ = false; + + /** + * Whether the block is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBlock_ = false; + + /** + * Whether the bubble is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBubble_ = false; + + /** + * The event that most recently updated this gesture. + * @type {!Event} + * @private + */ + this.mostRecentEvent_ = e; + + /** + * A handle to use to unbind a mouse move listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onMoveWrapper_ = null; + + /** + * A handle to use to unbind a mouse up listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onUpWrapper_ = null; + + /** + * The object tracking a bubble drag, or null if none is in progress. + * @type {Blockly.BubbleDragger} + * @private + */ + this.bubbleDragger_ = null; + + /** + * The object tracking a block drag, or null if none is in progress. + * @type {Blockly.BlockDragger} + * @private + */ + this.blockDragger_ = null; + + /** + * The object tracking a workspace or flyout workspace drag, or null if none + * is in progress. + * @type {Blockly.WorkspaceDragger} + * @private + */ + this.workspaceDragger_ = null; + + /** + * The flyout a gesture started in, if any. + * @type {Blockly.Flyout} + * @private + */ + this.flyout_ = null; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.calledUpdateIsDragging_ = false; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.hasStarted_ = false; + + /** + * Boolean used internally to break a cycle in disposal. + * @type {boolean} + * @private + */ + this.isEnding_ = false; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.Gesture.prototype.dispose = function() { + Blockly.Touch.clearTouchIdentifier(); + Blockly.Tooltip.unblock(); + // Clear the owner's reference to this gesture. + this.creatorWorkspace_.clearGesture(); + + if (this.onMoveWrapper_) { + Blockly.unbindEvent_(this.onMoveWrapper_); + } + if (this.onUpWrapper_) { + Blockly.unbindEvent_(this.onUpWrapper_); + } + + + this.startField_ = null; + this.startBlock_ = null; + this.targetBlock_ = null; + this.startWorkspace_ = null; + this.flyout_ = null; + + if (this.blockDragger_) { + this.blockDragger_.dispose(); + this.blockDragger_ = null; + } + if (this.workspaceDragger_) { + this.workspaceDragger_.dispose(); + this.workspaceDragger_ = null; + } + if (this.bubbleDragger_) { + this.bubbleDragger_.dispose(); + this.bubbleDragger_ = null; + } +}; + +/** + * Update internal state based on an event. + * @param {!Event} e The most recent mouse or touch event. + * @private + */ +Blockly.Gesture.prototype.updateFromEvent_ = function(e) { + var currentXY = new goog.math.Coordinate(e.clientX, e.clientY); + var changed = this.updateDragDelta_(currentXY); + // Exceeded the drag radius for the first time. + if (changed) { + this.updateIsDragging_(); + Blockly.longStop_(); + } + this.mostRecentEvent_ = e; +}; + +/** + * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. + * @param {!goog.math.Coordinate} currentXY The most recent mouse/pointer + * position, in pixel units, with (0, 0) at the window's top left corner. + * @return {boolean} True if the drag just exceeded the drag radius for the + * first time. + * @private + */ +Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) { + this.currentDragDeltaXY_ = goog.math.Coordinate.difference(currentXY, + this.mouseDownXY_); + + if (!this.hasExceededDragRadius_) { + var currentDragDelta = goog.math.Coordinate.magnitude( + this.currentDragDeltaXY_); + + // The flyout has a different drag radius from the rest of Blockly. + var limitRadius = this.flyout_ ? Blockly.FLYOUT_DRAG_RADIUS : + Blockly.DRAG_RADIUS; + + this.hasExceededDragRadius_ = currentDragDelta > limitRadius; + return this.hasExceededDragRadius_; + } + return false; +}; + +/** + * Update this gesture to record whether a block is being dragged from the + * flyout. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a block should be dragged from the flyout this function creates the new + * block on the main workspace and updates targetBlock_ and startWorkspace_. + * @return {boolean} True if a block is being dragged from the flyout. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() { + // Disabled blocks may not be dragged from the flyout. + if (this.targetBlock_.disabled) { + return false; + } + if (!this.flyout_.isScrollable() || + this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) { + this.startWorkspace_ = this.flyout_.targetWorkspace_; + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + // Start the event group now, so that the same event group is used for block + // creation and block dragging. + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + // The start block is no longer relevant, because this is a drag. + this.startBlock_ = null; + this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_); + this.targetBlock_.select(); + return true; + } + return false; +}; + +/** + * Update this gesture to record whether a bubble is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a bubble should be dragged this function creates the necessary + * BubbleDragger and starts the drag. + * @return {boolean} true if a bubble is being dragged. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingBubble_ = function() { + if (!this.startBubble_) { + return false; + } + + this.isDraggingBubble_ = true; + this.startDraggingBubble_(); + return true; +}; + +/** + * Update this gesture to record whether a block is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a block should be dragged, either from the flyout or in the workspace, + * this function creates the necessary BlockDragger and starts the drag. + * @return {boolean} true if a block is being dragged. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() { + if (!this.targetBlock_) { + return false; + } + + if (this.flyout_) { + this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_(); + } else if (this.targetBlock_.isMovable()) { + this.isDraggingBlock_ = true; + } + + if (this.isDraggingBlock_) { + this.startDraggingBlock_(); + return true; + } + return false; +}; + +/** + * Update this gesture to record whether a workspace is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a workspace is being dragged this function creates the necessary + * WorkspaceDragger or FlyoutDragger and starts the drag. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() { + var wsMovable = this.flyout_ ? this.flyout_.isScrollable() : + this.startWorkspace_ && this.startWorkspace_.isDraggable(); + + if (!wsMovable) { + return; + } + + if (this.flyout_) { + this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_); + } else { + this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_); + } + + this.isDraggingWorkspace_ = true; + this.workspaceDragger_.startDrag(); +}; + +/** + * Update this gesture to record whether anything is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * @private + */ +Blockly.Gesture.prototype.updateIsDragging_ = function() { + // Sanity check. + goog.asserts.assert(!this.calledUpdateIsDragging_, + 'updateIsDragging_ should only be called once per gesture.'); + this.calledUpdateIsDragging_ = true; + + // First check if it was a bubble drag. Bubbles always sit on top of blocks. + if (this.updateIsDraggingBubble_()) { + return; + } + // Then check if it was a block drag. + if (this.updateIsDraggingBlock_()) { + return; + } + // Then check if it's a workspace drag. + this.updateIsDraggingWorkspace_(); +}; + +/** + * Create a block dragger and start dragging the selected block. + * @private + */ +Blockly.Gesture.prototype.startDraggingBlock_ = function() { + this.blockDragger_ = new Blockly.BlockDragger(this.targetBlock_, + this.startWorkspace_); + this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_); + this.blockDragger_.dragBlock(this.mostRecentEvent_, + this.currentDragDeltaXY_); +}; + +/** + * Create a bubble dragger and start dragging the selected bubble. + * TODO (fenichel): Possibly combine this and startDraggingBlock_. + * @private + */ +Blockly.Gesture.prototype.startDraggingBubble_ = function() { + this.bubbleDragger_ = new Blockly.BubbleDragger(this.startBubble_, + this.startWorkspace_); + this.bubbleDragger_.startBubbleDrag(); + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); +}; + +/** + * Start a gesture: update the workspace to indicate that a gesture is in + * progress and bind mousemove and mouseup handlers. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.Gesture.prototype.doStart = function(e) { + if (Blockly.utils.isTargetInput(e)) { + this.cancel(); + return; + } + this.hasStarted_ = true; + + Blockly.BlockSvg.disconnectUiStop_(); + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + if (this.startWorkspace_.isMutator) { + // Mutator's coordinate system could be out of date because the bubble was + // dragged, the block was moved, the parent workspace zoomed, etc. + this.startWorkspace_.resize(); + } + this.startWorkspace_.markFocused(); + this.mostRecentEvent_ = e; + + // Hide chaff also hides the flyout, so don't do it if the click is in a flyout. + Blockly.hideChaff(!!this.flyout_); + Blockly.Tooltip.block(); + + if (this.targetBlock_) { + this.targetBlock_.select(); + } + + if (Blockly.utils.isRightButton(e)) { + this.handleRightClick(e); + return; + } + + if (goog.string.caseInsensitiveEquals(e.type, 'touchstart') || + goog.string.caseInsensitiveEquals(e.type, 'pointerdown')) { + Blockly.longStart_(e, this); + } + + this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY); + + this.bindMouseEvents(e); +}; + +/** + * Bind gesture events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.Gesture.prototype.bindMouseEvents = function(e) { + this.onMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', null, this.handleMove.bind(this)); + this.onUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', null, this.handleUp.bind(this)); + + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Handle a mouse move or touch move event. + * @param {!Event} e A mouse move or touch move event. + * @package + */ +Blockly.Gesture.prototype.handleMove = function(e) { + this.updateFromEvent_(e); + if (this.isDraggingWorkspace_) { + this.workspaceDragger_.drag(this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.dragBlock(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingBubble_) { + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Handle a mouse up or touch end event. + * @param {!Event} e A mouse up or touch end event. + * @package + */ +Blockly.Gesture.prototype.handleUp = function(e) { + this.updateFromEvent_(e); + Blockly.longStop_(); + + if (this.isEnding_) { + console.log('Trying to end a gesture recursively.'); + return; + } + this.isEnding_ = true; + // The ordering of these checks is important: drags have higher priority than + // clicks. Fields have higher priority than blocks; blocks have higher + // priority than workspaces. + // The ordering within drags does not matter, because the three types of + // dragging are exclusive. + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_); + } else if (this.isDraggingWorkspace_) { + this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } else if (this.isBubbleClick_()) { + // Bubbles are in front of all fields and blocks. + this.doBubbleClick_(); + } else if (this.isFieldClick_()) { + this.doFieldClick_(); + } else if (this.isBlockClick_()) { + this.doBlockClick_(); + } else if (this.isWorkspaceClick_()) { + this.doWorkspaceClick_(); + } + + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); +}; + +/** + * Cancel an in-progress gesture. If a workspace or block drag is in progress, + * end the drag at the most recent location. + * @package + */ +Blockly.Gesture.prototype.cancel = function() { + // Disposing of a block cancels in-progress drags, but dragging to a delete + // area disposes of a block and leads to recursive disposal. Break that cycle. + if (this.isEnding_) { + return; + } + Blockly.longStop_(); + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.endBlockDrag(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingWorkspace_) { + this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } + this.dispose(); +}; + +/** + * Handle a real or faked right-click event by showing a context menu. + * @param {!Event} e A mouse move or touch move event. + * @package + */ +Blockly.Gesture.prototype.handleRightClick = function(e) { + if (this.targetBlock_) { + this.bringBlockToFront_(); + Blockly.hideChaff(this.flyout_); + this.targetBlock_.showContextMenu_(e); + } else if (this.startWorkspace_ && !this.flyout_) { + Blockly.hideChaff(); + this.startWorkspace_.showContextMenu_(e); + } + + // TODO: Handle right-click on a bubble. + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); +}; + +/** + * Handle a mousedown/touchstart event on a workspace. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Workspace} ws The workspace the event hit. + * @package + */ +Blockly.Gesture.prototype.handleWsStart = function(e, ws) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleWsStart, but the gesture had already been ' + + 'started.'); + this.setStartWorkspace_(ws); + this.mostRecentEvent_ = e; + this.doStart(e); +}; + +/** + * Handle a mousedown/touchstart event on a flyout. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Flyout} flyout The flyout the event hit. + * @package + */ +Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleFlyoutStart, but the gesture had already ' + + 'been started.'); + this.setStartFlyout_(flyout); + this.handleWsStart(e, flyout.getWorkspace()); +}; + +/** + * Handle a mousedown/touchstart event on a block. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.BlockSvg} block The block the event hit. + * @package + */ +Blockly.Gesture.prototype.handleBlockStart = function(e, block) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleBlockStart, but the gesture had already ' + + 'been started.'); + this.setStartBlock(block); + this.mostRecentEvent_ = e; +}; + +/** + * Handle a mousedown/touchstart event on a bubble. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Bubble} bubble The bubble the event hit. + * @package + */ +Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleBubbleStart, but the gesture had already ' + + 'been started.'); + this.setStartBubble(bubble); + this.mostRecentEvent_ = e; +}; + +/* Begin functions defining what actions to take to execute clicks on each type + * of target. Any developer wanting to add behaviour on clicks should modify + * only this code. */ + +/** + * Execute a bubble click. + * @private + */ +Blockly.Gesture.prototype.doBubbleClick_ = function() { + // TODO: This isn't really enough, is it. + this.startBubble_.promote_(); +}; + +/** + * Execute a field click. + * @private + */ +Blockly.Gesture.prototype.doFieldClick_ = function() { + this.startField_.showEditor_(); + this.bringBlockToFront_(); +}; + +/** + * Execute a block click. + * @private + */ +Blockly.Gesture.prototype.doBlockClick_ = function() { + // Block click in an autoclosing flyout. + if (this.flyout_ && this.flyout_.autoClose) { + if (!this.targetBlock_.disabled) { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + var newBlock = this.flyout_.createBlock(this.targetBlock_); + newBlock.scheduleSnapAndBump(); + } + } else { + // Clicks events are on the start block, even if it was a shadow. + Blockly.Events.fire( + new Blockly.Events.Ui(this.startBlock_, 'click', undefined, undefined)); + } + this.bringBlockToFront_(); + Blockly.Events.setGroup(false); +}; + +/** + * Execute a workspace click. + * @private + */ +Blockly.Gesture.prototype.doWorkspaceClick_ = function() { + if (Blockly.selected) { + Blockly.selected.unselect(); + } +}; + +/* End functions defining what actions to take to execute clicks on each type + * of target. */ + +// TODO (fenichel): Move bubbles to the front. +/** + * Move the dragged/clicked block to the front of the workspace so that it is + * not occluded by other blocks. + * @private + */ +Blockly.Gesture.prototype.bringBlockToFront_ = function() { + // Blocks in the flyout don't overlap, so skip the work. + if (this.targetBlock_ && !this.flyout_) { + this.targetBlock_.bringToFront(); + } +}; + +/* Begin functions for populating a gesture at mouse down. */ + +/** + * Record the field that a gesture started on. + * @param {Blockly.Field} field The field the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartField = function(field) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.setStartField, but the gesture had already been ' + + 'started.'); + if (!this.startField_) { + this.startField_ = field; + } +}; + +/** + * Record the bubble that a gesture started on + * @param {Blockly.Bubble} bubble The bubble the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartBubble = function(bubble) { + if (!this.startBubble_) { + this.startBubble_ = bubble; + } +}; + +/** + * Record the block that a gesture started on, and set the target block + * appropriately. + * @param {Blockly.BlockSvg} block The block the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartBlock = function(block) { + if (!this.startBlock_) { + this.startBlock_ = block; + if (block.isInFlyout && block != block.getRootBlock()) { + this.setTargetBlock_(block.getRootBlock()); + } else { + this.setTargetBlock_(block); + } + } +}; + +/** + * Record the block that a gesture targets, meaning the block that will be + * dragged if this turns into a drag. If this block is a shadow, that will be + * its first non-shadow parent. + * @param {Blockly.BlockSvg} block The block the gesture targets. + * @private + */ +Blockly.Gesture.prototype.setTargetBlock_ = function(block) { + if (block.isShadow()) { + this.setTargetBlock_(block.getParent()); + } else { + this.targetBlock_ = block; + } +}; + +/** + * Record the workspace that a gesture started on. + * @param {Blockly.WorkspaceSvg} ws The workspace the gesture started on. + * @private + */ +Blockly.Gesture.prototype.setStartWorkspace_ = function(ws) { + if (!this.startWorkspace_) { + this.startWorkspace_ = ws; + } +}; + +/** + * Record the flyout that a gesture started on. + * @param {Blockly.Flyout} flyout The flyout the gesture started on. + * @private + */ +Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) { + if (!this.flyout_) { + this.flyout_ = flyout; + } +}; + +/* End functions for populating a gesture at mouse down. */ + +/* Begin helper functions defining types of clicks. Any developer wanting + * to change the definition of a click should modify only this code. */ + +/** + * Whether this gesture is a click on a bubble. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a bubble. + * @private + */ +Blockly.Gesture.prototype.isBubbleClick_ = function() { + // A bubble click starts on a bubble and never escapes the drag radius. + var hasStartBubble = !!this.startBubble_; + return hasStartBubble && !this.hasExceededDragRadius_; +}; + +/** + * Whether this gesture is a click on a block. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a block. + * @private + */ +Blockly.Gesture.prototype.isBlockClick_ = function() { + // A block click starts on a block, never escapes the drag radius, and is not + // a field click. + var hasStartBlock = !!this.startBlock_; + return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_(); +}; + +/** + * Whether this gesture is a click on a field. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a field. + * @private + */ +Blockly.Gesture.prototype.isFieldClick_ = function() { + var fieldEditable = this.startField_ ? + this.startField_.isCurrentlyEditable() : false; + return fieldEditable && !this.hasExceededDragRadius_ && (!this.flyout_ || + !this.flyout_.autoClose); +}; + +/** + * Whether this gesture is a click on a workspace. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a workspace. + * @private + */ +Blockly.Gesture.prototype.isWorkspaceClick_ = function() { + var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_; + return onlyTouchedWorkspace && !this.hasExceededDragRadius_; +}; + +/* End helper functions defining types of clicks. */ + +/** + * Whether this gesture is a drag of either a workspace or block. + * This function is called externally to block actions that cannot be taken + * mid-drag (e.g. using the keyboard to delete the selected blocks). + * @return {boolean} true if this gesture is a drag of a workspace or block. + * @package + */ +Blockly.Gesture.prototype.isDragging = function() { + return this.isDraggingWorkspace_ || this.isDraggingBlock_ || + this.isDraggingBubble_; +}; + +/** + * Whether this gesture has already been started. In theory every mouse down + * has a corresponding mouse up, but in reality it is possible to lose a + * mouse up, leaving an in-process gesture hanging. + * @return {boolean} whether this gesture was a click on a workspace. + * @package + */ +Blockly.Gesture.prototype.hasStarted = function() { + return this.hasStarted_; +}; diff --git a/core/.svn/pristine/c9/c91768f552f679229d2acc0314c6d96d8d5c5856.svn-base b/core/.svn/pristine/c9/c91768f552f679229d2acc0314c6d96d8d5c5856.svn-base new file mode 100644 index 0000000..a8490ab --- /dev/null +++ b/core/.svn/pristine/c9/c91768f552f679229d2acc0314c6d96d8d5c5856.svn-base @@ -0,0 +1,423 @@ +/** + * @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 Text input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldTextInput'); + +goog.require('Blockly.Field'); +goog.require('Blockly.Msg'); +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable text field. + * @param {string} text The initial content of the field. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldTextInput = function(text, opt_validator) { + Blockly.FieldTextInput.superClass_.constructor.call(this, text, + opt_validator); +}; +goog.inherits(Blockly.FieldTextInput, Blockly.Field); + +/** + * Construct a FieldTextInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, class, and + * spellcheck). + * @returns {!Blockly.FieldTextInput} The new field instance. + * @package + */ +Blockly.FieldTextInput.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + var field = new Blockly.FieldTextInput(text, options['class']); + if (typeof options['spellcheck'] === 'boolean') { + field.setSpellcheck(options['spellcheck']); + } + return field; +}; + +/** + * Point size of text. Should match blocklyText's font-size in CSS. + */ +Blockly.FieldTextInput.FONTSIZE = 11; + +/** + * The HTML input element for the user to type, or null if no FieldTextInput + * editor is currently open. + * @type {HTMLInputElement} + * @private + */ +Blockly.FieldTextInput.htmlInput_ = null; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldTextInput.prototype.CURSOR = 'text'; + +/** + * Allow browser to spellcheck this field. + * @private + */ +Blockly.FieldTextInput.prototype.spellcheck_ = true; + +/** + * Close the input widget if this input is being deleted. + */ +Blockly.FieldTextInput.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldTextInput.superClass_.dispose.call(this); +}; + +/** + * Set the value of this field. + * @param {?string} newValue New value. + * @override + */ +Blockly.FieldTextInput.prototype.setValue = function(newValue) { + if (newValue === null) { + return; // No change if null. + } + if (this.sourceBlock_) { + var validated = this.callValidator(newValue); + // If the new value is invalid, validation returns null. + // In this case we still want to display the illegal result. + if (validated !== null) { + newValue = validated; + } + } + Blockly.Field.prototype.setValue.call(this, newValue); +}; + +/** + * Set the text in this field and fire a change event. + * @param {*} newText New text. + */ +Blockly.FieldTextInput.prototype.setText = function(newText) { + if (newText === null) { + // No change if null. + return; + } + newText = String(newText); + if (newText === this.text_) { + // No change. + return; + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.text_, newText)); + } + Blockly.Field.prototype.setText.call(this, newText); +}; + +/** + * Set whether this field is spellchecked by the browser. + * @param {boolean} check True if checked. + */ +Blockly.FieldTextInput.prototype.setSpellcheck = function(check) { + this.spellcheck_ = check; +}; + +/** + * Show the inline free-text editor on top of the text. + * @param {boolean=} opt_quietInput True if editor should be created without + * focus. Defaults to false. + * @private + */ +Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { + this.workspace_ = this.sourceBlock_.workspace; + var quietInput = opt_quietInput || false; + if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID || + goog.userAgent.IPAD)) { + this.showPromptEditor_(); + } else { + this.showInlineEditor_(quietInput); + } +}; + +/** + * Create and show a text input editor that is a prompt (usually a popup). + * Mobile browsers have issues with in-line textareas (focus and keyboards). + * @private + */ +Blockly.FieldTextInput.prototype.showPromptEditor_ = function() { + var fieldText = this; + Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_, + function(newValue) { + if (fieldText.sourceBlock_) { + newValue = fieldText.callValidator(newValue); + } + fieldText.setValue(newValue); + }); +}; + +/** + * Create and show a text input editor that sits directly over the text input. + * @param {boolean} quietInput True if editor should be created without + * focus. + * @private + */ +Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_()); + var div = Blockly.WidgetDiv.DIV; + // Create the input. + var htmlInput = + goog.dom.createDom(goog.dom.TagName.INPUT, 'blocklyHtmlInput'); + htmlInput.setAttribute('spellcheck', this.spellcheck_); + var fontSize = + (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; + div.style.fontSize = fontSize; + htmlInput.style.fontSize = fontSize; + + Blockly.FieldTextInput.htmlInput_ = htmlInput; + div.appendChild(htmlInput); + + htmlInput.value = htmlInput.defaultValue = this.text_; + htmlInput.oldValue_ = null; + this.validate_(); + this.resizeEditor_(); + if (!quietInput) { + htmlInput.focus(); + htmlInput.select(); + } + + this.bindEvents_(htmlInput); +}; + +/** + * Bind handlers for user input on this field and size changes on the workspace. + * @param {!HTMLInputElement} htmlInput The htmlInput created in showEditor, to + * which event handlers will be bound. + * @private + */ +Blockly.FieldTextInput.prototype.bindEvents_ = function(htmlInput) { + // Bind to keydown -- trap Enter without IME and Esc to hide. + htmlInput.onKeyDownWrapper_ = + Blockly.bindEventWithChecks_( + htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); + // Bind to keyup -- trap Enter; resize after every keystroke. + htmlInput.onKeyUpWrapper_ = + Blockly.bindEventWithChecks_( + htmlInput, 'keyup', this, this.onHtmlInputChange_); + // Bind to keyPress -- repeatedly resize when holding down a key. + htmlInput.onKeyPressWrapper_ = + Blockly.bindEventWithChecks_( + htmlInput, 'keypress', this, this.onHtmlInputChange_); + htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); + this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_); +}; + +/** + * Unbind handlers for user input and workspace size changes. + * @param {!HTMLInputElement} htmlInput The html for this text input. + * @private + */ +Blockly.FieldTextInput.prototype.unbindEvents_ = function(htmlInput) { + Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_); + this.workspace_.removeChangeListener( + htmlInput.onWorkspaceChangeWrapper_); +}; + +/** + * Handle key down to the editor. + * @param {!Event} e Keyboard event. + * @private + */ +Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + var tabKey = 9, enterKey = 13, escKey = 27; + if (e.keyCode == enterKey) { + Blockly.WidgetDiv.hide(); + } else if (e.keyCode == escKey) { + htmlInput.value = htmlInput.defaultValue; + Blockly.WidgetDiv.hide(); + } else if (e.keyCode == tabKey) { + Blockly.WidgetDiv.hide(); + this.sourceBlock_.tab(this, !e.shiftKey); + e.preventDefault(); + } +}; + +/** + * Handle a change to the editor. + * @param {!Event} e Keyboard event. + * @private + */ +Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Update source block. + var text = htmlInput.value; + if (text !== htmlInput.oldValue_) { + htmlInput.oldValue_ = text; + this.setValue(text); + this.validate_(); + } else if (goog.userAgent.WEBKIT) { + // Cursor key. Render the source block to show the caret moving. + // Chrome only (version 26, OS X). + this.sourceBlock_.render(); + } + this.resizeEditor_(); + Blockly.svgResize(this.sourceBlock_.workspace); +}; + +/** + * Check to see if the contents of the editor validates. + * Style the editor accordingly. + * @private + */ +Blockly.FieldTextInput.prototype.validate_ = function() { + var valid = true; + goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_); + var htmlInput = Blockly.FieldTextInput.htmlInput_; + if (this.sourceBlock_) { + valid = this.callValidator(htmlInput.value); + } + if (valid === null) { + Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput'); + } else { + Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput'); + } +}; + +/** + * Resize the editor and the underlying block to fit the text. + * @private + */ +Blockly.FieldTextInput.prototype.resizeEditor_ = function() { + var div = Blockly.WidgetDiv.DIV; + var bBox = this.getScaledBBox_(); + div.style.width = bBox.right - bBox.left + 'px'; + div.style.height = bBox.bottom - bBox.top + 'px'; + + // In RTL mode block fields and LTR input fields the left edge moves, + // whereas the right edge is fixed. Reposition the editor. + var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left; + var xy = new goog.math.Coordinate(x, bBox.top); + + // Shift by a few pixels to line up exactly. + xy.y += 1; + if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) { + // Firefox mis-reports the location of the border by a pixel + // once the WidgetDiv is moved into position. + xy.x -= 1; + xy.y -= 1; + } + if (goog.userAgent.WEBKIT) { + xy.y -= 3; + } + div.style.left = xy.x + 'px'; + div.style.top = xy.y + 'px'; +}; + +/** + * Close the editor, save the results, and dispose of the editable + * text field's elements. + * @return {!Function} Closure to call on destruction of the WidgetDiv. + * @private + */ +Blockly.FieldTextInput.prototype.widgetDispose_ = function() { + var thisField = this; + return function() { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Save the edit (if it validates). + thisField.maybeSaveEdit_(); + + thisField.unbindEvents_(htmlInput); + Blockly.FieldTextInput.htmlInput_ = null; + Blockly.Events.setGroup(false); + + // Delete style properties. + var style = Blockly.WidgetDiv.DIV.style; + style.width = 'auto'; + style.height = 'auto'; + style.fontSize = ''; + }; +}; + +Blockly.FieldTextInput.prototype.maybeSaveEdit_ = function() { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Save the edit (if it validates). + var text = htmlInput.value; + if (this.sourceBlock_) { + var text1 = this.callValidator(text); + if (text1 === null) { + // Invalid edit. + text = htmlInput.defaultValue; + } else { + // Validation function has changed the text. + text = text1; + if (this.onFinishEditing_) { + this.onFinishEditing_(text); + } + } + } + this.setText(text); + this.sourceBlock_.rendered && this.sourceBlock_.render(); +}; + +/** + * Ensure that only a number may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldTextInput.numberValidator = function(text) { + console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' + + 'Use Blockly.FieldNumber instead.'); + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + return isNaN(n) ? null : String(n); +}; + +/** + * Ensure that only a nonnegative integer may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid int, or null if invalid. + */ +Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) { + var n = Blockly.FieldTextInput.numberValidator(text); + if (n) { + n = String(Math.max(0, Math.floor(n))); + } + return n; +}; diff --git a/core/.svn/pristine/c9/c96133480b78674f78fb0a7caf691ebd51768ec7.svn-base b/core/.svn/pristine/c9/c96133480b78674f78fb0a7caf691ebd51768ec7.svn-base new file mode 100644 index 0000000..fe9cae2 --- /dev/null +++ b/core/.svn/pristine/c9/c96133480b78674f78fb0a7caf691ebd51768ec7.svn-base @@ -0,0 +1,155 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object in charge of loading, storing, and playing audio for a + * workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceAudio'); + + +/** + * Class for loading, storing, and playing audio for a workspace. + * @param {Blockly.WorkspaceSvg} parentWorkspace The parent of the workspace + * this audio object belongs to, or null. + * @constructor + */ +Blockly.WorkspaceAudio = function(parentWorkspace) { + + /** + * The parent of the workspace this object belongs to, or null. May be + * checked for sounds that this object can't find. + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.parentWorkspace_ = parentWorkspace; + + /** + * Database of pre-loaded sounds. + * @private + * @const + */ + this.SOUNDS_ = Object.create(null); +}; + +/** + * Time that the last sound was played. + * @type {Date} + * @private + */ +Blockly.WorkspaceAudio.prototype.lastSound_ = null; + +/** + * Dispose of this audio manager. + * @package + */ +Blockly.WorkspaceAudio.prototype.dispose = function() { + this.parentWorkspace_ = null; + this.SOUNDS_ = null; +}; + +/** + * Load an audio file. Cache it, ready for instantaneous playing. + * @param {!Array.} filenames List of file types in decreasing order of + * preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav'] + * Filenames include path from Blockly's root. File extensions matter. + * @param {string} name Name of sound. + */ +Blockly.WorkspaceAudio.prototype.load = function(filenames, name) { + if (!filenames.length) { + return; + } + try { + var audioTest = new window['Audio'](); + } catch (e) { + // No browser support for Audio. + // IE can throw an error even if the Audio object exists. + return; + } + var sound; + for (var i = 0; i < filenames.length; i++) { + var filename = filenames[i]; + var ext = filename.match(/\.(\w+)$/); + if (ext && audioTest.canPlayType('audio/' + ext[1])) { + // Found an audio format we can play. + sound = new window['Audio'](filename); + break; + } + } + if (sound && sound.play) { + this.SOUNDS_[name] = sound; + } +}; + +/** + * Preload all the audio files so that they play quickly when asked for. + * @package + */ +Blockly.WorkspaceAudio.prototype.preload = function() { + for (var name in this.SOUNDS_) { + var sound = this.SOUNDS_[name]; + sound.volume = 0.01; + sound.play(); + sound.pause(); + // iOS can only process one sound at a time. Trying to load more than one + // corrupts the earlier ones. Just load one and leave the others uncached. + if (goog.userAgent.IPAD || goog.userAgent.IPHONE) { + break; + } + } +}; + +/** + * Play a named sound at specified volume. If volume is not specified, + * use full volume (1). + * @param {string} name Name of sound. + * @param {number=} opt_volume Volume of sound (0-1). + */ +Blockly.WorkspaceAudio.prototype.play = function(name, opt_volume) { + var sound = this.SOUNDS_[name]; + if (sound) { + // Don't play one sound on top of another. + var now = new Date; + if (this.lastSound_ != null && + now - this.lastSound_ < Blockly.SOUND_LIMIT) { + return; + } + this.lastSound_ = now; + var mySound; + var ie9 = goog.userAgent.DOCUMENT_MODE && + goog.userAgent.DOCUMENT_MODE === 9; + if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) { + // Creating a new audio node causes lag in IE9, Android and iPad. Android + // and IE9 refetch the file from the server, iPad uses a singleton audio + // node which must be deleted and recreated for each new audio tag. + mySound = sound; + } else { + mySound = sound.cloneNode(); + } + mySound.volume = (opt_volume === undefined ? 1 : opt_volume); + mySound.play(); + } else if (this.parentWorkspace_) { + // Maybe a workspace on a lower level knows about this sound. + this.parentWorkspace_.getAudioManager().play(name, opt_volume); + } +}; diff --git a/core/.svn/pristine/d0/d097937e4fb155c4cea376657aa9545fc1089e87.svn-base b/core/.svn/pristine/d0/d097937e4fb155c4cea376657aa9545fc1089e87.svn-base new file mode 100644 index 0000000..a57727a --- /dev/null +++ b/core/.svn/pristine/d0/d097937e4fb155c4cea376657aa9545fc1089e87.svn-base @@ -0,0 +1,449 @@ +/** + * @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 Object representing a mutator dialog. A mutator allows the + * user to change the shape of a block using a nested blocks editor. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Mutator'); + +goog.require('Blockly.Bubble'); +goog.require('Blockly.Icon'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.Timer'); +goog.require('goog.dom'); + + +/** + * Class for a mutator dialog. + * @param {!Array.} quarkNames List of names of sub-blocks for flyout. + * @extends {Blockly.Icon} + * @constructor + */ +Blockly.Mutator = function(quarkNames) { + Blockly.Mutator.superClass_.constructor.call(this, null); + this.quarkNames_ = quarkNames; +}; +goog.inherits(Blockly.Mutator, Blockly.Icon); + +/** + * Width of workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceWidth_ = 0; + +/** + * Height of workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceHeight_ = 0; + +/** + * Draw the mutator icon. + * @param {!Element} group The icon group. + * @private + */ +Blockly.Mutator.prototype.drawIcon_ = function(group) { + // Square with rounded corners. + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconShape', + 'rx': '4', + 'ry': '4', + 'height': '16', + 'width': '16' + }, + group); + // Gear teeth. + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + + '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + + '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + + '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + + '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + + '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + + '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z' + }, + group); + // Axle hole. + Blockly.utils.createSvgElement( + 'circle', + { + 'class': 'blocklyIconShape', + 'r': '2.7', + 'cx': '8', + 'cy': '8' + }, + group); +}; + +/** + * Clicking on the icon toggles if the mutator bubble is visible. + * Disable if block is uneditable. + * @param {!Event} e Mouse click event. + * @private + * @override + */ +Blockly.Mutator.prototype.iconClick_ = function(e) { + if (this.block_.isEditable()) { + Blockly.Icon.prototype.iconClick_.call(this, e); + } +}; + +/** + * Create the editor for the mutator's bubble. + * @return {!Element} The top-level node of the editor. + * @private + */ +Blockly.Mutator.prototype.createEditor_ = function() { + /* Create the editor. Here's the markup that will be generated: + + [Workspace] + + */ + this.svgDialog_ = Blockly.utils.createSvgElement('svg', + {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, + null); + // Convert the list of names into a list of XML objects for the flyout. + if (this.quarkNames_.length) { + var quarkXml = goog.dom.createDom('xml'); + for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) { + quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName})); + } + } else { + var quarkXml = null; + } + var workspaceOptions = { + languageTree: quarkXml, + parentWorkspace: this.block_.workspace, + pathToMedia: this.block_.workspace.options.pathToMedia, + RTL: this.block_.RTL, + toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT : + Blockly.TOOLBOX_AT_LEFT, + horizontalLayout: false, + getMetrics: this.getFlyoutMetrics_.bind(this), + setMetrics: null + }; + this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.isMutator = true; + + // Mutator flyouts go inside the mutator workspace's rather than in + // a top level svg. Instead of handling scale themselves, mutators + // inherit scale from the parent workspace. + // To fix this, scale needs to be applied at a different level in the dom. + var flyoutSvg = this.workspace_.addFlyout_('g'); + var background = this.workspace_.createDom('blocklyMutatorBackground'); + + // Insert the flyout after the but before the block canvas so that + // the flyout is underneath in z-order. This makes blocks layering during + // dragging work properly. + background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_); + this.svgDialog_.appendChild(background); + + return this.svgDialog_; +}; + +/** + * Add or remove the UI indicating if this icon may be clicked or not. + */ +Blockly.Mutator.prototype.updateEditable = function() { + if (!this.block_.isInFlyout) { + if (this.block_.isEditable()) { + if (this.iconGroup_) { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } else { + // Close any mutator bubble. Icon is not clickable. + this.setVisible(false); + if (this.iconGroup_) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } + } + // Default behaviour for an icon. + Blockly.Icon.prototype.updateEditable.call(this); +}; + +/** + * Callback function triggered when the bubble has resized. + * Resize the workspace accordingly. + * @private + */ +Blockly.Mutator.prototype.resizeBubble_ = function() { + var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; + var workspaceSize = this.workspace_.getCanvas().getBBox(); + var width; + if (this.block_.RTL) { + width = -workspaceSize.x; + } else { + width = workspaceSize.width + workspaceSize.x; + } + var height = workspaceSize.height + doubleBorderWidth * 3; + if (this.workspace_.flyout_) { + var flyoutMetrics = this.workspace_.flyout_.getMetrics_(); + height = Math.max(height, flyoutMetrics.contentHeight + 20); + } + width += doubleBorderWidth * 3; + // Only resize if the size difference is significant. Eliminates shuddering. + if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth || + Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) { + // Record some layout information for getFlyoutMetrics_. + this.workspaceWidth_ = width; + this.workspaceHeight_ = height; + // Resize the bubble. + this.bubble_.setBubbleSize( + width + doubleBorderWidth, height + doubleBorderWidth); + this.svgDialog_.setAttribute('width', this.workspaceWidth_); + this.svgDialog_.setAttribute('height', this.workspaceHeight_); + } + + if (this.block_.RTL) { + // Scroll the workspace to always left-align. + var translation = 'translate(' + this.workspaceWidth_ + ',0)'; + this.workspace_.getCanvas().setAttribute('transform', translation); + } + this.workspace_.resize(); +}; + +/** + * Show or hide the mutator bubble. + * @param {boolean} visible True if the bubble should be visible. + */ +Blockly.Mutator.prototype.setVisible = function(visible) { + if (visible == this.isVisible()) { + // No change. + return; + } + Blockly.Events.fire( + new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible)); + if (visible) { + // Create the bubble. + this.bubble_ = new Blockly.Bubble( + /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), + this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null); + var tree = this.workspace_.options.languageTree; + if (tree) { + this.workspace_.flyout_.init(this.workspace_); + this.workspace_.flyout_.show(tree.childNodes); + } + + this.rootBlock_ = this.block_.decompose(this.workspace_); + var blocks = this.rootBlock_.getDescendants(); + for (var i = 0, child; child = blocks[i]; i++) { + child.render(); + } + // The root block should not be dragable or deletable. + this.rootBlock_.setMovable(false); + this.rootBlock_.setDeletable(false); + if (this.workspace_.flyout_) { + var margin = this.workspace_.flyout_.CORNER_RADIUS * 2; + var x = this.workspace_.flyout_.width_ + margin; + } else { + var margin = 16; + var x = margin; + } + if (this.block_.RTL) { + x = -x; + } + this.rootBlock_.moveBy(x, margin); + // Save the initial connections, then listen for further changes. + if (this.block_.saveConnections) { + var thisMutator = this; + this.block_.saveConnections(this.rootBlock_); + this.sourceListener_ = function() { + thisMutator.block_.saveConnections(thisMutator.rootBlock_); + }; + this.block_.workspace.addChangeListener(this.sourceListener_); + } + this.resizeBubble_(); + // When the mutator's workspace changes, update the source block. + this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); + this.updateColour(); + } else { + // Dispose of the bubble. + this.svgDialog_ = null; + this.workspace_.dispose(); + this.workspace_ = null; + this.rootBlock_ = null; + this.bubble_.dispose(); + this.bubble_ = null; + this.workspaceWidth_ = 0; + this.workspaceHeight_ = 0; + if (this.sourceListener_) { + this.block_.workspace.removeChangeListener(this.sourceListener_); + this.sourceListener_ = null; + } + } +}; + +/** + * Update the source block when the mutator's blocks are changed. + * Bump down any block that's too high. + * Fired whenever a change is made to the mutator's workspace. + * @private + */ +Blockly.Mutator.prototype.workspaceChanged_ = function() { + if (!this.workspace_.isDragging()) { + var blocks = this.workspace_.getTopBlocks(false); + var MARGIN = 20; + for (var b = 0, block; block = blocks[b]; b++) { + var blockXY = block.getRelativeToSurfaceXY(); + var blockHW = block.getHeightWidth(); + if (blockXY.y + blockHW.height < MARGIN) { + // Bump any block that's above the top back inside. + block.moveBy(0, MARGIN - blockHW.height - blockXY.y); + } + } + } + + // When the mutator's workspace changes, update the source block. + if (this.rootBlock_.workspace == this.workspace_) { + Blockly.Events.setGroup(true); + var block = this.block_; + var oldMutationDom = block.mutationToDom(); + var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + // Switch off rendering while the source block is rebuilt. + var savedRendered = block.rendered; + block.rendered = false; + // Allow the source block to rebuild itself. + block.compose(this.rootBlock_); + // Restore rendering and show the changes. + block.rendered = savedRendered; + // Mutation may have added some elements that need initializing. + block.initSvg(); + var newMutationDom = block.mutationToDom(); + var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); + if (oldMutation != newMutation) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + block, 'mutation', null, oldMutation, newMutation)); + // Ensure that any bump is part of this mutation's event group. + var group = Blockly.Events.getGroup(); + setTimeout(function() { + Blockly.Events.setGroup(group); + block.bumpNeighbours_(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY); + } + if (block.rendered) { + block.render(); + } + // Don't update the bubble until the drag has ended, to avoid moving blocks + // under the cursor. + if (!this.workspace_.isDragging()) { + this.resizeBubble_(); + } + Blockly.Events.setGroup(false); + } +}; + +/** + * Return an object with all the metrics required to size scrollbars for the + * mutator flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + * @return {!Object} Contains size and position metrics of mutator dialog's + * workspace. + * @private + */ +Blockly.Mutator.prototype.getFlyoutMetrics_ = function() { + return { + viewHeight: this.workspaceHeight_, + viewWidth: this.workspaceWidth_, + absoluteTop: 0, + absoluteLeft: 0 + }; +}; + +/** + * Dispose of this mutator. + */ +Blockly.Mutator.prototype.dispose = function() { + this.block_.mutator = null; + Blockly.Icon.prototype.dispose.call(this); +}; + +/** + * Reconnect an block to a mutated input. + * @param {Blockly.Connection} connectionChild Connection on child block. + * @param {!Blockly.Block} block Parent block. + * @param {string} inputName Name of input on parent block. + * @return {boolean} True iff a reconnection was made, false otherwise. + */ +Blockly.Mutator.reconnect = function(connectionChild, block, inputName) { + if (!connectionChild || !connectionChild.getSourceBlock().workspace) { + return false; // No connection or block has been deleted. + } + var connectionParent = block.getInput(inputName).connection; + var currentParent = connectionChild.targetBlock(); + if ((!currentParent || currentParent == block) && + connectionParent.targetConnection != connectionChild) { + if (connectionParent.isConnected()) { + // There's already something connected here. Get rid of it. + connectionParent.disconnect(); + } + connectionParent.connect(connectionChild); + return true; + } + return false; +}; + +/** + * Get the parent workspace of a workspace that is inside a mutator, taking into + * account whether it is a flyout. + * @param {?Blockly.Workspace} workspace The workspace that is inside a mutator. + * @return {?Blockly.Workspace} The mutator's parent workspace or null. + * @package + */ +Blockly.Mutator.findParentWs = function(workspace) { + var outerWs = null; + if (workspace && workspace.options) { + var parent = workspace.options.parentWorkspace; + // If we were in a flyout in a mutator, need to go up two levels to find + // the actual parent. + if (workspace.isFlyout) { + if (parent && parent.options) { + outerWs = parent.options.parentWorkspace; + } + } else if (parent) { + outerWs = parent; + } + } + return outerWs; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +if (!goog.global['Blockly']['Mutator']) { + goog.global['Blockly']['Mutator'] = {}; +} +goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect; diff --git a/core/.svn/pristine/d9/d97e6eeb4963bebb044d878ffebb57a24c8bc8df.svn-base b/core/.svn/pristine/d9/d97e6eeb4963bebb044d878ffebb57a24c8bc8df.svn-base new file mode 100644 index 0000000..031daf7 --- /dev/null +++ b/core/.svn/pristine/d9/d97e6eeb4963bebb044d878ffebb57a24c8bc8df.svn-base @@ -0,0 +1,344 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Object representing a trash can icon. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Trashcan'); + +goog.require('goog.Timer'); +goog.require('goog.dom'); +goog.require('goog.math'); +goog.require('goog.math.Rect'); + + +/** + * Class for a trash can. + * @param {!Blockly.Workspace} workspace The workspace to sit in. + * @constructor + */ +Blockly.Trashcan = function(workspace) { + this.workspace_ = workspace; +}; + +/** + * Width of both the trash can and lid images. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.WIDTH_ = 47; + +/** + * Height of the trashcan image (minus lid). + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.BODY_HEIGHT_ = 44; + +/** + * Height of the lid image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.LID_HEIGHT_ = 16; + +/** + * Distance between trashcan and bottom edge of workspace. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = 20; + +/** + * Distance between trashcan and right edge of workspace. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_SIDE_ = 20; + +/** + * Extent of hotspot on all sides beyond the size of the image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.MARGIN_HOTSPOT_ = 10; + +/** + * Location of trashcan in sprite image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.SPRITE_LEFT_ = 0; + +/** + * Location of trashcan in sprite image. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.SPRITE_TOP_ = 32; + +/** + * Current open/close state of the lid. + * @type {boolean} + */ +Blockly.Trashcan.prototype.isOpen = false; + +/** + * The SVG group containing the trash can. + * @type {Element} + * @private + */ +Blockly.Trashcan.prototype.svgGroup_ = null; + +/** + * The SVG image element of the trash can lid. + * @type {Element} + * @private + */ +Blockly.Trashcan.prototype.svgLid_ = null; + +/** + * Task ID of opening/closing animation. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.lidTask_ = 0; + +/** + * Current state of lid opening (0.0 = closed, 1.0 = open). + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.lidOpen_ = 0; + +/** + * Left coordinate of the trash can. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.left_ = 0; + +/** + * Top coordinate of the trash can. + * @type {number} + * @private + */ +Blockly.Trashcan.prototype.top_ = 0; + +/** + * Create the trash can elements. + * @return {!Element} The trash can's SVG group. + */ +Blockly.Trashcan.prototype.createDom = function() { + /* Here's the markup that will be generated: + + + + + + + + + + + */ + this.svgGroup_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyTrash'}, null); + var clip; + var rnd = String(Math.random()).substring(2); + clip = Blockly.utils.createSvgElement('clipPath', + {'id': 'blocklyTrashBodyClipPath' + rnd}, + this.svgGroup_); + Blockly.utils.createSvgElement('rect', + { + 'width': this.WIDTH_, + 'height': this.BODY_HEIGHT_, + 'y': this.LID_HEIGHT_ + }, + clip); + var body = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')' + }, + this.svgGroup_); + body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + this.workspace_.options.pathToMedia + Blockly.SPRITE.url); + + clip = Blockly.utils.createSvgElement('clipPath', + {'id': 'blocklyTrashLidClipPath' + rnd}, + this.svgGroup_); + Blockly.utils.createSvgElement('rect', + {'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip); + this.svgLid_ = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')' + }, + this.svgGroup_); + this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', + this.workspace_.options.pathToMedia + Blockly.SPRITE.url); + + Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup', this, this.click); + this.animateLid_(); + return this.svgGroup_; +}; + +/** + * Initialize the trash can. + * @param {number} bottom Distance from workspace bottom to bottom of trashcan. + * @return {number} Distance from workspace bottom to the top of trashcan. + */ +Blockly.Trashcan.prototype.init = function(bottom) { + this.bottom_ = this.MARGIN_BOTTOM_ + bottom; + this.setOpen_(false); + return this.bottom_ + this.BODY_HEIGHT_ + this.LID_HEIGHT_; +}; + +/** + * Dispose of this trash can. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Trashcan.prototype.dispose = function() { + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgLid_ = null; + this.workspace_ = null; + goog.Timer.clear(this.lidTask_); +}; + +/** + * Move the trash can to the bottom-right corner. + */ +Blockly.Trashcan.prototype.position = function() { + var metrics = this.workspace_.getMetrics(); + if (!metrics) { + // There are no metrics available (workspace is probably not visible). + return; + } + if (this.workspace_.RTL) { + this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness; + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + this.left_ += metrics.flyoutWidth; + if (this.workspace_.toolbox_) { + this.left_ += metrics.absoluteLeft; + } + } + } else { + this.left_ = metrics.viewWidth + metrics.absoluteLeft - + this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + this.left_ -= metrics.flyoutWidth; + } + } + this.top_ = metrics.viewHeight + metrics.absoluteTop - + (this.BODY_HEIGHT_ + this.LID_HEIGHT_) - this.bottom_; + + if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + this.top_ -= metrics.flyoutHeight; + } + this.svgGroup_.setAttribute('transform', + 'translate(' + this.left_ + ',' + this.top_ + ')'); +}; + +/** + * Return the deletion rectangle for this trash can. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.Trashcan.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var trashRect = this.svgGroup_.getBoundingClientRect(); + var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_; + var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_; + var width = this.WIDTH_ + 2 * this.MARGIN_HOTSPOT_; + var height = this.LID_HEIGHT_ + this.BODY_HEIGHT_ + 2 * this.MARGIN_HOTSPOT_; + return new goog.math.Rect(left, top, width, height); + +}; + +/** + * Flip the lid open or shut. + * @param {boolean} state True if open. + * @private + */ +Blockly.Trashcan.prototype.setOpen_ = function(state) { + if (this.isOpen == state) { + return; + } + goog.Timer.clear(this.lidTask_); + this.isOpen = state; + this.animateLid_(); +}; + +/** + * Rotate the lid open or closed by one step. Then wait and recurse. + * @private + */ +Blockly.Trashcan.prototype.animateLid_ = function() { + this.lidOpen_ += this.isOpen ? 0.2 : -0.2; + this.lidOpen_ = goog.math.clamp(this.lidOpen_, 0, 1); + var lidAngle = this.lidOpen_ * 45; + this.svgLid_.setAttribute('transform', 'rotate(' + + (this.workspace_.RTL ? -lidAngle : lidAngle) + ',' + + (this.workspace_.RTL ? 4 : this.WIDTH_ - 4) + ',' + + (this.LID_HEIGHT_ - 2) + ')'); + var opacity = goog.math.lerp(0.4, 0.8, this.lidOpen_); + this.svgGroup_.style.opacity = opacity; + if (this.lidOpen_ > 0 && this.lidOpen_ < 1) { + this.lidTask_ = goog.Timer.callOnce(this.animateLid_, 20, this); + } +}; + +/** + * Flip the lid shut. + * Called externally after a drag. + */ +Blockly.Trashcan.prototype.close = function() { + this.setOpen_(false); +}; + +/** + * Inspect the contents of the trash. + */ +Blockly.Trashcan.prototype.click = function() { + var dx = this.workspace_.startScrollX - this.workspace_.scrollX; + var dy = this.workspace_.startScrollY - this.workspace_.scrollY; + if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { + return; + } + console.log('TODO: Inspect trash.'); +}; diff --git a/core/.svn/pristine/d9/d9809cd1ff722121d05698ba9943fdeb5e161917.svn-base b/core/.svn/pristine/d9/d9809cd1ff722121d05698ba9943fdeb5e161917.svn-base new file mode 100644 index 0000000..427d451 --- /dev/null +++ b/core/.svn/pristine/d9/d9809cd1ff722121d05698ba9943fdeb5e161917.svn-base @@ -0,0 +1,564 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Core JavaScript library for Blockly. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * The top level namespace used to access the Blockly library. + * @namespace Blockly + **/ +goog.provide('Blockly'); + +goog.require('Blockly.BlockSvg.render'); +goog.require('Blockly.Events'); +goog.require('Blockly.FieldAngle'); +goog.require('Blockly.FieldCheckbox'); +goog.require('Blockly.FieldColour'); +// Date picker commented out since it increases footprint by 60%. +// Add it only if you need it. +//goog.require('Blockly.FieldDate'); +goog.require('Blockly.FieldDropdown'); +goog.require('Blockly.FieldImage'); +goog.require('Blockly.FieldTextInput'); +goog.require('Blockly.FieldNumber'); +goog.require('Blockly.FieldVariable'); +goog.require('Blockly.Generator'); +goog.require('Blockly.Msg'); +goog.require('Blockly.Procedures'); +goog.require('Blockly.Toolbox'); +goog.require('Blockly.Touch'); +goog.require('Blockly.WidgetDiv'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('Blockly.constants'); +goog.require('Blockly.inject'); +goog.require('Blockly.utils'); +goog.require('goog.color'); +goog.require('goog.userAgent'); + + +// Turn off debugging when compiled. +// Unused within the Blockly library, but used in Closure. +/* eslint-disable no-unused-vars */ +var CLOSURE_DEFINES = {'goog.DEBUG': false}; +/* eslint-enable no-unused-vars */ + +/** + * The main workspace most recently used. + * Set by Blockly.WorkspaceSvg.prototype.markFocused + * @type {Blockly.Workspace} + */ +Blockly.mainWorkspace = null; + +/** + * Currently selected block. + * @type {Blockly.Block} + */ +Blockly.selected = null; + +/** + * All of the connections on blocks that are currently being dragged. + * @type {!Array.} + * @private + */ +Blockly.draggingConnections_ = []; + +/** + * Contents of the local clipboard. + * @type {Element} + * @private + */ +Blockly.clipboardXml_ = null; + +/** + * Source of the local clipboard. + * @type {Blockly.WorkspaceSvg} + * @private + */ +Blockly.clipboardSource_ = null; + +/** + * Cached value for whether 3D is supported. + * @type {!boolean} + * @private + */ +Blockly.cache3dSupported_ = null; + +/** + * Convert a hue (HSV model) into an RGB hex triplet. + * @param {number} hue Hue on a colour wheel (0-360). + * @return {string} RGB code, e.g. '#5ba65b'. + */ +Blockly.hueToRgb = function(hue) { + return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION, + Blockly.HSV_VALUE * 255); +}; + +/** + * Returns the dimensions of the specified SVG image. + * @param {!Element} svg SVG image. + * @return {!Object} Contains width and height properties. + */ +Blockly.svgSize = function(svg) { + return { + width: svg.cachedWidth_, + height: svg.cachedHeight_ + }; +}; + +/** + * Size the workspace when the contents change. This also updates + * scrollbars accordingly. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. + */ +Blockly.resizeSvgContents = function(workspace) { + workspace.resizeContents(); +}; + +/** + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See Blockly.resizeSvgContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). + * Record the height/width of the SVG image. + * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. + */ +Blockly.svgResize = function(workspace) { + var mainWorkspace = workspace; + while (mainWorkspace.options.parentWorkspace) { + mainWorkspace = mainWorkspace.options.parentWorkspace; + } + var svg = mainWorkspace.getParentSvg(); + var div = svg.parentNode; + if (!div) { + // Workspace deleted, or something. + return; + } + var width = div.offsetWidth; + var height = div.offsetHeight; + if (svg.cachedWidth_ != width) { + svg.setAttribute('width', width + 'px'); + svg.cachedWidth_ = width; + } + if (svg.cachedHeight_ != height) { + svg.setAttribute('height', height + 'px'); + svg.cachedHeight_ = height; + } + mainWorkspace.resize(); +}; + +/** + * Handle a key-down on SVG drawing surface. + * @param {!Event} e Key down event. + * @private + */ +Blockly.onKeyDown_ = function(e) { + if (Blockly.mainWorkspace.options.readOnly || Blockly.utils.isTargetInput(e)) { + // No key actions on readonly workspaces. + // When focused on an HTML text input widget, don't trap any keys. + return; + } + var deleteBlock = false; + if (e.keyCode == 27) { + // Pressing esc closes the context menu. + Blockly.hideChaff(); + } else if (e.keyCode == 8 || e.keyCode == 46) { + // Delete or backspace. + // Stop the browser from going back to the previous page. + // Do this first to prevent an error in the delete code from resulting in + // data loss. + e.preventDefault(); + // Don't delete while dragging. Jeez. + if (Blockly.mainWorkspace.isDragging()) { + return; + } + if (Blockly.selected && Blockly.selected.isDeletable()) { + deleteBlock = true; + } + } else if (e.altKey || e.ctrlKey || e.metaKey) { + // Don't use meta keys during drags. + if (Blockly.mainWorkspace.isDragging()) { + return; + } + if (Blockly.selected && + Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { + // Don't allow copying immovable or undeletable blocks. The next step + // would be to paste, which would create additional undeletable/immovable + // blocks on the workspace. + if (e.keyCode == 67) { + // 'c' for copy. + Blockly.hideChaff(); + Blockly.copy_(Blockly.selected); + } else if (e.keyCode == 88 && !Blockly.selected.workspace.isFlyout) { + // 'x' for cut, but not in a flyout. + // Don't even copy the selected item in the flyout. + Blockly.copy_(Blockly.selected); + deleteBlock = true; + } + } + if (e.keyCode == 86) { + // 'v' for paste. + if (Blockly.clipboardXml_) { + Blockly.Events.setGroup(true); + // Pasting always pastes to the main workspace, even if the copy started + // in a flyout workspace. + var workspace = Blockly.clipboardSource_; + if (workspace.isFlyout) { + workspace = workspace.targetWorkspace; + } + workspace.paste(Blockly.clipboardXml_); + Blockly.Events.setGroup(false); + } + } else if (e.keyCode == 90) { + // 'z' for undo 'Z' is for redo. + Blockly.hideChaff(); + Blockly.mainWorkspace.undo(e.shiftKey); + } + } + // Common code for delete and cut. + // Don't delete in the flyout. + if (deleteBlock && !Blockly.selected.workspace.isFlyout) { + Blockly.Events.setGroup(true); + Blockly.hideChaff(); + Blockly.selected.dispose(/* heal */ true, true); + Blockly.Events.setGroup(false); + } +}; + +/** + * Copy a block onto the local clipboard. + * @param {!Blockly.Block} block Block to be copied. + * @private + */ +Blockly.copy_ = function(block) { + var xmlBlock = Blockly.Xml.blockToDom(block); + // Copy only the selected block and internal blocks. + Blockly.Xml.deleteNext(xmlBlock); + // Encode start position in XML. + var xy = block.getRelativeToSurfaceXY(); + xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x); + xmlBlock.setAttribute('y', xy.y); + Blockly.clipboardXml_ = xmlBlock; + Blockly.clipboardSource_ = block.workspace; +}; + +/** + * Duplicate this block and its children. + * @param {!Blockly.Block} block Block to be copied. + * @private + */ +Blockly.duplicate_ = function(block) { + // Save the clipboard. + var clipboardXml = Blockly.clipboardXml_; + var clipboardSource = Blockly.clipboardSource_; + + // Create a duplicate via a copy/paste operation. + Blockly.copy_(block); + block.workspace.paste(Blockly.clipboardXml_); + + // Restore the clipboard. + Blockly.clipboardXml_ = clipboardXml; + Blockly.clipboardSource_ = clipboardSource; +}; + +/** + * Cancel the native context menu, unless the focus is on an HTML input widget. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.onContextMenu_ = function(e) { + if (!Blockly.utils.isTargetInput(e)) { + // When focused on an HTML text input widget, don't cancel the context menu. + e.preventDefault(); + } +}; + +/** + * Close tooltips, context menus, dropdown selections, etc. + * @param {boolean=} opt_allowToolbox If true, don't close the toolbox. + */ +Blockly.hideChaff = function(opt_allowToolbox) { + Blockly.Tooltip.hide(); + Blockly.WidgetDiv.hide(); + if (!opt_allowToolbox) { + var workspace = Blockly.getMainWorkspace(); + if (workspace.toolbox_ && + workspace.toolbox_.flyout_ && + workspace.toolbox_.flyout_.autoClose) { + workspace.toolbox_.clearSelection(); + } + } +}; + +/** + * When something in Blockly's workspace changes, call a function. + * @param {!Function} func Function to call. + * @return {!Array.} Opaque data that can be passed to + * removeChangeListener. + * @deprecated April 2015 + */ +Blockly.addChangeListener = function(func) { + // Backwards compatibility from before there could be multiple workspaces. + console.warn( + 'Deprecated call to Blockly.addChangeListener, ' + + 'use workspace.addChangeListener instead.'); + return Blockly.getMainWorkspace().addChangeListener(func); +}; + +/** + * Returns the main workspace. Returns the last used main workspace (based on + * focus). Try not to use this function, particularly if there are multiple + * Blockly instances on a page. + * @return {!Blockly.Workspace} The main workspace. + */ +Blockly.getMainWorkspace = function() { + return Blockly.mainWorkspace; +}; + +/** + * Wrapper to window.alert() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {function()=} opt_callback The callback when the alert is dismissed. + */ +Blockly.alert = function(message, opt_callback) { + window.alert(message); + if (opt_callback) { + opt_callback(); + } +}; + +/** + * Wrapper to window.confirm() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {!function(boolean)} callback The callback for handling user response. + */ +Blockly.confirm = function(message, callback) { + callback(window.confirm(message)); +}; + +/** + * Wrapper to window.prompt() that app developers may override to provide + * alternatives to the modal browser window. Built-in browser prompts are + * often used for better text input experience on mobile device. We strongly + * recommend testing mobile when overriding this. + * @param {string} message The message to display to the user. + * @param {string} defaultValue The value to initialize the prompt with. + * @param {!function(string)} callback The callback for handling user response. + */ +Blockly.prompt = function(message, defaultValue, callback) { + callback(window.prompt(message, defaultValue)); +}; + +/** + * Helper function for defining a block from JSON. The resulting function has + * the correct value of jsonDef at the point in code where jsonInit is called. + * @param {!Object} jsonDef The JSON definition of a block. + * @return {function()} A function that calls jsonInit with the correct value + * of jsonDef. + * @private + */ +Blockly.jsonInitFactory_ = function(jsonDef) { + return function() { + this.jsonInit(jsonDef); + }; +}; + +/** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array.} jsonArray An array of JSON block definitions. + */ +Blockly.defineBlocksWithJsonArray = function(jsonArray) { + for (var i = 0, elem; elem = jsonArray[i]; i++) { + var typename = elem.type; + if (typename == null || typename === '') { + console.warn( + 'Block definition #' + i + + ' in JSON array is missing a type attribute. Skipping.'); + } else { + if (Blockly.Blocks[typename]) { + console.warn( + 'Block definition #' + i + ' in JSON array' + + ' overwrites prior definition of "' + typename + '".'); + } + Blockly.Blocks[typename] = { + init: Blockly.jsonInitFactory_(elem) + }; + } + } +}; + +/** + * Bind an event to a function call. When calling the function, verifies that + * it belongs to the touch stream that is currently being processed, and splits + * multitouch events into multiple events as needed. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event + * should not block execution of other event handlers on this touch or other + * simultaneous touches. + * @param {boolean=} opt_noPreventDefault True if triggering on this event + * should prevent the default handler. False by default. If + * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be + * provided. + * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @private + */ +Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, + opt_noCaptureIdentifier, opt_noPreventDefault) { + var handled = false; + var wrapFunc = function(e) { + var captureIdentifier = !opt_noCaptureIdentifier; + // Handle each touch point separately. If the event was a mouse event, this + // will hand back an array with one element, which we're fine handling. + var events = Blockly.Touch.splitEventByTouches(e); + for (var i = 0, event; event = events[i]; i++) { + if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) { + continue; + } + Blockly.Touch.setClientFromTouch(event); + if (thisObject) { + func.call(thisObject, event); + } else { + func(event); + } + handled = true; + } + }; + + node.addEventListener(name, wrapFunc, false); + var bindData = [[node, name, wrapFunc]]; + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + wrapFunc(e); + // Calling preventDefault stops the browser from scrolling/zooming the + // page. + var preventDef = !opt_noPreventDefault; + if (handled && preventDef) { + e.preventDefault(); + } + }; + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); + } + } + return bindData; +}; + + +/** + * Bind an event to a function call. Handles multitouch events by using the + * coordinates of the first changed touch, and doesn't do any safety checks for + * simultaneous event processing. + * @deprecated in favor of bindEventWithChecks_, but preserved for external + * users. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @private + */ +Blockly.bindEvent_ = function(node, name, thisObject, func) { + var wrapFunc = function(e) { + if (thisObject) { + func.call(thisObject, e); + } else { + func(e); + } + }; + + node.addEventListener(name, wrapFunc, false); + var bindData = [[node, name, wrapFunc]]; + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + // Punt on multitouch events. + if (e.changedTouches && e.changedTouches.length == 1) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } + wrapFunc(e); + + // Stop the browser from scrolling/zooming the page. + e.preventDefault(); + }; + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); + } + } + return bindData; +}; + +/** + * Unbind one or more events event from a function call. + * @param {!Array.} bindData Opaque data from bindEvent_. + * This list is emptied during the course of calling this function. + * @return {!Function} The function call. + * @private + */ +Blockly.unbindEvent_ = function(bindData) { + while (bindData.length) { + var bindDatum = bindData.pop(); + var node = bindDatum[0]; + var name = bindDatum[1]; + var func = bindDatum[2]; + node.removeEventListener(name, func, false); + } + return func; +}; + +/** + * Is the given string a number (includes negative and decimals). + * @param {string} str Input string. + * @return {boolean} True if number, false otherwise. + */ +Blockly.isNumber = function(str) { + return /^\s*-?\d+(\.\d+)?\s*$/.test(str); +}; + +// IE9 does not have a console. Create a stub to stop errors. +if (!goog.global['console']) { + goog.global['console'] = { + 'log': function() {}, + 'warn': function() {} + }; +} + +// Export symbols that would otherwise be renamed by Closure compiler. +if (!goog.global['Blockly']) { + goog.global['Blockly'] = {}; +} +goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; +goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener; diff --git a/core/.svn/pristine/db/db42d9962d809664dcf7db83796d7fe5ad87afb1.svn-base b/core/.svn/pristine/db/db42d9962d809664dcf7db83796d7fe5ad87afb1.svn-base new file mode 100644 index 0000000..3ecca22 --- /dev/null +++ b/core/.svn/pristine/db/db42d9962d809664dcf7db83796d7fe5ad87afb1.svn-base @@ -0,0 +1,565 @@ +/** + * @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 Dropdown input field. Used for editable titles and variables. + * In the interests of a consistent UI, the toolbox shares some functions and + * properties with the context menu. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldDropdown'); + +goog.require('Blockly.Field'); +goog.require('Blockly.utils'); +goog.require('Blockly.utils.uiMenu'); + +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('goog.ui.Menu'); +goog.require('goog.ui.MenuItem'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable dropdown field. + * @param {(!Array.|!Function)} menuGenerator An array of options + * for a dropdown list, or a function which generates these options. + * @param {Function=} opt_validator A function that is executed when a new + * option is selected, with the newly selected value as its sole argument. + * If it returns a value, that value (which must be one of the options) will + * become selected in place of the newly selected option, unless the return + * value is null, in which case the change is aborted. + * @extends {Blockly.Field} + * @constructor + */ +Blockly.FieldDropdown = function(menuGenerator, opt_validator) { + this.menuGenerator_ = menuGenerator; + this.trimOptions_(); + var firstTuple = this.getOptions()[0]; + + // Call parent's constructor. + Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1], + opt_validator); +}; +goog.inherits(Blockly.FieldDropdown, Blockly.Field); + +/** + * Construct a FieldDropdown from a JSON arg object. + * @param {!Object} options A JSON object with options (options). + * @returns {!Blockly.FieldDropdown} The new field instance. + * @package + */ +Blockly.FieldDropdown.fromJson = function(options) { + return new Blockly.FieldDropdown(options['options']); +}; + +/** + * Horizontal distance that a checkmark overhangs the dropdown. + */ +Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25; + +/** + * Maximum height of the dropdown menu,it's also referenced in css.js as + * part of .blocklyDropdownMenu. + */ +Blockly.FieldDropdown.MAX_MENU_HEIGHT = 300; + +/** + * Android can't (in 2014) display "▾", so use "▼" instead. + */ +Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE'; + +/** + * Mouse cursor style when over the hotspot that initiates the editor. + */ +Blockly.FieldDropdown.prototype.CURSOR = 'default'; + +/** + * Language-neutral currently selected string or image object. + * @type {string|!Object} + * @private + */ +Blockly.FieldDropdown.prototype.value_ = ''; + +/** + * SVG image element if currently selected option is an image, or null. + * @type {SVGElement} + * @private + */ +Blockly.FieldDropdown.prototype.imageElement_ = null; + +/** + * Object with src, height, width, and alt attributes if currently selected + * option is an image, or null. + * @type {Object} + * @private + */ +Blockly.FieldDropdown.prototype.imageJson_ = null; + +/** + * Install this dropdown on a block. + */ +Blockly.FieldDropdown.prototype.init = function() { + if (this.fieldGroup_) { + // Dropdown has already been initialized once. + return; + } + // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL) + this.arrow_ = Blockly.utils.createSvgElement('tspan', {}, null); + this.arrow_.appendChild(document.createTextNode(this.sourceBlock_.RTL ? + Blockly.FieldDropdown.ARROW_CHAR + ' ' : + ' ' + Blockly.FieldDropdown.ARROW_CHAR)); + + Blockly.FieldDropdown.superClass_.init.call(this); +}; + +/** + * Create a dropdown menu under the text. + * @private + */ +Blockly.FieldDropdown.prototype.showEditor_ = function() { + Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null); + var menu = this.createMenu_(); + this.addEventListeners_(menu); + this.positionMenu_(menu); +}; + +/** + * Add event listeners for actions on the items in the dropdown menu. + * @param {!goog.ui.Menu} menu The menu to add listeners to. + * @private + */ +Blockly.FieldDropdown.prototype.addEventListeners_ = function(menu) { + this.addActionListener_(menu); + this.addTouchStartListener_(menu); + this.addTouchEndListener_(menu); +}; + +/** + * Add a listener for mouse and keyboard events in the menu and its items. + * @param {!goog.ui.Menu} menu The menu to add listeners to. + * @private + */ +Blockly.FieldDropdown.prototype.addActionListener_ = function(menu) { + var thisField = this; + + function callback(e) { + var menu = this; + var menuItem = e.target; + if (menuItem) { + thisField.onItemSelected(menu, menuItem); + } + Blockly.WidgetDiv.hideIfOwner(thisField); + Blockly.Events.setGroup(false); + } + // Listen for mouse/keyboard events. + goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); +}; + +/** + * Add a listener for touch start events on menu items. + * @param {!goog.ui.Menu} menu The menu to add the listener to. + * @private + */ +Blockly.FieldDropdown.prototype.addTouchStartListener_ = function(menu) { + // Listen for touch events (why doesn't Closure handle this already?). + function callback(e) { + var control = this.getOwnerControl(/** @type {Node} */ (e.target)); + // Highlight the menu item. + control.handleMouseDown(e); + } + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHSTART, callback); +}; + +/** + * Add a listener for touch end events on menu items. + * @param {!goog.ui.Menu} menu The menu to add the listener to. + * @private + */ +Blockly.FieldDropdown.prototype.addTouchEndListener_ = function(menu) { + // Listen for touch events (why doesn't Closure handle this already?). + function callbackTouchEnd(e) { + var control = this.getOwnerControl(/** @type {Node} */ (e.target)); + // Activate the menu item. + control.performActionInternal(e); + } + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHEND, callbackTouchEnd); +}; + +/** + * Create and populate the menu and menu items for this dropdown, based on + * the options list. + * @return {!goog.ui.Menu} The populated dropdown menu. + * @private + */ +Blockly.FieldDropdown.prototype.createMenu_ = function() { + var menu = new goog.ui.Menu(); + menu.setRightToLeft(this.sourceBlock_.RTL); + var options = this.getOptions(); + for (var i = 0; i < options.length; i++) { + var content = options[i][0]; // Human-readable text or image. + var value = options[i][1]; // Language-neutral value. + if (typeof content == 'object') { + // An image, not text. + var image = new Image(content['width'], content['height']); + image.src = content['src']; + image.alt = content['alt'] || ''; + content = image; + } + var menuItem = new goog.ui.MenuItem(content); + menuItem.setRightToLeft(this.sourceBlock_.RTL); + menuItem.setValue(value); + menuItem.setCheckable(true); + menu.addChild(menuItem, true); + menuItem.setChecked(value == this.value_); + } + return menu; +}; + +/** + * Place the menu correctly on the screen, taking into account the dimensions + * of the menu and the dimensions of the screen so that it doesn't run off any + * edges. + * @param {!goog.ui.Menu} menu The menu to position. + * @private + */ +Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) { + // Record viewport dimensions before adding the dropdown. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getAnchorDimensions_(); + + this.createWidget_(menu); + var menuSize = Blockly.utils.uiMenu.getSize(menu); + + if (menuSize.height > Blockly.FieldDropdown.MAX_MENU_HEIGHT) { + menuSize.height = Blockly.FieldDropdown.MAX_MENU_HEIGHT; + } + + if (this.sourceBlock_.RTL) { + Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); + } + // Position the menu. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, + this.sourceBlock_.RTL); + // Calling menuDom.focus() has to wait until after the menu has been placed + // correctly. Otherwise it will cause a page scroll to get the misplaced menu + // in view. See issue #1329. + menu.getElement().focus(); +}; + +/** + * Create and render the menu widget inside Blockly's widget div. + * @param {!goog.ui.Menu} menu The menu to add to the widget div. + * @private + */ +Blockly.FieldDropdown.prototype.createWidget_ = function(menu) { + var div = Blockly.WidgetDiv.DIV; + menu.render(div); + Blockly.utils.addClass(menu.getElement(), 'blocklyDropdownMenu'); + // Enable autofocus after the initial render to avoid issue #1329. + menu.setAllowAutoFocus(true); +}; + +/** + * Returns the coordinates of the anchor rectangle for the widget div. + * On a FieldDropdown we take the top-left corner of the field, then adjust for + * the size of the checkmark that is displayed next to the currently selected + * item. This means that the item text will be positioned directly under the + * field text, rather than offset slightly. + * @returns {!Object} The bounding rectangle of the anchor, in window + * coordinates. + * @private + */ +Blockly.FieldDropdown.prototype.getAnchorDimensions_ = function() { + var boundingBox = this.getScaledBBox_(); + if (this.sourceBlock_.RTL) { + boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG; + } else { + boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG; + } + + return boundingBox; +}; + +/** + * Handle the selection of an item in the dropdown menu. + * @param {!goog.ui.Menu} menu The Menu component clicked. + * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu. + */ +Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) { + var value = menuItem.getValue(); + if (this.sourceBlock_) { + // Call any validation function, and allow it to override. + value = this.callValidator(value); + } + if (value !== null) { + this.setValue(value); + } +}; + +/** + * Factor out common words in statically defined options. + * Create prefix and/or suffix labels. + * @private + */ +Blockly.FieldDropdown.prototype.trimOptions_ = function() { + this.prefixField = null; + this.suffixField = null; + var options = this.menuGenerator_; + if (!goog.isArray(options)) { + return; + } + var hasImages = false; + + // Localize label text and image alt text. + for (var i = 0; i < options.length; i++) { + var label = options[i][0]; + if (typeof label == 'string') { + options[i][0] = Blockly.utils.replaceMessageReferences(label); + } else { + if (label.alt != null) { + options[i][0].alt = Blockly.utils.replaceMessageReferences(label.alt); + } + hasImages = true; + } + } + if (hasImages || options.length < 2) { + return; // Do nothing if too few items or at least one label is an image. + } + var strings = []; + for (var i = 0; i < options.length; i++) { + strings.push(options[i][0]); + } + var shortest = Blockly.utils.shortestStringLength(strings); + var prefixLength = Blockly.utils.commonWordPrefix(strings, shortest); + var suffixLength = Blockly.utils.commonWordSuffix(strings, shortest); + if (!prefixLength && !suffixLength) { + return; + } + if (shortest <= prefixLength + suffixLength) { + // One or more strings will entirely vanish if we proceed. Abort. + return; + } + if (prefixLength) { + this.prefixField = strings[0].substring(0, prefixLength - 1); + } + if (suffixLength) { + this.suffixField = strings[0].substr(1 - suffixLength); + } + + this.menuGenerator_ = Blockly.FieldDropdown.applyTrim_(options, prefixLength, + suffixLength); +}; + +/** + * Use the calculated prefix and suffix lengths to trim all of the options in + * the given array. + * @param {!Array.} options Array of option tuples: + * (human-readable text or image, language-neutral name). + * @param {number} prefixLength The length of the common prefix. + * @param {number} suffixLength The length of the common suffix + * @return {!Array.} A new array with all of the option text trimmed. + */ +Blockly.FieldDropdown.applyTrim_ = function(options, prefixLength, suffixLength) { + var newOptions = []; + // Remove the prefix and suffix from the options. + for (var i = 0; i < options.length; i++) { + var text = options[i][0]; + var value = options[i][1]; + text = text.substring(prefixLength, text.length - suffixLength); + newOptions[i] = [text, value]; + } + return newOptions; +}; + +/** + * @return {boolean} True if the option list is generated by a function. + * Otherwise false. + */ +Blockly.FieldDropdown.prototype.isOptionListDynamic = function() { + return goog.isFunction(this.menuGenerator_); +}; + +/** + * Return a list of the options for this dropdown. + * @return {!Array.} Array of option tuples: + * (human-readable text or image, language-neutral name). + */ +Blockly.FieldDropdown.prototype.getOptions = function() { + if (goog.isFunction(this.menuGenerator_)) { + return this.menuGenerator_.call(this); + } + return /** @type {!Array.>} */ (this.menuGenerator_); +}; + +/** + * Get the language-neutral value from this dropdown menu. + * @return {string} Current text. + */ +Blockly.FieldDropdown.prototype.getValue = function() { + return this.value_; +}; + +/** + * Set the language-neutral value for this dropdown menu. + * @param {string} newValue New value to set. + */ +Blockly.FieldDropdown.prototype.setValue = function(newValue) { + if (newValue === null || newValue === this.value_) { + return; // No change if null. + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.value_, newValue)); + } + this.value_ = newValue; + // Look up and display the human-readable text. + var options = this.getOptions(); + for (var i = 0; i < options.length; i++) { + // Options are tuples of human-readable text and language-neutral values. + if (options[i][1] == newValue) { + var content = options[i][0]; + if (typeof content == 'object') { + this.imageJson_ = content; + this.text_ = content.alt; + } else { + this.imageJson_ = null; + this.text_ = content; + } + // Always rerender if either the value or the text has changed. + this.forceRerender(); + return; + } + } + // Value not found. Add it, maybe it will become valid once set + // (like variable names). + this.text_ = newValue; + this.forceRerender(); +}; + +/** + * Draws the border with the correct width. + * @private + */ +Blockly.FieldDropdown.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } + if (this.sourceBlock_ && this.arrow_) { + // Update arrow's colour. + this.arrow_.style.fill = this.sourceBlock_.getColour(); + } + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + goog.dom.removeNode(this.imageElement_); + this.imageElement_ = null; + + if (this.imageJson_) { + this.renderSelectedImage_(); + } else { + this.renderSelectedText_(); + } + this.borderRect_.setAttribute('height', this.size_.height - 9); + this.borderRect_.setAttribute('width', + this.size_.width + Blockly.BlockSvg.SEP_SPACE_X); +}; + +/** + * Renders the selected option, which must be an image. + * @private + */ +Blockly.FieldDropdown.prototype.renderSelectedImage_ = function() { + // Image option is selected. + this.imageElement_ = Blockly.utils.createSvgElement('image', + { + 'y': 5, + 'height': this.imageJson_.height + 'px', + 'width': this.imageJson_.width + 'px' + }, this.fieldGroup_); + this.imageElement_.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', this.imageJson_.src); + // Insert dropdown arrow. + this.textElement_.appendChild(this.arrow_); + var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); + this.size_.height = Number(this.imageJson_.height) + 19; + this.size_.width = Number(this.imageJson_.width) + arrowWidth; + if (this.sourceBlock_.RTL) { + this.imageElement_.setAttribute('x', arrowWidth); + this.textElement_.setAttribute('x', -1); + } else { + this.textElement_.setAttribute('text-anchor', 'end'); + this.textElement_.setAttribute('x', this.size_.width + 1); + } +}; + +/** + * Renders the selected option, which must be text. + * @private + */ +Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { + // Text option is selected. + // Replace the text. + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); + // Insert dropdown arrow. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.arrow_); + } + this.textElement_.setAttribute('text-anchor', 'start'); + this.textElement_.setAttribute('x', 0); + + this.size_.height = Blockly.BlockSvg.MIN_BLOCK_Y; + this.size_.width = Blockly.Field.getCachedWidth(this.textElement_); +}; + +/** + * Updates the width of the field. Overrides field.prototype.updateWidth to + * deal with image selections on IE and Edge. If the selected item is not an + * image, or if the browser is not IE / Edge, this simply calls the parent + * implementation. + */ +Blockly.FieldDropdown.prototype.updateWidth = function() { + if (this.imageJson_ && (goog.userAgent.IE || goog.userAgent.EDGE)) { + // Recalculate the full width. + var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); + var width = Number(this.imageJson_.width) + arrowWidth + Blockly.BlockSvg.SEP_SPACE_X; + if (this.borderRect_) { + this.borderRect_.setAttribute('width', width); + } + this.size_.width = width; + } else { + Blockly.Field.prototype.updateWidth.call(this); + } +}; + +/** + * Close the dropdown menu if this input is being deleted. + */ +Blockly.FieldDropdown.prototype.dispose = function() { + Blockly.WidgetDiv.hideIfOwner(this); + Blockly.FieldDropdown.superClass_.dispose.call(this); +}; diff --git a/core/.svn/pristine/dd/dd39be0d1fb04959137f222f34d452a29ef94abf.svn-base b/core/.svn/pristine/dd/dd39be0d1fb04959137f222f34d452a29ef94abf.svn-base new file mode 100644 index 0000000..98ebf41 --- /dev/null +++ b/core/.svn/pristine/dd/dd39be0d1fb04959137f222f34d452a29ef94abf.svn-base @@ -0,0 +1,318 @@ +/** + * @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 procedures. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.Procedures + * @namespace + **/ +goog.provide('Blockly.Procedures'); + +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.Field'); +goog.require('Blockly.Names'); +goog.require('Blockly.Workspace'); + + +/** + * Constant to separate procedure names from variables and generated functions + * when running generators. + * @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME + */ +Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME; + +/** + * Find all user-created procedure definitions in a workspace. + * @param {!Blockly.Workspace} root Root workspace. + * @return {!Array.>} Pair of arrays, the + * first contains procedures without return variables, the second with. + * Each procedure is defined by a three-element list of name, parameter + * list, and return value boolean. + */ +Blockly.Procedures.allProcedures = function(root) { + var blocks = root.getAllBlocks(); + var proceduresReturn = []; + var proceduresNoReturn = []; + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureDef) { + var tuple = blocks[i].getProcedureDef(); + if (tuple) { + if (tuple[2]) { + proceduresReturn.push(tuple); + } else { + proceduresNoReturn.push(tuple); + } + } + } + } + proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_); + proceduresReturn.sort(Blockly.Procedures.procTupleComparator_); + return [proceduresNoReturn, proceduresReturn]; +}; + +/** + * Comparison function for case-insensitive sorting of the first element of + * a tuple. + * @param {!Array} ta First tuple. + * @param {!Array} tb Second tuple. + * @return {number} -1, 0, or 1 to signify greater than, equality, or less than. + * @private + */ +Blockly.Procedures.procTupleComparator_ = function(ta, tb) { + return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase()); +}; + +/** + * Ensure two identically-named procedures don't exist. + * @param {string} name Proposed procedure name. + * @param {!Blockly.Block} block Block to disambiguate. + * @return {string} Non-colliding name. + */ +Blockly.Procedures.findLegalName = function(name, block) { + if (block.isInFlyout) { + // Flyouts can have multiple procedures called 'do something'. + return name; + } + while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) { + // Collision with another procedure. + var r = name.match(/^(.*?)(\d+)$/); + if (!r) { + name += '2'; + } else { + name = r[1] + (parseInt(r[2], 10) + 1); + } + } + return name; +}; + +/** + * Does this procedure have a legal name? Illegal names include names of + * procedures already defined. + * @param {string} name The questionable name. + * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. + * @param {Blockly.Block=} opt_exclude Optional block to exclude from + * comparisons (one doesn't want to collide with oneself). + * @return {boolean} True if the name is legal. + * @private + */ +Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) { + return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude); +}; + +/** + * Return if the given name is already a procedure name. + * @param {string} name The questionable name. + * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. + * @param {Blockly.Block=} opt_exclude Optional block to exclude from + * comparisons (one doesn't want to collide with oneself). + * @return {boolean} True if the name is used, otherwise return false. + */ +Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) { + var blocks = workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + if (blocks[i] == opt_exclude) { + continue; + } + if (blocks[i].getProcedureDef) { + var procName = blocks[i].getProcedureDef(); + if (Blockly.Names.equals(procName[0], name)) { + return true; + } + } + } + return false; +}; + +/** + * Rename a procedure. Called by the editable field. + * @param {string} name The proposed new name. + * @return {string} The accepted name. + * @this {Blockly.Field} + */ +Blockly.Procedures.rename = function(name) { + // Strip leading and trailing whitespace. Beyond this, all names are legal. + name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); + + // Ensure two identically-named procedures don't exist. + var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_); + var oldName = this.text_; + if (oldName != name && oldName != legalName) { + // Rename any callers. + var blocks = this.sourceBlock_.workspace.getAllBlocks(); + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].renameProcedure) { + blocks[i].renameProcedure(oldName, legalName); + } + } + } + return legalName; +}; + +/** + * Construct the blocks required by the flyout for the procedure category. + * @param {!Blockly.Workspace} workspace The workspace contianing procedures. + * @return {!Array.} Array of XML block elements. + */ +Blockly.Procedures.flyoutCategory = function(workspace) { + var xmlList = []; + if (Blockly.Blocks['procedures_defnoreturn']) { + // + // do something + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_defnoreturn'); + block.setAttribute('gap', 16); + var nameField = goog.dom.createDom('field', null, + Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE); + nameField.setAttribute('name', 'NAME'); + block.appendChild(nameField); + xmlList.push(block); + } + if (Blockly.Blocks['procedures_defreturn']) { + // + // do something + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_defreturn'); + block.setAttribute('gap', 16); + var nameField = goog.dom.createDom('field', null, + Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE); + nameField.setAttribute('name', 'NAME'); + block.appendChild(nameField); + xmlList.push(block); + } + if (Blockly.Blocks['procedures_ifreturn']) { + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', 'procedures_ifreturn'); + block.setAttribute('gap', 16); + xmlList.push(block); + } + if (xmlList.length) { + // Add slightly larger gap between system blocks and user calls. + xmlList[xmlList.length - 1].setAttribute('gap', 24); + } + + function populateProcedures(procedureList, templateName) { + for (var i = 0; i < procedureList.length; i++) { + var name = procedureList[i][0]; + var args = procedureList[i][1]; + // + // + // + // + // + var block = goog.dom.createDom('block'); + block.setAttribute('type', templateName); + block.setAttribute('gap', 16); + var mutation = goog.dom.createDom('mutation'); + mutation.setAttribute('name', name); + block.appendChild(mutation); + for (var j = 0; j < args.length; j++) { + var arg = goog.dom.createDom('arg'); + arg.setAttribute('name', args[j]); + mutation.appendChild(arg); + } + xmlList.push(block); + } + } + + var tuple = Blockly.Procedures.allProcedures(workspace); + populateProcedures(tuple[0], 'procedures_callnoreturn'); + populateProcedures(tuple[1], 'procedures_callreturn'); + return xmlList; +}; + +/** + * Find all the callers of a named procedure. + * @param {string} name Name of procedure. + * @param {!Blockly.Workspace} workspace The workspace to find callers in. + * @return {!Array.} Array of caller blocks. + */ +Blockly.Procedures.getCallers = function(name, workspace) { + var callers = []; + var blocks = workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureCall) { + var procName = blocks[i].getProcedureCall(); + // Procedure name may be null if the block is only half-built. + if (procName && Blockly.Names.equals(procName, name)) { + callers.push(blocks[i]); + } + } + } + return callers; +}; + +/** + * When a procedure definition changes its parameters, find and edit all its + * callers. + * @param {!Blockly.Block} defBlock Procedure definition block. + */ +Blockly.Procedures.mutateCallers = function(defBlock) { + var oldRecordUndo = Blockly.Events.recordUndo; + var name = defBlock.getProcedureDef()[0]; + var xmlElement = defBlock.mutationToDom(true); + var callers = Blockly.Procedures.getCallers(name, defBlock.workspace); + for (var i = 0, caller; caller = callers[i]; i++) { + var oldMutationDom = caller.mutationToDom(); + var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); + caller.domToMutation(xmlElement); + var newMutationDom = caller.mutationToDom(); + var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); + if (oldMutation != newMutation) { + // Fire a mutation on every caller block. But don't record this as an + // undo action since it is deterministically tied to the procedure's + // definition mutation. + Blockly.Events.recordUndo = false; + Blockly.Events.fire(new Blockly.Events.BlockChange( + caller, 'mutation', null, oldMutation, newMutation)); + Blockly.Events.recordUndo = oldRecordUndo; + } + } +}; + +/** + * Find the definition block for the named procedure. + * @param {string} name Name of procedure. + * @param {!Blockly.Workspace} workspace The workspace to search. + * @return {Blockly.Block} The procedure definition block, or null not found. + */ +Blockly.Procedures.getDefinition = function(name, workspace) { + // Assume that a procedure definition is a top block. + var blocks = workspace.getTopBlocks(false); + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].getProcedureDef) { + var tuple = blocks[i].getProcedureDef(); + if (tuple && Blockly.Names.equals(tuple[0], name)) { + return blocks[i]; + } + } + } + return null; +}; diff --git a/core/.svn/pristine/de/dee0f42b68e40c9accd56f04292cfac3e4e84c03.svn-base b/core/.svn/pristine/de/dee0f42b68e40c9accd56f04292cfac3e4e84c03.svn-base new file mode 100644 index 0000000..5c7495d --- /dev/null +++ b/core/.svn/pristine/de/dee0f42b68e40c9accd56f04292cfac3e4e84c03.svn-base @@ -0,0 +1,251 @@ +/** + * @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 Object representing an input (value, statement, or dummy). + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Input'); + +goog.require('Blockly.Connection'); +goog.require('Blockly.FieldLabel'); +goog.require('goog.asserts'); + + +/** + * Class for an input with an optional field. + * @param {number} type The type of the input. + * @param {string} name Language-neutral identifier which may used to find this + * input again. + * @param {!Blockly.Block} block The block containing this input. + * @param {Blockly.Connection} connection Optional connection for this input. + * @constructor + */ +Blockly.Input = function(type, name, block, connection) { + if (type != Blockly.DUMMY_INPUT && !name) { + throw 'Value inputs and statement inputs must have non-empty name.'; + } + /** @type {number} */ + this.type = type; + /** @type {string} */ + this.name = name; + /** + * @type {!Blockly.Block} + * @private + */ + this.sourceBlock_ = block; + /** @type {Blockly.Connection} */ + this.connection = connection; + /** @type {!Array.} */ + this.fieldRow = []; +}; + +/** + * Alignment of input's fields (left, right or centre). + * @type {number} + */ +Blockly.Input.prototype.align = Blockly.ALIGN_LEFT; + +/** + * Is the input visible? + * @type {boolean} + * @private + */ +Blockly.Input.prototype.visible_ = true; + +/** + * Add a field (or label from string), and all prefix and suffix fields, to the + * end of the input's field row. + * @param {string|!Blockly.Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this field again. Should be unique to the host block. + * @return {!Blockly.Input} The input being append to (to allow chaining). + */ +Blockly.Input.prototype.appendField = function(field, opt_name) { + this.insertFieldAt(this.fieldRow.length, field, opt_name); + return this; +}; + +/** + * Inserts a field (or label from string), and all prefix and suffix fields, at + * the location of the input's field row. + * @param {number} index The index at which to insert field. + * @param {string|!Blockly.Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this field again. Should be unique to the host block. + * @return {number} The index following the last inserted field. + */ +Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) { + if (index < 0 || index > this.fieldRow.length) { + throw new Error('index ' + index + ' out of bounds.'); + } + + // Empty string, Null or undefined generates no field, unless field is named. + if (!field && !opt_name) { + return index; + } + // Generate a FieldLabel when given a plain text field. + if (goog.isString(field)) { + field = new Blockly.FieldLabel(/** @type {string} */ (field)); + } + field.setSourceBlock(this.sourceBlock_); + if (this.sourceBlock_.rendered) { + field.init(); + } + field.name = opt_name; + + if (field.prefixField) { + // Add any prefix. + index = this.insertFieldAt(index, field.prefixField); + } + // Add the field to the field row. + this.fieldRow.splice(index, 0, field); + ++index; + if (field.suffixField) { + // Add any suffix. + index = this.insertFieldAt(index, field.suffixField); + } + + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + // Adding a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours_(); + } + return index; +}; + +/** + * Remove a field from this input. + * @param {string} name The name of the field. + * @throws {goog.asserts.AssertionError} if the field is not present. + */ +Blockly.Input.prototype.removeField = function(name) { + for (var i = 0, field; field = this.fieldRow[i]; i++) { + if (field.name === name) { + field.dispose(); + this.fieldRow.splice(i, 1); + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + // Removing a field will cause the block to change shape. + this.sourceBlock_.bumpNeighbours_(); + } + return; + } + } + goog.asserts.fail('Field "%s" not found.', name); +}; + +/** + * Gets whether this input is visible or not. + * @return {boolean} True if visible. + */ +Blockly.Input.prototype.isVisible = function() { + return this.visible_; +}; + +/** + * Sets whether this input is visible or not. + * Used to collapse/uncollapse a block. + * @param {boolean} visible True if visible. + * @return {!Array.} List of blocks to render. + */ +Blockly.Input.prototype.setVisible = function(visible) { + var renderList = []; + if (this.visible_ == visible) { + return renderList; + } + this.visible_ = visible; + + var display = visible ? 'block' : 'none'; + for (var y = 0, field; field = this.fieldRow[y]; y++) { + field.setVisible(visible); + } + if (this.connection) { + // Has a connection. + if (visible) { + renderList = this.connection.unhideAll(); + } else { + this.connection.hideAll(); + } + var child = this.connection.targetBlock(); + if (child) { + child.getSvgRoot().style.display = display; + if (!visible) { + child.rendered = false; + } + } + } + return renderList; +}; + +/** + * Change a connection's compatibility. + * @param {string|Array.|null} check Compatible value type or + * list of value types. Null if all types are compatible. + * @return {!Blockly.Input} The input being modified (to allow chaining). + */ +Blockly.Input.prototype.setCheck = function(check) { + if (!this.connection) { + throw 'This input does not have a connection.'; + } + this.connection.setCheck(check); + return this; +}; + +/** + * Change the alignment of the connection's field(s). + * @param {number} align One of Blockly.ALIGN_LEFT, ALIGN_CENTRE, ALIGN_RIGHT. + * In RTL mode directions are reversed, and ALIGN_RIGHT aligns to the left. + * @return {!Blockly.Input} The input being modified (to allow chaining). + */ +Blockly.Input.prototype.setAlign = function(align) { + this.align = align; + if (this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + } + return this; +}; + +/** + * Initialize the fields on this input. + */ +Blockly.Input.prototype.init = function() { + if (!this.sourceBlock_.workspace.rendered) { + return; // Headless blocks don't need fields initialized. + } + for (var i = 0; i < this.fieldRow.length; i++) { + this.fieldRow[i].init(); + } +}; + +/** + * Sever all links to this input. + */ +Blockly.Input.prototype.dispose = function() { + for (var i = 0, field; field = this.fieldRow[i]; i++) { + field.dispose(); + } + if (this.connection) { + this.connection.dispose(); + } + this.sourceBlock_ = null; +}; diff --git a/core/.svn/pristine/df/df58dd8f2794d472ddb296adb222c05d61041e42.svn-base b/core/.svn/pristine/df/df58dd8f2794d472ddb296adb222c05d61041e42.svn-base new file mode 100644 index 0000000..59384d0 --- /dev/null +++ b/core/.svn/pristine/df/df58dd8f2794d472ddb296adb222c05d61041e42.svn-base @@ -0,0 +1,329 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 Angle input field. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.FieldAngle'); + +goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); +goog.require('goog.userAgent'); + + +/** + * Class for an editable angle field. + * @param {(string|number)=} opt_value The initial content of the field. The + * value should cast to a number, and if it does not, '0' will be used. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns the accepted text or null to abort + * the change. + * @extends {Blockly.FieldTextInput} + * @constructor + */ +Blockly.FieldAngle = function(opt_value, opt_validator) { + // Add degree symbol: '360°' (LTR) or '°360' (RTL) + this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null); + this.symbol_.appendChild(document.createTextNode('\u00B0')); + + opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0'; + Blockly.FieldAngle.superClass_.constructor.call( + this, opt_value, opt_validator); +}; +goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); + +/** + * Construct a FieldAngle from a JSON arg object. + * @param {!Object} options A JSON object with options (angle). + * @returns {!Blockly.FieldAngle} The new field instance. + * @package + */ +Blockly.FieldAngle.fromJson = function(options) { + return new Blockly.FieldAngle(options['angle']); +}; + +/** + * Round angles to the nearest 15 degrees when using mouse. + * Set to 0 to disable rounding. + */ +Blockly.FieldAngle.ROUND = 15; + +/** + * Half the width of protractor image. + */ +Blockly.FieldAngle.HALF = 100 / 2; + +/* The following two settings work together to set the behaviour of the angle + * picker. While many combinations are possible, two modes are typical: + * Math mode. + * 0 deg is right, 90 is up. This is the style used by protractors. + * Blockly.FieldAngle.CLOCKWISE = false; + * Blockly.FieldAngle.OFFSET = 0; + * Compass mode. + * 0 deg is up, 90 is right. This is the style used by maps. + * Blockly.FieldAngle.CLOCKWISE = true; + * Blockly.FieldAngle.OFFSET = 90; + */ + +/** + * Angle increases clockwise (true) or counterclockwise (false). + */ +Blockly.FieldAngle.CLOCKWISE = false; + +/** + * Offset the location of 0 degrees (and all angles) by a constant. + * Usually either 0 (0 = right) or 90 (0 = up). + */ +Blockly.FieldAngle.OFFSET = 0; + +/** + * Maximum allowed angle before wrapping. + * Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180). + */ +Blockly.FieldAngle.WRAP = 360; + +/** + * Radius of protractor circle. Slightly smaller than protractor size since + * otherwise SVG crops off half the border at the edges. + */ +Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1; + +/** + * Adds degree symbol and recalculates width. + * Saves the computed width in a property. + * @private + */ +Blockly.FieldAngle.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } + + // Update textElement. + this.textElement_.textContent = this.getDisplayText_(); + + // Insert degree symbol. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.symbol_); + } + this.updateWidth(); +}; + +/** + * Clean up this FieldAngle, as well as the inherited FieldTextInput. + * @return {!Function} Closure to call on destruction of the WidgetDiv. + * @private + */ +Blockly.FieldAngle.prototype.dispose_ = function() { + var thisField = this; + return function() { + Blockly.FieldAngle.superClass_.dispose_.call(thisField)(); + thisField.gauge_ = null; + if (thisField.clickWrapper_) { + Blockly.unbindEvent_(thisField.clickWrapper_); + } + if (thisField.moveWrapper1_) { + Blockly.unbindEvent_(thisField.moveWrapper1_); + } + if (thisField.moveWrapper2_) { + Blockly.unbindEvent_(thisField.moveWrapper2_); + } + }; +}; + +/** + * Show the inline free-text editor on top of the text. + * @private + */ +Blockly.FieldAngle.prototype.showEditor_ = function() { + var noFocus = + goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD; + // Mobile browsers have issues with in-line textareas (focus & keyboards). + Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus); + var div = Blockly.WidgetDiv.DIV; + if (!div.firstChild) { + // Mobile interface uses Blockly.prompt. + return; + } + // Build the SVG DOM. + var svg = Blockly.utils.createSvgElement('svg', { + 'xmlns': 'http://www.w3.org/2000/svg', + 'xmlns:html': 'http://www.w3.org/1999/xhtml', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'height': (Blockly.FieldAngle.HALF * 2) + 'px', + 'width': (Blockly.FieldAngle.HALF * 2) + 'px' + }, div); + var circle = Blockly.utils.createSvgElement('circle', { + 'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF, + 'r': Blockly.FieldAngle.RADIUS, + 'class': 'blocklyAngleCircle' + }, svg); + this.gauge_ = Blockly.utils.createSvgElement('path', + {'class': 'blocklyAngleGauge'}, svg); + this.line_ = Blockly.utils.createSvgElement('line', { + 'x1': Blockly.FieldAngle.HALF, + 'y1': Blockly.FieldAngle.HALF, + 'class': 'blocklyAngleLine' + }, svg); + // Draw markers around the edge. + for (var angle = 0; angle < 360; angle += 15) { + Blockly.utils.createSvgElement('line', { + 'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS, + 'y1': Blockly.FieldAngle.HALF, + 'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS - + (angle % 45 == 0 ? 10 : 5), + 'y2': Blockly.FieldAngle.HALF, + 'class': 'blocklyAngleMarks', + 'transform': 'rotate(' + angle + ',' + + Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')' + }, svg); + } + svg.style.marginLeft = (15 - Blockly.FieldAngle.RADIUS) + 'px'; + + // The angle picker is different from other fields in that it updates on + // mousemove even if it's not in the middle of a drag. In future we may + // change this behavior. For now, using bindEvent_ instead of + // bindEventWithChecks_ allows it to work without a mousedown/touchstart. + this.clickWrapper_ = + Blockly.bindEvent_(svg, 'click', this, Blockly.WidgetDiv.hide); + this.moveWrapper1_ = + Blockly.bindEvent_(circle, 'mousemove', this, this.onMouseMove); + this.moveWrapper2_ = + Blockly.bindEvent_(this.gauge_, 'mousemove', this, this.onMouseMove); + this.updateGraph_(); +}; + +/** + * Set the angle to match the mouse's position. + * @param {!Event} e Mouse move event. + */ +Blockly.FieldAngle.prototype.onMouseMove = function(e) { + var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect(); + var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF; + var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF; + var angle = Math.atan(-dy / dx); + if (isNaN(angle)) { + // This shouldn't happen, but let's not let this error propagate further. + return; + } + angle = goog.math.toDegrees(angle); + // 0: East, 90: North, 180: West, 270: South. + if (dx < 0) { + angle += 180; + } else if (dy > 0) { + angle += 360; + } + if (Blockly.FieldAngle.CLOCKWISE) { + angle = Blockly.FieldAngle.OFFSET + 360 - angle; + } else { + angle -= Blockly.FieldAngle.OFFSET; + } + if (Blockly.FieldAngle.ROUND) { + angle = Math.round(angle / Blockly.FieldAngle.ROUND) * + Blockly.FieldAngle.ROUND; + } + angle = this.callValidator(angle); + Blockly.FieldTextInput.htmlInput_.value = angle; + this.setValue(angle); + this.validate_(); + this.resizeEditor_(); +}; + +/** + * Insert a degree symbol. + * @param {?string} text New text. + */ +Blockly.FieldAngle.prototype.setText = function(text) { + Blockly.FieldAngle.superClass_.setText.call(this, text); + if (!this.textElement_) { + // Not rendered yet. + return; + } + this.updateGraph_(); + // Cached width is obsolete. Clear it. + this.size_.width = 0; +}; + +/** + * Redraw the graph with the current angle. + * @private + */ +Blockly.FieldAngle.prototype.updateGraph_ = function() { + if (!this.gauge_) { + return; + } + var angleDegrees = Number(this.getText()) + Blockly.FieldAngle.OFFSET; + var angleRadians = goog.math.toRadians(angleDegrees); + var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF]; + var x2 = Blockly.FieldAngle.HALF; + var y2 = Blockly.FieldAngle.HALF; + if (!isNaN(angleRadians)) { + var angle1 = goog.math.toRadians(Blockly.FieldAngle.OFFSET); + var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS; + var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS; + if (Blockly.FieldAngle.CLOCKWISE) { + angleRadians = 2 * angle1 - angleRadians; + } + x2 += Math.cos(angleRadians) * Blockly.FieldAngle.RADIUS; + y2 -= Math.sin(angleRadians) * Blockly.FieldAngle.RADIUS; + // Don't ask how the flag calculations work. They just do. + var largeFlag = Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2); + if (Blockly.FieldAngle.CLOCKWISE) { + largeFlag = 1 - largeFlag; + } + var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE); + path.push(' l ', x1, ',', y1, + ' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS, + ' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z'); + } + this.gauge_.setAttribute('d', path.join('')); + this.line_.setAttribute('x2', x2); + this.line_.setAttribute('y2', y2); +}; + +/** + * Ensure that only an angle may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid angle, or null if invalid. + */ +Blockly.FieldAngle.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + var n = parseFloat(text || 0); + if (isNaN(n)) { + return null; + } + n = n % 360; + if (n < 0) { + n += 360; + } + if (n > Blockly.FieldAngle.WRAP) { + n -= 360; + } + return String(n); +}; diff --git a/core/.svn/pristine/df/dfd0e86992c05eb75d6a785f8bfb3ecc64685fb1.svn-base b/core/.svn/pristine/df/dfd0e86992c05eb75d6a785f8bfb3ecc64685fb1.svn-base new file mode 100644 index 0000000..66f457a --- /dev/null +++ b/core/.svn/pristine/df/dfd0e86992c05eb75d6a785f8bfb3ecc64685fb1.svn-base @@ -0,0 +1,562 @@ +/** + * @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 Field. Used for editable titles, variables, etc. + * This is an abstract class that defines the UI on the block. Actual + * instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Field'); + +goog.require('Blockly.Gesture'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.math.Size'); +goog.require('goog.style'); +goog.require('goog.userAgent'); + + +/** + * Abstract class for an editable field. + * @param {string} text The initial content of the field. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @constructor + */ +Blockly.Field = function(text, opt_validator) { + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); + this.setValue(text); + this.setValidator(opt_validator); +}; + +/** + * Temporary cache of text widths. + * @type {Object} + * @private + */ +Blockly.Field.cacheWidths_ = null; + +/** + * Number of current references to cache. + * @type {number} + * @private + */ +Blockly.Field.cacheReference_ = 0; + + +/** + * Name of field. Unique within each block. + * Static labels are usually unnamed. + * @type {string|undefined} + */ +Blockly.Field.prototype.name = undefined; + +/** + * Maximum characters of text to display before adding an ellipsis. + * @type {number} + */ +Blockly.Field.prototype.maxDisplayLength = 50; + +/** + * Visible text to display. + * @type {string} + * @private + */ +Blockly.Field.prototype.text_ = ''; + +/** + * Block this field is attached to. Starts as null, then in set in init. + * @type {Blockly.Block} + * @private + */ +Blockly.Field.prototype.sourceBlock_ = null; + +/** + * Is the field visible, or hidden due to the block being collapsed? + * @type {boolean} + * @private + */ +Blockly.Field.prototype.visible_ = true; + +/** + * Validation function called when user edits an editable field. + * @type {Function} + * @private + */ +Blockly.Field.prototype.validator_ = null; + +/** + * Non-breaking space. + * @const + */ +Blockly.Field.NBSP = '\u00A0'; + +/** + * Editable fields are saved by the XML renderer, non-editable fields are not. + */ +Blockly.Field.prototype.EDITABLE = true; + +/** + * Attach this field to a block. + * @param {!Blockly.Block} block The block containing this field. + */ +Blockly.Field.prototype.setSourceBlock = function(block) { + goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.'); + this.sourceBlock_ = block; +}; + +/** + * Install this field on a block. + */ +Blockly.Field.prototype.init = function() { + if (this.fieldGroup_) { + // Field has already been initialized once. + return; + } + // Build the DOM. + this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null); + if (!this.visible_) { + this.fieldGroup_.style.display = 'none'; + } + this.borderRect_ = Blockly.utils.createSvgElement('rect', + { + 'rx': 4, + 'ry': 4, + 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, + 'y': 0, + 'height': 16 + }, this.fieldGroup_); + /** @type {!Element} */ + this.textElement_ = Blockly.utils.createSvgElement('text', + {'class': 'blocklyText', 'y': this.size_.height - 12.5}, + this.fieldGroup_); + + this.updateEditable(); + this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + this.mouseDownWrapper_ = + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); + // Force a render. + this.render_(); +}; + +/** + * Initializes the model of the field after it has been installed on a block. + * No-op by default. + */ +Blockly.Field.prototype.initModel = function() { +}; + +/** + * Dispose of all DOM objects belonging to this editable field. + */ +Blockly.Field.prototype.dispose = function() { + if (this.mouseDownWrapper_) { + Blockly.unbindEvent_(this.mouseDownWrapper_); + this.mouseDownWrapper_ = null; + } + this.sourceBlock_ = null; + goog.dom.removeNode(this.fieldGroup_); + this.fieldGroup_ = null; + this.textElement_ = null; + this.borderRect_ = null; + this.validator_ = null; +}; + +/** + * Add or remove the UI indicating if this field is editable or not. + */ +Blockly.Field.prototype.updateEditable = function() { + var group = this.fieldGroup_; + if (!this.EDITABLE || !group) { + return; + } + if (this.sourceBlock_.isEditable()) { + Blockly.utils.addClass(group, 'blocklyEditableText'); + Blockly.utils.removeClass(group, 'blocklyNonEditableText'); + this.fieldGroup_.style.cursor = this.CURSOR; + } else { + Blockly.utils.addClass(group, 'blocklyNonEditableText'); + Blockly.utils.removeClass(group, 'blocklyEditableText'); + this.fieldGroup_.style.cursor = ''; + } +}; + +/** + * Check whether this field is currently editable. Some fields are never + * editable (e.g. text labels). Those fields are not serialized to XML. Other + * fields may be editable, and therefore serialized, but may exist on + * non-editable blocks. + * @return {boolean} whether this field is editable and on an editable block + */ +Blockly.Field.prototype.isCurrentlyEditable = function() { + return this.EDITABLE && !!this.sourceBlock_ && this.sourceBlock_.isEditable(); +}; + +/** + * Gets whether this editable field is visible or not. + * @return {boolean} True if visible. + */ +Blockly.Field.prototype.isVisible = function() { + return this.visible_; +}; + +/** + * Sets whether this editable field is visible or not. + * @param {boolean} visible True if visible. + */ +Blockly.Field.prototype.setVisible = function(visible) { + if (this.visible_ == visible) { + return; + } + this.visible_ = visible; + var root = this.getSvgRoot(); + if (root) { + root.style.display = visible ? 'block' : 'none'; + this.render_(); + } +}; + +/** + * Sets a new validation function for editable fields. + * @param {Function} handler New validation function, or null. + */ +Blockly.Field.prototype.setValidator = function(handler) { + this.validator_ = handler; +}; + +/** + * Gets the validation function for editable fields. + * @return {Function} Validation function, or null. + */ +Blockly.Field.prototype.getValidator = function() { + return this.validator_; +}; + +/** + * Validates a change. Does nothing. Subclasses may override this. + * @param {string} text The user's text. + * @return {string} No change needed. + */ +Blockly.Field.prototype.classValidator = function(text) { + return text; +}; + +/** + * Calls the validation function for this field, as well as all the validation + * function for the field's class and its parents. + * @param {string} text Proposed text. + * @return {?string} Revised text, or null if invalid. + */ +Blockly.Field.prototype.callValidator = function(text) { + var classResult = this.classValidator(text); + if (classResult === null) { + // Class validator rejects value. Game over. + return null; + } else if (classResult !== undefined) { + text = classResult; + } + var userValidator = this.getValidator(); + if (userValidator) { + var userResult = userValidator.call(this, text); + if (userResult === null) { + // User validator rejects value. Game over. + return null; + } else if (userResult !== undefined) { + text = userResult; + } + } + return text; +}; + +/** + * Gets the group element for this editable field. + * Used for measuring the size and for positioning. + * @return {!Element} The group element. + */ +Blockly.Field.prototype.getSvgRoot = function() { + return /** @type {!Element} */ (this.fieldGroup_); +}; + +/** + * Draws the border with the correct width. + * Saves the computed width in a property. + * @private + */ +Blockly.Field.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } + + // Replace the text. + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); + + this.updateWidth(); +}; + +/** + * Updates thw width of the field. This calls getCachedWidth which won't cache + * the approximated width on IE/Edge when `getComputedTextLength` fails. Once + * it eventually does succeed, the result will be cached. + **/ +Blockly.Field.prototype.updateWidth = function() { + var width = Blockly.Field.getCachedWidth(this.textElement_); + if (this.borderRect_) { + this.borderRect_.setAttribute('width', + width + Blockly.BlockSvg.SEP_SPACE_X); + } + this.size_.width = width; +}; + +/** + * Gets the width of a text element, caching it in the process. + * @param {!Element} textElement An SVG 'text' element. + * @return {number} Width of element. + */ +Blockly.Field.getCachedWidth = function(textElement) { + var key = textElement.textContent + '\n' + textElement.className.baseVal; + var width; + + // Return the cached width if it exists. + if (Blockly.Field.cacheWidths_) { + width = Blockly.Field.cacheWidths_[key]; + if (width) { + return width; + } + } + + // Attempt to compute fetch the width of the SVG text element. + try { + if (goog.userAgent.IE || goog.userAgent.EDGE) { + width = textElement.getBBox().width; + } else { + width = textElement.getComputedTextLength(); + } + } catch (e) { + // In other cases where we fail to geth the computed text. Instead, use an + // approximation and do not cache the result. At some later point in time + // when the block is inserted into the visible DOM, this method will be + // called again and, at that point in time, will not throw an exception. + return textElement.textContent.length * 8; + } + + // Cache the computed width and return. + if (Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_[key] = width; + } + return width; +}; + +/** + * Start caching field widths. Every call to this function MUST also call + * stopCache. Caches must not survive between execution threads. + */ +Blockly.Field.startCache = function() { + Blockly.Field.cacheReference_++; + if (!Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_ = {}; + } +}; + +/** + * Stop caching field widths. Unless caching was already on when the + * corresponding call to startCache was made. + */ +Blockly.Field.stopCache = function() { + Blockly.Field.cacheReference_--; + if (!Blockly.Field.cacheReference_) { + Blockly.Field.cacheWidths_ = null; + } +}; + +/** + * Returns the height and width of the field. + * @return {!goog.math.Size} Height and width. + */ +Blockly.Field.prototype.getSize = function() { + if (!this.size_.width) { + this.render_(); + } + return this.size_; +}; + +/** + * Returns the bounding box of the rendered field, accounting for workspace + * scaling. + * @return {!Object} An object with top, bottom, left, and right in pixels + * relative to the top left corner of the page (window coordinates). + * @private + */ +Blockly.Field.prototype.getScaledBBox_ = function() { + var bBox = this.borderRect_.getBBox(); + var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale; + var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale; + var xy = this.getAbsoluteXY_(); + return { + top: xy.y, + bottom: xy.y + scaledHeight, + left: xy.x, + right: xy.x + scaledWidth + }; +}; + +/** + * Get the text from this field as displayed on screen. May differ from getText + * due to ellipsis, and other formatting. + * @return {string} Currently displayed text. + * @private + */ +Blockly.Field.prototype.getDisplayText_ = function() { + var text = this.text_; + if (!text) { + // Prevent the field from disappearing if empty. + return Blockly.Field.NBSP; + } + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; + } + // Replace whitespace with non-breaking spaces so the text doesn't collapse. + text = text.replace(/\s/g, Blockly.Field.NBSP); + if (this.sourceBlock_.RTL) { + // The SVG is LTR, force text to be RTL. + text += '\u200F'; + } + return text; +}; + +/** + * Get the text from this field. + * @return {string} Current text. + */ +Blockly.Field.prototype.getText = function() { + return this.text_; +}; + +/** + * Set the text in this field. Trigger a rerender of the source block. + * @param {*} newText New text. + */ +Blockly.Field.prototype.setText = function(newText) { + if (newText === null) { + // No change if null. + return; + } + newText = String(newText); + if (newText === this.text_) { + // No change. + return; + } + this.text_ = newText; + this.forceRerender(); +}; + +/** + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package + */ +Blockly.Field.prototype.forceRerender = function() { + // Set width to 0 to force a rerender of this field. + this.size_.width = 0; + + if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + this.sourceBlock_.bumpNeighbours_(); + } +}; + +/** + * By default there is no difference between the human-readable text and + * the language-neutral values. Subclasses (such as dropdown) may define this. + * @return {string} Current value. + */ +Blockly.Field.prototype.getValue = function() { + return this.getText(); +}; + +/** + * By default there is no difference between the human-readable text and + * the language-neutral values. Subclasses (such as dropdown) may define this. + * @param {string} newValue New value. + */ +Blockly.Field.prototype.setValue = function(newValue) { + if (newValue === null) { + // No change if null. + return; + } + var oldValue = this.getValue(); + if (oldValue == newValue) { + return; + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, newValue)); + } + this.setText(newValue); +}; + +/** + * Handle a mouse down event on a field. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Field.prototype.onMouseDown_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + if (!this.sourceBlock_ || !this.sourceBlock_.workspace) { + return; + } + var gesture = this.sourceBlock_.workspace.getGesture(e); + if (gesture) { + gesture.setStartField(this); + } +}; + +/** + * Change the tooltip text for this field. + * @param {string|!Element} newTip Text for tooltip or a parent element to + * link to for its tooltip. + */ +Blockly.Field.prototype.setTooltip = function( + /* eslint-disable no-unused-vars */ newTip + /* eslint-enable no-unused-vars */) { + // Non-abstract sub-classes may wish to implement this. See FieldLabel. +}; + +/** + * Return the absolute coordinates of the top-left corner of this field. + * The origin (0,0) is the top-left corner of the page body. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.Field.prototype.getAbsoluteXY_ = function() { + return goog.style.getPageOffset(this.borderRect_); +}; diff --git a/core/.svn/pristine/e0/e05056ae06c8ef16339ed68d6409fad0f092b79e.svn-base b/core/.svn/pristine/e0/e05056ae06c8ef16339ed68d6409fad0f092b79e.svn-base new file mode 100644 index 0000000..8fda602 --- /dev/null +++ b/core/.svn/pristine/e0/e05056ae06c8ef16339ed68d6409fad0f092b79e.svn-base @@ -0,0 +1,68 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 methods for working with the closure menu (goog.ui.menu). + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +/** + * @name Blockly.utils.uiMenu + * @namespace + **/ +goog.provide('Blockly.utils.uiMenu'); + + +/** + * Get the size of a rendered goog.ui.Menu. + * @param {!goog.ui.Menu} menu The menu to measure. + * @return {!goog.math.Size} Object with width and height properties. + * @package + */ +Blockly.utils.uiMenu.getSize = function(menu) { + var menuDom = menu.getElement(); + var menuSize = goog.style.getSize(menuDom); + // Recalculate height for the total content, not only box height. + menuSize.height = menuDom.scrollHeight; + return menuSize; +}; + +/** + * Adjust the bounding boxes used to position the widget div to deal with RTL + * goog.ui.Menu positioning. In RTL mode the menu renders down and to the left + * of its start point, instead of down and to the right. Adjusting all of the + * bounding boxes accordingly allows us to use the same code for all widgets. + * This function in-place modifies the provided bounding boxes. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {!goog.math.Size} menuSize The size of the menu that is inside the + * widget div, in window coordinates. + * @package + */ +Blockly.utils.uiMenu.adjustBBoxesForRTL = function(viewportBBox, anchorBBox, + menuSize) { + anchorBBox.left += menuSize.width; + anchorBBox.right += menuSize.width; + viewportBBox.left += menuSize.width; + viewportBBox.right += menuSize.width; +}; diff --git a/core/.svn/pristine/e1/e1ffbebde28d614261a80f52f59cfa7dfd8590da.svn-base b/core/.svn/pristine/e1/e1ffbebde28d614261a80f52f59cfa7dfd8590da.svn-base new file mode 100644 index 0000000..ca90d4d --- /dev/null +++ b/core/.svn/pristine/e1/e1ffbebde28d614261a80f52f59cfa7dfd8590da.svn-base @@ -0,0 +1,116 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Number input field + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FieldNumber'); + +goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); + + +/** + * Class for an editable number field. + * @param {(string|number)=} opt_value The initial content of the field. The value + * should cast to a number, and if it does not, '0' will be used. + * @param {(string|number)=} opt_min Minimum value. + * @param {(string|number)=} opt_max Maximum value. + * @param {(string|number)=} opt_precision Precision for value. + * @param {Function=} opt_validator An optional function that is called + * to validate any constraints on what the user entered. Takes the new + * text as an argument and returns either the accepted text, a replacement + * text, or null to abort the change. + * @extends {Blockly.FieldTextInput} + * @constructor + */ +Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision, + opt_validator) { + opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0'; + Blockly.FieldNumber.superClass_.constructor.call( + this, opt_value, opt_validator); + this.setConstraints(opt_min, opt_max, opt_precision); +}; +goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); + +/** + * Construct a FieldNumber from a JSON arg object. + * @param {!Object} options A JSON object with options (value, min, max, and + * precision). + * @returns {!Blockly.FieldNumber} The new field instance. + * @package + */ +Blockly.FieldNumber.fromJson = function(options) { + return new Blockly.FieldNumber(options['value'], + options['min'], options['max'], options['precision']); +}; + +/** + * Set the maximum, minimum and precision constraints on this field. + * Any of these properties may be undefiend or NaN to be disabled. + * Setting precision (usually a power of 10) enforces a minimum step between + * values. That is, the user's value will rounded to the closest multiple of + * precision. The least significant digit place is inferred from the precision. + * Integers values can be enforces by choosing an integer precision. + * @param {number|string|undefined} min Minimum value. + * @param {number|string|undefined} max Maximum value. + * @param {number|string|undefined} precision Precision for value. + */ +Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { + precision = parseFloat(precision); + this.precision_ = isNaN(precision) ? 0 : precision; + min = parseFloat(min); + this.min_ = isNaN(min) ? -Infinity : min; + max = parseFloat(max); + this.max_ = isNaN(max) ? Infinity : max; + this.setValue(this.callValidator(this.getValue())); +}; + +/** + * Ensure that only a number in the correct range may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldNumber.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + if (isNaN(n)) { + // Invalid number. + return null; + } + // Round to nearest multiple of precision. + if (this.precision_ && isFinite(n)) { + n = Math.round(n / this.precision_) * this.precision_; + } + // Get the value in range. + n = goog.math.clamp(n, this.min_, this.max_); + return String(n); +}; diff --git a/core/.svn/pristine/e9/e924d79ec0da448471d4f9b169880112329d8b0f.svn-base b/core/.svn/pristine/e9/e924d79ec0da448471d4f9b169880112329d8b0f.svn-base new file mode 100644 index 0000000..016fc4a --- /dev/null +++ b/core/.svn/pristine/e9/e924d79ec0da448471d4f9b169880112329d8b0f.svn-base @@ -0,0 +1,449 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Extensions are functions that help initialize blocks, usually + * adding dynamic behavior such as onchange handlers and mutators. These + * are applied using Block.applyExtension(), or the JSON "extensions" + * array attribute. + * @author Anm@anm.me (Andrew n marshall) + */ +'use strict'; + +/** + * @name Blockly.Extensions + * @namespace + **/ +goog.provide('Blockly.Extensions'); + +goog.require('Blockly.Mutator'); +goog.require('Blockly.utils'); +goog.require('goog.string'); + +/** + * The set of all registered extensions, keyed by extension name/id. + * @private + */ +Blockly.Extensions.ALL_ = {}; + +/** + * Registers a new extension function. Extensions are functions that help + * initialize blocks, usually adding dynamic behavior such as onchange + * handlers and mutators. These are applied using Block.applyExtension(), or + * the JSON "extensions" array attribute. + * @param {string} name The name of this extension. + * @param {Function} initFn The function to initialize an extended block. + * @throws {Error} if the extension name is empty, the extension is already + * registered, or extensionFn is not a function. + */ +Blockly.Extensions.register = function(name, initFn) { + if (!goog.isString(name) || goog.string.isEmptyOrWhitespace(name)) { + throw new Error('Error: Invalid extension name "' + name + '"'); + } + if (Blockly.Extensions.ALL_[name]) { + throw new Error('Error: Extension "' + name + '" is already registered.'); + } + if (!goog.isFunction(initFn)) { + throw new Error('Error: Extension "' + name + '" must be a function'); + } + Blockly.Extensions.ALL_[name] = initFn; +}; + +/** + * Registers a new extension function that adds all key/value of mixinObj. + * @param {string} name The name of this extension. + * @param {!Object} mixinObj The values to mix in. + * @throws {Error} if the extension name is empty or the extension is already + * registered. + */ +Blockly.Extensions.registerMixin = function(name, mixinObj) { + if (!goog.isObject(mixinObj)){ + throw new Error('Error: Mixin "' + name + '" must be a object'); + } + Blockly.Extensions.register(name, function() { + this.mixin(mixinObj); + }); +}; + +/** + * Registers a new extension function that adds a mutator to the block. + * At register time this performs some basic sanity checks on the mutator. + * The wrapper may also add a mutator dialog to the block, if both compose and + * decompose are defined on the mixin. + * @param {string} name The name of this mutator extension. + * @param {!Object} mixinObj The values to mix in. + * @param {(function())=} opt_helperFn An optional function to apply after + * mixing in the object. + * @param {Array.=} opt_blockList A list of blocks to appear in the + * flyout of the mutator dialog. + * @throws {Error} if the mutation is invalid or can't be applied to the block. + */ +Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn, + opt_blockList) { + var errorPrefix = 'Error when registering mutator "' + name + '": '; + + // Sanity check the mixin object before registering it. + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.domToMutation, 'domToMutation'); + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.mutationToDom, 'mutationToDom'); + + var hasMutatorDialog = + Blockly.Extensions.checkMutatorDialog_(mixinObj, errorPrefix); + + if (opt_helperFn && !goog.isFunction(opt_helperFn)) { + throw new Error('Extension "' + name + '" is not a function'); + } + + // Sanity checks passed. + Blockly.Extensions.register(name, function() { + if (hasMutatorDialog) { + this.setMutator(new Blockly.Mutator(opt_blockList)); + } + // Mixin the object. + this.mixin(mixinObj); + + if (opt_helperFn) { + opt_helperFn.apply(this); + } + }); +}; + +/** + * Applies an extension method to a block. This should only be called during + * block construction. + * @param {string} name The name of the extension. + * @param {!Blockly.Block} block The block to apply the named extension to. + * @param {boolean} isMutator True if this extension defines a mutator. + * @throws {Error} if the extension is not found. + */ +Blockly.Extensions.apply = function(name, block, isMutator) { + var extensionFn = Blockly.Extensions.ALL_[name]; + if (!goog.isFunction(extensionFn)) { + throw new Error('Error: Extension "' + name + '" not found.'); + } + if (isMutator) { + // Fail early if the block already has mutation properties. + Blockly.Extensions.checkNoMutatorProperties_(name, block); + } else { + // Record the old properties so we can make sure they don't change after + // applying the extension. + var mutatorProperties = Blockly.Extensions.getMutatorProperties_(block); + } + extensionFn.apply(block); + + if (isMutator) { + var errorPrefix = 'Error after applying mutator "' + name + '": '; + Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block); + } else { + if (!Blockly.Extensions.mutatorPropertiesMatch_(mutatorProperties, block)) { + throw new Error('Error when applying extension "' + name + '": ' + + 'mutation properties changed when applying a non-mutator extension.'); + } + } +}; + +/** + * Check that the given value is a function. + * @param {string} errorPrefix The string to prepend to any error message. + * @param {*} func Function to check. + * @param {string} propertyName Which property to check. + * @throws {Error} if the property does not exist or is not a function. + * @private + */ +Blockly.Extensions.checkHasFunction_ = function(errorPrefix, func, + propertyName) { + if (!func) { + throw new Error(errorPrefix + + 'missing required property "' + propertyName + '"'); + } else if (typeof func != 'function') { + throw new Error(errorPrefix + + '" required property "' + propertyName + '" must be a function'); + } +}; + +/** + * Check that the given block does not have any of the four mutator properties + * defined on it. This function should be called before applying a mutator + * extension to a block, to make sure we are not overwriting properties. + * @param {string} mutationName The name of the mutation to reference in error + * messages. + * @param {!Blockly.Block} block The block to check. + * @throws {Error} if any of the properties already exist on the block. + * @private + */ +Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) { + var properties = Blockly.Extensions.getMutatorProperties_(block); + if (properties.length) { + throw new Error('Error: tried to apply mutation "' + mutationName + + '" to a block that already has mutator functions.' + + ' Block id: ' + block.id); + } +}; + +/** + * Check that the given object has both or neither of the functions required + * to have a mutator dialog. + * These functions are 'compose' and 'decompose'. If a block has one, it must + * have both. + * @param {!Object} object The object to check. + * @param {string} errorPrefix The string to prepend to any error message. + * @return {boolean} True if the object has both functions. False if it has + * neither function. + * @throws {Error} if the object has only one of the functions. + * @private + */ +Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) { + var hasCompose = object.compose !== undefined; + var hasDecompose = object.decompose !== undefined; + + if (hasCompose && hasDecompose) { + if (typeof object.compose != 'function') { + throw new Error(errorPrefix + 'compose must be a function.'); + } else if (typeof object.decompose != 'function') { + throw new Error(errorPrefix + 'decompose must be a function.'); + } + return true; + } else if (!hasCompose && !hasDecompose) { + return false; + } else { + throw new Error(errorPrefix + + 'Must have both or neither of "compose" and "decompose"'); + } +}; + +/** + * Check that a block has required mutator properties. This should be called + * after applying a mutation extension. + * @param {string} errorPrefix The string to prepend to any error message. + * @param {!Blockly.Block} block The block to inspect. + * @private + */ +Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix, + block) { + if (typeof block.domToMutation != 'function') { + throw new Error(errorPrefix + + 'Applying a mutator didn\'t add "domToMutation"'); + } + if (typeof block.mutationToDom != 'function') { + throw new Error(errorPrefix + + 'Applying a mutator didn\'t add "mutationToDom"'); + } + + // A block with a mutator isn't required to have a mutation dialog, but + // it should still have both or neither of compose and decompose. + Blockly.Extensions.checkMutatorDialog_(block, errorPrefix); +}; + +/** + * Get a list of values of mutator properties on the given block. + * @param {!Blockly.Block} block The block to inspect. + * @return {!Array.} a list with all of the defined properties, which + * should be functions, but may be anything other than undefined. + * @private + */ +Blockly.Extensions.getMutatorProperties_ = function(block) { + var result = []; + // List each function explicitly by reference to allow for renaming + // during compilation. + if (block.domToMutation !== undefined) { + result.push(block.domToMutation); + } + if (block.mutationToDom !== undefined) { + result.push(block.mutationToDom); + } + if (block.compose !== undefined) { + result.push(block.compose); + } + if (block.decompose !== undefined) { + result.push(block.decompose); + } + return result; +}; + +/** + * Check that the current mutator properties match a list of old mutator + * properties. This should be called after applying a non-mutator extension, + * to verify that the extension didn't change properties it shouldn't. + * @param {!Array.} oldProperties The old values to compare to. + * @param {!Blockly.Block} block The block to inspect for new values. + * @return {boolean} True if the property lists match. + * @private + */ +Blockly.Extensions.mutatorPropertiesMatch_ = function(oldProperties, block) { + var newProperties = Blockly.Extensions.getMutatorProperties_(block); + if (newProperties.length != oldProperties.length) { + return false; + } + for (var i = 0; i < newProperties.length; i++) { + if (oldProperties[i] != newProperties[i]) { + return false; + } + } + return true; +}; + +/** + * Builds an extension function that will map a dropdown value to a tooltip + * string. + * + * This method includes multiple checks to ensure tooltips, dropdown options, + * and message references are aligned. This aims to catch errors as early as + * possible, without requiring developers to manually test tooltips under each + * option. After the page is loaded, each tooltip text string will be checked + * for matching message keys in the internationalized string table. Deferring + * this until the page is loaded decouples loading dependencies. Later, upon + * loading the first block of any given type, the extension will validate every + * dropdown option has a matching tooltip in the lookupTable. Errors are + * reported as warnings in the console, and are never fatal. + * @param {string} dropdownName The name of the field whose value is the key + * to the lookup table. + * @param {!Object.} lookupTable The table of field values to + * tooltip text. + * @return {Function} The extension function. + */ +Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, + lookupTable) { + // List of block types already validated, to minimize duplicate warnings. + var blockTypesChecked = []; + + // Check the tooltip string messages for invalid references. + // Wait for load, in case Blockly.Msg is not yet populated. + // runAfterPageLoad() does not run in a Node.js environment due to lack of + // document object, in which case skip the validation. + if (typeof document == 'object') { // Relies on document.readyState + Blockly.utils.runAfterPageLoad(function() { + for (var key in lookupTable) { + // Will print warnings if reference is missing. + Blockly.utils.checkMessageReferences(lookupTable[key]); + } + }); + } + + /** + * The actual extension. + * @this {Blockly.Block} + */ + var extensionFn = function() { + if (this.type && blockTypesChecked.indexOf(this.type) === -1) { + Blockly.Extensions.checkDropdownOptionsInTable_( + this, dropdownName, lookupTable); + blockTypesChecked.push(this.type); + } + + this.setTooltip(function() { + var value = this.getFieldValue(dropdownName); + var tooltip = lookupTable[value]; + if (tooltip == null) { + if (blockTypesChecked.indexOf(this.type) === -1) { + // Warn for missing values on generated tooltips. + var warning = 'No tooltip mapping for value ' + value + + ' of field ' + dropdownName; + if (this.type != null) { + warning += (' of block type ' + this.type); + } + console.warn(warning + '.'); + } + } else { + tooltip = Blockly.utils.replaceMessageReferences(tooltip); + } + return tooltip; + }.bind(this)); + }; + return extensionFn; +}; + +/** + * Checks all options keys are present in the provided string lookup table. + * Emits console warnings when they are not. + * @param {!Blockly.Block} block The block containing the dropdown + * @param {string} dropdownName The name of the dropdown + * @param {!Object.} lookupTable The string lookup table + * @private + */ +Blockly.Extensions.checkDropdownOptionsInTable_ = function(block, dropdownName, + lookupTable) { + // Validate all dropdown options have values. + var dropdown = block.getField(dropdownName); + if (!dropdown.isOptionListDynamic()) { + var options = dropdown.getOptions(); + for (var i = 0; i < options.length; ++i) { + var optionKey = options[i][1]; // label, then value + if (lookupTable[optionKey] == null) { + console.warn('No tooltip mapping for value ' + optionKey + + ' of field ' + dropdownName + ' of block type ' + block.type); + } + } + } +}; + +/** + * Builds an extension function that will install a dynamic tooltip. The + * tooltip message should include the string '%1' and that string will be + * replaced with the value of the named field. + * @param {string} msgTemplate The template form to of the message text, with + * %1 placeholder. + * @param {string} fieldName The field with the replacement value. + * @returns {Function} The extension function. + */ +Blockly.Extensions.buildTooltipWithFieldValue = function(msgTemplate, + fieldName) { + // Check the tooltip string messages for invalid references. + // Wait for load, in case Blockly.Msg is not yet populated. + // runAfterPageLoad() does not run in a Node.js environment due to lack of + // document object, in which case skip the validation. + if (typeof document == 'object') { // Relies on document.readyState + Blockly.utils.runAfterPageLoad(function() { + // Will print warnings if reference is missing. + Blockly.utils.checkMessageReferences(msgTemplate); + }); + } + + /** + * The actual extension. + * @this {Blockly.Block} + */ + var extensionFn = function() { + this.setTooltip(function() { + return Blockly.utils.replaceMessageReferences(msgTemplate) + .replace('%1', this.getFieldValue(fieldName)); + }.bind(this)); + }; + return extensionFn; +}; + +/** + * Configures the tooltip to mimic the parent block when connected. Otherwise, + * uses the tooltip text at the time this extension is initialized. This takes + * advantage of the fact that all other values from JSON are initialized before + * extensions. + * @this {Blockly.Block} + * @private + */ +Blockly.Extensions.extensionParentTooltip_ = function() { + this.tooltipWhenNotConnected_ = this.tooltip; + this.setTooltip(function() { + var parent = this.getParent(); + return (parent && parent.getInputsInline() && parent.tooltip) || + this.tooltipWhenNotConnected_; + }.bind(this)); +}; +Blockly.Extensions.register('parent_tooltip_when_inline', + Blockly.Extensions.extensionParentTooltip_); diff --git a/core/.svn/pristine/ef/eff96fdaf01ba1d96ed8533b0693e0e302a618e5.svn-base b/core/.svn/pristine/ef/eff96fdaf01ba1d96ed8533b0693e0e302a618e5.svn-base new file mode 100644 index 0000000..57c244d --- /dev/null +++ b/core/.svn/pristine/ef/eff96fdaf01ba1d96ed8533b0693e0e302a618e5.svn-base @@ -0,0 +1,886 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2013 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 Inject Blockly's CSS synchronously. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +/** + * @name Blockly.Css + * @namespace + */ +goog.provide('Blockly.Css'); + + +/** + * List of cursors. + * @enum {string} + */ +Blockly.Css.Cursor = { + OPEN: 'handopen', + CLOSED: 'handclosed', + DELETE: 'handdelete' +}; + +/** + * Current cursor (cached value). + * @type {string} + * @private + */ +Blockly.Css.currentCursor_ = ''; + +/** + * Large stylesheet added by Blockly.Css.inject. + * @type {Element} + * @private + */ +Blockly.Css.styleSheet_ = null; + +/** + * Path to media directory, with any trailing slash removed. + * @type {string} + * @private + */ +Blockly.Css.mediaPath_ = ''; + +/** + * Inject the CSS into the DOM. This is preferable over using a regular CSS + * file since: + * a) It loads synchronously and doesn't force a redraw later. + * b) It speeds up loading by not blocking on a separate HTTP transfer. + * c) The CSS content may be made dynamic depending on init options. + * @param {boolean} hasCss If false, don't inject CSS + * (providing CSS becomes the document's responsibility). + * @param {string} pathToMedia Path from page to the Blockly media directory. + */ +Blockly.Css.inject = function(hasCss, pathToMedia) { + // Only inject the CSS once. + if (Blockly.Css.styleSheet_) { + return; + } + // Placeholder for cursor rule. Must be first rule (index 0). + var text = '.blocklyDraggable {}\n'; + if (hasCss) { + text += Blockly.Css.CONTENT.join('\n'); + if (Blockly.FieldDate) { + text += Blockly.FieldDate.CSS.join('\n'); + } + } + // Strip off any trailing slash (either Unix or Windows). + Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); + text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); + // Inject CSS tag at start of head. + var cssNode = document.createElement('style'); + document.head.insertBefore(cssNode, document.head.firstChild); + + var cssTextNode = document.createTextNode(text); + cssNode.appendChild(cssTextNode); + Blockly.Css.styleSheet_ = cssNode.sheet; +}; + +/** + * Set the cursor to be displayed when over something draggable. + * See See https://github.com/google/blockly/issues/981 for context. + * @param {Blockly.Css.Cursor} cursor Enum. + * @deprecated April 2017. + */ +Blockly.Css.setCursor = function(cursor) { + console.warn('Deprecated call to Blockly.Css.setCursor.' + + 'See https://github.com/google/blockly/issues/981 for context'); +}; + +/** + * Array making up the CSS content for Blockly. + */ +Blockly.Css.CONTENT = [ + '.blocklySvg {', + 'background-color: #fff;', + 'outline: none;', + 'overflow: hidden;', /* IE overflows by default. */ + 'position: absolute;', + 'display: block;', + '}', + + '.blocklyWidgetDiv {', + 'display: none;', + 'position: absolute;', + 'z-index: 99999;', /* big value for bootstrap3 compatibility */ + '}', + + '.injectionDiv {', + 'height: 100%;', + 'position: relative;', + 'overflow: hidden;', /* So blocks in drag surface disappear at edges */ + 'touch-action: none', + '}', + + '.blocklyNonSelectable {', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', + '}', + + '.blocklyWsDragSurface {', + 'display: none;', + 'position: absolute;', + 'top: 0;', + 'left: 0;', + '}', + /* Added as a separate rule with multiple classes to make it more specific + than a bootstrap rule that selects svg:root. See issue #1275 for context. + */ + '.blocklyWsDragSurface.blocklyOverflowVisible {', + 'overflow: visible;', + '}', + + '.blocklyBlockDragSurface {', + 'display: none;', + 'position: absolute;', + 'top: 0;', + 'left: 0;', + 'right: 0;', + 'bottom: 0;', + 'overflow: visible !important;', + 'z-index: 50;', /* Display below toolbox, but above everything else. */ + '}', + + '.blocklyTooltipDiv {', + 'background-color: #ffffc7;', + 'border: 1px solid #ddc;', + 'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);', + 'color: #000;', + 'display: none;', + 'font-family: sans-serif;', + 'font-size: 9pt;', + 'opacity: .9;', + 'padding: 2px;', + 'position: absolute;', + 'z-index: 100000;', /* big value for bootstrap3 compatibility */ + '}', + + '.blocklyResizeSE {', + 'cursor: se-resize;', + 'fill: #aaa;', + '}', + + '.blocklyResizeSW {', + 'cursor: sw-resize;', + 'fill: #aaa;', + '}', + + '.blocklyResizeLine {', + 'stroke: #888;', + 'stroke-width: 1;', + '}', + + '.blocklyHighlightedConnectionPath {', + 'fill: none;', + 'stroke: #fc3;', + 'stroke-width: 4px;', + '}', + + '.blocklyPathLight {', + 'fill: none;', + 'stroke-linecap: round;', + 'stroke-width: 1;', + '}', + + '.blocklySelected>.blocklyPath {', + 'stroke: #fc3;', + 'stroke-width: 3px;', + '}', + + '.blocklySelected>.blocklyPathLight {', + 'display: none;', + '}', + + '.blocklyDraggable {', + /* backup for browsers (e.g. IE11) that don't support grab */ + 'cursor: url("<<>>/handopen.cur"), auto;', + 'cursor: grab;', + 'cursor: -webkit-grab;', + '}', + + '.blocklyDragging {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + /* Changes cursor on mouse down. Not effective in Firefox because of + https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */ + '.blocklyDraggable:active {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + /* Change the cursor on the whole drag surface in case the mouse gets + ahead of block during a drag. This way the cursor is still a closed hand. + */ + '.blocklyBlockDragSurface .blocklyDraggable {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + + '.blocklyDragging.blocklyDraggingDelete {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + + '.blocklyToolboxDelete {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + + '.blocklyToolboxGrab {', + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + + '.blocklyDragging>.blocklyPath,', + '.blocklyDragging>.blocklyPathLight {', + 'fill-opacity: .8;', + 'stroke-opacity: .8;', + '}', + + '.blocklyDragging>.blocklyPathDark {', + 'display: none;', + '}', + + '.blocklyDisabled>.blocklyPath {', + 'fill-opacity: .5;', + 'stroke-opacity: .5;', + '}', + + '.blocklyDisabled>.blocklyPathLight,', + '.blocklyDisabled>.blocklyPathDark {', + 'display: none;', + '}', + + '.blocklyText {', + 'cursor: default;', + 'fill: #fff;', + 'font-family: sans-serif;', + 'font-size: 11pt;', + '}', + + '.blocklyNonEditableText>text {', + 'pointer-events: none;', + '}', + + '.blocklyNonEditableText>rect,', + '.blocklyEditableText>rect {', + 'fill: #fff;', + 'fill-opacity: .6;', + '}', + + '.blocklyNonEditableText>text,', + '.blocklyEditableText>text {', + 'fill: #000;', + '}', + + '.blocklyEditableText:hover>rect {', + 'stroke: #fff;', + 'stroke-width: 2;', + '}', + + '.blocklyBubbleText {', + 'fill: #000;', + '}', + + '.blocklyFlyout {', + 'position: absolute;', + 'z-index: 20;', + '}', + '.blocklyFlyoutButton {', + 'fill: #888;', + 'cursor: default;', + '}', + + '.blocklyFlyoutButtonShadow {', + 'fill: #666;', + '}', + + '.blocklyFlyoutButton:hover {', + 'fill: #aaa;', + '}', + + '.blocklyFlyoutLabel {', + 'cursor: default;', + '}', + + '.blocklyFlyoutLabelBackground {', + 'opacity: 0;', + '}', + + '.blocklyFlyoutLabelText {', + 'fill: #000;', + '}', + + /* + Don't allow users to select text. It gets annoying when trying to + drag a block and selected text moves instead. + */ + '.blocklySvg text, .blocklyBlockDragSurface text {', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', + 'cursor: inherit;', + '}', + + '.blocklyHidden {', + 'display: none;', + '}', + + '.blocklyFieldDropdown:not(.blocklyHidden) {', + 'display: block;', + '}', + + '.blocklyIconGroup {', + 'cursor: default;', + '}', + + '.blocklyIconGroup:not(:hover),', + '.blocklyIconGroupReadonly {', + 'opacity: .6;', + '}', + + '.blocklyIconShape {', + 'fill: #00f;', + 'stroke: #fff;', + 'stroke-width: 1px;', + '}', + + '.blocklyIconSymbol {', + 'fill: #fff;', + '}', + + '.blocklyMinimalBody {', + 'margin: 0;', + 'padding: 0;', + '}', + + '.blocklyCommentTextarea {', + 'background-color: #ffc;', + 'border: 0;', + 'margin: 0;', + 'padding: 2px;', + 'resize: none;', + '}', + + '.blocklyHtmlInput {', + 'border: none;', + 'border-radius: 4px;', + 'font-family: sans-serif;', + 'height: 100%;', + 'margin: 0;', + 'outline: none;', + 'padding: 0 1px;', + 'width: 100%', + '}', + + '.blocklyMainBackground {', + 'stroke-width: 1;', + 'stroke: #c6c6c6;', /* Equates to #ddd due to border being off-pixel. */ + '}', + + '.blocklyMutatorBackground {', + 'fill: #fff;', + 'stroke: #ddd;', + 'stroke-width: 1;', + '}', + + '.blocklyFlyoutBackground {', + 'fill: #ddd;', + 'fill-opacity: .8;', + '}', + + '.blocklyTransparentBackground {', + 'opacity: 0;', + '}', + + '.blocklyMainWorkspaceScrollbar {', + 'z-index: 20;', + '}', + + '.blocklyFlyoutScrollbar {', + 'z-index: 30;', + '}', + + '.blocklyScrollbarHorizontal, .blocklyScrollbarVertical {', + 'position: absolute;', + 'outline: none;', + '}', + + '.blocklyScrollbarBackground {', + 'opacity: 0;', + '}', + + '.blocklyScrollbarHandle {', + 'fill: #ccc;', + '}', + + '.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyScrollbarHandle:hover {', + 'fill: #bbb;', + '}', + + '.blocklyZoom>image {', + 'opacity: .4;', + '}', + + '.blocklyZoom>image:hover {', + 'opacity: .6;', + '}', + + '.blocklyZoom>image:active {', + 'opacity: .8;', + '}', + + /* Darken flyout scrollbars due to being on a grey background. */ + /* By contrast, workspace scrollbars are on a white background. */ + '.blocklyFlyout .blocklyScrollbarHandle {', + 'fill: #bbb;', + '}', + + '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyFlyout .blocklyScrollbarHandle:hover {', + 'fill: #aaa;', + '}', + + '.blocklyInvalidInput {', + 'background: #faa;', + '}', + + '.blocklyAngleCircle {', + 'stroke: #444;', + 'stroke-width: 1;', + 'fill: #ddd;', + 'fill-opacity: .8;', + '}', + + '.blocklyAngleMarks {', + 'stroke: #444;', + 'stroke-width: 1;', + '}', + + '.blocklyAngleGauge {', + 'fill: #f88;', + 'fill-opacity: .8;', + '}', + + '.blocklyAngleLine {', + 'stroke: #f00;', + 'stroke-width: 2;', + 'stroke-linecap: round;', + 'pointer-events: none;', + '}', + + '.blocklyContextMenu {', + 'border-radius: 4px;', + '}', + + '.blocklyDropdownMenu {', + 'padding: 0 !important;', + /* max-height value is same as the constant + * Blockly.FieldDropdown.MAX_MENU_HEIGHT defined in field_dropdown.js. */ + 'max-height: 300px !important;', + '}', + + /* Override the default Closure URL. */ + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', + 'background: url(<<>>/sprites.png) no-repeat -48px -16px !important;', + '}', + + /* Category tree in Toolbox. */ + '.blocklyToolboxDiv {', + 'background-color: #ddd;', + 'overflow-x: visible;', + 'overflow-y: auto;', + 'position: absolute;', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', + 'z-index: 70;', /* so blocks go under toolbox when dragging */ + '-webkit-tap-highlight-color: transparent;', /* issue #1345 */ + '}', + + '.blocklyTreeRoot {', + 'padding: 4px 0;', + '}', + + '.blocklyTreeRoot:focus {', + 'outline: none;', + '}', + + '.blocklyTreeRow {', + 'height: 22px;', + 'line-height: 22px;', + 'margin-bottom: 3px;', + 'padding-right: 8px;', + 'white-space: nowrap;', + '}', + + '.blocklyHorizontalTree {', + 'float: left;', + 'margin: 1px 5px 8px 0;', + '}', + + '.blocklyHorizontalTreeRtl {', + 'float: right;', + 'margin: 1px 0 8px 5px;', + '}', + + '.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {', + 'margin-left: 8px;', + '}', + + '.blocklyTreeRow:not(.blocklyTreeSelected):hover {', + 'background-color: #e4e4e4;', + '}', + + '.blocklyTreeSeparator {', + 'border-bottom: solid #e5e5e5 1px;', + 'height: 0;', + 'margin: 5px 0;', + '}', + + '.blocklyTreeSeparatorHorizontal {', + 'border-right: solid #e5e5e5 1px;', + 'width: 0;', + 'padding: 5px 0;', + 'margin: 0 5px;', + '}', + + + '.blocklyTreeIcon {', + 'background-image: url(<<>>/sprites.png);', + 'height: 16px;', + 'vertical-align: middle;', + 'width: 16px;', + '}', + + '.blocklyTreeIconClosedLtr {', + 'background-position: -32px -1px;', + '}', + + '.blocklyTreeIconClosedRtl {', + 'background-position: 0 -1px;', + '}', + + '.blocklyTreeIconOpen {', + 'background-position: -16px -1px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconClosedLtr {', + 'background-position: -32px -17px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {', + 'background-position: 0 -17px;', + '}', + + '.blocklyTreeSelected>.blocklyTreeIconOpen {', + 'background-position: -16px -17px;', + '}', + + '.blocklyTreeIconNone,', + '.blocklyTreeSelected>.blocklyTreeIconNone {', + 'background-position: -48px -1px;', + '}', + + '.blocklyTreeLabel {', + 'cursor: default;', + 'font-family: sans-serif;', + 'font-size: 16px;', + 'padding: 0 3px;', + 'vertical-align: middle;', + '}', + + '.blocklyToolboxDelete .blocklyTreeLabel {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + + '.blocklyTreeSelected .blocklyTreeLabel {', + 'color: #fff;', + '}', + + /* Copied from: goog/css/colorpicker-simplegrid.css */ + /* + * Copyright 2007 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /* Author: pupius@google.com (Daniel Pupius) */ + + /* + Styles to make the colorpicker look like the old gmail color picker + NOTE: without CSS scoping this will override styles defined in palette.css + */ + '.blocklyWidgetDiv .goog-palette {', + 'outline: none;', + 'cursor: default;', + '}', + + '.blocklyWidgetDiv .goog-palette-table {', + 'border: 1px solid #666;', + 'border-collapse: collapse;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell {', + 'height: 13px;', + 'width: 15px;', + 'margin: 0;', + 'border: 0;', + 'text-align: center;', + 'vertical-align: middle;', + 'border-right: 1px solid #666;', + 'font-size: 1px;', + '}', + + '.blocklyWidgetDiv .goog-palette-colorswatch {', + 'position: relative;', + 'height: 13px;', + 'width: 15px;', + 'border: 1px solid #666;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {', + 'border: 1px solid #FFF;', + '}', + + '.blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {', + 'border: 1px solid #000;', + 'color: #fff;', + '}', + + /* Copied from: goog/css/menu.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + '.blocklyWidgetDiv .goog-menu {', + 'background: #fff;', + 'border-color: #ccc #666 #666 #ccc;', + 'border-style: solid;', + 'border-width: 1px;', + 'cursor: default;', + 'font: normal 13px Arial, sans-serif;', + 'margin: 0;', + 'outline: none;', + 'padding: 4px 0;', + 'position: absolute;', + 'overflow-y: auto;', + 'overflow-x: hidden;', + 'max-height: 100%;', + 'z-index: 20000;', /* Arbitrary, but some apps depend on it... */ + '}', + + /* Copied from: goog/css/menuitem.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuItemRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + /** + * State: resting. + * + * NOTE(mleibman,chrishenry): + * The RTL support in Closure is provided via two mechanisms -- "rtl" CSS + * classes and BiDi flipping done by the CSS compiler. Closure supports RTL + * with or without the use of the CSS compiler. In order for them not + * to conflict with each other, the "rtl" CSS classes need to have the #noflip + * annotation. The non-rtl counterparts should ideally have them as well, but, + * since .goog-menuitem existed without .goog-menuitem-rtl for so long before + * being added, there is a risk of people having templates where they are not + * rendering the .goog-menuitem-rtl class when in RTL and instead rely solely + * on the BiDi flipping by the CSS compiler. That's why we're not adding the + * #noflip to .goog-menuitem. + */ + '.blocklyWidgetDiv .goog-menuitem {', + 'color: #000;', + 'font: normal 13px Arial, sans-serif;', + 'list-style: none;', + 'margin: 0;', + /* 28px on the left for icon or checkbox; 7em on the right for shortcut. */ + 'padding: 4px 7em 4px 28px;', + 'white-space: nowrap;', + '}', + + /* BiDi override for the resting state. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {', + /* Flip left/right padding for BiDi. */ + 'padding-left: 7em;', + 'padding-right: 28px;', + '}', + + /* If a menu doesn't have checkable items or items with icons, remove padding. */ + '.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,', + '.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {', + 'padding-left: 12px;', + '}', + + /* + * If a menu doesn't have items with shortcuts, leave just enough room for + * submenu arrows, if they are rendered. + */ + '.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {', + 'padding-right: 20px;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-content {', + 'color: #000;', + 'font: normal 13px Arial, sans-serif;', + '}', + + /* State: disabled. */ + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,', + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {', + 'color: #ccc !important;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {', + 'opacity: 0.3;', + 'filter: alpha(opacity=30);', + '}', + + /* State: hover. */ + '.blocklyWidgetDiv .goog-menuitem-highlight,', + '.blocklyWidgetDiv .goog-menuitem-hover {', + 'background-color: #d6e9f8;', + /* Use an explicit top and bottom border so that the selection is visible', + * in high contrast mode. */ + 'border-color: #d6e9f8;', + 'border-style: dotted;', + 'border-width: 1px 0;', + 'padding-bottom: 3px;', + 'padding-top: 3px;', + '}', + + /* State: selected/checked. */ + '.blocklyWidgetDiv .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-menuitem-icon {', + 'background-repeat: no-repeat;', + 'height: 16px;', + 'left: 6px;', + 'position: absolute;', + 'right: auto;', + 'vertical-align: middle;', + 'width: 16px;', + '}', + + /* BiDi override for the selected/checked state. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {', + /* Flip left/right positioning. */ + 'left: auto;', + 'right: 6px;', + '}', + + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', + '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', + /* Client apps may override the URL at which they serve the sprite. */ + 'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;', + '}', + + /* Keyboard shortcut ("accelerator") style. */ + '.blocklyWidgetDiv .goog-menuitem-accel {', + 'color: #999;', + /* Keyboard shortcuts are untranslated; always left-to-right. */ + /* #noflip */ + 'direction: ltr;', + 'left: auto;', + 'padding: 0 6px;', + 'position: absolute;', + 'right: 0;', + 'text-align: right;', + '}', + + /* BiDi override for shortcut style. */ + /* #noflip */ + '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {', + /* Flip left/right positioning and text alignment. */ + 'left: 0;', + 'right: auto;', + 'text-align: left;', + '}', + + /* Mnemonic styles. */ + '.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {', + 'text-decoration: underline;', + '}', + + '.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {', + 'color: #999;', + 'font-size: 12px;', + 'padding-left: 4px;', + '}', + + /* Copied from: goog/css/menuseparator.css */ + /* + * Copyright 2009 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + + /** + * Standard styling for menus created by goog.ui.MenuSeparatorRenderer. + * + * @author attila@google.com (Attila Bodis) + */ + + '.blocklyWidgetDiv .goog-menuseparator {', + 'border-top: 1px solid #ccc;', + 'margin: 4px 0;', + 'padding: 0;', + '}', + + '' +]; diff --git a/core/.svn/pristine/f1/f1c403e08c08d74eaa36eee96f7bfa8cf5b64339.svn-base b/core/.svn/pristine/f1/f1c403e08c08d74eaa36eee96f7bfa8cf5b64339.svn-base new file mode 100644 index 0000000..c6a0a3b --- /dev/null +++ b/core/.svn/pristine/f1/f1c403e08c08d74eaa36eee96f7bfa8cf5b64339.svn-base @@ -0,0 +1,328 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a block visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BlockDragger'); + +goog.require('Blockly.DraggedConnectionManager'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a block dragger. It moves blocks around the workspace when they + * are being dragged by a mouse or touch. + * @param {!Blockly.Block} block The block to drag. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on. + * @constructor + */ +Blockly.BlockDragger = function(block, workspace) { + /** + * The top block in the stack that is being dragged. + * @type {!Blockly.BlockSvg} + * @private + */ + this.draggingBlock_ = block; + + /** + * The workspace on which the block is being dragged. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * Object that keeps track of connections on dragged blocks. + * @type {!Blockly.DraggedConnectionManager} + * @private + */ + this.draggedConnectionManager_ = new Blockly.DraggedConnectionManager( + this.draggingBlock_); + + /** + * Which delete area the mouse pointer is over, if any. + * One of {@link Blockly.DELETE_AREA_TRASH}, + * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. + * @type {?number} + * @private + */ + this.deleteArea_ = null; + + /** + * Whether the block would be deleted if dropped immediately. + * @type {boolean} + * @private + */ + this.wouldDeleteBlock_ = false; + + /** + * The location of the top left corner of the dragging block at the beginning + * of the drag in workspace coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY(); + + /** + * A list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @type {Array.} + * @private + */ + this.dragIconData_ = Blockly.BlockDragger.initIconData_(block); +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.BlockDragger.prototype.dispose = function() { + this.draggingBlock_ = null; + this.workspace_ = null; + this.startWorkspace_ = null; + this.dragIconData_.length = 0; + + if (this.draggedConnectionManager_) { + this.draggedConnectionManager_.dispose(); + this.draggedConnectionManager_ = null; + } +}; + +/** + * Make a list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @param {!Blockly.BlockSvg} block The root block that is being dragged. + * @return {!Array.} The list of all icons and their locations. + * @private + */ +Blockly.BlockDragger.initIconData_ = function(block) { + // Build a list of icons that need to be moved and where they started. + var dragIconData = []; + var descendants = block.getDescendants(); + for (var i = 0, descendant; descendant = descendants[i]; i++) { + var icons = descendant.getIcons(); + for (var j = 0; j < icons.length; j++) { + var data = { + // goog.math.Coordinate with x and y properties (workspace coordinates). + location: icons[j].getIconLocation(), + // Blockly.Icon + icon: icons[j] + }; + dragIconData.push(data); + } + } + return dragIconData; +}; + +/** + * Start dragging a block. This includes moving it to the drag surface. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + + this.workspace_.setResizesEnabled(false); + Blockly.BlockSvg.disconnectUiStop_(); + + if (this.draggingBlock_.getParent()) { + this.draggingBlock_.unplug(); + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBlock_.translate(newLoc.x, newLoc.y); + this.draggingBlock_.disconnectUiEffect(); + } + this.draggingBlock_.setDragging(true); + // For future consideration: we may be able to put moveToDragSurface inside + // the block dragger, which would also let the block not track the block drag + // surface. + this.draggingBlock_.moveToDragSurface_(); + + if (this.workspace_.toolbox_) { + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.addStyle(style); + } +}; + +/** + * Execute a step of block dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) { + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBlock_.moveDuringDrag(newLoc); + this.dragIcons_(delta); + + this.deleteArea_ = this.workspace_.isDeleteArea(e); + this.draggedConnectionManager_.update(delta, this.deleteArea_); + + this.updateCursorDuringBlockDrag_(); +}; + +/** + * Finish a block drag and put the block back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBlock(e, currentDragDeltaXY); + this.dragIconData_ = []; + + Blockly.BlockSvg.disconnectUiStop_(); + + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + this.draggingBlock_.moveOffDragSurface_(newLoc); + + var deleted = this.maybeDeleteBlock_(); + if (!deleted) { + // These are expensive and don't need to be done if we're deleting. + this.draggingBlock_.moveConnections_(delta.x, delta.y); + this.draggingBlock_.setDragging(false); + this.draggedConnectionManager_.applyConnections(); + this.draggingBlock_.render(); + this.fireMoveEvent_(); + this.draggingBlock_.scheduleSnapAndBump(); + } + this.workspace_.setResizesEnabled(true); + + if (this.workspace_.toolbox_) { + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.removeStyle(style); + } + Blockly.Events.setGroup(false); +}; + +/** + * Fire a move event at the end of a block drag. + * @private + */ +Blockly.BlockDragger.prototype.fireMoveEvent_ = function() { + var event = new Blockly.Events.BlockMove(this.draggingBlock_); + event.oldCoordinate = this.startXY_; + event.recordNew(); + Blockly.Events.fire(event); +}; + +/** + * Shut the trash can and, if necessary, delete the dragging block. + * Should be called at the end of a block drag. + * @return {boolean} whether the block was deleted. + * @private + */ +Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() { + var trashcan = this.workspace_.trashcan; + + if (this.wouldDeleteBlock_) { + if (trashcan) { + goog.Timer.callOnce(trashcan.close, 100, trashcan); + } + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBlock_.dispose(false, true); + } else if (trashcan) { + // Make sure the trash can is closed. + trashcan.close(); + } + return this.wouldDeleteBlock_; +}; + +/** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging block would be deleted if released immediately. + * @private + */ +Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() { + this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); + var trashcan = this.workspace_.trashcan; + if (this.wouldDeleteBlock_) { + this.draggingBlock_.setDeleteStyle(true); + if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) { + trashcan.setOpen_(true); + } + } else { + this.draggingBlock_.setDeleteStyle(false); + if (trashcan) { + trashcan.setOpen_(false); + } + } +}; + +/** + * Convert a coordinate object from pixels to workspace units, including a + * correction for mutator workspaces. + * This function does not consider differing origins. It simply scales the + * input's x and y values. + * @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values + * in css pixel units. + * @return {!goog.math.Coordinate} The input coordinate divided by the workspace + * scale. + * @private + */ +Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { + var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale, + pixelCoord.y / this.workspace_.scale); + if (this.workspace_.isMutator) { + // If we're in a mutator, its scale is always 1, purely because of some + // oddities in our rendering optimizations. The actual scale is the same as + // the scale on the parent workspace. + // Fix that for dragging. + var mainScale = this.workspace_.options.parentWorkspace.scale; + result = result.scale(1 / mainScale); + } + return result; +}; + +/** + * Move all of the icons connected to this drag. + * @param {!goog.math.Coordinate} dxy How far to move the icons from their + * original positions, in workspace units. + * @private + */ +Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) { + // Moving icons moves their associated bubbles. + for (var i = 0; i < this.dragIconData_.length; i++) { + var data = this.dragIconData_[i]; + data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy)); + } +}; diff --git a/core/.svn/pristine/f3/f33fcd4b59ae252e6c03c900375124e0e2b9e931.svn-base b/core/.svn/pristine/f3/f33fcd4b59ae252e6c03c900375124e0e2b9e931.svn-base new file mode 100644 index 0000000..4dd4879 --- /dev/null +++ b/core/.svn/pristine/f3/f33fcd4b59ae252e6c03c900375124e0e2b9e931.svn-base @@ -0,0 +1,99 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Components for the variable model. + * @author marisaleung@google.com (Marisa Leung) + */ +'use strict'; + +goog.provide('Blockly.VariableModel'); + +goog.require('goog.string'); + + +/** + * Class for a variable model. + * Holds information for the variable including name, ID, and type. + * @param {!Blockly.Workspace} workspace The variable's workspace. + * @param {!string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @see {Blockly.FieldVariable} + * @constructor + */ +Blockly.VariableModel = function(workspace, name, opt_type, opt_id) { + /** + * The workspace the variable is in. + * @type {!Blockly.Workspace} + */ + this.workspace = workspace; + + /** + * The name of the variable, typically defined by the user. It must be + * unique across all names used for procedures and variables. It may be + * changed by the user. + * @type {string} + */ + this.name = name; + + /** + * The type of the variable, such as 'int' or 'sound_effect'. This may be + * used to build a list of variables of a specific type. By default this is + * the empty string '', which is a specific type. + * @see {Blockly.FieldVariable} + * @type {string} + */ + this.type = opt_type || ''; + + /** + * A unique id for the variable. This should be defined at creation and + * not change, even if the name changes. In most cases this should be a + * UUID. + * @type {string} + * @private + */ + this.id_ = opt_id || Blockly.utils.genUid(); + + Blockly.Events.fire(new Blockly.Events.VarCreate(this)); +}; + +/** + * @return {!string} The ID for the variable. + */ +Blockly.VariableModel.prototype.getId = function() { + return this.id_; +}; + +/** + * A custom compare function for the VariableModel objects. + * @param {Blockly.VariableModel} var1 First variable to compare. + * @param {Blockly.VariableModel} var2 Second variable to compare. + * @return {number} -1 if name of var1 is less than name of var2, 0 if equal, + * and 1 if greater. + * @package + */ +Blockly.VariableModel.compareByName = function(var1, var2) { + return goog.string.caseInsensitiveCompare(var1.name, var2.name); +}; diff --git a/core/.svn/pristine/f6/f617f54e293e3aa161941ccd823519471be2750b.svn-base b/core/.svn/pristine/f6/f617f54e293e3aa161941ccd823519471be2750b.svn-base new file mode 100644 index 0000000..ec0458e --- /dev/null +++ b/core/.svn/pristine/f6/f617f54e293e3aa161941ccd823519471be2750b.svn-base @@ -0,0 +1,862 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Library for creating scrollbars. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Scrollbar'); +goog.provide('Blockly.ScrollbarPair'); + +goog.require('goog.dom'); +goog.require('goog.events'); + + +/** + * A note on units: most of the numbers that are in CSS pixels are scaled if the + * scrollbar is in a mutator. + */ + +/** + * Class for a pair of scrollbars. Horizontal and vertical. + * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to. + * @constructor + */ +Blockly.ScrollbarPair = function(workspace) { + this.workspace_ = workspace; + this.hScroll = new Blockly.Scrollbar( + workspace, true, true, 'blocklyMainWorkspaceScrollbar'); + this.vScroll = new Blockly.Scrollbar( + workspace, false, true, 'blocklyMainWorkspaceScrollbar'); + this.corner_ = Blockly.utils.createSvgElement( + 'rect', + { + 'height': Blockly.Scrollbar.scrollbarThickness, + 'width': Blockly.Scrollbar.scrollbarThickness, + 'class': 'blocklyScrollbarBackground' + }, + null); + Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas()); +}; + +/** + * Previously recorded metrics from the workspace. + * @type {Object} + * @private + */ +Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null; + +/** + * Dispose of this pair of scrollbars. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.ScrollbarPair.prototype.dispose = function() { + goog.dom.removeNode(this.corner_); + this.corner_ = null; + this.workspace_ = null; + this.oldHostMetrics_ = null; + this.hScroll.dispose(); + this.hScroll = null; + this.vScroll.dispose(); + this.vScroll = null; +}; + +/** + * Recalculate both of the scrollbars' locations and lengths. + * Also reposition the corner rectangle. + */ +Blockly.ScrollbarPair.prototype.resize = function() { + // Look up the host metrics once, and use for both scrollbars. + var hostMetrics = this.workspace_.getMetrics(); + if (!hostMetrics) { + // Host element is likely not visible. + return; + } + + // Only change the scrollbars if there has been a change in metrics. + var resizeH = false; + var resizeV = false; + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || + this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop || + this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { + // The window has been resized or repositioned. + resizeH = true; + resizeV = true; + } else { + // Has the content been resized or moved? + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth || + this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft || + this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) { + resizeH = true; + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight || + this.oldHostMetrics_.viewTop != hostMetrics.viewTop || + this.oldHostMetrics_.contentTop != hostMetrics.contentTop) { + resizeV = true; + } + } + if (resizeH) { + this.hScroll.resize(hostMetrics); + } + if (resizeV) { + this.vScroll.resize(hostMetrics); + } + + // Reposition the corner square. + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || + this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { + this.corner_.setAttribute('x', this.vScroll.position_.x); + } + if (!this.oldHostMetrics_ || + this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || + this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) { + this.corner_.setAttribute('y', this.hScroll.position_.y); + } + + // Cache the current metrics to potentially short-cut the next resize event. + this.oldHostMetrics_ = hostMetrics; +}; + +/** + * Set the handles of both scrollbars to be at a certain position in CSS pixels + * relative to their parents. + * @param {number} x Horizontal scroll value. + * @param {number} y Vertical scroll value. + */ +Blockly.ScrollbarPair.prototype.set = function(x, y) { + // This function is equivalent to: + // this.hScroll.set(x); + // this.vScroll.set(y); + // However, that calls setMetrics twice which causes a chain of + // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. + // Combining them speeds up rendering. + var xyRatio = {}; + + var hHandlePosition = x * this.hScroll.ratio_; + var vHandlePosition = y * this.vScroll.ratio_; + + var hBarLength = this.hScroll.scrollViewSize_; + var vBarLength = this.vScroll.scrollViewSize_; + + xyRatio.x = this.getRatio_(hHandlePosition, hBarLength); + xyRatio.y = this.getRatio_(vHandlePosition, vBarLength); + this.workspace_.setMetrics(xyRatio); + + this.hScroll.setHandlePosition(hHandlePosition); + this.vScroll.setHandlePosition(vHandlePosition); +}; + +/** + * Helper to calculate the ratio of handle position to scrollbar view size. + * @param {number} handlePosition The value of the handle. + * @param {number} viewSize The total size of the scrollbar's view. + * @return {number} Ratio. + * @private + */ +Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) { + var ratio = handlePosition / viewSize; + if (isNaN(ratio)) { + return 0; + } + return ratio; +}; + +// -------------------------------------------------------------------- + +/** + * Class for a pure SVG scrollbar. + * This technique offers a scrollbar that is guaranteed to work, but may not + * look or behave like the system's scrollbars. + * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to. + * @param {boolean} horizontal True if horizontal, false if vertical. + * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. + * @param {string=} opt_class A class to be applied to this scrollbar. + * @constructor + */ +Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) { + this.workspace_ = workspace; + this.pair_ = opt_pair || false; + this.horizontal_ = horizontal; + this.oldHostMetrics_ = null; + + this.createDom_(opt_class); + + /** + * The upper left corner of the scrollbar's SVG group in CSS pixels relative + * to the scrollbar's origin. This is usually relative to the injection div + * origin. + * @type {goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); + + // Store the thickness in a temp variable for readability. + var scrollbarThickness = Blockly.Scrollbar.scrollbarThickness; + if (horizontal) { + this.svgBackground_.setAttribute('height', scrollbarThickness); + this.outerSvg_.setAttribute('height', scrollbarThickness); + this.svgHandle_.setAttribute('height', scrollbarThickness - 5); + this.svgHandle_.setAttribute('y', 2.5); + + this.lengthAttribute_ = 'width'; + this.positionAttribute_ = 'x'; + } else { + this.svgBackground_.setAttribute('width', scrollbarThickness); + this.outerSvg_.setAttribute('width', scrollbarThickness); + this.svgHandle_.setAttribute('width', scrollbarThickness - 5); + this.svgHandle_.setAttribute('x', 2.5); + + this.lengthAttribute_ = 'height'; + this.positionAttribute_ = 'y'; + } + var scrollbar = this; + this.onMouseDownBarWrapper_ = Blockly.bindEventWithChecks_( + this.svgBackground_, 'mousedown', scrollbar, scrollbar.onMouseDownBar_); + this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_, + 'mousedown', scrollbar, scrollbar.onMouseDownHandle_); +}; + +/** + * The location of the origin of the workspace that the scrollbar is in, + * measured in CSS pixels relative to the injection div origin. This is usually + * (0, 0). When the scrollbar is in a flyout it may have a different origin. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0); + +/** + * The position of the mouse along this scrollbar's major axis at the start of + * the most recent drag. + * Units are CSS pixels, with (0, 0) at the top left of the browser window. + * For a horizontal scrollbar this is the x coordinate of the mouse down event; + * for a vertical scrollbar it's the y coordinate of the mouse down event. + * @type {goog.math.Coordinate} + */ +Blockly.Scrollbar.prototype.startDragMouse_ = 0; + +/** + * The size of the area within which the scrollbar handle can move, in CSS + * pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.scrollViewSize_ = 0; + +/** + * The length of the scrollbar handle in CSS pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handleLength_ = 0; + +/** + * The offset of the start of the handle from the scrollbar position, in CSS + * pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handlePosition_ = 0; + +/** + * Whether the scrollbar handle is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.isVisible_ = true; + +/** + * Whether the workspace containing this scrollbar is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.containerVisible_ = true; + +/** + * Width of vertical scrollbar or height of horizontal scrollbar in CSS pixels. + * Scrollbars should be larger on touch devices. + */ +Blockly.Scrollbar.scrollbarThickness = 15; +if (goog.events.BrowserFeature.TOUCH_ENABLED) { + Blockly.Scrollbar.scrollbarThickness = 25; +} + +/** + * @param {!Object} first An object containing computed measurements of a + * workspace. + * @param {!Object} second Another object containing computed measurements of a + * workspace. + * @return {boolean} Whether the two sets of metrics are equivalent. + * @private + */ +Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) { + if (!(first && second)) { + return false; + } + + if (first.viewWidth != second.viewWidth || + first.viewHeight != second.viewHeight || + first.viewLeft != second.viewLeft || + first.viewTop != second.viewTop || + first.absoluteTop != second.absoluteTop || + first.absoluteLeft != second.absoluteLeft || + first.contentWidth != second.contentWidth || + first.contentHeight != second.contentHeight || + first.contentLeft != second.contentLeft || + first.contentTop != second.contentTop) { + return false; + } + + return true; +}; + +/** + * Dispose of this scrollbar. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Scrollbar.prototype.dispose = function() { + this.cleanUp_(); + Blockly.unbindEvent_(this.onMouseDownBarWrapper_); + this.onMouseDownBarWrapper_ = null; + Blockly.unbindEvent_(this.onMouseDownHandleWrapper_); + this.onMouseDownHandleWrapper_ = null; + + goog.dom.removeNode(this.outerSvg_); + this.outerSvg_ = null; + this.svgGroup_ = null; + this.svgBackground_ = null; + this.svgHandle_ = null; + this.workspace_ = null; +}; + +/** + * Set the length of the scrollbar's handle and change the SVG attribute + * accordingly. + * @param {number} newLength The new scrollbar handle length in CSS pixels. + */ +Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) { + this.handleLength_ = newLength; + this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_); +}; + +/** + * Set the offset of the scrollbar's handle from the scrollbar's position, and + * change the SVG attribute accordingly. + * @param {number} newPosition The new scrollbar handle offset in CSS pixels. + */ +Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) { + this.handlePosition_ = newPosition; + this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_); +}; + +/** + * Set the size of the scrollbar's background and change the SVG attribute + * accordingly. + * @param {number} newSize The new scrollbar background length in CSS pixels. + * @private + */ +Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) { + this.scrollViewSize_ = newSize; + this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); + this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); +}; + +/** + * Set whether this scrollbar's container is visible. + * @param {boolean} visible Whether the container is visible. + */ +Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) { + this.hScroll.setContainerVisible(visible); + this.vScroll.setContainerVisible(visible); +}; + +/** + * Set the position of the scrollbar's SVG group in CSS pixels relative to the + * scrollbar's origin. This sets the scrollbar's location within the workspace. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + * @private + */ +Blockly.Scrollbar.prototype.setPosition_ = function(x, y) { + this.position_.x = x; + this.position_.y = y; + + var tempX = this.position_.x + this.origin_.x; + var tempY = this.position_.y + this.origin_.y; + var transform = 'translate(' + tempX + 'px,' + tempY + 'px)'; + Blockly.utils.setCssTransform(this.outerSvg_, transform); +}; + +/** + * Recalculate the scrollbar's location and its length. + * @param {Object=} opt_metrics A data structure of from the describing all the + * required dimensions. If not provided, it will be fetched from the host + * object. + */ +Blockly.Scrollbar.prototype.resize = function(opt_metrics) { + // Determine the location, height and width of the host element. + var hostMetrics = opt_metrics; + if (!hostMetrics) { + hostMetrics = this.workspace_.getMetrics(); + if (!hostMetrics) { + // Host element is likely not visible. + return; + } + } + + if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics, + this.oldHostMetrics_)) { + return; + } + this.oldHostMetrics_ = hostMetrics; + + /* hostMetrics is an object with the following properties. + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the content, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .viewLeft: Offset of left edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + */ + if (this.horizontal_) { + this.resizeHorizontal_(hostMetrics); + } else { + this.resizeVertical_(hostMetrics); + } + // Resizing may have caused some scrolling. + this.onScroll_(); +}; + +/** + * Recalculate a horizontal scrollbar's location and length. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + * @private + */ +Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) { + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) { + var viewSize = hostMetrics.viewWidth - 1; + if (this.pair_) { + // Shorten the scrollbar to make room for the corner square. + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (this.pair_ && this.workspace_.RTL) { + xCoordinate += Blockly.Scrollbar.scrollbarThickness; + } + + // Horizontal toolbar should always be just above the bottom of the workspace. + var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - + Blockly.Scrollbar.scrollbarThickness - 0.5; + this.setPosition_(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) { + if (!this.pair_) { + // Only show the scrollbar if needed. + // Ideally this would also apply to scrollbar pairs, but that's a bigger + // headache (due to interactions with the corner square). + this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth); + } + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || + isNaN(this.ratio_)) { + this.ratio_ = 0; + } + + var handleLength = hostMetrics.viewWidth * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) * + this.ratio_; + this.setHandlePosition(this.constrainHandle_(handlePosition)); +}; + +/** + * Recalculate a vertical scrollbar's location and length. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + * @private + */ +Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) { + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) { + var viewSize = hostMetrics.viewHeight - 1; + if (this.pair_) { + // Shorten the scrollbar to make room for the corner square. + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (!this.workspace_.RTL) { + xCoordinate += hostMetrics.viewWidth - + Blockly.Scrollbar.scrollbarThickness - 1; + } + var yCoordinate = hostMetrics.absoluteTop + 0.5; + this.setPosition_(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) { + if (!this.pair_) { + // Only show the scrollbar if needed. + this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight); + } + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || + isNaN(this.ratio_)) { + this.ratio_ = 0; + } + + var handleLength = hostMetrics.viewHeight * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) * + this.ratio_; + this.setHandlePosition(this.constrainHandle_(handlePosition)); +}; + +/** + * Create all the DOM elements required for a scrollbar. + * The resulting widget is not sized. + * @param {string=} opt_class A class to be applied to this scrollbar. + * @private + */ +Blockly.Scrollbar.prototype.createDom_ = function(opt_class) { + /* Create the following DOM: + + + + + + + */ + var className = 'blocklyScrollbar' + + (this.horizontal_ ? 'Horizontal' : 'Vertical'); + if (opt_class) { + className += ' ' + opt_class; + } + this.outerSvg_ = Blockly.utils.createSvgElement( + 'svg', {'class': className}, null); + this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, this.outerSvg_); + this.svgBackground_ = Blockly.utils.createSvgElement( + 'rect', {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); + var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); + this.svgHandle_ = Blockly.utils.createSvgElement( + 'rect', + { + 'class': 'blocklyScrollbarHandle', + 'rx': radius, + 'ry': radius + }, + this.svgGroup_); + Blockly.utils.insertAfter_(this.outerSvg_, this.workspace_.getParentSvg()); +}; + +/** + * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't + * needed. + * @return {boolean} True if visible. + */ +Blockly.Scrollbar.prototype.isVisible = function() { + return this.isVisible_; +}; + +/** + * Set whether the scrollbar's container is visible and update + * display accordingly if visibility has changed. + * @param {boolean} visible Whether the container is visible + */ +Blockly.Scrollbar.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Set whether the scrollbar is visible. + * Only applies to non-paired scrollbars. + * @param {boolean} visible True if visible. + */ +Blockly.Scrollbar.prototype.setVisible = function(visible) { + var visibilityChanged = (visible != this.isVisible()); + + // Ideally this would also apply to scrollbar pairs, but that's a bigger + // headache (due to interactions with the corner square). + if (this.pair_) { + throw 'Unable to toggle visibility of paired scrollbars.'; + } + this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Update visibility of scrollbar based on whether it thinks it should + * be visible and whether its containing workspace is visible. + * We cannot rely on the containing workspace being hidden to hide us + * because it is not necessarily our parent in the DOM. + */ +Blockly.Scrollbar.prototype.updateDisplay_ = function() { + var show = true; + // Check whether our parent/container is visible. + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + if (show) { + this.outerSvg_.setAttribute('display', 'block'); + } else { + this.outerSvg_.setAttribute('display', 'none'); + } +}; + +/** + * Scroll by one pageful. + * Called when scrollbar background is clicked. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { + this.workspace_.markFocused(); + Blockly.Touch.clearTouchIdentifier(); // This is really a click. + this.cleanUp_(); + if (Blockly.utils.isRightButton(e)) { + // Right-click. + // Scrollbars have no context menu. + e.stopPropagation(); + return; + } + var mouseXY = Blockly.utils.mouseToSvg(e, this.workspace_.getParentSvg(), + this.workspace_.getInverseScreenCTM()); + var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; + + var handleXY = Blockly.utils.getInjectionDivXY_(this.svgHandle_); + var handleStart = this.horizontal_ ? handleXY.x : handleXY.y; + var handlePosition = this.handlePosition_; + + var pageLength = this.handleLength_ * 0.95; + if (mouseLocation <= handleStart) { + // Decrease the scrollbar's value by a page. + handlePosition -= pageLength; + } else if (mouseLocation >= handleStart + this.handleLength_) { + // Increase the scrollbar's value by a page. + handlePosition += pageLength; + } + + this.setHandlePosition(this.constrainHandle_(handlePosition)); + + this.onScroll_(); + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Start a dragging operation. + * Called when scrollbar handle is clicked. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) { + this.workspace_.markFocused(); + this.cleanUp_(); + if (Blockly.utils.isRightButton(e)) { + // Right-click. + // Scrollbars have no context menu. + e.stopPropagation(); + return; + } + // Look up the current translation and record it. + this.startDragHandle = this.handlePosition_; + + // Tell the workspace to setup its drag surface since it is about to move. + // onMouseMoveHandle will call onScroll which actually tells the workspace + // to move. + this.workspace_.setupDragSurface(); + + // Record the current mouse position. + this.startDragMouse_ = this.horizontal_ ? e.clientX : e.clientY; + Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document, + 'mouseup', this, this.onMouseUpHandle_); + Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document, + 'mousemove', this, this.onMouseMoveHandle_); + e.stopPropagation(); + e.preventDefault(); +}; + +/** + * Drag the scrollbar's handle. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) { + var currentMouse = this.horizontal_ ? e.clientX : e.clientY; + var mouseDelta = currentMouse - this.startDragMouse_; + var handlePosition = this.startDragHandle + mouseDelta; + // Position the bar. + this.setHandlePosition(this.constrainHandle_(handlePosition)); + this.onScroll_(); +}; + +/** + * Release the scrollbar handle and reset state accordingly. + * @private + */ +Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() { + // Tell the workspace to clean up now that the workspace is done moving. + this.workspace_.resetDragSurface(); + Blockly.Touch.clearTouchIdentifier(); + this.cleanUp_(); +}; + +/** + * Hide chaff and stop binding to mouseup and mousemove events. Call this to + * wrap up lose ends associated with the scrollbar. + * @private + */ +Blockly.Scrollbar.prototype.cleanUp_ = function() { + Blockly.hideChaff(true); + if (Blockly.Scrollbar.onMouseUpWrapper_) { + Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_); + Blockly.Scrollbar.onMouseUpWrapper_ = null; + } + if (Blockly.Scrollbar.onMouseMoveWrapper_) { + Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_); + Blockly.Scrollbar.onMouseMoveWrapper_ = null; + } +}; + +/** + * Constrain the handle's position within the minimum (0) and maximum + * (length of scrollbar) values allowed for the scrollbar. + * @param {number} value Value that is potentially out of bounds, in CSS pixels. + * @return {number} Constrained value, in CSS pixels. + * @private + */ +Blockly.Scrollbar.prototype.constrainHandle_ = function(value) { + if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) { + value = 0; + } else { + value = Math.min(value, this.scrollViewSize_ - this.handleLength_); + } + return value; +}; + +/** + * Called when scrollbar is moved. + * @private + */ +Blockly.Scrollbar.prototype.onScroll_ = function() { + var ratio = this.handlePosition_ / this.scrollViewSize_; + if (isNaN(ratio)) { + ratio = 0; + } + var xyRatio = {}; + if (this.horizontal_) { + xyRatio.x = ratio; + } else { + xyRatio.y = ratio; + } + this.workspace_.setMetrics(xyRatio); +}; + +/** + * Set the scrollbar handle's position. + * @param {number} value The distance from the top/left end of the bar, in CSS + * pixels. It may be larger than the maximum allowable position of the + * scrollbar handle. + */ +Blockly.Scrollbar.prototype.set = function(value) { + this.setHandlePosition(this.constrainHandle_(value * this.ratio_)); + this.onScroll_(); +}; + +/** + * Record the origin of the workspace that the scrollbar is in, in pixels + * relative to the injection div origin. This is for times when the scrollbar is + * used in an object whose origin isn't the same as the main workspace + * (e.g. in a flyout.) + * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. + */ +Blockly.Scrollbar.prototype.setOrigin = function(x, y) { + this.origin_ = new goog.math.Coordinate(x, y); +}; diff --git a/core/.svn/pristine/fe/fe332fa4337e064273bf32ca938bebc7cd8bd2aa.svn-base b/core/.svn/pristine/fe/fe332fa4337e064273bf32ca938bebc7cd8bd2aa.svn-base new file mode 100644 index 0000000..eadde65 --- /dev/null +++ b/core/.svn/pristine/fe/fe332fa4337e064273bf32ca938bebc7cd8bd2aa.svn-base @@ -0,0 +1,434 @@ +/** + * @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 generating executable code from + * Blockly code. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Generator'); + +goog.require('Blockly.Block'); +goog.require('goog.asserts'); + + +/** + * Class for a code generator that translates the blocks into a language. + * @param {string} name Language name of this generator. + * @constructor + */ +Blockly.Generator = function(name) { + this.name_ = name; + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = + new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g'); +}; + +/** + * Category to separate generated function names from variables and procedures. + */ +Blockly.Generator.NAME_TYPE = 'generated_function'; + +/** + * Arbitrary code to inject into locations that risk causing infinite loops. + * Any instances of '%1' will be replaced by the block ID that failed. + * E.g. ' checkTimeout(%1);\n' + * @type {?string} + */ +Blockly.Generator.prototype.INFINITE_LOOP_TRAP = null; + +/** + * Arbitrary code to inject before every statement. + * Any instances of '%1' will be replaced by the block ID of the statement. + * E.g. 'highlight(%1);\n' + * @type {?string} + */ +Blockly.Generator.prototype.STATEMENT_PREFIX = null; + +/** + * The method of indenting. Defaults to two spaces, but language generators + * may override this to increase indent or change to tabs. + * @type {string} + */ +Blockly.Generator.prototype.INDENT = ' '; + +/** + * Maximum length for a comment before wrapping. Does not account for + * indenting level. + * @type {number} + */ +Blockly.Generator.prototype.COMMENT_WRAP = 60; + +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.Generator.prototype.ORDER_OVERRIDES = []; + +/** + * Generate code for all blocks in the workspace to the specified language. + * @param {Blockly.Workspace} workspace Workspace to generate code from. + * @return {string} Generated code. + */ +Blockly.Generator.prototype.workspaceToCode = function(workspace) { + if (!workspace) { + // Backwards compatibility from before there could be multiple workspaces. + console.warn('No workspace specified in workspaceToCode call. Guessing.'); + workspace = Blockly.getMainWorkspace(); + } + var code = []; + this.init(workspace); + var blocks = workspace.getTopBlocks(true); + for (var x = 0, block; block = blocks[x]; x++) { + var line = this.blockToCode(block); + if (goog.isArray(line)) { + // Value blocks return tuples of code and operator order. + // Top-level blocks don't care about operator order. + line = line[0]; + } + if (line) { + if (block.outputConnection) { + // This block is a naked value. Ask the language's code generator if + // it wants to append a semicolon, or something. + line = this.scrubNakedValue(line); + } + code.push(line); + } + } + code = code.join('\n'); // Blank line between each section. + code = this.finish(code); + // Final scrubbing of whitespace. + code = code.replace(/^\s+\n/, ''); + code = code.replace(/\n\s+$/, '\n'); + code = code.replace(/[ \t]+\n/g, '\n'); + return code; +}; + +// The following are some helpful functions which can be used by multiple +// languages. + +/** + * Prepend a common prefix onto each line of code. + * @param {string} text The lines of code. + * @param {string} prefix The common prefix. + * @return {string} The prefixed lines of code. + */ +Blockly.Generator.prototype.prefixLines = function(text, prefix) { + return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix); +}; + +/** + * Recursively spider a tree of blocks, returning all their comments. + * @param {!Blockly.Block} block The block from which to start spidering. + * @return {string} Concatenated list of comments. + */ +Blockly.Generator.prototype.allNestedComments = function(block) { + var comments = []; + var blocks = block.getDescendants(); + for (var i = 0; i < blocks.length; i++) { + var comment = blocks[i].getCommentText(); + if (comment) { + comments.push(comment); + } + } + // Append an empty string to create a trailing line break when joined. + if (comments.length) { + comments.push(''); + } + return comments.join('\n'); +}; + +/** + * Generate code for the specified block (and attached blocks). + * @param {Blockly.Block} block The block to generate code for. + * @return {string|!Array} For statement blocks, the generated code. + * For value blocks, an array containing the generated code and an + * operator order value. Returns '' if block is null. + */ +Blockly.Generator.prototype.blockToCode = function(block) { + if (!block) { + return ''; + } + if (block.disabled) { + // Skip past this block if it is disabled. + return this.blockToCode(block.getNextBlock()); + } + + var func = this[block.type]; + goog.asserts.assertFunction(func, + 'Language "%s" does not know how to generate code for block type "%s".', + this.name_, block.type); + // First argument to func.call is the value of 'this' in the generator. + // Prior to 24 September 2013 'this' was the only way to access the block. + // The current prefered method of accessing the block is through the second + // argument to func.call, which becomes the first parameter to the generator. + var code = func.call(block, block); + if (goog.isArray(code)) { + // Value blocks return tuples of code and operator order. + goog.asserts.assert(block.outputConnection, + 'Expecting string from statement block "%s".', block.type); + return [this.scrub_(block, code[0]), code[1]]; + } else if (goog.isString(code)) { + var id = block.id.replace(/\$/g, '$$$$'); // Issue 251. + if (this.STATEMENT_PREFIX) { + code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + id + '\'') + + code; + } + return this.scrub_(block, code); + } else if (code === null) { + // Block has handled code generation itself. + return ''; + } else { + goog.asserts.fail('Invalid code generated: %s', code); + } +}; + +/** + * Generate code representing the specified value input. + * @param {!Blockly.Block} block The block containing the input. + * @param {string} name The name of the input. + * @param {number} outerOrder The maximum binding strength (minimum order value) + * of any operators adjacent to "block". + * @return {string} Generated code or '' if no blocks are connected or the + * specified input does not exist. + */ +Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) { + if (isNaN(outerOrder)) { + goog.asserts.fail('Expecting valid order from block "%s".', block.type); + } + var targetBlock = block.getInputTargetBlock(name); + if (!targetBlock) { + return ''; + } + var tuple = this.blockToCode(targetBlock); + if (tuple === '') { + // Disabled block. + return ''; + } + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".', + targetBlock.type); + var code = tuple[0]; + var innerOrder = tuple[1]; + if (isNaN(innerOrder)) { + goog.asserts.fail('Expecting valid order from value block "%s".', + targetBlock.type); + } + if (!code) { + return ''; + } + + // Add parentheses if needed. + var parensNeeded = false; + var outerOrderClass = Math.floor(outerOrder); + var innerOrderClass = Math.floor(innerOrder); + if (outerOrderClass <= innerOrderClass) { + if (outerOrderClass == innerOrderClass && + (outerOrderClass == 0 || outerOrderClass == 99)) { + // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. + // 0 is the atomic order, 99 is the none order. No parentheses needed. + // In all known languages multiple such code blocks are not order + // sensitive. In fact in Python ('a' 'b') 'c' would fail. + } else { + // The operators outside this code are stronger than the operators + // inside this code. To prevent the code from being pulled apart, + // wrap the code in parentheses. + parensNeeded = true; + // Check for special exceptions. + for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) { + if (this.ORDER_OVERRIDES[i][0] == outerOrder && + this.ORDER_OVERRIDES[i][1] == innerOrder) { + parensNeeded = false; + break; + } + } + } + } + if (parensNeeded) { + // Technically, this should be handled on a language-by-language basis. + // However all known (sane) languages use parentheses for grouping. + code = '(' + code + ')'; + } + return code; +}; + +/** + * Generate code representing the statement. Indent the code. + * @param {!Blockly.Block} block The block containing the input. + * @param {string} name The name of the input. + * @return {string} Generated code or '' if no blocks are connected. + */ +Blockly.Generator.prototype.statementToCode = function(block, name) { + var targetBlock = block.getInputTargetBlock(name); + var code = this.blockToCode(targetBlock); + // Value blocks must return code and order of operations info. + // Statement blocks must only return code. + goog.asserts.assertString(code, 'Expecting code from statement block "%s".', + targetBlock && targetBlock.type); + if (code) { + code = this.prefixLines(/** @type {string} */ (code), this.INDENT); + } + return code; +}; + +/** + * Add an infinite loop trap to the contents of a loop. + * If loop is empty, add a statment prefix for the loop block. + * @param {string} branch Code for loop contents. + * @param {string} id ID of enclosing block. + * @return {string} Loop contents, with infinite loop trap added. + */ +Blockly.Generator.prototype.addLoopTrap = function(branch, id) { + id = id.replace(/\$/g, '$$$$'); // Issue 251. + if (this.INFINITE_LOOP_TRAP) { + branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch; + } + if (this.STATEMENT_PREFIX) { + branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g, + '\'' + id + '\''), this.INDENT); + } + return branch; +}; + +/** + * Comma-separated list of reserved words. + * @type {string} + * @private + */ +Blockly.Generator.prototype.RESERVED_WORDS_ = ''; + +/** + * Add one or more words to the list of reserved words for this language. + * @param {string} words Comma-separated list of words to add to the list. + * No spaces. Duplicates are ok. + */ +Blockly.Generator.prototype.addReservedWords = function(words) { + this.RESERVED_WORDS_ += words + ','; +}; + +/** + * This is used as a placeholder in functions defined using + * Blockly.Generator.provideFunction_. It must not be legal code that could + * legitimately appear in a function definition (or comment), and it must + * not confuse the regular expression parser. + * @type {string} + * @private + */ +Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}'; + +/** + * Define a function to be included in the generated code. + * The first time this is called with a given desiredName, the code is + * saved and an actual name is generated. Subsequent calls with the + * same desiredName have no effect but have the same return value. + * + * It is up to the caller to make sure the same desiredName is not + * used for different code values. + * + * The code gets output when Blockly.Generator.finish() is called. + * + * @param {string} desiredName The desired name of the function (e.g., isPrime). + * @param {!Array.} code A list of statements. Use ' ' for indents. + * @return {string} The actual name of the new function. This may differ + * from desiredName if the former has already been taken by the user. + * @private + */ +Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) { + if (!this.definitions_[desiredName]) { + var functionName = this.variableDB_.getDistinctName(desiredName, + Blockly.Procedures.NAME_TYPE); + this.functionNames_[desiredName] = functionName; + var codeText = code.join('\n').replace( + this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); + // Change all ' ' indents into the desired indent. + // To avoid an infinite loop of replacements, change all indents to '\0' + // character first, then replace them all with the indent. + // We are assuming that no provided functions contain a literal null char. + var oldCodeText; + while (oldCodeText != codeText) { + oldCodeText = codeText; + codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0'); + } + codeText = codeText.replace(/\0/g, this.INDENT); + this.definitions_[desiredName] = codeText; + } + return this.functionNames_[desiredName]; +}; + +/** + * Hook for code to run before code generation starts. + * Subclasses may override this, e.g. to initialise the database of variable + * names. + * @param {!Blockly.Workspace} workspace Workspace to generate code from. + */ +Blockly.Generator.prototype.init = function( + /* eslint-disable no-unused-vars */ workspace + /* eslint-enable no-unused-vars */) { + // Optionally override +}; + +/** + * Common tasks for generating code from blocks. This is called from + * blockToCode and is called on every block, not just top level blocks. + * Subclasses may override this, e.g. to generate code for statements following + * the block, or to handle comments for the specified block and any connected + * value blocks. + * @param {!Blockly.Block} block The current block. + * @param {string} code The JavaScript code created for this block. + * @return {string} JavaScript code with comments and subsequent blocks added. + * @private + */ +Blockly.Generator.prototype.scrub_ = function( + /* eslint-disable no-unused-vars */ block, code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; + +/** + * Hook for code to run at end of code generation. + * Subclasses may override this, e.g. to prepend the generated code with the + * variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ +Blockly.Generator.prototype.finish = function( + /* eslint-disable no-unused-vars */ code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; + +/** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. + * Subclasses may override this, e.g. if their language does not allow + * naked values. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ +Blockly.Generator.prototype.scrubNakedValue = function( + /* eslint-disable no-unused-vars */ line + /* eslint-enable no-unused-vars */) { + // Optionally override + return line; +}; diff --git a/core/.svn/wc.db b/core/.svn/wc.db new file mode 100644 index 0000000..0f667cf Binary files /dev/null and b/core/.svn/wc.db differ diff --git a/core/.svn/wc.db-journal b/core/.svn/wc.db-journal new file mode 100644 index 0000000..e69de29 diff --git a/core/.toolbox.js.swp b/core/.toolbox.js.swp new file mode 100644 index 0000000..1081bcf Binary files /dev/null and b/core/.toolbox.js.swp differ diff --git a/core/block.js b/core/block.js index b186cb3..2367066 100644 --- a/core/block.js +++ b/core/block.js @@ -29,6 +29,7 @@ goog.provide('Blockly.Block'); goog.require('Blockly.Blocks'); goog.require('Blockly.Comment'); goog.require('Blockly.Connection'); +goog.require('Blockly.Extensions'); goog.require('Blockly.Input'); goog.require('Blockly.Mutator'); goog.require('Blockly.Warning'); @@ -46,14 +47,22 @@ goog.require('goog.string'); * @param {!Blockly.Workspace} workspace The block's workspace. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. - * @param {string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. * @constructor */ Blockly.Block = function(workspace, prototypeName, opt_id) { + if (typeof Blockly.Generator.prototype[prototypeName] !== 'undefined') { + console.warn('FUTURE ERROR: Block prototypeName "' + prototypeName + + '" conflicts with Blockly.Generator members. Registering Generators ' + + 'for this block type will incur errors.' + + '\nThis name will be DISALLOWED (throwing an error) in future ' + + 'versions of Blockly.'); + } + /** @type {string} */ this.id = (opt_id && !workspace.getBlockById(opt_id)) ? - opt_id : Blockly.genUid(); + opt_id : Blockly.utils.genUid(); workspace.blockDB_[this.id] = this; /** @type {Blockly.Connection} */ this.outputConnection = null; @@ -118,6 +127,8 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { this.comment = null; /** + * The block's position in workspace units. (0, 0) is at the workspace's + * origin; scale does not change this value. * @type {!goog.math.Coordinate} * @private */ @@ -127,6 +138,9 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { this.workspace = workspace; /** @type {boolean} */ this.isInFlyout = workspace.isFlyout; + /** @type {boolean} */ + this.isInMutator = workspace.isMutator; + /** @type {boolean} */ this.RTL = workspace.RTL; @@ -136,7 +150,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { this.type = prototypeName; var prototype = Blockly.Blocks[prototypeName]; goog.asserts.assertObject(prototype, - 'Error: "%s" is an unknown language block.', prototypeName); + 'Error: Unknown block type "%s".', prototypeName); goog.mixin(this, prototype); } @@ -149,13 +163,25 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { // Record initial inline state. /** @type {boolean|undefined} */ this.inputsInlineDefault = this.inputsInline; + + // Fire a create event. if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Create(this)); + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + Blockly.Events.fire(new Blockly.Events.BlockCreate(this)); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } + } // Bind an onchange function, if it exists. if (goog.isFunction(this.onchange)) { - this.onchangeWrapper_ = this.onchange.bind(this); - this.workspace.addChangeListener(this.onchangeWrapper_); + this.setOnChange(this.onchange); } }; @@ -187,6 +213,13 @@ Blockly.Block.prototype.data = null; */ Blockly.Block.prototype.colour_ = '#000000'; +/** + * Colour of the block as HSV hue value (0-360) + * @type {?number} + * @private + */ +Blockly.Block.prototype.hue_ = null; + /** * Dispose of this block. * @param {boolean} healStack If true, then try to heal any gap by connecting @@ -194,55 +227,81 @@ Blockly.Block.prototype.colour_ = '#000000'; * all children of this block. */ Blockly.Block.prototype.dispose = function(healStack) { + if (!this.workspace) { + // Already deleted. + return; + } // Terminate onchange event calls. if (this.onchangeWrapper_) { this.workspace.removeChangeListener(this.onchangeWrapper_); } this.unplug(healStack); if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Delete(this)); + Blockly.Events.fire(new Blockly.Events.BlockDelete(this)); } Blockly.Events.disable(); - // This block is now at the top of the workspace. - // Remove this block from the workspace's list of top-most blocks. - if (this.workspace) { - this.workspace.removeTopBlock(this); - // Remove from block database. - delete this.workspace.blockDB_[this.id]; - this.workspace = null; - } + try { + // This block is now at the top of the workspace. + // Remove this block from the workspace's list of top-most blocks. + if (this.workspace) { + this.workspace.removeTopBlock(this); + // Remove from block database. + delete this.workspace.blockDB_[this.id]; + this.workspace = null; + } - // Just deleting this block from the DOM would result in a memory leak as - // well as corruption of the connection database. Therefore we must - // methodically step through the blocks and carefully disassemble them. + // Just deleting this block from the DOM would result in a memory leak as + // well as corruption of the connection database. Therefore we must + // methodically step through the blocks and carefully disassemble them. - // First, dispose of all my children. - for (var i = this.childBlocks_.length - 1; i >= 0; i--) { - this.childBlocks_[i].dispose(false); + // First, dispose of all my children. + for (var i = this.childBlocks_.length - 1; i >= 0; i--) { + this.childBlocks_[i].dispose(false); + } + // Then dispose of myself. + // Dispose of all inputs and their fields. + for (var i = 0, input; input = this.inputList[i]; i++) { + input.dispose(); + } + this.inputList.length = 0; + // Dispose of any remaining connections (next/previous/output). + var connections = this.getConnections_(true); + for (var i = 0; i < connections.length; i++) { + var connection = connections[i]; + if (connection.isConnected()) { + connection.disconnect(); + } + connections[i].dispose(); + } + } finally { + Blockly.Events.enable(); } - // Then dispose of myself. - // Dispose of all inputs and their fields. +}; + +/** + * Call initModel on all fields on the block. + * May be called more than once. + * Either initModel or initSvg must be called after creating a block and before + * the first interaction with it. Interactions include UI actions + * (e.g. clicking and dragging) and firing events (e.g. create, delete, and + * change). + * @public + */ +Blockly.Block.prototype.initModel = function() { for (var i = 0, input; input = this.inputList[i]; i++) { - input.dispose(); - } - this.inputList.length = 0; - // Dispose of any remaining connections (next/previous/output). - var connections = this.getConnections_(true); - for (var i = 0; i < connections.length; i++) { - var connection = connections[i]; - if (connection.isConnected()) { - connection.disconnect(); + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field.initModel) { + field.initModel(); + } } - connections[i].dispose(); } - Blockly.Events.enable(); }; /** * Unplug this block from its superior block. If this block is a statement, * optionally reconnect the block underneath with the block on top. - * @param {boolean} opt_healStack Disconnect child statement and reconnect + * @param {boolean=} opt_healStack Disconnect child statement and reconnect * stack. Defaults to false. */ Blockly.Block.prototype.unplug = function(opt_healStack) { @@ -274,10 +333,13 @@ Blockly.Block.prototype.unplug = function(opt_healStack) { /** * Returns all connections originating from this block. + * @param {boolean} all If true, return all connections even hidden ones. * @return {!Array.} Array of connections. * @private */ -Blockly.Block.prototype.getConnections_ = function() { +Blockly.Block.prototype.getConnections_ = function( + /* eslint-disable no-unused-vars */ all + /* eslint-enable no-unused-vars */) { var myConnections = []; if (this.outputConnection) { myConnections.push(this.outputConnection); @@ -299,7 +361,7 @@ Blockly.Block.prototype.getConnections_ = function() { /** * Walks down a stack of blocks and finds the last next connection on the stack. * @return {Blockly.Connection} The last next connection on the stack, or null. - * @private + * @package */ Blockly.Block.prototype.lastConnectionInStack_ = function() { var nextConnection = this.nextConnection; @@ -321,41 +383,8 @@ Blockly.Block.prototype.lastConnectionInStack_ = function() { * @private */ Blockly.Block.prototype.bumpNeighbours_ = function() { - if (!this.workspace) { - return; // Deleted block. - } - if (Blockly.dragMode_ != Blockly.DRAG_NONE) { - return; // Don't bump blocks during a drag. - } - var rootBlock = this.getRootBlock(); - if (rootBlock.isInFlyout) { - return; // Don't move blocks around in a flyout. - } - // Loop though every connection on this block. - var myConnections = this.getConnections_(false); - for (var i = 0, connection; connection = myConnections[i]; i++) { - // Spider down from this block bumping all sub-blocks. - if (connection.isConnected() && connection.isSuperior()) { - connection.targetBlock().bumpNeighbours_(); - } - - var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS); - for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) { - // If both connections are connected, that's probably fine. But if - // either one of them is unconnected, then there could be confusion. - if (!connection.isConnected() || !otherConnection.isConnected()) { - // Only bump blocks if they are from different tree structures. - if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) { - // Always bump the inferior block. - if (connection.isSuperior()) { - otherConnection.bumpAwayFrom_(connection); - } else { - connection.bumpAwayFrom_(otherConnection); - } - } - } - } - } + console.warn('Not expected to reach this bumpNeighbours_ function. The ' + + 'BlockSvg function for bumpNeighbours_ was expected to be called instead.'); }; /** @@ -444,13 +473,7 @@ Blockly.Block.prototype.setParent = function(newParent) { } if (this.parentBlock_) { // Remove this block from the old parent's child list. - var children = this.parentBlock_.childBlocks_; - for (var child, x = 0; child = children[x]; x++) { - if (child == this) { - children.splice(x, 1); - break; - } - } + goog.array.remove(this.parentBlock_.childBlocks_, this); // Disconnect from superior blocks. if (this.previousConnection && this.previousConnection.isConnected()) { @@ -622,21 +645,55 @@ Blockly.Block.prototype.getColour = function() { return this.colour_; }; +/** + * Get the HSV hue value of a block. Null if hue not set. + * @return {?number} Hue value (0-360) + */ +Blockly.Block.prototype.getHue = function() { + return this.hue_; +}; + /** * Change the colour of a block. * @param {number|string} colour HSV hue value, or #RRGGBB string. */ Blockly.Block.prototype.setColour = function(colour) { - var hue = parseFloat(colour); - if (!isNaN(hue)) { + var hue = Number(colour); + if (!isNaN(hue) && 0 <= hue && hue <= 360) { + this.hue_ = hue; this.colour_ = Blockly.hueToRgb(hue); - } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) { + } else if (goog.isString(colour) && /^#[0-9a-fA-F]{6}$/.test(colour)) { this.colour_ = colour; + // Only store hue if colour is set as a hue. + this.hue_ = null; } else { throw 'Invalid colour: ' + colour; } }; +/** + * Sets a callback function to use whenever the block's parent workspace + * changes, replacing any prior onchange handler. This is usually only called + * from the constructor, the block type initializer function, or an extension + * initializer function. + * @param {function(Blockly.Events.Abstract)} onchangeFn The callback to call + * when the block's workspace changes. + * @throws {Error} if onchangeFn is not falsey or a function. + */ +Blockly.Block.prototype.setOnChange = function(onchangeFn) { + if (onchangeFn && !goog.isFunction(onchangeFn)) { + throw new Error("onchange must be a function."); + } + if (this.onchangeWrapper_) { + this.workspace.removeChangeListener(this.onchangeWrapper_); + } + this.onchange = onchangeFn; + if (this.onchange) { + this.onchangeWrapper_ = onchangeFn.bind(this); + this.workspace.addChangeListener(this.onchangeWrapper_); + } +}; + /** * Returns the named field from a block. * @param {string} name The name of the field. @@ -656,6 +713,7 @@ Blockly.Block.prototype.getField = function(name) { /** * Return all variables referenced by this block. * @return {!Array.} List of variable names. + * @package */ Blockly.Block.prototype.getVars = function() { var vars = []; @@ -670,61 +728,57 @@ Blockly.Block.prototype.getVars = function() { }; /** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. + * Return all variables referenced by this block. + * @return {!Array.} List of variable models. + * @package */ -Blockly.Block.prototype.renameVar = function(oldName, newName) { +Blockly.Block.prototype.getVarModels = function() { + var vars = []; for (var i = 0, input; input = this.inputList[i]; i++) { for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldVariable && - Blockly.Names.equals(oldName, field.getValue())) { - field.setValue(newName); + if (field instanceof Blockly.FieldVariable) { + var model = this.workspace.getVariableById(field.getValue()); + // Check if the variable actually exists (and isn't just a potential + // variable). + if (model) { + vars.push(model); + } } } } + return vars; }; /** - * Return all instances referenced by this block. - * @param {string=} opt_instanceType Optional type of the instances to collect, - * if not defined it collects all instances. - * @return {!Array.} List of instance names. + * Notification that a variable is renaming but keeping the same ID. If the + * variable is in use on this block, rerender to show the new name. + * @param {!Blockly.VariableModel} variable The variable being renamed. + * @package */ -Blockly.Block.prototype.getInstances = function(opt_instanceType) { - var vars = []; +Blockly.Block.prototype.updateVarName = function(variable) { for (var i = 0, input; input = this.inputList[i]; i++) { for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldInstance) { - var validInstance = opt_instanceType ? - field.getInstanceTypeValue(opt_instanceType) : - field.getValue(); - if (validInstance) { - vars.push(validInstance); - } + if (field instanceof Blockly.FieldVariable && + variable.getId() == field.getValue()) { + field.setText(variable.name); } } } - return vars; }; /** - * Notification that a instance is renaming. - * If the name and type matches one of this block's instances, rename it. - * @param {string} oldName Previous name of the instance. - * @param {string} newName Renamed instance. - * @param {string} instanceType Type of the instances to rename. + * Notification that a variable is renaming. + * If the ID matches one of this block's variables, rename it. + * @param {string} oldId ID of variable to rename. + * @param {string} newId ID of new variable. May be the same as oldId, but with + * an updated name. */ -Blockly.Block.prototype.renameInstance = function( - oldName, newName, instanceType) { +Blockly.Block.prototype.renameVarById = function(oldId, newId) { for (var i = 0, input; input = this.inputList[i]; i++) { for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldInstance) { - var validInstance = field.getInstanceTypeValue(instanceType); - if (validInstance && Blockly.Names.equals(oldName, validInstance)) { - field.setValue(newName); - } + if (field instanceof Blockly.FieldVariable && + oldId == field.getValue()) { + field.setValue(newId); } } } @@ -743,17 +797,6 @@ Blockly.Block.prototype.getFieldValue = function(name) { return null; }; -/** - * Returns the language-neutral value from the field of a block. - * @param {string} name The name of the field. - * @return {?string} Value from the field or null if field does not exist. - * @deprecated December 2013 - */ -Blockly.Block.prototype.getTitleValue = function(name) { - console.warn('Deprecated call to getTitleValue, use getFieldValue instead.'); - return this.getFieldValue(name); -}; - /** * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). * @param {string} newValue Value to be the new field. @@ -765,21 +808,10 @@ Blockly.Block.prototype.setFieldValue = function(newValue, name) { field.setValue(newValue); }; -/** - * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). - * @param {string} newValue Value to be the new field. - * @param {string} name The name of the field. - * @deprecated December 2013 - */ -Blockly.Block.prototype.setTitleValue = function(newValue, name) { - console.warn('Deprecated call to setTitleValue, use setFieldValue instead.'); - this.setFieldValue(newValue, name); -}; - /** * Set whether this block can chain onto the bottom of another block. * @param {boolean} newBoolean True if there can be a previous statement. - * @param {string|Array.|null|undefined} opt_check Statement type or + * @param {(string|Array.|null)=} opt_check Statement type or * list of statement types. Null/undefined if any type could be connected. */ Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { @@ -807,7 +839,7 @@ Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { /** * Set whether another block can chain onto the bottom of this block. * @param {boolean} newBoolean True if there can be a next statement. - * @param {string|Array.|null|undefined} opt_check Statement type or + * @param {(string|Array.|null)=} opt_check Statement type or * list of statement types. Null/undefined if any type could be connected. */ Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) { @@ -832,7 +864,7 @@ Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) { /** * Set whether this block returns a value. * @param {boolean} newBoolean True if there is an output. - * @param {string|Array.|null|undefined} opt_check Returned type or list + * @param {(string|Array.|null)=} opt_check Returned type or list * of returned types. Null or undefined if any type could be returned * (e.g. variable get). */ @@ -863,7 +895,7 @@ Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) { */ Blockly.Block.prototype.setInputsInline = function(newBoolean) { if (this.inputsInline != newBoolean) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this, 'inline', null, this.inputsInline, newBoolean)); this.inputsInline = newBoolean; } @@ -902,7 +934,7 @@ Blockly.Block.prototype.getInputsInline = function() { */ Blockly.Block.prototype.setDisabled = function(disabled) { if (this.disabled != disabled) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this, 'disabled', null, this.disabled, disabled)); this.disabled = disabled; } @@ -914,16 +946,15 @@ Blockly.Block.prototype.setDisabled = function(disabled) { * @return {boolean} True if disabled. */ Blockly.Block.prototype.getInheritedDisabled = function() { - var block = this; - while (true) { - block = block.getSurroundParent(); - if (!block) { - // Ran off the top. - return false; - } else if (block.disabled) { + var ancestor = this.getSurroundParent(); + while (ancestor) { + if (ancestor.disabled) { return true; } + ancestor = ancestor.getSurroundParent(); } + // Ran off the top. + return false; }; /** @@ -940,7 +971,7 @@ Blockly.Block.prototype.isCollapsed = function() { */ Blockly.Block.prototype.setCollapsed = function(collapsed) { if (this.collapsed_ != collapsed) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this, 'collapsed', null, this.collapsed_, collapsed)); this.collapsed_ = collapsed; } @@ -949,23 +980,30 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) { /** * Create a human-readable text representation of this block and any children. * @param {number=} opt_maxLength Truncate the string to this length. + * @param {string=} opt_emptyToken The placeholder string used to denote an + * empty field. If not specified, '?' is used. * @return {string} Text of block. */ -Blockly.Block.prototype.toString = function(opt_maxLength) { +Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { var text = []; + var emptyFieldPlaceholder = opt_emptyToken || '?'; if (this.collapsed_) { text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_); } else { for (var i = 0, input; input = this.inputList[i]; i++) { for (var j = 0, field; field = input.fieldRow[j]; j++) { - text.push(field.getText()); + if (field instanceof Blockly.FieldDropdown && !field.getValue()) { + text.push(emptyFieldPlaceholder); + } else { + text.push(field.getText()); + } } if (input.connection) { var child = input.connection.targetBlock(); if (child) { - text.push(child.toString()); + text.push(child.toString(undefined, opt_emptyToken)); } else { - text.push('?'); + text.push(emptyFieldPlaceholder); } } } @@ -1016,14 +1054,18 @@ Blockly.Block.prototype.appendDummyInput = function(opt_name) { * @param {!Object} json Structured data describing the block. */ Blockly.Block.prototype.jsonInit = function(json) { + // Validate inputs. - goog.asserts.assert(json['output'] == undefined || - json['previousStatement'] == undefined, + goog.asserts.assert( + json['output'] == undefined || json['previousStatement'] == undefined, 'Must not have both an output and a previousStatement.'); // Set basic properties of block. if (json['colour'] !== undefined) { - this.setColour(json['colour']); + var rawValue = json['colour']; + var colour = goog.isString(rawValue) ? + Blockly.utils.replaceMessageReferences(rawValue) : rawValue; + this.setColour(colour); } // Interpolate the message blocks. @@ -1048,11 +1090,65 @@ Blockly.Block.prototype.jsonInit = function(json) { this.setNextStatement(true, json['nextStatement']); } if (json['tooltip'] !== undefined) { - this.setTooltip(json['tooltip']); + var rawValue = json['tooltip']; + var localizedText = Blockly.utils.replaceMessageReferences(rawValue); + this.setTooltip(localizedText); + } + if (json['enableContextMenu'] !== undefined) { + var rawValue = json['enableContextMenu']; + this.contextMenu = !!rawValue; } if (json['helpUrl'] !== undefined) { - this.setHelpUrl(json['helpUrl']); + var rawValue = json['helpUrl']; + var localizedValue = Blockly.utils.replaceMessageReferences(rawValue); + this.setHelpUrl(localizedValue); + } + if (goog.isString(json['extensions'])) { + console.warn('JSON attribute \'extensions\' should be an array of ' + + 'strings. Found raw string in JSON for \'' + json['type'] + '\' block.'); + json['extensions'] = [json['extensions']]; // Correct and continue. + } + + // Add the mutator to the block + if (json['mutator'] !== undefined) { + Blockly.Extensions.apply(json['mutator'], this, true); + } + + if (Array.isArray(json['extensions'])) { + var extensionNames = json['extensions']; + for (var i = 0; i < extensionNames.length; ++i) { + var extensionName = extensionNames[i]; + Blockly.Extensions.apply(extensionName, this, false); + } + } +}; + +/** + * Add key/values from mixinObj to this block object. By default, this method + * will check that the keys in mixinObj will not overwrite existing values in + * the block, including prototype values. This provides some insurance against + * mixin / extension incompatibilities with future block features. This check + * can be disabled by passing true as the second argument. + * @param {!Object} mixinObj The key/values pairs to add to this block object. + * @param {boolean=} opt_disableCheck Option flag to disable overwrite checks. + */ +Blockly.Block.prototype.mixin = function(mixinObj, opt_disableCheck) { + if (goog.isDef(opt_disableCheck) && !goog.isBoolean(opt_disableCheck)) { + throw new Error("opt_disableCheck must be a boolean if provided"); + } + if (!opt_disableCheck) { + var overwrites = []; + for (var key in mixinObj) { + if (this[key] !== undefined) { + overwrites.push(key); + } + } + if (overwrites.length) { + throw new Error('Mixin will overwrite block members: ' + + JSON.stringify(overwrites)); + } } + goog.mixin(this, mixinObj); }; /** @@ -1060,12 +1156,12 @@ Blockly.Block.prototype.jsonInit = function(json) { * @param {string} message Text contains interpolation tokens (%1, %2, ...) * that match with fields or inputs defined in the args array. * @param {!Array} args Array of arguments to be interpolated. - * @param {string} lastDummyAlign If a dummy input is added at the end, + * @param {string=} lastDummyAlign If a dummy input is added at the end, * how should it be aligned? * @private */ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { - var tokens = Blockly.tokenizeInterpolation(message); + var tokens = Blockly.utils.tokenizeInterpolation(message); // Interpolate the arguments. Build a list of elements. var indexDup = []; var indexCount = 0; @@ -1073,10 +1169,14 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (typeof token == 'number') { - goog.asserts.assert(token > 0 && token <= args.length, - 'Message index "%s" out of range.', token); - goog.asserts.assert(!indexDup[token], - 'Message index "%s" duplicated.', token); + if (token <= 0 || token > args.length) { + throw new Error('Block "' + this.type + '": ' + + 'Message index %' + token + ' out of range.'); + } + if (indexDup[token]) { + throw new Error('Block "' + this.type + '": ' + + 'Message index %' + token + ' duplicated.'); + } indexDup[token] = true; indexCount++; elements.push(args[token - 1]); @@ -1087,11 +1187,14 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { } } } - goog.asserts.assert(indexCount == args.length, - 'Message does not reference all %s arg(s).', args.length); + if(indexCount != args.length) { + throw new Error('Block "' + this.type + '": ' + + 'Message does not reference all ' + args.length + ' arg(s).'); + } // Add last dummy input if needed. if (elements.length && (typeof elements[elements.length - 1] == 'string' || - elements[elements.length - 1]['type'].indexOf('field_') == 0)) { + goog.string.startsWith( + elements[elements.length - 1]['type'], 'field_'))) { var dummyInput = {type: 'input_dummy'}; if (lastDummyAlign) { dummyInput['align'] = lastDummyAlign; @@ -1115,60 +1218,59 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { var input = null; do { var altRepeat = false; - switch (element['type']) { - case 'input_value': - input = this.appendValueInput(element['name']); - break; - case 'input_statement': - input = this.appendStatementInput(element['name']); - break; - case 'input_dummy': - input = this.appendDummyInput(element['name']); - break; - case 'field_label': - field = new Blockly.FieldLabel(element['text'], element['class']); - break; - case 'field_input': - field = new Blockly.FieldTextInput(element['text']); - if (typeof element['spellcheck'] == 'boolean') { - field.setSpellcheck(element['spellcheck']); - } - break; - case 'field_angle': - field = new Blockly.FieldAngle(element['angle']); - break; - case 'field_checkbox': - field = new Blockly.FieldCheckbox( - element['checked'] ? 'TRUE' : 'FALSE'); - break; - case 'field_colour': - field = new Blockly.FieldColour(element['colour']); - break; - case 'field_variable': - field = new Blockly.FieldVariable(element['variable']); - break; - case 'field_dropdown': - field = new Blockly.FieldDropdown(element['options']); - break; - case 'field_image': - field = new Blockly.FieldImage(element['src'], - element['width'], element['height'], element['alt']); - break; - case 'field_number': - field = new Blockly.FieldNumber(element['text']); - break; - case 'field_date': - if (Blockly.FieldDate) { - field = new Blockly.FieldDate(element['date']); + if (typeof element == 'string') { + field = new Blockly.FieldLabel(element); + } else { + switch (element['type']) { + case 'input_value': + input = this.appendValueInput(element['name']); + break; + case 'input_statement': + input = this.appendStatementInput(element['name']); + break; + case 'input_dummy': + input = this.appendDummyInput(element['name']); + break; + case 'field_label': + field = Blockly.FieldLabel.fromJson(element); + break; + case 'field_input': + field = Blockly.FieldTextInput.fromJson(element); + break; + case 'field_angle': + field = Blockly.FieldAngle.fromJson(element); + break; + case 'field_checkbox': + field = Blockly.FieldCheckbox.fromJson(element); break; - } - // Fall through if FieldDate is not compiled in. - default: - // Unknown field. - if (element['alt']) { - element = element['alt']; - altRepeat = true; - } + case 'field_colour': + field = Blockly.FieldColour.fromJson(element); + break; + case 'field_variable': + field = Blockly.FieldVariable.fromJson(element); + break; + case 'field_dropdown': + field = Blockly.FieldDropdown.fromJson(element); + break; + case 'field_image': + field = Blockly.FieldImage.fromJson(element); + break; + case 'field_number': + field = Blockly.FieldNumber.fromJson(element); + break; + case 'field_date': + if (Blockly.FieldDate) { + field = Blockly.FieldDate.fromJson(element); + break; + } + // Fall through if FieldDate is not compiled in. + default: + // Unknown field. + if (element['alt']) { + element = element['alt']; + altRepeat = true; + } + } } } while (altRepeat); if (field) { @@ -1236,8 +1338,8 @@ Blockly.Block.prototype.moveInputBefore = function(name, refName) { } } goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name); - goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.', - refName); + goog.asserts.assert( + refIndex != -1, 'Reference input "%s" not found.', refName); this.moveNumberedInputBefore(inputIndex, refIndex); }; @@ -1251,9 +1353,9 @@ Blockly.Block.prototype.moveNumberedInputBefore = function( // Validate arguments. goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); goog.asserts.assert(inputIndex < this.inputList.length, - 'Input index ' + inputIndex + ' out of bounds.'); + 'Input index ' + inputIndex + ' out of bounds.'); goog.asserts.assert(refIndex <= this.inputList.length, - 'Reference input ' + refIndex + ' out of bounds.'); + 'Reference input ' + refIndex + ' out of bounds.'); // Remove input. var input = this.inputList[inputIndex]; this.inputList.splice(inputIndex, 1); @@ -1335,7 +1437,7 @@ Blockly.Block.prototype.getCommentText = function() { */ Blockly.Block.prototype.setCommentText = function(text) { if (this.comment != text) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this, 'comment', null, this.comment, text || '')); this.comment = text; } @@ -1344,8 +1446,12 @@ Blockly.Block.prototype.setCommentText = function(text) { /** * Set this block's warning text. * @param {?string} text The text, or null to delete. + * @param {string=} opt_id An optional ID for the warning text to be able to + * maintain multiple warnings. */ -Blockly.Block.prototype.setWarningText = function(text) { +Blockly.Block.prototype.setWarningText = function(text, + /* eslint-disable no-unused-vars */ opt_id + /* eslint-enable no-unused-vars */) { // NOP. }; @@ -1353,13 +1459,15 @@ Blockly.Block.prototype.setWarningText = function(text) { * Give this block a mutator dialog. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. */ -Blockly.Block.prototype.setMutator = function(mutator) { +Blockly.Block.prototype.setMutator = function( + /* eslint-disable no-unused-vars */ mutator + /* eslint-enable no-unused-vars */) { // NOP. }; /** * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0). + * drawing surface's origin (0,0), in workspace units. * @return {!goog.math.Coordinate} Object with .x and .y properties. */ Blockly.Block.prototype.getRelativeToSurfaceXY = function() { @@ -1368,12 +1476,12 @@ Blockly.Block.prototype.getRelativeToSurfaceXY = function() { /** * Move a block by a relative offset. - * @param {number} dx Horizontal offset. - * @param {number} dy Vertical offset. + * @param {number} dx Horizontal offset, in workspace units. + * @param {number} dy Vertical offset, in workspace units. */ Blockly.Block.prototype.moveBy = function(dx, dy) { goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); - var event = new Blockly.Events.Move(this); + var event = new Blockly.Events.BlockMove(this); this.xy_.translate(dx, dy); event.recordNew(); Blockly.Events.fire(event); @@ -1388,3 +1496,56 @@ Blockly.Block.prototype.moveBy = function(dx, dy) { Blockly.Block.prototype.makeConnection_ = function(type) { return new Blockly.Connection(this, type); }; + +/** + * Recursively checks whether all statement and value inputs are filled with + * blocks. Also checks all following statement blocks in this stack. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling + * whether shadow blocks are counted as filled. Defaults to true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ +Blockly.Block.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) { + // Account for the shadow block filledness toggle. + if (opt_shadowBlocksAreFilled === undefined) { + opt_shadowBlocksAreFilled = true; + } + if (!opt_shadowBlocksAreFilled && this.isShadow()) { + return false; + } + + // Recursively check each input block of the current block. + for (var i = 0, input; input = this.inputList[i]; i++) { + if (!input.connection) { + continue; + } + var target = input.connection.targetBlock(); + if (!target || !target.allInputsFilled(opt_shadowBlocksAreFilled)) { + return false; + } + } + + // Recursively check the next block after the current block. + var next = this.getNextBlock(); + if (next) { + return next.allInputsFilled(opt_shadowBlocksAreFilled); + } + + return true; +}; + +/** + * This method returns a string describing this Block in developer terms (type + * name and ID; English only). + * + * Intended to on be used in console logs and errors. If you need a string that + * uses the user's native language (including block text, field values, and + * child blocks), use [toString()]{@link Blockly.Block#toString}. + * @return {string} The description. + */ +Blockly.Block.prototype.toDevString = function() { + var msg = this.type ? '"' + this.type + '" block' : 'Block'; + if (this.id) { + msg += ' (id="' + this.id + '")'; + } + return msg; +}; diff --git a/core/block_drag_surface.js b/core/block_drag_surface.js new file mode 100644 index 0000000..be1edb3 --- /dev/null +++ b/core/block_drag_surface.js @@ -0,0 +1,220 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 A class that manages a surface for dragging blocks. When a + * block drag is started, we move the block (and children) to a separate DOM + * element that we move around using translate3d. At the end of the drag, the + * blocks are put back in into the SVG they came from. This helps performance by + * avoiding repainting the entire SVG on every mouse move while dragging blocks. + * @author picklesrus + */ + +'use strict'; + +goog.provide('Blockly.BlockDragSurfaceSvg'); +goog.require('Blockly.utils'); +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a drag surface for the currently dragged block. This is a separate + * SVG that contains only the currently moving block, or nothing. + * @param {!Element} container Containing element. + * @constructor + */ +Blockly.BlockDragSurfaceSvg = function(container) { + /** + * @type {!Element} + * @private + */ + this.container_ = container; + this.createDom(); +}; + +/** + * The SVG drag surface. Set once by Blockly.BlockDragSurfaceSvg.createDom. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.SVG_ = null; + +/** + * This is where blocks live while they are being dragged if the drag surface + * is enabled. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.dragGroup_ = null; + +/** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {Element} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.container_ = null; + +/** + * Cached value for the scale of the drag surface. + * Used to set/get the correct translation during and after a drag. + * @type {number} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1; + +/** + * Cached value for the translation of the drag surface. + * This translation is in pixel units, because the scale is applied to the + * drag group rather than the top-level SVG. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null; + +/** + * Create the drag surface and inject it into the container. + */ +Blockly.BlockDragSurfaceSvg.prototype.createDom = function() { + if (this.SVG_) { + return; // Already created. + } + this.SVG_ = Blockly.utils.createSvgElement('svg', { + 'xmlns': Blockly.SVG_NS, + 'xmlns:html': Blockly.HTML_NS, + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklyBlockDragSurface' + }, this.container_); + this.dragGroup_ = Blockly.utils.createSvgElement('g', {}, this.SVG_); +}; + +/** + * Set the SVG blocks on the drag surface's group and show the surface. + * Only one block group should be on the drag surface at a time. + * @param {!Element} blocks Block or group of blocks to place on the drag + * surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) { + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Already dragging a block.'); + // appendChild removes the blocks from the previous parent + this.dragGroup_.appendChild(blocks); + this.SVG_.style.display = 'block'; + this.surfaceXY_ = new goog.math.Coordinate(0, 0); +}; + +/** + * Translate and scale the entire drag surface group to the given position, to + * keep in sync with the workspace. + * @param {number} x X translation in workspace coordinates. + * @param {number} y Y translation in workspace coordinates. + * @param {number} scale Scale of the group. + */ +Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) { + this.scale_ = scale; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + this.dragGroup_.setAttribute('transform', 'translate('+ x + ','+ y + ')' + + ' scale(' + scale + ')'); +}; + +/** + * Translate the drag surface's SVG based on its internal state. + * @private + */ +Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() { + var x = this.surfaceXY_.x; + var y = this.surfaceXY_.y; + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being dragged on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + this.SVG_.style.display = 'block'; + + Blockly.utils.setCssTransform(this.SVG_, + 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); +}; + +/** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface. + * @param {number} y Y translation for the entire surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) { + this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_); + this.translateSurfaceInternal_(); +}; + +/** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!goog.math.Coordinate} Current translation of the surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() { + var xy = Blockly.utils.getRelativeXY(this.SVG_); + return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_); +}; + +/** + * Provide a reference to the drag group (primarily for + * BlockSvg.getRelativeToSurfaceXY). + * @return {Element} Drag surface group element. + */ +Blockly.BlockDragSurfaceSvg.prototype.getGroup = function() { + return this.dragGroup_; +}; + +/** + * Get the current blocks on the drag surface, if any (primarily + * for BlockSvg.getRelativeToSurfaceXY). + * @return {!Element|undefined} Drag surface block DOM element, or undefined + * if no blocks exist. + */ +Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() { + return this.dragGroup_.firstChild; +}; + +/** + * Clear the group and hide the surface; move the blocks off onto the provided + * element. + * If the block is being deleted it doesn't need to go back to the original + * surface, since it would be removed immediately during dispose. + * @param {Element=} opt_newSurface Surface the dragging blocks should be moved + * to, or null if the blocks should be removed from this surface without + * being moved to a different surface. + */ +Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) { + if (opt_newSurface) { + // appendChild removes the node from this.dragGroup_ + opt_newSurface.appendChild(this.getCurrentBlock()); + } else { + this.dragGroup_.removeChild(this.getCurrentBlock()); + } + this.SVG_.style.display = 'none'; + goog.asserts.assert( + this.dragGroup_.childNodes.length == 0, 'Drag group was not cleared.'); + this.surfaceXY_ = null; +}; diff --git a/core/block_dragger.js b/core/block_dragger.js new file mode 100644 index 0000000..c6a0a3b --- /dev/null +++ b/core/block_dragger.js @@ -0,0 +1,328 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a block visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BlockDragger'); + +goog.require('Blockly.DraggedConnectionManager'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a block dragger. It moves blocks around the workspace when they + * are being dragged by a mouse or touch. + * @param {!Blockly.Block} block The block to drag. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on. + * @constructor + */ +Blockly.BlockDragger = function(block, workspace) { + /** + * The top block in the stack that is being dragged. + * @type {!Blockly.BlockSvg} + * @private + */ + this.draggingBlock_ = block; + + /** + * The workspace on which the block is being dragged. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * Object that keeps track of connections on dragged blocks. + * @type {!Blockly.DraggedConnectionManager} + * @private + */ + this.draggedConnectionManager_ = new Blockly.DraggedConnectionManager( + this.draggingBlock_); + + /** + * Which delete area the mouse pointer is over, if any. + * One of {@link Blockly.DELETE_AREA_TRASH}, + * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. + * @type {?number} + * @private + */ + this.deleteArea_ = null; + + /** + * Whether the block would be deleted if dropped immediately. + * @type {boolean} + * @private + */ + this.wouldDeleteBlock_ = false; + + /** + * The location of the top left corner of the dragging block at the beginning + * of the drag in workspace coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY(); + + /** + * A list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @type {Array.} + * @private + */ + this.dragIconData_ = Blockly.BlockDragger.initIconData_(block); +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.BlockDragger.prototype.dispose = function() { + this.draggingBlock_ = null; + this.workspace_ = null; + this.startWorkspace_ = null; + this.dragIconData_.length = 0; + + if (this.draggedConnectionManager_) { + this.draggedConnectionManager_.dispose(); + this.draggedConnectionManager_ = null; + } +}; + +/** + * Make a list of all of the icons (comment, warning, and mutator) that are + * on this block and its descendants. Moving an icon moves the bubble that + * extends from it if that bubble is open. + * @param {!Blockly.BlockSvg} block The root block that is being dragged. + * @return {!Array.} The list of all icons and their locations. + * @private + */ +Blockly.BlockDragger.initIconData_ = function(block) { + // Build a list of icons that need to be moved and where they started. + var dragIconData = []; + var descendants = block.getDescendants(); + for (var i = 0, descendant; descendant = descendants[i]; i++) { + var icons = descendant.getIcons(); + for (var j = 0; j < icons.length; j++) { + var data = { + // goog.math.Coordinate with x and y properties (workspace coordinates). + location: icons[j].getIconLocation(), + // Blockly.Icon + icon: icons[j] + }; + dragIconData.push(data); + } + } + return dragIconData; +}; + +/** + * Start dragging a block. This includes moving it to the drag surface. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + + this.workspace_.setResizesEnabled(false); + Blockly.BlockSvg.disconnectUiStop_(); + + if (this.draggingBlock_.getParent()) { + this.draggingBlock_.unplug(); + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBlock_.translate(newLoc.x, newLoc.y); + this.draggingBlock_.disconnectUiEffect(); + } + this.draggingBlock_.setDragging(true); + // For future consideration: we may be able to put moveToDragSurface inside + // the block dragger, which would also let the block not track the block drag + // surface. + this.draggingBlock_.moveToDragSurface_(); + + if (this.workspace_.toolbox_) { + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.addStyle(style); + } +}; + +/** + * Execute a step of block dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) { + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBlock_.moveDuringDrag(newLoc); + this.dragIcons_(delta); + + this.deleteArea_ = this.workspace_.isDeleteArea(e); + this.draggedConnectionManager_.update(delta, this.deleteArea_); + + this.updateCursorDuringBlockDrag_(); +}; + +/** + * Finish a block drag and put the block back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBlock(e, currentDragDeltaXY); + this.dragIconData_ = []; + + Blockly.BlockSvg.disconnectUiStop_(); + + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + this.draggingBlock_.moveOffDragSurface_(newLoc); + + var deleted = this.maybeDeleteBlock_(); + if (!deleted) { + // These are expensive and don't need to be done if we're deleting. + this.draggingBlock_.moveConnections_(delta.x, delta.y); + this.draggingBlock_.setDragging(false); + this.draggedConnectionManager_.applyConnections(); + this.draggingBlock_.render(); + this.fireMoveEvent_(); + this.draggingBlock_.scheduleSnapAndBump(); + } + this.workspace_.setResizesEnabled(true); + + if (this.workspace_.toolbox_) { + var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' : + 'blocklyToolboxGrab'; + this.workspace_.toolbox_.removeStyle(style); + } + Blockly.Events.setGroup(false); +}; + +/** + * Fire a move event at the end of a block drag. + * @private + */ +Blockly.BlockDragger.prototype.fireMoveEvent_ = function() { + var event = new Blockly.Events.BlockMove(this.draggingBlock_); + event.oldCoordinate = this.startXY_; + event.recordNew(); + Blockly.Events.fire(event); +}; + +/** + * Shut the trash can and, if necessary, delete the dragging block. + * Should be called at the end of a block drag. + * @return {boolean} whether the block was deleted. + * @private + */ +Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() { + var trashcan = this.workspace_.trashcan; + + if (this.wouldDeleteBlock_) { + if (trashcan) { + goog.Timer.callOnce(trashcan.close, 100, trashcan); + } + // Fire a move event, so we know where to go back to for an undo. + this.fireMoveEvent_(); + this.draggingBlock_.dispose(false, true); + } else if (trashcan) { + // Make sure the trash can is closed. + trashcan.close(); + } + return this.wouldDeleteBlock_; +}; + +/** + * Update the cursor (and possibly the trash can lid) to reflect whether the + * dragging block would be deleted if released immediately. + * @private + */ +Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() { + this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock(); + var trashcan = this.workspace_.trashcan; + if (this.wouldDeleteBlock_) { + this.draggingBlock_.setDeleteStyle(true); + if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) { + trashcan.setOpen_(true); + } + } else { + this.draggingBlock_.setDeleteStyle(false); + if (trashcan) { + trashcan.setOpen_(false); + } + } +}; + +/** + * Convert a coordinate object from pixels to workspace units, including a + * correction for mutator workspaces. + * This function does not consider differing origins. It simply scales the + * input's x and y values. + * @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values + * in css pixel units. + * @return {!goog.math.Coordinate} The input coordinate divided by the workspace + * scale. + * @private + */ +Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { + var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale, + pixelCoord.y / this.workspace_.scale); + if (this.workspace_.isMutator) { + // If we're in a mutator, its scale is always 1, purely because of some + // oddities in our rendering optimizations. The actual scale is the same as + // the scale on the parent workspace. + // Fix that for dragging. + var mainScale = this.workspace_.options.parentWorkspace.scale; + result = result.scale(1 / mainScale); + } + return result; +}; + +/** + * Move all of the icons connected to this drag. + * @param {!goog.math.Coordinate} dxy How far to move the icons from their + * original positions, in workspace units. + * @private + */ +Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) { + // Moving icons moves their associated bubbles. + for (var i = 0; i < this.dragIconData_.length; i++) { + var data = this.dragIconData_[i]; + data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy)); + } +}; diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 7535db4..2166860 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -29,6 +29,8 @@ goog.provide('Blockly.BlockSvg.render'); goog.require('Blockly.BlockSvg'); +goog.require('goog.userAgent'); + // UI constants for rendering blocks. /** @@ -253,6 +255,28 @@ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR = Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' + (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 0.5); +/** + * Returns a bounding box describing the dimensions of this block + * and any blocks stacked below it. + * @return {!{height: number, width: number}} Object with height and width + * properties in workspace units. + */ +Blockly.BlockSvg.prototype.getHeightWidth = function() { + var height = this.height; + var width = this.width; + // Recursively add size of subsequent blocks. + var nextBlock = this.getNextBlock(); + if (nextBlock) { + var nextHeightWidth = nextBlock.getHeightWidth(); + height += nextHeightWidth.height - 4; // Height of tab. + width = Math.max(width, nextHeightWidth.width); + } else if (!this.nextConnection && !this.outputConnection) { + // Add a bit of margin under blocks with no bottom tab. + height += 2; + } + return {height: height, width: width}; +}; + /** * Render the block. * Lays out and reflows a block based on its contents and settings. @@ -279,6 +303,7 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { var inputRows = this.renderCompute_(cursorX); this.renderDraw_(cursorX, inputRows); + this.renderMoveConnections_(); if (opt_bubble !== false) { // Render all blocks above this one (propagate a reflow). @@ -287,7 +312,7 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { parentBlock.render(true); } else { // Top-most block. Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this.workspace); + this.workspace.resizeContents(); } } Blockly.Field.stopCache(); @@ -301,8 +326,8 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { * @return {number} X-coordinate of the end of the field row (plus a gap). * @private */ -Blockly.BlockSvg.prototype.renderFields_ = - function(fieldList, cursorX, cursorY) { +Blockly.BlockSvg.prototype.renderFields_ = function(fieldList, + cursorX, cursorY) { cursorY += Blockly.BlockSvg.INLINE_PADDING_Y; if (this.RTL) { cursorX = -cursorX; @@ -312,6 +337,7 @@ Blockly.BlockSvg.prototype.renderFields_ = if (!root) { continue; } + if (this.RTL) { cursorX -= field.renderSep + field.renderWidth; root.setAttribute('transform', @@ -502,7 +528,7 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { var prevBlock = this.previousConnection.targetBlock(); if (prevBlock && prevBlock.getNextBlock() == this) { this.squareTopLeftCorner_ = true; - } + } } else if (Blockly.BlockSvg.START_HAT) { // No output or previous connection. this.squareTopLeftCorner_ = true; @@ -516,10 +542,6 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { } } - // Fetch the block's coordinates on the surface for use in anchoring - // the connections. - var connectionsXY = this.getRelativeToSurfaceXY(); - // Assemble the block's path. var steps = []; var inlineSteps = []; @@ -529,12 +551,11 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { var highlightSteps = []; var highlightInlineSteps = []; - this.renderDrawTop_(steps, highlightSteps, connectionsXY, - inputRows.rightEdge); + this.renderDrawTop_(steps, highlightSteps, inputRows.rightEdge); var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps, - highlightInlineSteps, connectionsXY, inputRows, iconWidth); - this.renderDrawBottom_(steps, highlightSteps, connectionsXY, cursorY); - this.renderDrawLeft_(steps, highlightSteps, connectionsXY, cursorY); + highlightInlineSteps, inputRows, iconWidth); + this.renderDrawBottom_(steps, highlightSteps, cursorY); + this.renderDrawLeft_(steps, highlightSteps); var pathString = steps.join(' ') + '\n' + inlineSteps.join(' '); this.svgPath_.setAttribute('d', pathString); @@ -549,16 +570,51 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { } }; +/** + * Update all of the connections on this block with the new locations calculated + * in renderCompute. Also move all of the connected blocks based on the new + * connection locations. + * @private + */ +Blockly.BlockSvg.prototype.renderMoveConnections_ = function() { + var blockTL = this.getRelativeToSurfaceXY(); + // Don't tighten previous or output connections because they are inferior + // connections. + if (this.previousConnection) { + this.previousConnection.moveToOffset(blockTL); + } + if (this.outputConnection) { + this.outputConnection.moveToOffset(blockTL); + } + + for (var i = 0; i < this.inputList.length; i++) { + var conn = this.inputList[i].connection; + if (conn) { + conn.moveToOffset(blockTL); + if (conn.isConnected()) { + conn.tighten_(); + } + } + } + + if (this.nextConnection) { + this.nextConnection.moveToOffset(blockTL); + if (this.nextConnection.isConnected()) { + this.nextConnection.tighten_(); + } + } + +}; + /** * Render the top edge of the block. * @param {!Array.} steps Path of block outline. * @param {!Array.} highlightSteps Path of block highlights. - * @param {!Object} connectionsXY Location of block. * @param {number} rightEdge Minimum width of block. * @private */ -Blockly.BlockSvg.prototype.renderDrawTop_ = - function(steps, highlightSteps, connectionsXY, rightEdge) { +Blockly.BlockSvg.prototype.renderDrawTop_ = function(steps, + highlightSteps, rightEdge) { // Position the cursor at the top-left starting point. if (this.squareTopLeftCorner_) { steps.push('m 0,0'); @@ -585,12 +641,10 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15); steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT); highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT); - // Create previous block connection. - var connectionX = connectionsXY.x + (this.RTL ? + + var connectionX = (this.RTL ? -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH); - var connectionY = connectionsXY.y; - this.previousConnection.moveTo(connectionX, connectionY); - // This connection will be tightened when the parent renders. + this.previousConnection.setOffsetInBlock(connectionX, 0); } steps.push('H', rightEdge); highlightSteps.push('H', rightEdge - 0.5); @@ -603,7 +657,6 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = * @param {!Array.} highlightSteps Path of block highlights. * @param {!Array.} inlineSteps Inline block outlines. * @param {!Array.} highlightInlineSteps Inline block highlights. - * @param {!Object} connectionsXY Location of block. * @param {!Array.>} inputRows 2D array of objects, each * containing position information. * @param {number} iconWidth Offset of first row due to icons. @@ -611,7 +664,7 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = * @private */ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, - inlineSteps, highlightInlineSteps, connectionsXY, inputRows, iconWidth) { + inlineSteps, highlightInlineSteps, inputRows, iconWidth) { var cursorX; var cursorY = 0; var connectionX, connectionY; @@ -692,20 +745,16 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, } // Create inline input connection. if (this.RTL) { - connectionX = connectionsXY.x - cursorX - + connectionX = -cursorX - Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X + input.renderWidth + 1; } else { - connectionX = connectionsXY.x + cursorX + + connectionX = cursorX + Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X - input.renderWidth - 1; } - connectionY = connectionsXY.y + cursorY + - Blockly.BlockSvg.INLINE_PADDING_Y + 1; - input.connection.moveTo(connectionX, connectionY); - if (input.connection.isConnected()) { - input.connection.tighten_(); - } + connectionY = cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1; + input.connection.setOffsetInBlock(connectionX, connectionY); } } @@ -747,12 +796,10 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, ',-2.1'); } // Create external input connection. - connectionX = connectionsXY.x + - (this.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1); - connectionY = connectionsXY.y + cursorY; - input.connection.moveTo(connectionX, connectionY); + connectionX = this.RTL ? -inputRows.rightEdge - 1 : + inputRows.rightEdge + 1; + input.connection.setOffsetInBlock(connectionX, cursorY); if (input.connection.isConnected()) { - input.connection.tighten_(); this.width = Math.max(this.width, inputRows.rightEdge + input.connection.targetBlock().getHeightWidth().width - Blockly.BlockSvg.TAB_WIDTH + 1); @@ -830,11 +877,10 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, highlightSteps.push('H', inputRows.rightEdge - 0.5); } // Create statement connection. - connectionX = connectionsXY.x + (this.RTL ? -cursorX : cursorX + 1); - connectionY = connectionsXY.y + cursorY + 1; - input.connection.moveTo(connectionX, connectionY); + connectionX = this.RTL ? -cursorX : cursorX + 1; + input.connection.setOffsetInBlock(connectionX, cursorY + 1); + if (input.connection.isConnected()) { - input.connection.tighten_(); this.width = Math.max(this.width, inputRows.statementEdge + input.connection.targetBlock().getHeightWidth().width); } @@ -865,12 +911,11 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, * Render the bottom edge of the block. * @param {!Array.} steps Path of block outline. * @param {!Array.} highlightSteps Path of block highlights. - * @param {!Object} connectionsXY Location of block. * @param {number} cursorY Height of block. * @private */ -Blockly.BlockSvg.prototype.renderDrawBottom_ = - function(steps, highlightSteps, connectionsXY, cursorY) { +Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, + highlightSteps, cursorY) { this.height += cursorY + 1; // Add one for the shadow. if (this.nextConnection) { steps.push('H', (Blockly.BlockSvg.NOTCH_WIDTH + (this.RTL ? 0.5 : - 0.5)) + @@ -878,15 +923,11 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = // Create next block connection. var connectionX; if (this.RTL) { - connectionX = connectionsXY.x - Blockly.BlockSvg.NOTCH_WIDTH; + connectionX = -Blockly.BlockSvg.NOTCH_WIDTH; } else { - connectionX = connectionsXY.x + Blockly.BlockSvg.NOTCH_WIDTH; - } - var connectionY = connectionsXY.y + cursorY + 1; - this.nextConnection.moveTo(connectionX, connectionY); - if (this.nextConnection.isConnected()) { - this.nextConnection.tighten_(); + connectionX = Blockly.BlockSvg.NOTCH_WIDTH; } + this.nextConnection.setOffsetInBlock(connectionX, cursorY + 1); this.height += 4; // Height of tab. } @@ -916,16 +957,12 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = * Render the left edge of the block. * @param {!Array.} steps Path of block outline. * @param {!Array.} highlightSteps Path of block highlights. - * @param {!Object} connectionsXY Location of block. - * @param {number} cursorY Height of block. * @private */ -Blockly.BlockSvg.prototype.renderDrawLeft_ = - function(steps, highlightSteps, connectionsXY, cursorY) { +Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, highlightSteps) { if (this.outputConnection) { // Create output connection. - this.outputConnection.moveTo(connectionsXY.x, connectionsXY.y); - // This connection will be tightened when the parent renders. + this.outputConnection.setOffsetInBlock(0, 0); steps.push('V', Blockly.BlockSvg.TAB_HEIGHT); steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH + diff --git a/core/block_svg.js b/core/block_svg.js index 745d58b..ae4087c 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -28,7 +28,11 @@ goog.provide('Blockly.BlockSvg'); goog.require('Blockly.Block'); goog.require('Blockly.ContextMenu'); +goog.require('Blockly.Grid'); goog.require('Blockly.RenderedConnection'); +goog.require('Blockly.Tooltip'); +goog.require('Blockly.Touch'); +goog.require('Blockly.utils'); goog.require('goog.Timer'); goog.require('goog.asserts'); goog.require('goog.dom'); @@ -42,8 +46,8 @@ goog.require('goog.userAgent'); * @param {!Blockly.Workspace} workspace The block's workspace. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. * @extends {Blockly.Block} * @constructor */ @@ -53,13 +57,14 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { * @type {SVGElement} * @private */ - this.svgGroup_ = Blockly.createSvgElement('g', {}, null); + this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, null); + this.svgGroup_.translate_ = ''; /** * @type {SVGElement} * @private */ - this.svgPathDark_ = Blockly.createSvgElement('path', + this.svgPathDark_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}, this.svgGroup_); @@ -67,20 +72,28 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { * @type {SVGElement} * @private */ - this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'}, + this.svgPath_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyPath'}, this.svgGroup_); /** * @type {SVGElement} * @private */ - this.svgPathLight_ = Blockly.createSvgElement('path', + this.svgPathLight_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyPathLight'}, this.svgGroup_); this.svgPath_.tooltip = this; /** @type {boolean} */ this.rendered = false; + /** + * Whether to move the block to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ + this.useDragSurface_ = Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_; + Blockly.Tooltip.bindMouseEvents(this.svgPath_); Blockly.BlockSvg.superClass_.constructor.call(this, workspace, prototypeName, opt_id); @@ -89,10 +102,12 @@ goog.inherits(Blockly.BlockSvg, Blockly.Block); /** * Height of this block, not including any statement blocks above or below. + * Height is in workspace units. */ Blockly.BlockSvg.prototype.height = 0; /** * Width of this block, including any connected value blocks. + * Width is in workspace units. */ Blockly.BlockSvg.prototype.width = 0; @@ -103,6 +118,14 @@ Blockly.BlockSvg.prototype.width = 0; */ Blockly.BlockSvg.prototype.dragStartXY_ = null; +/** + * Map from IDs for warnings text to PIDs of functions to apply them. + * Used to be able to maintain multiple warnings. + * @type {Object.} + * @private + */ +Blockly.BlockSvg.prototype.warningTextDb_ = null; + /** * Constant for identifying rows that are to be rendered inline. * Don't collide with Blockly.INPUT_VALUE and friends. @@ -126,11 +149,8 @@ Blockly.BlockSvg.prototype.initSvg = function() { this.updateColour(); this.updateMovable(); if (!this.workspace.options.readOnly && !this.eventsInit_) { - Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this, - this.onMouseDown_); - var thisBlock = this; - Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null, - function(e) {Blockly.longStart_(e, thisBlock);}); + Blockly.bindEventWithChecks_( + this.getSvgRoot(), 'mousedown', this, this.onMouseDown_); } this.eventsInit_ = true; @@ -156,8 +176,11 @@ Blockly.BlockSvg.prototype.select = function() { oldId = Blockly.selected.id; // Unselect any previously selected block. Blockly.Events.disable(); - Blockly.selected.unselect(); - Blockly.Events.enable(); + try { + Blockly.selected.unselect(); + } finally { + Blockly.Events.enable(); + } } var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id); event.workspaceId = this.workspace.id; @@ -216,70 +239,6 @@ Blockly.BlockSvg.prototype.getIcons = function() { return icons; }; -/** - * Wrapper function called when a mouseUp occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.BlockSvg.onMouseUpWrapper_ = null; - -/** - * Wrapper function called when a mouseMove occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.BlockSvg.onMouseMoveWrapper_ = null; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.BlockSvg.terminateDrag_ = function() { - Blockly.BlockSvg.disconnectUiStop_(); - if (Blockly.BlockSvg.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_); - Blockly.BlockSvg.onMouseUpWrapper_ = null; - } - if (Blockly.BlockSvg.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_); - Blockly.BlockSvg.onMouseMoveWrapper_ = null; - } - var selected = Blockly.selected; - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Terminate a drag operation. - if (selected) { - // Update the connection locations. - var xy = selected.getRelativeToSurfaceXY(); - var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_); - var event = new Blockly.Events.Move(selected); - event.oldCoordinate = selected.dragStartXY_; - event.recordNew(); - Blockly.Events.fire(event); - - selected.moveConnections_(dxy.x, dxy.y); - delete selected.draggedBubbles_; - selected.setDragging_(false); - selected.render(); - // Ensure that any stap and bump are part of this move's event group. - var group = Blockly.Events.getGroup(); - setTimeout(function() { - Blockly.Events.setGroup(group); - selected.snapToGrid(); - Blockly.Events.setGroup(false); - }, Blockly.BUMP_DELAY / 2); - setTimeout(function() { - Blockly.Events.setGroup(group); - selected.bumpNeighbours_(); - Blockly.Events.setGroup(false); - }, Blockly.BUMP_DELAY); - // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this.workspace); - } - } - Blockly.dragMode_ = Blockly.DRAG_NONE; - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); -}; - /** * Set parent of this block to be a new block or null. * @param {Blockly.BlockSvg} newParent New parent block. @@ -311,41 +270,134 @@ Blockly.BlockSvg.prototype.setParent = function(newParent) { /** * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0). - * @return {!goog.math.Coordinate} Object with .x and .y properties. + * drawing surface's origin (0,0), in workspace units. + * If the block is on the workspace, (0, 0) is the origin of the workspace + * coordinate system. + * This does not change with workspace scale. + * @return {!goog.math.Coordinate} Object with .x and .y properties in + * workspace coordinates. */ Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() { var x = 0; var y = 0; + + var dragSurfaceGroup = this.useDragSurface_ ? + this.workspace.blockDragSurface_.getGroup() : null; + var element = this.getSvgRoot(); if (element) { do { // Loop through this block and every parent. - var xy = Blockly.getRelativeXY_(element); + var xy = Blockly.utils.getRelativeXY(element); x += xy.x; y += xy.y; + // If this element is the current element on the drag surface, include + // the translation of the drag surface itself. + if (this.useDragSurface_ && + this.workspace.blockDragSurface_.getCurrentBlock() == element) { + var surfaceTranslation = this.workspace.blockDragSurface_.getSurfaceTranslation(); + x += surfaceTranslation.x; + y += surfaceTranslation.y; + } element = element.parentNode; - } while (element && element != this.workspace.getCanvas()); + } while (element && element != this.workspace.getCanvas() && + element != dragSurfaceGroup); } return new goog.math.Coordinate(x, y); }; /** * Move a block by a relative offset. - * @param {number} dx Horizontal offset. - * @param {number} dy Vertical offset. + * @param {number} dx Horizontal offset in workspace units. + * @param {number} dy Vertical offset in workspace units. */ Blockly.BlockSvg.prototype.moveBy = function(dx, dy) { goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); - var event = new Blockly.Events.Move(this); + var event = new Blockly.Events.BlockMove(this); var xy = this.getRelativeToSurfaceXY(); - this.getSvgRoot().setAttribute('transform', - 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')'); + this.translate(xy.x + dx, xy.y + dy); this.moveConnections_(dx, dy); event.recordNew(); + this.workspace.resizeContents(); Blockly.Events.fire(event); }; +/** + * Transforms a block by setting the translation on the transform attribute + * of the block's SVG. + * @param {number} x The x coordinate of the translation in workspace units. + * @param {number} y The y coordinate of the translation in workspace units. + */ +Blockly.BlockSvg.prototype.translate = function(x, y) { + this.getSvgRoot().setAttribute('transform', + 'translate(' + x + ',' + y + ')'); +}; + +/** + * Move this block to its workspace's drag surface, accounting for positioning. + * Generally should be called at the same time as setDragging_(true). + * Does nothing if useDragSurface_ is false. + * @private + */ +Blockly.BlockSvg.prototype.moveToDragSurface_ = function() { + if (!this.useDragSurface_) { + return; + } + // The translation for drag surface blocks, + // is equal to the current relative-to-surface position, + // to keep the position in sync as it move on/off the surface. + // This is in workspace coordinates. + var xy = this.getRelativeToSurfaceXY(); + this.clearTransformAttributes_(); + this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y); + // Execute the move on the top-level SVG component + this.workspace.blockDragSurface_.setBlocksAndShow(this.getSvgRoot()); +}; + +/** + * Move this block back to the workspace block canvas. + * Generally should be called at the same time as setDragging_(false). + * Does nothing if useDragSurface_ is false. + * @param {!goog.math.Coordinate} newXY The position the block should take on + * on the workspace canvas, in workspace coordinates. + * @private + */ +Blockly.BlockSvg.prototype.moveOffDragSurface_ = function(newXY) { + if (!this.useDragSurface_) { + return; + } + // Translate to current position, turning off 3d. + this.translate(newXY.x, newXY.y); + this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas()); +}; + +/** + * Move this block during a drag, taking into account whether we are using a + * drag surface to translate blocks. + * This block must be a top-level block. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) { + if (this.useDragSurface_) { + this.workspace.blockDragSurface_.translateSurface(newLoc.x, newLoc.y); + } else { + this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')'; + this.svgGroup_.setAttribute('transform', + this.svgGroup_.translate_ + this.svgGroup_.skew_); + } +}; + +/** + * Clear the block of transform="..." attributes. + * Used when the block is switching from 3d to 2d transform or vice versa. + * @private + */ +Blockly.BlockSvg.prototype.clearTransformAttributes_ = function() { + Blockly.utils.removeAttribute(this.getSvgRoot(), 'transform'); +}; + /** * Snap this block to the nearest grid point. */ @@ -353,7 +405,7 @@ Blockly.BlockSvg.prototype.snapToGrid = function() { if (!this.workspace) { return; // Deleted block. } - if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + if (this.workspace.isDragging()) { return; // Don't bump blocks during a drag. } if (this.getParent()) { @@ -362,11 +414,11 @@ Blockly.BlockSvg.prototype.snapToGrid = function() { if (this.isInFlyout) { return; // Don't move blocks around in a flyout. } - if (!this.workspace.options.gridOptions || - !this.workspace.options.gridOptions['snap']) { + var grid = this.workspace.getGrid(); + if (!grid || !grid.shouldSnap()) { return; // Config says no snapping. } - var spacing = this.workspace.options.gridOptions['spacing']; + var spacing = grid.getSpacing(); var half = spacing / 2; var xy = this.getRelativeToSurfaceXY(); var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x; @@ -378,31 +430,10 @@ Blockly.BlockSvg.prototype.snapToGrid = function() { } }; -/** - * Returns a bounding box describing the dimensions of this block - * and any blocks stacked below it. - * @return {!{height: number, width: number}} Object with height and width - * properties. - */ -Blockly.BlockSvg.prototype.getHeightWidth = function() { - var height = this.height; - var width = this.width; - // Recursively add size of subsequent blocks. - var nextBlock = this.getNextBlock(); - if (nextBlock) { - var nextHeightWidth = nextBlock.getHeightWidth(); - height += nextHeightWidth.height - 4; // Height of tab. - width = Math.max(width, nextHeightWidth.width); - } else if (!this.nextConnection && !this.outputConnection) { - // Add a bit of margin under blocks with no bottom tab. - height += 2; - } - return {height: height, width: width}; -}; - /** * Returns the coordinates of a bounding box describing the dimensions of this * block and any blocks stacked below it. + * Coordinate system: workspace coordinates. * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}} * Object with top left and bottom right coordinates of the bounding box. */ @@ -481,23 +512,7 @@ Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) { * @param {boolean} forward If true go forward, otherwise backward. */ Blockly.BlockSvg.prototype.tab = function(start, forward) { - // This function need not be efficient since it runs once on a keypress. - // Create an ordered list of all text fields and connected inputs. - var list = []; - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldTextInput) { - // TODO: Also support dropdown fields. - list.push(field); - } - } - if (input.connection) { - var block = input.connection.targetBlock(); - if (block) { - list.push(block); - } - } - } + var list = this.createTabList_(); var i = list.indexOf(start); if (i == -1) { // No start location, start at the beginning or end. @@ -518,112 +533,39 @@ Blockly.BlockSvg.prototype.tab = function(start, forward) { }; /** - * Handle a mouse-down on an SVG block. - * @param {!Event} e Mouse down event. + * Create an ordered list of all text fields and connected inputs. + * @return {!Array.} The ordered list. * @private */ -Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { - if (this.workspace.options.readOnly) { - return; - } - if (this.isInFlyout) { - e.stopPropagation(); - return; - } - this.workspace.markFocused(); - // Update Blockly's knowledge of its own location. - Blockly.svgResize(this.workspace); - Blockly.terminateDrag_(); - this.select(); - Blockly.hideChaff(); - this.workspace.recordDeleteAreas(); - if (Blockly.isRightButton(e)) { - // Right-click. - this.showContextMenu_(e); - } else if (!this.isMovable()) { - // Allow immovable blocks to be selected and context menued, but not - // dragged. Let this event bubble up to document, so the workspace may be - // dragged instead. - return; - } else { - if (!Blockly.Events.getGroup()) { - Blockly.Events.setGroup(true); +Blockly.BlockSvg.prototype.createTabList_ = function() { + // This function need not be efficient since it runs once on a keypress. + var list = []; + for (var i = 0, input; input = this.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + if (field instanceof Blockly.FieldTextInput) { + // TODO(# 1276): Also support dropdown fields. + list.push(field); + } } - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - - this.dragStartXY_ = this.getRelativeToSurfaceXY(); - this.workspace.startDrag(e, this.dragStartXY_); - - Blockly.dragMode_ = Blockly.DRAG_STICKY; - Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, this.onMouseUp_); - Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.onMouseMove_); - // Build a list of bubbles that need to be moved and where they started. - this.draggedBubbles_ = []; - var descendants = this.getDescendants(); - for (var i = 0, descendant; descendant = descendants[i]; i++) { - var icons = descendant.getIcons(); - for (var j = 0; j < icons.length; j++) { - var data = icons[j].getIconLocation(); - data.bubble = icons[j]; - this.draggedBubbles_.push(data); + if (input.connection) { + var block = input.connection.targetBlock(); + if (block) { + list.push(block); } } } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); + return list; }; /** - * Handle a mouse-up anywhere in the SVG pane. Is only registered when a - * block is clicked. We can't use mouseUp on the block since a fast-moving - * cursor can briefly escape the block before it catches up. - * @param {!Event} e Mouse up event. + * Handle a mouse-down on an SVG block. + * @param {!Event} e Mouse down event or touch start event. * @private */ -Blockly.BlockSvg.prototype.onMouseUp_ = function(e) { - if (Blockly.dragMode_ != Blockly.DRAG_FREE && - !Blockly.WidgetDiv.isVisible()) { - Blockly.Events.fire( - new Blockly.Events.Ui(this, 'click', undefined, undefined)); - } - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { - // Connect two blocks together. - Blockly.localConnection_.connect(Blockly.highlightedConnection_); - if (this.rendered) { - // Trigger a connection animation. - // Determine which connection is inferior (lower in the source stack). - var inferiorConnection = Blockly.localConnection_.isSuperior() ? - Blockly.highlightedConnection_ : Blockly.localConnection_; - inferiorConnection.getSourceBlock().connectionUiEffect(); - } - if (this.workspace.trashcan) { - // Don't throw an object in the trash can if it just got connected. - this.workspace.trashcan.close(); - } - } else if (!this.getParent() && Blockly.selected.isDeletable() && - this.workspace.isDeleteArea(e)) { - var trashcan = this.workspace.trashcan; - if (trashcan) { - goog.Timer.callOnce(trashcan.close, 100, trashcan); - } - Blockly.selected.dispose(false, true); - // Dropping a block on the trash can will usually cause the workspace to - // resize to contain the newly positioned block. Force a second resize - // now that the block has been deleted. - Blockly.asyncSvgResize(this.workspace); - } - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); - if (!Blockly.WidgetDiv.isVisible()) { - Blockly.Events.setGroup(false); +Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { + var gesture = this.workspace.getGesture(e); + if (gesture) { + gesture.handleBlockStart(e, this); } }; @@ -652,35 +594,10 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { var menuOptions = []; if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { - // Option to duplicate this block. - var duplicateOption = { - text: Blockly.Msg.DUPLICATE_BLOCK, - enabled: true, - callback: function() { - Blockly.duplicate_(block); - } - }; - if (this.getDescendants().length > this.workspace.remainingCapacity()) { - duplicateOption.enabled = false; - } - menuOptions.push(duplicateOption); - + menuOptions.push(Blockly.ContextMenu.blockDuplicateOption(block)); if (this.isEditable() && !this.collapsed_ && this.workspace.options.comments) { - // Option to add/remove a comment. - var commentOption = {enabled: !goog.userAgent.IE}; - if (this.comment) { - commentOption.text = Blockly.Msg.REMOVE_COMMENT; - commentOption.callback = function() { - block.setCommentText(null); - }; - } else { - commentOption.text = Blockly.Msg.ADD_COMMENT; - commentOption.callback = function() { - block.setCommentText(''); - }; - } - menuOptions.push(commentOption); + menuOptions.push(Blockly.ContextMenu.blockCommentOption(block)); } // Option to make block inline. @@ -735,38 +652,13 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { menuOptions.push(disableOption); } - // Option to delete this block. - // Count the number of blocks that are nested in this block. - var descendantCount = this.getDescendants().length; - var nextBlock = this.getNextBlock(); - if (nextBlock) { - // Blocks in the current stack would survive this block's deletion. - descendantCount -= nextBlock.getDescendants().length; - } - var deleteOption = { - text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK : - Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)), - enabled: true, - callback: function() { - Blockly.Events.setGroup(true); - block.dispose(true, true); - Blockly.Events.setGroup(false); - } - }; - menuOptions.push(deleteOption); + menuOptions.push(Blockly.ContextMenu.blockDeleteOption(block)); } - // Option to get help. - var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; - var helpOption = {enabled: !!url}; - helpOption.text = Blockly.Msg.HELP; - helpOption.callback = function() { - block.showHelp_(); - }; - menuOptions.push(helpOption); + menuOptions.push(Blockly.ContextMenu.blockHelpOption(block)); // Allow the block to add or modify menuOptions. - if (this.customContextMenu && !block.isInFlyout) { + if (this.customContextMenu) { this.customContextMenu(menuOptions); } @@ -777,8 +669,10 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { /** * Move the connections for this block and all blocks attached under it. * Also update any attached bubbles. - * @param {number} dx Horizontal offset from current location. - * @param {number} dy Vertical offset from current location. + * @param {number} dx Horizontal offset from current location, in workspace + * units. + * @param {number} dy Vertical offset from current location, in workspace + * units. * @private */ Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) { @@ -805,120 +699,26 @@ Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) { /** * Recursively adds or removes the dragging class to this node and its children. * @param {boolean} adding True if adding, false if removing. - * @private + * @package */ -Blockly.BlockSvg.prototype.setDragging_ = function(adding) { +Blockly.BlockSvg.prototype.setDragging = function(adding) { if (adding) { var group = this.getSvgRoot(); group.translate_ = ''; group.skew_ = ''; - this.addDragging(); Blockly.draggingConnections_ = Blockly.draggingConnections_.concat(this.getConnections_(true)); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); } else { - this.removeDragging(); Blockly.draggingConnections_ = []; + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging'); } // Recurse through all blocks attached under this one. for (var i = 0; i < this.childBlocks_.length; i++) { - this.childBlocks_[i].setDragging_(adding); - } -}; - -/** - * Drag this block to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { - if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && - e.button == 0) { - /* HACK: - Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove - events on certain touch actions. Ignore events with these signatures. - This may result in a one-pixel blind spot in other browsers, - but this shouldn't be noticeable. */ - e.stopPropagation(); - return; - } - - var oldXY = this.getRelativeToSurfaceXY(); - var newXY = this.workspace.moveDrag(e); - - if (Blockly.dragMode_ == Blockly.DRAG_STICKY) { - // Still dragging within the sticky DRAG_RADIUS. - var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale; - if (dr > Blockly.DRAG_RADIUS) { - // Switch to unrestricted dragging. - Blockly.dragMode_ = Blockly.DRAG_FREE; - Blockly.longStop_(); - if (this.parentBlock_) { - // Push this block to the very top of the stack. - this.unplug(); - var group = this.getSvgRoot(); - group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; - this.disconnectUiEffect(); - } - this.setDragging_(true); - } - } - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Unrestricted dragging. - var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_); - var group = this.getSvgRoot(); - group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; - group.setAttribute('transform', group.translate_ + group.skew_); - // Drag all the nested bubbles. - for (var i = 0; i < this.draggedBubbles_.length; i++) { - var commentData = this.draggedBubbles_[i]; - commentData.bubble.setIconLocation( - goog.math.Coordinate.sum(commentData, dxy)); - } - - // Check to see if any of this block's connections are within range of - // another block's connection. - var myConnections = this.getConnections_(false); - // Also check the last connection on this stack - var lastOnStack = this.lastConnectionInStack_(); - if (lastOnStack && lastOnStack != this.nextConnection) { - myConnections.push(lastOnStack); - } - var closestConnection = null; - var localConnection = null; - var radiusConnection = Blockly.SNAP_RADIUS; - for (var i = 0; i < myConnections.length; i++) { - var myConnection = myConnections[i]; - var neighbour = myConnection.closest(radiusConnection, dxy); - if (neighbour.connection) { - closestConnection = neighbour.connection; - localConnection = myConnection; - radiusConnection = neighbour.radius; - } - } - - // Remove connection highlighting if needed. - if (Blockly.highlightedConnection_ && - Blockly.highlightedConnection_ != closestConnection) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - Blockly.localConnection_ = null; - } - // Add connection highlighting if needed. - if (closestConnection && - closestConnection != Blockly.highlightedConnection_) { - closestConnection.highlight(); - Blockly.highlightedConnection_ = closestConnection; - Blockly.localConnection_ = localConnection; - } - // Provide visual indication of whether the block will be deleted if - // dropped here. - if (this.isDeletable()) { - this.workspace.isDeleteArea(e); - } + this.childBlocks_[i].setDragging(adding); } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); }; /** @@ -926,11 +726,11 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { */ Blockly.BlockSvg.prototype.updateMovable = function() { if (this.isMovable()) { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); } else { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); } }; @@ -949,11 +749,9 @@ Blockly.BlockSvg.prototype.setMovable = function(movable) { */ Blockly.BlockSvg.prototype.setEditable = function(editable) { Blockly.BlockSvg.superClass_.setEditable.call(this, editable); - if (this.rendered) { - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].updateEditable(); - } + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].updateEditable(); } }; @@ -982,11 +780,19 @@ Blockly.BlockSvg.prototype.getSvgRoot = function() { * @param {boolean} animate If true, show a disposal animation and sound. */ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { + if (!this.workspace) { + // The block has already been deleted. + return; + } + Blockly.Tooltip.hide(); Blockly.Field.startCache(); + // Save the block's workspace temporarily so we can resize the + // contents once the block is disposed. + var blockWorkspace = this.workspace; // If this block is being dragged, unlink the mouse events. if (Blockly.selected == this) { this.unselect(); - Blockly.terminateDrag_(); + this.workspace.cancelCurrentGesture(); } // If this block has a context menu open, close it. if (Blockly.ContextMenu.currentBlock == this) { @@ -1000,15 +806,27 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { // Stop rerendering. this.rendered = false; + // Clear pending warnings. + if (this.warningTextDb_) { + for (var n in this.warningTextDb_) { + clearTimeout(this.warningTextDb_[n]); + } + this.warningTextDb_ = null; + } + Blockly.Events.disable(); - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].dispose(); + try { + var icons = this.getIcons(); + for (var i = 0; i < icons.length; i++) { + icons[i].dispose(); + } + } finally { + Blockly.Events.enable(); } - Blockly.Events.enable(); Blockly.BlockSvg.superClass_.dispose.call(this, healStack); goog.dom.removeNode(this.svgGroup_); + blockWorkspace.resizeContents(); // Sever JavaScript to DOM connections. this.svgGroup_ = null; this.svgPath_ = null; @@ -1021,10 +839,9 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { * Play some UI effects (sound, animation) when disposing of a block. */ Blockly.BlockSvg.prototype.disposeUiEffect = function() { - this.workspace.playAudio('delete'); + this.workspace.getAudioManager().play('delete'); - var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), - this.workspace); + var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); // Deeply clone the current block. var clone = this.svgGroup_.cloneNode(true); clone.translateX_ = xy.x; @@ -1034,13 +851,13 @@ Blockly.BlockSvg.prototype.disposeUiEffect = function() { this.workspace.getParentSvg().appendChild(clone); clone.bBox_ = clone.getBBox(); // Start the animation. - Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date(), + Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date, this.workspace.scale); }; /** * Animate a cloned block and eventually dispose of it. - * This is a class method, not an instace method since the original block has + * This is a class method, not an instance method since the original block has * been destroyed and is no longer accessible. * @param {!Element} clone SVG element to animate and dispose of. * @param {boolean} rtl True if RTL, false if LTR. @@ -1049,7 +866,7 @@ Blockly.BlockSvg.prototype.disposeUiEffect = function() { * @private */ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { - var ms = (new Date()) - start; + var ms = new Date - start; var percent = ms / 150; if (percent > 1) { goog.dom.removeNode(clone); @@ -1060,10 +877,8 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { var scale = (1 - percent) * workspaceScale; clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + ' scale(' + scale + ')'); - var closure = function() { - Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale); - }; - setTimeout(closure, 10); + setTimeout( + Blockly.BlockSvg.disposeUiStep_, 10, clone, rtl, start, workspaceScale); } }; @@ -1071,13 +886,12 @@ Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { * Play some UI effects (sound, ripple) after a connection has been established. */ Blockly.BlockSvg.prototype.connectionUiEffect = function() { - this.workspace.playAudio('click'); + this.workspace.getAudioManager().play('click'); if (this.workspace.scale < 1) { return; // Too small to care about visual effects. } // Determine the absolute coordinates of the inferior block. - var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), - this.workspace); + var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_)); // Offset the coordinates based on the two connection types, fix scale. if (this.outputConnection) { xy.x += (this.RTL ? 3 : -3) * this.workspace.scale; @@ -1086,12 +900,18 @@ Blockly.BlockSvg.prototype.connectionUiEffect = function() { xy.x += (this.RTL ? -23 : 23) * this.workspace.scale; xy.y += 3 * this.workspace.scale; } - var ripple = Blockly.createSvgElement('circle', - {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none', - 'stroke': '#888', 'stroke-width': 10}, + var ripple = Blockly.utils.createSvgElement('circle', + { + 'cx': xy.x, + 'cy': xy.y, + 'r': 0, + 'fill': 'none', + 'stroke': '#888', + 'stroke-width': 10 + }, this.workspace.getParentSvg()); // Start the animation. - Blockly.BlockSvg.connectionUiStep_(ripple, new Date(), this.workspace.scale); + Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); }; /** @@ -1102,17 +922,15 @@ Blockly.BlockSvg.prototype.connectionUiEffect = function() { * @private */ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { - var ms = (new Date()) - start; + var ms = new Date - start; var percent = ms / 150; if (percent > 1) { goog.dom.removeNode(ripple); } else { ripple.setAttribute('r', percent * 25 * workspaceScale); ripple.style.opacity = 1 - percent; - var closure = function() { - Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale); - }; - Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10); + Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout( + Blockly.BlockSvg.connectionUiStep_, 10, ripple, start, workspaceScale); } }; @@ -1120,7 +938,7 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { * Play some UI effects (sound, animation) when disconnecting a block. */ Blockly.BlockSvg.prototype.disconnectUiEffect = function() { - this.workspace.playAudio('disconnect'); + this.workspace.getAudioManager().play('disconnect'); if (this.workspace.scale < 1) { return; // Too small to care about visual effects. } @@ -1133,7 +951,7 @@ Blockly.BlockSvg.prototype.disconnectUiEffect = function() { magnitude *= -1; } // Start the animation. - Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date()); + Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date); }; /** @@ -1147,20 +965,19 @@ Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { var DURATION = 200; // Milliseconds. var WIGGLES = 3; // Half oscillations. - var ms = (new Date()) - start; + var ms = new Date - start; var percent = ms / DURATION; if (percent > 1) { group.skew_ = ''; } else { - var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) * - (1 - percent) * magnitude); + var skew = Math.round( + Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude); group.skew_ = 'skewX(' + skew + ')'; - var closure = function() { - Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start); - }; Blockly.BlockSvg.disconnectUiStop_.group = group; - Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10); + Blockly.BlockSvg.disconnectUiStop_.pid = + setTimeout( + Blockly.BlockSvg.disconnectUiStep_, 10, group, magnitude, start); } group.setAttribute('transform', group.translate_ + group.skew_); }; @@ -1221,9 +1038,10 @@ Blockly.BlockSvg.prototype.updateColour = function() { } // Bump every dropdown to change its colour. + // TODO (#1456) for (var x = 0, input; input = this.inputList[x]; x++) { for (var y = 0, field; field = input.fieldRow[y]; y++) { - field.setText(null); + field.forceRerender(); } } }; @@ -1232,19 +1050,17 @@ Blockly.BlockSvg.prototype.updateColour = function() { * Enable or disable a block. */ Blockly.BlockSvg.prototype.updateDisabled = function() { - var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); if (this.disabled || this.getInheritedDisabled()) { - if (!hasClass) { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); + var added = Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (added) { this.svgPath_.setAttribute('fill', 'url(#' + this.workspace.options.disabledPatternId + ')'); } } else { - if (hasClass) { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); + var removed = Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); + if (removed) { this.updateColour(); } } @@ -1299,30 +1115,30 @@ Blockly.BlockSvg.prototype.setCommentText = function(text) { * maintain multiple warnings. */ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { - if (!this.setWarningText.pid_) { + if (!this.warningTextDb_) { // Create a database of warning PIDs. // Only runs once per block (and only those with warnings). - this.setWarningText.pid_ = Object.create(null); + this.warningTextDb_ = Object.create(null); } var id = opt_id || ''; if (!id) { - // Kill all previous pending processes, this edit supercedes them all. - for (var n in this.setWarningText.pid_) { - clearTimeout(this.setWarningText.pid_[n]); - delete this.setWarningText.pid_[n]; + // Kill all previous pending processes, this edit supersedes them all. + for (var n in this.warningTextDb_) { + clearTimeout(this.warningTextDb_[n]); + delete this.warningTextDb_[n]; } - } else if (this.setWarningText.pid_[id]) { + } else if (this.warningTextDb_[id]) { // Only queue up the latest change. Kill any earlier pending process. - clearTimeout(this.setWarningText.pid_[id]); - delete this.setWarningText.pid_[id]; + clearTimeout(this.warningTextDb_[id]); + delete this.warningTextDb_[id]; } - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { + if (this.workspace.isDragging()) { // Don't change the warning text during a drag. // Wait until the drag finishes. var thisBlock = this; - this.setWarningText.pid_[id] = setTimeout(function() { + this.warningTextDb_[id] = setTimeout(function() { if (thisBlock.workspace) { // Check block wasn't deleted. - delete thisBlock.setWarningText.pid_[id]; + delete thisBlock.warningTextDb_[id]; thisBlock.setWarningText(text, id); } }, 100); @@ -1353,7 +1169,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { } this.warning.setText(/** @type {string} */ (text), id); } else { - // Dispose all warnings if no id is given. + // Dispose all warnings if no ID is given. if (this.warning && !id) { this.warning.dispose(); changedState = true; @@ -1364,7 +1180,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { if (!newText) { this.warning.dispose(); } - changedState = oldText == newText; + changedState = oldText != newText; } } if (changedState && this.rendered) { @@ -1403,38 +1219,54 @@ Blockly.BlockSvg.prototype.setDisabled = function(disabled) { }; /** - * Select this block. Highlight it visually. + * Set whether the block is highlighted or not. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {boolean} highlighted True if highlighted. */ -Blockly.BlockSvg.prototype.addSelect = function() { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); - // Move the selected block to the top of the stack. - this.svgGroup_.parentNode.appendChild(this.svgGroup_); +Blockly.BlockSvg.prototype.setHighlighted = function(highlighted) { + if (!this.rendered) { + return; + } + if (highlighted) { + this.svgPath_.setAttribute('filter', + 'url(#' + this.workspace.options.embossFilterId + ')'); + this.svgPathLight_.style.display = 'none'; + } else { + Blockly.utils.removeAttribute(this.svgPath_, 'filter'); + delete this.svgPathLight_.style.display; + } }; /** - * Unselect this block. Remove its highlighting. + * Select this block. Highlight it visually. */ -Blockly.BlockSvg.prototype.removeSelect = function() { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); +Blockly.BlockSvg.prototype.addSelect = function() { + Blockly.utils.addClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); }; /** - * Adds the dragging class to this block. - * Also disables the highlights/shadows to improve performance. + * Unselect this block. Remove its highlighting. */ -Blockly.BlockSvg.prototype.addDragging = function() { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); +Blockly.BlockSvg.prototype.removeSelect = function() { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.svgGroup_), 'blocklySelected'); }; /** - * Removes the dragging class from this block. + * Update the cursor over this block by adding or removing a class. + * @param {boolean} enable True if the delete cursor should be shown, false + * otherwise. + * @package */ -Blockly.BlockSvg.prototype.removeDragging = function() { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); +Blockly.BlockSvg.prototype.setDeleteStyle = function(enable) { + if (enable) { + Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggingDelete'); + } else { + Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_), + 'blocklyDraggingDelete'); + } }; // Overrides of functions on Blockly.Block that take into account whether the @@ -1452,14 +1284,30 @@ Blockly.BlockSvg.prototype.setColour = function(colour) { } }; +/** + * Move this block to the front of the visible workspace. + * tags do not respect z-index so SVG renders them in the + * order that they are in the DOM. By placing this block first within the + * block group's , it will render on top of any other blocks. + * @package + */ +Blockly.BlockSvg.prototype.bringToFront = function() { + var block = this; + do { + var root = block.getSvgRoot(); + root.parentNode.appendChild(root); + block = block.getParent(); + } while (block); +}; + /** * Set whether this block can chain onto the bottom of another block. * @param {boolean} newBoolean True if there can be a previous statement. - * @param {string|Array.|null|undefined} opt_check Statement type or + * @param {(string|Array.|null)=} opt_check Statement type or * list of statement types. Null/undefined if any type could be connected. */ -Blockly.BlockSvg.prototype.setPreviousStatement = - function(newBoolean, opt_check) { +Blockly.BlockSvg.prototype.setPreviousStatement = function(newBoolean, + opt_check) { Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, opt_check); @@ -1472,7 +1320,7 @@ Blockly.BlockSvg.prototype.setPreviousStatement = /** * Set whether another block can chain onto the bottom of this block. * @param {boolean} newBoolean True if there can be a next statement. - * @param {string|Array.|null|undefined} opt_check Statement type or + * @param {(string|Array.|null)=} opt_check Statement type or * list of statement types. Null/undefined if any type could be connected. */ Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { @@ -1488,7 +1336,7 @@ Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { /** * Set whether this block returns a value. * @param {boolean} newBoolean True if there is an output. - * @param {string|Array.|null|undefined} opt_check Returned type or list + * @param {(string|Array.|null)=} opt_check Returned type or list * of returned types. Null or undefined if any type could be returned * (e.g. variable get). */ @@ -1574,7 +1422,7 @@ Blockly.BlockSvg.prototype.appendInput_ = function(type, name) { * Otherwise, for a non-rendered block return an empty list, and for a * collapsed block don't return inputs connections. * @return {!Array.} Array of connections. - * @private + * @package */ Blockly.BlockSvg.prototype.getConnections_ = function(all) { var myConnections = []; @@ -1608,3 +1456,72 @@ Blockly.BlockSvg.prototype.getConnections_ = function(all) { Blockly.BlockSvg.prototype.makeConnection_ = function(type) { return new Blockly.RenderedConnection(this, type); }; + +/** + * Bump unconnected blocks out of alignment. Two blocks which aren't actually + * connected should not coincidentally line up on screen. + * @private + */ +Blockly.BlockSvg.prototype.bumpNeighbours_ = function() { + if (!this.workspace) { + return; // Deleted block. + } + if (this.workspace.isDragging()) { + return; // Don't bump blocks during a drag. + } + var rootBlock = this.getRootBlock(); + if (rootBlock.isInFlyout) { + return; // Don't move blocks around in a flyout. + } + // Loop through every connection on this block. + var myConnections = this.getConnections_(false); + for (var i = 0, connection; connection = myConnections[i]; i++) { + + // Spider down from this block bumping all sub-blocks. + if (connection.isConnected() && connection.isSuperior()) { + connection.targetBlock().bumpNeighbours_(); + } + + var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS); + for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) { + + // If both connections are connected, that's probably fine. But if + // either one of them is unconnected, then there could be confusion. + if (!connection.isConnected() || !otherConnection.isConnected()) { + // Only bump blocks if they are from different tree structures. + if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) { + + // Always bump the inferior block. + if (connection.isSuperior()) { + otherConnection.bumpAwayFrom_(connection); + } else { + connection.bumpAwayFrom_(otherConnection); + } + } + } + } + } +}; + +/** + * Schedule snapping to grid and bumping neighbours to occur after a brief + * delay. + * @package + */ +Blockly.BlockSvg.prototype.scheduleSnapAndBump = function() { + var block = this; + // Ensure that any snap and bump are part of this move's event group. + var group = Blockly.Events.getGroup(); + + setTimeout(function() { + Blockly.Events.setGroup(group); + block.snapToGrid(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY / 2); + + setTimeout(function() { + Blockly.Events.setGroup(group); + block.bumpNeighbours_(); + Blockly.Events.setGroup(false); + }, Blockly.BUMP_DELAY); +}; diff --git a/core/blockly.js b/core/blockly.js index eae51dd..427d451 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -24,7 +24,10 @@ */ 'use strict'; -// Top level object for Blockly. +/** + * The top level namespace used to access the Blockly library. + * @namespace Blockly + **/ goog.provide('Blockly'); goog.require('Blockly.BlockSvg.render'); @@ -36,16 +39,15 @@ goog.require('Blockly.FieldColour'); // Add it only if you need it. //goog.require('Blockly.FieldDate'); goog.require('Blockly.FieldDropdown'); -goog.require('Blockly.FieldInstance'); goog.require('Blockly.FieldImage'); goog.require('Blockly.FieldTextInput'); goog.require('Blockly.FieldNumber'); goog.require('Blockly.FieldVariable'); goog.require('Blockly.Generator'); goog.require('Blockly.Msg'); -goog.require('Blockly.StaticTyping'); goog.require('Blockly.Procedures'); goog.require('Blockly.Toolbox'); +goog.require('Blockly.Touch'); goog.require('Blockly.WidgetDiv'); goog.require('Blockly.WorkspaceSvg'); goog.require('Blockly.constants'); @@ -56,7 +58,10 @@ goog.require('goog.userAgent'); // Turn off debugging when compiled. +// Unused within the Blockly library, but used in Closure. +/* eslint-disable no-unused-vars */ var CLOSURE_DEFINES = {'goog.DEBUG': false}; +/* eslint-enable no-unused-vars */ /** * The main workspace most recently used. @@ -71,20 +76,6 @@ Blockly.mainWorkspace = null; */ Blockly.selected = null; -/** - * Currently highlighted connection (during a drag). - * @type {Blockly.Connection} - * @private - */ -Blockly.highlightedConnection_ = null; - -/** - * Connection on dragged block that matches the highlighted connection. - * @type {Blockly.Connection} - * @private - */ -Blockly.localConnection_ = null; - /** * All of the connections on blocks that are currently being dragged. * @type {!Array.} @@ -107,20 +98,11 @@ Blockly.clipboardXml_ = null; Blockly.clipboardSource_ = null; /** - * Is the mouse dragging a block? - * 0 - No drag operation. - * 1 - Still inside the sticky DRAG_RADIUS. - * 2 - Freely draggable. + * Cached value for whether 3D is supported. + * @type {!boolean} * @private */ -Blockly.dragMode_ = Blockly.DRAG_NONE; - -/** - * Wrapper function called when a touch mouseUp occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.onTouchUpWrapper_ = null; +Blockly.cache3dSupported_ = null; /** * Convert a hue (HSV model) into an RGB hex triplet. @@ -138,41 +120,30 @@ Blockly.hueToRgb = function(hue) { * @return {!Object} Contains width and height properties. */ Blockly.svgSize = function(svg) { - return {width: svg.cachedWidth_, - height: svg.cachedHeight_}; + return { + width: svg.cachedWidth_, + height: svg.cachedHeight_ + }; }; /** - * Schedule a call to the resize handler. Groups of simultaneous events (e.g. - * a tree of blocks being deleted) are merged into one call. - * @param {Blockly.WorkspaceSvg} workspace Any workspace in the SVG. + * Size the workspace when the contents change. This also updates + * scrollbars accordingly. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. */ -Blockly.asyncSvgResize = function(workspace) { - if (Blockly.svgResizePending_) { - return; - } - if (!workspace) { - workspace = Blockly.getMainWorkspace(); - } - Blockly.svgResizePending_ = true; - setTimeout(function() {Blockly.svgResize(workspace);}, 0); +Blockly.resizeSvgContents = function(workspace) { + workspace.resizeContents(); }; /** - * Flag indicating a resize event is scheduled. - * Used to fire only one resize after multiple changes. - * @type {boolean} - * @private - */ -Blockly.svgResizePending_ = false; - -/** - * Size the SVG image to completely fill its container. + * Size the SVG image to completely fill its container. Call this when the view + * actually changes sizes (e.g. on a window resize/device orientation change). + * See Blockly.resizeSvgContents to resize the workspace when the contents + * change (e.g. when a block is added or removed). * Record the height/width of the SVG image. * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. */ Blockly.svgResize = function(workspace) { - Blockly.svgResizePending_ = false; var mainWorkspace = workspace; while (mainWorkspace.options.parentWorkspace) { mainWorkspace = mainWorkspace.options.parentWorkspace; @@ -196,68 +167,13 @@ Blockly.svgResize = function(workspace) { mainWorkspace.resize(); }; -/** - * Handle a mouse-up anywhere on the page. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.onMouseUp_ = function(e) { - var workspace = Blockly.getMainWorkspace(); - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); - workspace.isScrolling = false; - // Unbind the touch event if it exists. - if (Blockly.onTouchUpWrapper_) { - Blockly.unbindEvent_(Blockly.onTouchUpWrapper_); - Blockly.onTouchUpWrapper_ = null; - } - if (Blockly.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_); - Blockly.onMouseMoveWrapper_ = null; - } -}; - -/** - * Handle a mouse-move on SVG drawing surface. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.onMouseMove_ = function(e) { - if (e.touches && e.touches.length >= 2) { - return; // Multi-touch gestures won't have e.clientX. - } - var workspace = Blockly.getMainWorkspace(); - if (workspace.isScrolling) { - var dx = e.clientX - workspace.startDragMouseX; - var dy = e.clientY - workspace.startDragMouseY; - var metrics = workspace.startDragMetrics; - var x = workspace.startScrollX + dx; - var y = workspace.startScrollY + dy; - x = Math.min(x, -metrics.contentLeft); - y = Math.min(y, -metrics.contentTop); - x = Math.max(x, metrics.viewWidth - metrics.contentLeft - - metrics.contentWidth); - y = Math.max(y, metrics.viewHeight - metrics.contentTop - - metrics.contentHeight); - - // Move the scrollbars and the page will scroll automatically. - workspace.scrollbar.set(-x - metrics.contentLeft, - -y - metrics.contentTop); - // Cancel the long-press if the drag has moved too far. - if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { - Blockly.longStop_(); - } - e.stopPropagation(); - e.preventDefault(); - } -}; - /** * Handle a key-down on SVG drawing surface. * @param {!Event} e Key down event. * @private */ Blockly.onKeyDown_ = function(e) { - if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) { + if (Blockly.mainWorkspace.options.readOnly || Blockly.utils.isTargetInput(e)) { // No key actions on readonly workspaces. // When focused on an HTML text input widget, don't trap any keys. return; @@ -272,18 +188,30 @@ Blockly.onKeyDown_ = function(e) { // Do this first to prevent an error in the delete code from resulting in // data loss. e.preventDefault(); + // Don't delete while dragging. Jeez. + if (Blockly.mainWorkspace.isDragging()) { + return; + } if (Blockly.selected && Blockly.selected.isDeletable()) { deleteBlock = true; } } else if (e.altKey || e.ctrlKey || e.metaKey) { + // Don't use meta keys during drags. + if (Blockly.mainWorkspace.isDragging()) { + return; + } if (Blockly.selected && Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { + // Don't allow copying immovable or undeletable blocks. The next step + // would be to paste, which would create additional undeletable/immovable + // blocks on the workspace. if (e.keyCode == 67) { // 'c' for copy. Blockly.hideChaff(); Blockly.copy_(Blockly.selected); - } else if (e.keyCode == 88) { - // 'x' for cut. + } else if (e.keyCode == 88 && !Blockly.selected.workspace.isFlyout) { + // 'x' for cut, but not in a flyout. + // Don't even copy the selected item in the flyout. Blockly.copy_(Blockly.selected); deleteBlock = true; } @@ -291,7 +219,15 @@ Blockly.onKeyDown_ = function(e) { if (e.keyCode == 86) { // 'v' for paste. if (Blockly.clipboardXml_) { - Blockly.clipboardSource_.paste(Blockly.clipboardXml_); + Blockly.Events.setGroup(true); + // Pasting always pastes to the main workspace, even if the copy started + // in a flyout workspace. + var workspace = Blockly.clipboardSource_; + if (workspace.isFlyout) { + workspace = workspace.targetWorkspace; + } + workspace.paste(Blockly.clipboardXml_); + Blockly.Events.setGroup(false); } } else if (e.keyCode == 90) { // 'z' for undo 'Z' is for redo. @@ -299,66 +235,16 @@ Blockly.onKeyDown_ = function(e) { Blockly.mainWorkspace.undo(e.shiftKey); } } - if (deleteBlock) { - // Common code for delete and cut. + // Common code for delete and cut. + // Don't delete in the flyout. + if (deleteBlock && !Blockly.selected.workspace.isFlyout) { Blockly.Events.setGroup(true); Blockly.hideChaff(); - var heal = Blockly.dragMode_ != Blockly.DRAG_FREE; - Blockly.selected.dispose(heal, true); - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } + Blockly.selected.dispose(/* heal */ true, true); Blockly.Events.setGroup(false); } }; -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.terminateDrag_ = function() { - Blockly.BlockSvg.terminateDrag_(); - Blockly.Flyout.terminateDrag_(); -}; - -/** - * PID of queued long-press task. - * @private - */ -Blockly.longPid_ = 0; - -/** - * Context menus on touch devices are activated using a long-press. - * Unfortunately the contextmenu touch event is currently (2015) only suported - * by Chrome. This function is fired on any touchstart event, queues a task, - * which after about a second opens the context menu. The tasks is killed - * if the touch event terminates early. - * @param {!Event} e Touch start event. - * @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace - * under the touchstart event. - * @private - */ -Blockly.longStart_ = function(e, uiObject) { - Blockly.longStop_(); - Blockly.longPid_ = setTimeout(function() { - e.button = 2; // Simulate a right button click. - uiObject.onMouseDown_(e); - }, Blockly.LONGPRESS); -}; - -/** - * Nope, that's not a long-press. Either touchend or touchcancel was fired, - * or a drag hath begun. Kill the queued long-press task. - * @private - */ -Blockly.longStop_ = function() { - if (Blockly.longPid_) { - clearTimeout(Blockly.longPid_); - Blockly.longPid_ = 0; - } -}; - /** * Copy a block onto the local clipboard. * @param {!Blockly.Block} block Block to be copied. @@ -366,9 +252,8 @@ Blockly.longStop_ = function() { */ Blockly.copy_ = function(block) { var xmlBlock = Blockly.Xml.blockToDom(block); - if (Blockly.dragMode_ != Blockly.DRAG_FREE) { - Blockly.Xml.deleteNext(xmlBlock); - } + // Copy only the selected block and internal blocks. + Blockly.Xml.deleteNext(xmlBlock); // Encode start position in XML. var xy = block.getRelativeToSurfaceXY(); xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x); @@ -402,7 +287,7 @@ Blockly.duplicate_ = function(block) { * @private */ Blockly.onContextMenu_ = function(e) { - if (!Blockly.isTargetInput_(e)) { + if (!Blockly.utils.isTargetInput(e)) { // When focused on an HTML text input widget, don't cancel the context menu. e.preventDefault(); } @@ -426,150 +311,251 @@ Blockly.hideChaff = function(opt_allowToolbox) { }; /** - * Return an object with all the metrics required to size scrollbars for the - * main workspace. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the content, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .viewLeft: Offset of left edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .contentLeft: Offset of the left-most content from the x=0 coordinate. - * .absoluteTop: Top-edge of view. - * .absoluteLeft: Left-edge of view. - * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero. - * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero. - * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. - * .flyoutHeight: Height of flyout if it is always open. Otherwise zero. - * .toolboxPosition: Top, bottom, left or right. - * @return {Object} Contains size and position metrics of main workspace. + * When something in Blockly's workspace changes, call a function. + * @param {!Function} func Function to call. + * @return {!Array.} Opaque data that can be passed to + * removeChangeListener. + * @deprecated April 2015 + */ +Blockly.addChangeListener = function(func) { + // Backwards compatibility from before there could be multiple workspaces. + console.warn( + 'Deprecated call to Blockly.addChangeListener, ' + + 'use workspace.addChangeListener instead.'); + return Blockly.getMainWorkspace().addChangeListener(func); +}; + +/** + * Returns the main workspace. Returns the last used main workspace (based on + * focus). Try not to use this function, particularly if there are multiple + * Blockly instances on a page. + * @return {!Blockly.Workspace} The main workspace. + */ +Blockly.getMainWorkspace = function() { + return Blockly.mainWorkspace; +}; + +/** + * Wrapper to window.alert() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {function()=} opt_callback The callback when the alert is dismissed. + */ +Blockly.alert = function(message, opt_callback) { + window.alert(message); + if (opt_callback) { + opt_callback(); + } +}; + +/** + * Wrapper to window.confirm() that app developers may override to + * provide alternatives to the modal browser window. + * @param {string} message The message to display to the user. + * @param {!function(boolean)} callback The callback for handling user response. + */ +Blockly.confirm = function(message, callback) { + callback(window.confirm(message)); +}; + +/** + * Wrapper to window.prompt() that app developers may override to provide + * alternatives to the modal browser window. Built-in browser prompts are + * often used for better text input experience on mobile device. We strongly + * recommend testing mobile when overriding this. + * @param {string} message The message to display to the user. + * @param {string} defaultValue The value to initialize the prompt with. + * @param {!function(string)} callback The callback for handling user response. + */ +Blockly.prompt = function(message, defaultValue, callback) { + callback(window.prompt(message, defaultValue)); +}; + +/** + * Helper function for defining a block from JSON. The resulting function has + * the correct value of jsonDef at the point in code where jsonInit is called. + * @param {!Object} jsonDef The JSON definition of a block. + * @return {function()} A function that calls jsonInit with the correct value + * of jsonDef. * @private - * @this Blockly.WorkspaceSvg */ -Blockly.getMainWorkspaceMetrics_ = function() { - var svgSize = Blockly.svgSize(this.getParentSvg()); - if (this.toolbox_) { - if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP || - this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { - svgSize.height -= this.toolbox_.getHeight(); - } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT || - this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { - svgSize.width -= this.toolbox_.getWidth(); +Blockly.jsonInitFactory_ = function(jsonDef) { + return function() { + this.jsonInit(jsonDef); + }; +}; + +/** + * Define blocks from an array of JSON block definitions, as might be generated + * by the Blockly Developer Tools. + * @param {!Array.} jsonArray An array of JSON block definitions. + */ +Blockly.defineBlocksWithJsonArray = function(jsonArray) { + for (var i = 0, elem; elem = jsonArray[i]; i++) { + var typename = elem.type; + if (typename == null || typename === '') { + console.warn( + 'Block definition #' + i + + ' in JSON array is missing a type attribute. Skipping.'); + } else { + if (Blockly.Blocks[typename]) { + console.warn( + 'Block definition #' + i + ' in JSON array' + + ' overwrites prior definition of "' + typename + '".'); + } + Blockly.Blocks[typename] = { + init: Blockly.jsonInitFactory_(elem) + }; } } - // Set the margin to match the flyout's margin so that the workspace does - // not jump as blocks are added. - var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1; - var viewWidth = svgSize.width - MARGIN; - var viewHeight = svgSize.height - MARGIN; - var blockBox = this.getBlocksBoundingBox(); - - // Fix scale. - var contentWidth = blockBox.width * this.scale; - var contentHeight = blockBox.height * this.scale; - var contentX = blockBox.x * this.scale; - var contentY = blockBox.y * this.scale; - if (this.scrollbar) { - // Add a border around the content that is at least half a screenful wide. - // Ensure border is wide enough that blocks can scroll over entire screen. - var leftEdge = Math.min(contentX - viewWidth / 2, - contentX + contentWidth - viewWidth); - var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2, - contentX + viewWidth); - var topEdge = Math.min(contentY - viewHeight / 2, - contentY + contentHeight - viewHeight); - var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2, - contentY + viewHeight); - } else { - var leftEdge = blockBox.x; - var rightEdge = leftEdge + blockBox.width; - var topEdge = blockBox.y; - var bottomEdge = topEdge + blockBox.height; - } - var absoluteLeft = 0; - if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { - absoluteLeft = this.toolbox_.getWidth(); - } - var absoluteTop = 0; - if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { - absoluteTop = this.toolbox_.getHeight(); - } +}; - var metrics = { - viewHeight: svgSize.height, - viewWidth: svgSize.width, - contentHeight: bottomEdge - topEdge, - contentWidth: rightEdge - leftEdge, - viewTop: -this.scrollY, - viewLeft: -this.scrollX, - contentTop: topEdge, - contentLeft: leftEdge, - absoluteTop: absoluteTop, - absoluteLeft: absoluteLeft, - toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0, - toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0, - flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0, - flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0, - toolboxPosition: this.toolboxPosition +/** + * Bind an event to a function call. When calling the function, verifies that + * it belongs to the touch stream that is currently being processed, and splits + * multitouch events into multiple events as needed. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @param {boolean=} opt_noCaptureIdentifier True if triggering on this event + * should not block execution of other event handlers on this touch or other + * simultaneous touches. + * @param {boolean=} opt_noPreventDefault True if triggering on this event + * should prevent the default handler. False by default. If + * opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be + * provided. + * @return {!Array.} Opaque data that can be passed to unbindEvent_. + * @private + */ +Blockly.bindEventWithChecks_ = function(node, name, thisObject, func, + opt_noCaptureIdentifier, opt_noPreventDefault) { + var handled = false; + var wrapFunc = function(e) { + var captureIdentifier = !opt_noCaptureIdentifier; + // Handle each touch point separately. If the event was a mouse event, this + // will hand back an array with one element, which we're fine handling. + var events = Blockly.Touch.splitEventByTouches(e); + for (var i = 0, event; event = events[i]; i++) { + if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) { + continue; + } + Blockly.Touch.setClientFromTouch(event); + if (thisObject) { + func.call(thisObject, event); + } else { + func(event); + } + handled = true; + } }; - return metrics; + + node.addEventListener(name, wrapFunc, false); + var bindData = [[node, name, wrapFunc]]; + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + wrapFunc(e); + // Calling preventDefault stops the browser from scrolling/zooming the + // page. + var preventDef = !opt_noPreventDefault; + if (handled && preventDef) { + e.preventDefault(); + } + }; + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); + } + } + return bindData; }; + /** - * Sets the X/Y translations of the main workspace to match the scrollbars. - * @param {!Object} xyRatio Contains an x and/or y property which is a float - * between 0 and 1 specifying the degree of scrolling. + * Bind an event to a function call. Handles multitouch events by using the + * coordinates of the first changed touch, and doesn't do any safety checks for + * simultaneous event processing. + * @deprecated in favor of bindEventWithChecks_, but preserved for external + * users. + * @param {!EventTarget} node Node upon which to listen. + * @param {string} name Event name to listen to (e.g. 'mousedown'). + * @param {Object} thisObject The value of 'this' in the function. + * @param {!Function} func Function to call when event is triggered. + * @return {!Array.} Opaque data that can be passed to unbindEvent_. * @private - * @this Blockly.WorkspaceSvg */ -Blockly.setMainWorkspaceMetrics_ = function(xyRatio) { - if (!this.scrollbar) { - throw 'Attempt to set main workspace scroll without scrollbars.'; - } - var metrics = this.getMetrics(); - if (goog.isNumber(xyRatio.x)) { - this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; - } - if (goog.isNumber(xyRatio.y)) { - this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; - } - var x = this.scrollX + metrics.absoluteLeft; - var y = this.scrollY + metrics.absoluteTop; - this.translate(x, y); - if (this.options.gridPattern) { - this.options.gridPattern.setAttribute('x', x); - this.options.gridPattern.setAttribute('y', y); - if (goog.userAgent.IE) { - // IE doesn't notice that the x/y offsets have changed. Force an update. - this.updateGridPattern_(); +Blockly.bindEvent_ = function(node, name, thisObject, func) { + var wrapFunc = function(e) { + if (thisObject) { + func.call(thisObject, e); + } else { + func(e); + } + }; + + node.addEventListener(name, wrapFunc, false); + var bindData = [[node, name, wrapFunc]]; + + // Add equivalent touch event. + if (name in Blockly.Touch.TOUCH_MAP) { + var touchWrapFunc = function(e) { + // Punt on multitouch events. + if (e.changedTouches && e.changedTouches.length == 1) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } + wrapFunc(e); + + // Stop the browser from scrolling/zooming the page. + e.preventDefault(); + }; + for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) { + node.addEventListener(type, touchWrapFunc, false); + bindData.push([node, type, touchWrapFunc]); } } + return bindData; }; /** - * When something in Blockly's workspace changes, call a function. - * @param {!Function} func Function to call. - * @return {!Array.} Opaque data that can be passed to - * removeChangeListener. - * @deprecated April 2015 + * Unbind one or more events event from a function call. + * @param {!Array.} bindData Opaque data from bindEvent_. + * This list is emptied during the course of calling this function. + * @return {!Function} The function call. + * @private */ -Blockly.addChangeListener = function(func) { - // Backwards compatability from before there could be multiple workspaces. - console.warn('Deprecated call to Blockly.addChangeListener, ' + - 'use workspace.addChangeListener instead.'); - return Blockly.getMainWorkspace().addChangeListener(func); +Blockly.unbindEvent_ = function(bindData) { + while (bindData.length) { + var bindDatum = bindData.pop(); + var node = bindDatum[0]; + var name = bindDatum[1]; + var func = bindDatum[2]; + node.removeEventListener(name, func, false); + } + return func; }; /** - * Returns the main workspace. Returns the last used main workspace (based on - * focus). - * @return {!Blockly.Workspace} The main workspace. + * Is the given string a number (includes negative and decimals). + * @param {string} str Input string. + * @return {boolean} True if number, false otherwise. */ -Blockly.getMainWorkspace = function() { - return Blockly.mainWorkspace; +Blockly.isNumber = function(str) { + return /^\s*-?\d+(\.\d+)?\s*$/.test(str); }; +// IE9 does not have a console. Create a stub to stop errors. +if (!goog.global['console']) { + goog.global['console'] = { + 'log': function() {}, + 'warn': function() {} + }; +} + // Export symbols that would otherwise be renamed by Closure compiler. if (!goog.global['Blockly']) { goog.global['Blockly'] = {}; diff --git a/core/blocks.js b/core/blocks.js index ce3f788..c27be1d 100644 --- a/core/blocks.js +++ b/core/blocks.js @@ -19,14 +19,19 @@ */ /** - * @fileoverview Empty name space for the Blocks singleton. + * @fileoverview A mapping of block type names to block prototype objects. * @author spertus@google.com (Ellen Spertus) */ 'use strict'; +/** + * A mapping of block type names to block prototype objects. + * @name Blockly.Blocks + */ goog.provide('Blockly.Blocks'); -/** - * @namespace Blockly.Blocks +/* + * A mapping of block type names to block prototype objects. + * @type {!Object.} */ -Blockly.Blocks = {}; \ No newline at end of file +Blockly.Blocks = new Object(null); diff --git a/core/bubble.js b/core/bubble.js index d4c1e27..92dc288 100644 --- a/core/bubble.js +++ b/core/bubble.js @@ -26,6 +26,7 @@ goog.provide('Blockly.Bubble'); +goog.require('Blockly.Touch'); goog.require('Blockly.Workspace'); goog.require('goog.dom'); goog.require('goog.math'); @@ -46,7 +47,7 @@ goog.require('goog.userAgent'); * @constructor */ Blockly.Bubble = function(workspace, content, shape, anchorXY, - bubbleWidth, bubbleHeight) { + bubbleWidth, bubbleHeight) { this.workspace_ = workspace; this.content_ = content; this.shape_ = shape; @@ -74,11 +75,11 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY, this.rendered_ = true; if (!workspace.options.readOnly) { - Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this, - this.bubbleMouseDown_); + Blockly.bindEventWithChecks_( + this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_); if (this.resizeGroup_) { - Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this, - this.resizeMouseDown_); + Blockly.bindEventWithChecks_( + this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_); } } }; @@ -92,7 +93,7 @@ Blockly.Bubble.BORDER_WIDTH = 6; * Determines the thickness of the base of the arrow in relation to the size * of the bubble. Higher numbers result in thinner arrows. */ -Blockly.Bubble.ARROW_THICKNESS = 10; +Blockly.Bubble.ARROW_THICKNESS = 5; /** * The number of degrees that the arrow bends counter-clockwise. @@ -144,6 +145,16 @@ Blockly.Bubble.unbindDragEvents_ = function() { } }; +/* + * Handle a mouse-up event while dragging a bubble's border or resize handle. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) { + Blockly.Touch.clearTouchIdentifier(); + Blockly.Bubble.unbindDragEvents_(); +}; + /** * Flag to stop incremental rendering during construction. * @private @@ -151,14 +162,15 @@ Blockly.Bubble.unbindDragEvents_ = function() { Blockly.Bubble.prototype.rendered_ = false; /** - * Absolute coordinate of anchor point. + * Absolute coordinate of anchor point, in workspace coordinates. * @type {goog.math.Coordinate} * @private */ Blockly.Bubble.prototype.anchorXY_ = null; /** - * Relative X coordinate of bubble with respect to the anchor's centre. + * Relative X coordinate of bubble with respect to the anchor's centre, + * in workspace units. * In RTL mode the initial value is negated. * @private */ @@ -210,7 +222,7 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { [...content goes here...] */ - this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null); + this.bubbleGroup_ = Blockly.utils.createSvgElement('g', {}, null); var filter = {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'}; if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) { @@ -220,30 +232,41 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { // https://github.com/google/blockly/issues/99 filter = {}; } - var bubbleEmboss = Blockly.createSvgElement('g', + var bubbleEmboss = Blockly.utils.createSvgElement('g', filter, this.bubbleGroup_); - this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss); - this.bubbleBack_ = Blockly.createSvgElement('rect', - {'class': 'blocklyDraggable', 'x': 0, 'y': 0, - 'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH}, + this.bubbleArrow_ = Blockly.utils.createSvgElement('path', {}, bubbleEmboss); + this.bubbleBack_ = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyDraggable', + 'x': 0, + 'y': 0, + 'rx': Blockly.Bubble.BORDER_WIDTH, + 'ry': Blockly.Bubble.BORDER_WIDTH + }, bubbleEmboss); if (hasResize) { - this.resizeGroup_ = Blockly.createSvgElement('g', + this.resizeGroup_ = Blockly.utils.createSvgElement('g', {'class': this.workspace_.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, this.bubbleGroup_); var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; - Blockly.createSvgElement('polygon', + Blockly.utils.createSvgElement('polygon', {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, this.resizeGroup_); - Blockly.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_); - Blockly.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_); + Blockly.utils.createSvgElement('line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize / 3, 'y1': resizeSize - 1, + 'x2': resizeSize - 1, 'y2': resizeSize / 3 + }, this.resizeGroup_); + Blockly.utils.createSvgElement('line', + { + 'class': 'blocklyResizeLine', + 'x1': resizeSize * 2 / 3, + 'y1': resizeSize - 1, + 'x2': resizeSize - 1, + 'y2': resizeSize * 2 / 3 + }, this.resizeGroup_); } else { this.resizeGroup_ = null; } @@ -252,49 +275,23 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { }; /** - * Handle a mouse-down on bubble's border. - * @param {!Event} e Mouse down event. - * @private + * Return the root node of the bubble's SVG group. + * @return {Element} The root SVG node of the bubble's group. */ -Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { - this.promote_(); - Blockly.Bubble.unbindDragEvents_(); - if (Blockly.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } else if (Blockly.isTargetInput_(e)) { - // When focused on an HTML text input widget, don't trap any events. - return; - } - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - - this.workspace_.startDrag(e, new goog.math.Coordinate( - this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_, - this.relativeTop_)); - - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, Blockly.Bubble.unbindDragEvents_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.bubbleMouseMove_); - Blockly.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); +Blockly.Bubble.prototype.getSvgRoot = function() { + return this.bubbleGroup_; }; /** - * Drag this bubble to follow the mouse. - * @param {!Event} e Mouse move event. + * Handle a mouse-down on bubble's border. + * @param {!Event} e Mouse down event. * @private */ -Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) { - this.autoLayout_ = false; - var newXY = this.workspace_.moveDrag(e); - this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x; - this.relativeTop_ = newXY.y; - this.positionBubble_(); - this.renderArrow_(); +Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { + var gesture = this.workspace_.getGesture(e); + if (gesture) { + gesture.handleBubbleStart(e, this); + } }; /** @@ -305,20 +302,18 @@ Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) { Blockly.Bubble.prototype.resizeMouseDown_ = function(e) { this.promote_(); Blockly.Bubble.unbindDragEvents_(); - if (Blockly.isRightButton(e)) { + if (Blockly.utils.isRightButton(e)) { // No right-click. e.stopPropagation(); return; } // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - this.workspace_.startDrag(e, new goog.math.Coordinate( this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, Blockly.Bubble.unbindDragEvents_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, + Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document, + 'mouseup', this, Blockly.Bubble.bubbleMouseUp_); + Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document, 'mousemove', this, this.resizeMouseMove_); Blockly.hideChaff(); // This event has been handled. No need to bubble up to the document. @@ -427,8 +422,17 @@ Blockly.Bubble.prototype.positionBubble_ = function() { left += this.relativeLeft_; } var top = this.relativeTop_ + this.anchorXY_.y; - this.bubbleGroup_.setAttribute('transform', - 'translate(' + left + ',' + top + ')'); + this.moveTo(left, top); +}; + +/** + * Move the bubble group to the specified location in workspace coordinates. + * @param {number} x The x position to move to. + * @param {number} y The y position to move to. + * @package + */ +Blockly.Bubble.prototype.moveTo = function(x, y) { + this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); }; /** @@ -518,7 +522,7 @@ Blockly.Bubble.prototype.renderArrow_ = function() { var bubbleSize = this.getBubbleSize(); var thickness = (bubbleSize.width + bubbleSize.height) / Blockly.Bubble.ARROW_THICKNESS; - thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2; + thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4; // Back the tip of the arrow off of the anchor. var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse; @@ -577,3 +581,50 @@ Blockly.Bubble.prototype.dispose = function() { this.content_ = null; this.shape_ = null; }; + +/** + * Move this bubble during a drag, taking into account whether or not there is + * a drag surface. + * @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries + * rendered items during a drag, or null if no drag surface is in use. + * @param {!goog.math.Coordinate} newLoc The location to translate to, in + * workspace coordinates. + * @package + */ +Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) { + if (dragSurface) { + dragSurface.translateSurface(newLoc.x, newLoc.y); + } else { + this.moveTo(newLoc.x, newLoc.y); + } + if (this.workspace_.RTL) { + this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_; + } else { + this.relativeLeft_ = newLoc.x - this.anchorXY_.x; + } + this.relativeTop_ = newLoc.y - this.anchorXY_.y; + this.renderArrow_(); +}; + +/** + * Return the coordinates of the top-left corner of this bubble's body relative + * to the drawing surface's origin (0,0), in workspace units. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() { + return new goog.math.Coordinate( + this.anchorXY_.x + this.relativeLeft_, + this.anchorXY_.y + this.relativeTop_); +}; + +/** + * Set whether auto-layout of this bubble is enabled. The first time a bubble + * is shown it positions itself to not cover any blocks. Once a user has + * dragged it to reposition, it renders where the user put it. + * @param {boolean} enable True if auto-layout should be enabled, false + * otherwise. + * @package + */ +Blockly.Bubble.prototype.setAutoLayout = function(enable) { + this.autoLayout_ = enable; +}; diff --git a/core/bubble_dragger.js b/core/bubble_dragger.js new file mode 100644 index 0000000..e0dc03e --- /dev/null +++ b/core/bubble_dragger.js @@ -0,0 +1,188 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2018 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 Methods for dragging a bubble visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.BubbleDragger'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a bubble dragger. It moves bubbles around the workspace when they + * are being dragged by a mouse or touch. + * @param {!Blockly.Bubble} bubble The bubble to drag. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on. + * @constructor + */ +Blockly.BubbleDragger = function(bubble, workspace) { + /** + * The bubble that is being dragged. + * @type {!Blockly.Bubble} + * @private + */ + this.draggingBubble_ = bubble; + + /** + * The workspace on which the bubble is being dragged. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The location of the top left corner of the dragging bubble's body at the + * beginning of the drag, in workspace coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY(); + + /** + * The drag surface to move bubbles to during a drag, or null if none should + * be used. Block dragging and bubble dragging use the same surface. + * @type {?Blockly.BlockDragSurfaceSvg} + * @private + */ + this.dragSurface_ = + Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface() ? + workspace.getBlockDragSurface() : null; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.BubbleDragger.prototype.dispose = function() { + this.draggingBubble_ = null; + this.workspace_ = null; + this.dragSurface_ = null; +}; + +/** + * Start dragging a bubble. This includes moving it to the drag surface. + * @package + */ +Blockly.BubbleDragger.prototype.startBubbleDrag = function() { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + + this.workspace_.setResizesEnabled(false); + this.draggingBubble_.setAutoLayout(false); + if (this.dragSurface_) { + this.moveToDragSurface_(); + } +}; + +/** + * Execute a step of bubble dragging, based on the given event. Update the + * display accordingly. + * @param {!Event} e The most recent move event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) { + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc); + // TODO (fenichel): Possibly update the cursor if dragging to the trash can + // is allowed. +}; + +/** + * Finish a bubble drag and put the bubble back on the workspace. + * @param {!Event} e The mouseup/touchend event. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel units. + * @package + */ +Blockly.BubbleDragger.prototype.endBubbleDrag = function( + e, currentDragDeltaXY) { + // Make sure internal state is fresh. + this.dragBubble(e, currentDragDeltaXY); + + var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY); + var newLoc = goog.math.Coordinate.sum(this.startXY_, delta); + + // Move the bubble to its final location. + this.draggingBubble_.moveTo(newLoc.x, newLoc.y); + // Put everything back onto the bubble canvas. + if (this.dragSurface_) { + this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas()); + } + + this.fireMoveEvent_(); + this.workspace_.setResizesEnabled(true); + + Blockly.Events.setGroup(false); +}; + +/** + * Fire a move event at the end of a bubble drag. + * @private + */ +Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() { + // TODO (fenichel): move events for comments. + return; +}; + +/** + * Convert a coordinate object from pixels to workspace units, including a + * correction for mutator workspaces. + * This function does not consider differing origins. It simply scales the + * input's x and y values. + * @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values + * in css pixel units. + * @return {!goog.math.Coordinate} The input coordinate divided by the workspace + * scale. + * @private + */ +Blockly.BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) { + var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale, + pixelCoord.y / this.workspace_.scale); + if (this.workspace_.isMutator) { + // If we're in a mutator, its scale is always 1, purely because of some + // oddities in our rendering optimizations. The actual scale is the same as + // the scale on the parent workspace. + // Fix that for dragging. + var mainScale = this.workspace_.options.parentWorkspace.scale; + result = result.scale(1 / mainScale); + } + return result; +}; +/** + * Move the bubble onto the drag surface at the beginning of a drag. Move the + * drag surface to preserve the apparent location of the bubble. + * @private + */ +Blockly.BubbleDragger.prototype.moveToDragSurface_ = function() { + this.draggingBubble_.moveTo(0, 0); + this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y); + // Execute the move on the top-level SVG component. + this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot()); +}; diff --git a/core/comment.js b/core/comment.js index 2121530..2372e2b 100644 --- a/core/comment.js +++ b/core/comment.js @@ -68,21 +68,29 @@ Blockly.Comment.prototype.height_ = 80; */ Blockly.Comment.prototype.drawIcon_ = function(group) { // Circle. - Blockly.createSvgElement('circle', + Blockly.utils.createSvgElement('circle', {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, - group); + group); // Can't use a real '?' text character since different browsers and operating // systems render it differently. // Body of question mark. - Blockly.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25 -1.201,0.998 -1.201,1.528 -1.204,2.19z'}, - group); - // Dot of question point. - Blockly.createSvgElement('rect', - {'class': 'blocklyIconSymbol', - 'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'}, - group); + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' + + '0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' + + '-1.201,0.998 -1.201,1.528 -1.204,2.19z'}, + group); + // Dot of question mark. + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconSymbol', + 'x': '6.8', + 'y': '10.78', + 'height': '2', + 'width': '2' + }, + group); }; /** @@ -100,7 +108,7 @@ Blockly.Comment.prototype.createEditor_ = function() { */ - this.foreignObject_ = Blockly.createSvgElement('foreignObject', + this.foreignObject_ = Blockly.utils.createSvgElement('foreignObject', {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, null); var body = document.createElementNS(Blockly.HTML_NS, 'body'); @@ -112,15 +120,17 @@ Blockly.Comment.prototype.createEditor_ = function() { body.appendChild(textarea); this.textarea_ = textarea; this.foreignObject_.appendChild(body); - Blockly.bindEvent_(textarea, 'mouseup', this, this.textareaFocus_); + Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_); // Don't zoom with mousewheel. - Blockly.bindEvent_(textarea, 'wheel', this, function(e) { + Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) { e.stopPropagation(); }); - Blockly.bindEvent_(textarea, 'change', this, function(e) { + Blockly.bindEventWithChecks_(textarea, 'change', this, function( + /* eslint-disable no-unused-vars */ e + /* eslint-enable no-unused-vars */) { if (this.text_ != textarea.value) { - Blockly.Events.fire(new Blockly.Events.Change( - this.block_, 'comment', null, this.text_, textarea.value)); + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.block_, 'comment', null, this.text_, textarea.value)); this.text_ = textarea.value; } }); @@ -207,7 +217,8 @@ Blockly.Comment.prototype.setVisible = function(visible) { * @param {!Event} e Mouse up event. * @private */ -Blockly.Comment.prototype.textareaFocus_ = function(e) { +Blockly.Comment.prototype.textareaFocus_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { // Ideally this would be hooked to the focus event for the comment. // However doing so in Firefox swallows the cursor for unknown reasons. // So this is hooked to mouseup instead. No big deal. @@ -257,8 +268,8 @@ Blockly.Comment.prototype.getText = function() { */ Blockly.Comment.prototype.setText = function(text) { if (this.text_ != text) { - Blockly.Events.fire(new Blockly.Events.Change( - this.block_, 'comment', null, this.text_, text)); + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.block_, 'comment', null, this.text_, text)); this.text_ = text; } if (this.textarea_) { diff --git a/core/connection.js b/core/connection.js index c522804..fc13cfa 100644 --- a/core/connection.js +++ b/core/connection.js @@ -62,6 +62,7 @@ Blockly.Connection.REASON_WRONG_TYPE = 2; Blockly.Connection.REASON_TARGET_NULL = 3; Blockly.Connection.REASON_CHECKS_FAILED = 4; Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5; +Blockly.Connection.REASON_SHADOW_PARENT = 6; /** * Connection this connection connects to. Null if not connected. @@ -179,8 +180,9 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { // block. Since this block may be a stack, walk down to the end. var newBlock = childBlock; while (newBlock.nextConnection) { - if (newBlock.nextConnection.isConnected()) { - newBlock = newBlock.getNextBlock(); + var nextBlock = newBlock.getNextBlock(); + if (nextBlock && !nextBlock.isShadow()) { + newBlock = nextBlock; } else { if (orphanBlock.previousConnection.checkType_( newBlock.nextConnection)) { @@ -217,7 +219,7 @@ Blockly.Connection.prototype.connect_ = function(childConnection) { var event; if (Blockly.Events.isEnabled()) { - event = new Blockly.Events.Move(childBlock); + event = new Blockly.Events.BlockMove(childBlock); } // Establish the connections. Blockly.Connection.connectReciprocally_(parentConnection, childConnection); @@ -239,12 +241,6 @@ Blockly.Connection.prototype.dispose = function() { if (this.inDB_) { this.db_.removeConnection_(this); } - if (Blockly.highlightedConnection_ == this) { - Blockly.highlightedConnection_ = null; - } - if (Blockly.localConnection_ == this) { - Blockly.localConnection_ = null; - } this.db_ = null; this.dbOpposite_ = null; }; @@ -285,16 +281,24 @@ Blockly.Connection.prototype.isConnected = function() { Blockly.Connection.prototype.canConnectWithReason_ = function(target) { if (!target) { return Blockly.Connection.REASON_TARGET_NULL; - } else if (this.sourceBlock_ && - target.getSourceBlock() == this.sourceBlock_) { + } + if (this.isSuperior()) { + var blockA = this.sourceBlock_; + var blockB = target.getSourceBlock(); + } else { + var blockB = this.sourceBlock_; + var blockA = target.getSourceBlock(); + } + if (blockA && blockA == blockB) { return Blockly.Connection.REASON_SELF_CONNECTION; } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { return Blockly.Connection.REASON_WRONG_TYPE; - } else if (this.sourceBlock_ && target.getSourceBlock() && - this.sourceBlock_.workspace !== target.getSourceBlock().workspace) { + } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; } else if (!this.checkType_(target)) { return Blockly.Connection.REASON_CHECKS_FAILED; + } else if (blockA.isShadow() && !blockB.isShadow()) { + return Blockly.Connection.REASON_SHADOW_PARENT; } return Blockly.Connection.CAN_CONNECT; }; @@ -320,7 +324,11 @@ Blockly.Connection.prototype.checkConnection_ = function(target) { case Blockly.Connection.REASON_TARGET_NULL: throw 'Target connection is null.'; case Blockly.Connection.REASON_CHECKS_FAILED: - throw 'Connection checks failed.'; + var msg = 'Connection checks failed. '; + msg += this + ' expected ' + this.check_ + ', found ' + target.check_; + throw msg; + case Blockly.Connection.REASON_SHADOW_PARENT: + throw 'Connecting non-shadow to shadow block.'; default: throw 'Unknown connection failure: this should never happen!'; } @@ -334,8 +342,7 @@ Blockly.Connection.prototype.checkConnection_ = function(target) { Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { // Type checking. var canConnect = this.canConnectWithReason_(candidate); - if (canConnect != Blockly.Connection.CAN_CONNECT && - canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) { + if (canConnect != Blockly.Connection.CAN_CONNECT) { return false; } @@ -359,10 +366,14 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { } // Don't let a block with no next connection bump other blocks out of the - // stack. + // stack. But covering up a shadow block or stack of shadow blocks is fine. + // Similarly, replacing a terminal statement with another terminal statement + // is allowed. if (this.type == Blockly.PREVIOUS_STATEMENT && candidate.isConnected() && - !this.sourceBlock_.nextConnection) { + !this.sourceBlock_.nextConnection && + !candidate.targetBlock().isShadow() && + candidate.targetBlock().nextConnection) { return false; } @@ -397,7 +408,7 @@ Blockly.Connection.prototype.connect = function(otherConnection) { /** * Update two connections to target each other. * @param {Blockly.Connection} first The first connection to update. - * @param {Blockly.Connection} second The second conneciton to update. + * @param {Blockly.Connection} second The second connection to update. * @private */ Blockly.Connection.connectReciprocally_ = function(first, second) { @@ -491,7 +502,7 @@ Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock, childBlock) { var event; if (Blockly.Events.isEnabled()) { - event = new Blockly.Events.Move(childBlock); + event = new Blockly.Events.BlockMove(childBlock); } var otherConnection = this.targetConnection; otherConnection.targetConnection = null; @@ -505,8 +516,6 @@ Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock, /** * Respawn the shadow block if there was one connected to the this connection. - * @return {Blockly.Block} The newly spawned shadow block, or null if none was - * spawned. * @private */ Blockly.Connection.prototype.respawnShadow_ = function() { @@ -522,9 +531,7 @@ Blockly.Connection.prototype.respawnShadow_ = function() { } else { throw 'Child block does not have output or previous statement.'; } - return blockShadow; } - return null; }; /** @@ -560,6 +567,18 @@ Blockly.Connection.prototype.checkType_ = function(otherConnection) { return false; }; +/** + * Function to be called when this connection's compatible types have changed. + * @private + */ +Blockly.Connection.prototype.onCheckChanged_ = function() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && !this.checkType_(this.targetConnection)) { + var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + } +}; + /** * Change a connection's compatibility. * @param {*} check Compatible value type or list of value types. @@ -574,13 +593,7 @@ Blockly.Connection.prototype.setCheck = function(check) { check = [check]; } this.check_ = check; - // The new value type may not be compatible with the existing connection. - if (this.isConnected() && !this.checkType_(this.targetConnection)) { - var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; - child.unplug(); - // Bump away. - this.sourceBlock_.bumpNeighbours_(); - } + this.onCheckChanged_(); } else { this.check_ = null; } @@ -602,3 +615,49 @@ Blockly.Connection.prototype.setShadowDom = function(shadow) { Blockly.Connection.prototype.getShadowDom = function() { return this.shadowDom_; }; + +/** + * Find all nearby compatible connections to this connection. + * Type checking does not apply, since this function is used for bumping. + * + * Headless configurations (the default) do not have neighboring connection, + * and always return an empty list (the default). + * {@link Blockly.RenderedConnection} overrides this behavior with a list + * computed from the rendered positioning. + * @param {number} maxLimit The maximum radius to another connection. + * @return {!Array.} List of connections. + * @private + */ +Blockly.Connection.prototype.neighbours_ = function(/* maxLimit */) { + return []; +}; + +/** + * This method returns a string describing this Connection in developer terms + * (English only). Intended to on be used in console logs and errors. + * @return {string} The description. + */ +Blockly.Connection.prototype.toString = function() { + var msg; + var block = this.sourceBlock_; + if (!block) { + return 'Orphan Connection'; + } else if (block.outputConnection == this) { + msg = 'Output Connection of '; + } else if (block.previousConnection == this) { + msg = 'Previous Connection of '; + } else if (block.nextConnection == this) { + msg = 'Next Connection of '; + } else { + var parentInput = goog.array.find(block.inputList, function(input) { + return input.connection == this; + }, this); + if (parentInput) { + msg = 'Input "' + parentInput.name + '" connection on '; + } else { + console.warn('Connection not actually connected to sourceBlock_'); + return 'Orphan Connection'; + } + } + return msg + block.toDevString(); +}; diff --git a/core/connection_db.js b/core/connection_db.js index 9d143e3..acb044c 100644 --- a/core/connection_db.js +++ b/core/connection_db.js @@ -109,8 +109,8 @@ Blockly.ConnectionDB.prototype.findConnection = function(conn) { * @return {number} The candidate index. * @private */ -Blockly.ConnectionDB.prototype.findPositionForConnection_ = - function(connection) { +Blockly.ConnectionDB.prototype.findPositionForConnection_ = function( + connection) { if (!this.length) { return 0; } diff --git a/core/constants.js b/core/constants.js index 3302940..54ed1fb 100644 --- a/core/constants.js +++ b/core/constants.js @@ -32,6 +32,13 @@ goog.provide('Blockly.constants'); */ Blockly.DRAG_RADIUS = 5; +/** + * Number of pixels the mouse must move before a drag/scroll starts from the + * flyout. Because the drag-intention is determined when this is reached, it is + * larger than Blockly.DRAG_RADIUS so that the drag-direction is clearer. + */ +Blockly.FLYOUT_DRAG_RADIUS = 10; + /** * Maximum misalignment between connections for them to snap together. */ @@ -52,11 +59,23 @@ Blockly.COLLAPSE_CHARS = 30; */ Blockly.LONGPRESS = 750; +/** + * Prevent a sound from playing if another sound preceded it within this many + * milliseconds. + */ +Blockly.SOUND_LIMIT = 100; + +/** + * When dragging a block out of a stack, split the stack in two (true), or drag + * out the block healing the stack (false). + */ +Blockly.DRAG_STACK = true; + /** * The richness of block colours, regardless of the hue. * Must be in the range of 0 (inclusive) to 1 (exclusive). */ -Blockly.HSV_SATURATION = 0.55; +Blockly.HSV_SATURATION = 0.45; /** * The intensity of block colours, regardless of the hue. @@ -148,7 +167,14 @@ Blockly.DRAG_NONE = 0; Blockly.DRAG_STICKY = 1; /** - * ENUM for freely draggable. + * ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between + * clicks and drags. + * @const + */ +Blockly.DRAG_BEGIN = 1; + +/** + * ENUM for freely draggable (outside the DRAG_RADIUS, if one applies). * @const */ Blockly.DRAG_FREE = 2; @@ -187,3 +213,62 @@ Blockly.TOOLBOX_AT_LEFT = 2; * @const */ Blockly.TOOLBOX_AT_RIGHT = 3; + +/** + * ENUM representing that an event is not in any delete areas. + * Null for backwards compatibility reasons. + * @const + */ +Blockly.DELETE_AREA_NONE = null; + +/** + * ENUM representing that an event is in the delete area of the trash can. + * @const + */ +Blockly.DELETE_AREA_TRASH = 1; + +/** + * ENUM representing that an event is in the delete area of the toolbox or + * flyout. + * @const + */ +Blockly.DELETE_AREA_TOOLBOX = 2; + +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * variable blocks. + * @const {string} + */ +Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE'; +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * variable blocks. + * @const {string} + */ +Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME = 'VARIABLE_DYNAMIC'; + +/** + * String for use in the "custom" attribute of a category in toolbox xml. + * This string indicates that the category should be dynamically populated with + * procedure blocks. + * @const {string} + */ +Blockly.PROCEDURE_CATEGORY_NAME = 'PROCEDURE'; + +/** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Rename + * variable...' and if selected, should trigger the prompt to rename a variable. + * @const {string} + */ +Blockly.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID'; + +/** + * String for use in the dropdown created in field_variable. + * This string indicates that this option in the dropdown is 'Delete the "%1" + * variable' and if selected, should trigger the prompt to delete a variable. + * @const {string} + */ +Blockly.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID'; diff --git a/core/contextmenu.js b/core/contextmenu.js index ebd92df..ba25701 100644 --- a/core/contextmenu.js +++ b/core/contextmenu.js @@ -24,8 +24,15 @@ */ 'use strict'; +/** + * @name Blockly.ContextMenu + * @namespace + */ goog.provide('Blockly.ContextMenu'); +goog.require('Blockly.utils'); +goog.require('Blockly.utils.uiMenu'); + goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.style'); @@ -39,6 +46,13 @@ goog.require('goog.ui.MenuItem'); */ Blockly.ContextMenu.currentBlock = null; +/** + * Opaque data that can be passed to unbindEvent_. + * @type {Array.} + * @private + */ +Blockly.ContextMenu.eventWrapper_ = null; + /** * Construct the menu based on the list of options and show the menu. * @param {!Event} e Mouse event. @@ -51,6 +65,26 @@ Blockly.ContextMenu.show = function(e, options, rtl) { Blockly.ContextMenu.hide(); return; } + var menu = Blockly.ContextMenu.populate_(options, rtl); + + goog.events.listen( + menu, goog.ui.Component.EventType.ACTION, Blockly.ContextMenu.hide); + + Blockly.ContextMenu.position_(menu, e, rtl); + // 1ms delay is required for focusing on context menus because some other + // mouse event is still waiting in the queue and clears focus. + setTimeout(function() {menu.getElement().focus();}, 1); + Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block. +}; + +/** + * Create the context menu object and populate it with the given options. + * @param {!Array.} options Array of menu options. + * @param {boolean} rtl True if RTL, false if LTR. + * @return {!goog.ui.Menu} The menu that will be shown on right click. + * @private + */ +Blockly.ContextMenu.populate_ = function(options, rtl) { /* Here's what one option object looks like: {text: 'Make It So', enabled: true, @@ -64,46 +98,66 @@ Blockly.ContextMenu.show = function(e, options, rtl) { menu.addChild(menuItem, true); menuItem.setEnabled(option.enabled); if (option.enabled) { - goog.events.listen(menuItem, goog.ui.Component.EventType.ACTION, - option.callback); + goog.events.listen( + menuItem, goog.ui.Component.EventType.ACTION, option.callback); + menuItem.handleContextMenu = function(/* e */) { + // Right-clicking on menu option should count as a click. + goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION); + }; } } - goog.events.listen(menu, goog.ui.Component.EventType.ACTION, - Blockly.ContextMenu.hide); + return menu; +}; + +/** + * Add the menu to the page and position it correctly. + * @param {!goog.ui.Menu} menu The menu to add and position. + * @param {!Event} e Mouse event for the right click that is making the context + * menu appear. + * @param {boolean} rtl True if RTL, false if LTR. + * @private + */ +Blockly.ContextMenu.position_ = function(menu, e, rtl) { // Record windowSize and scrollOffset before adding menu. - var windowSize = goog.dom.getViewportSize(); - var scrollOffset = goog.style.getViewportPageOffset(document); - var div = Blockly.WidgetDiv.DIV; - menu.render(div); - var menuDom = menu.getElement(); - Blockly.addClass_(menuDom, 'blocklyContextMenu'); - // Record menuSize after adding menu. - var menuSize = goog.style.getSize(menuDom); - - // Position the menu. - var x = e.clientX + scrollOffset.x; - var y = e.clientY + scrollOffset.y; - // Flip menu vertically if off the bottom. - if (e.clientY + menuSize.height >= windowSize.height) { - y -= menuSize.height; - } - // Flip menu horizontally if off the edge. + var viewportBBox = Blockly.utils.getViewportBBox(); + // This one is just a point, but we'll pretend that it's a rect so we can use + // some helper functions. + var anchorBBox = { + top: e.clientY + viewportBBox.top, + bottom: e.clientY + viewportBBox.top, + left: e.clientX + viewportBBox.left, + right: e.clientX + viewportBBox.left + }; + + Blockly.ContextMenu.createWidget_(menu); + var menuSize = Blockly.utils.uiMenu.getSize(menu); + if (rtl) { - if (menuSize.width >= e.clientX) { - x += menuSize.width; - } - } else { - if (e.clientX + menuSize.width >= windowSize.width) { - x -= menuSize.width; - } + Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); } - Blockly.WidgetDiv.position(x, y, windowSize, scrollOffset, rtl); + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, rtl); + // Calling menuDom.focus() has to wait until after the menu has been placed + // correctly. Otherwise it will cause a page scroll to get the misplaced menu + // in view. See issue #1329. + menu.getElement().focus(); +}; + +/** + * Create and render the menu widget inside Blockly's widget div. + * @param {!goog.ui.Menu} menu The menu to add to the widget div. + * @private + */ +Blockly.ContextMenu.createWidget_ = function(menu) { + var div = Blockly.WidgetDiv.DIV; + menu.render(div); + var menuDom = menu.getElement(); + Blockly.utils.addClass(menuDom, 'blocklyContextMenu'); + // Prevent system context menu when right-clicking a Blockly context menu. + Blockly.bindEventWithChecks_( + menuDom, 'contextmenu', null, Blockly.utils.noEvent); + // Enable autofocus after the initial render to avoid issue #1329. menu.setAllowAutoFocus(true); - // 1ms delay is required for focusing on context menus because some other - // mouse event is still waiting in the queue and clears focus. - setTimeout(function() {menuDom.focus();}, 1); - Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block. }; /** @@ -112,6 +166,9 @@ Blockly.ContextMenu.show = function(e, options, rtl) { Blockly.ContextMenu.hide = function() { Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu); Blockly.ContextMenu.currentBlock = null; + if (Blockly.ContextMenu.eventWrapper_) { + Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_); + } }; /** @@ -124,20 +181,119 @@ Blockly.ContextMenu.hide = function() { Blockly.ContextMenu.callbackFactory = function(block, xml) { return function() { Blockly.Events.disable(); - var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); - // Move the new block next to the old block. - var xy = block.getRelativeToSurfaceXY(); - if (block.RTL) { - xy.x -= Blockly.SNAP_RADIUS; - } else { - xy.x += Blockly.SNAP_RADIUS; + try { + var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); + // Move the new block next to the old block. + var xy = block.getRelativeToSurfaceXY(); + if (block.RTL) { + xy.x -= Blockly.SNAP_RADIUS; + } else { + xy.x += Blockly.SNAP_RADIUS; + } + xy.y += Blockly.SNAP_RADIUS * 2; + newBlock.moveBy(xy.x, xy.y); + } finally { + Blockly.Events.enable(); } - xy.y += Blockly.SNAP_RADIUS * 2; - newBlock.moveBy(xy.x, xy.y); - Blockly.Events.enable(); if (Blockly.Events.isEnabled() && !newBlock.isShadow()) { - Blockly.Events.fire(new Blockly.Events.Create(newBlock)); + Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); } newBlock.select(); }; }; + +// Helper functions for creating context menu options. + +/** + * Make a context menu option for deleting the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockDeleteOption = function(block) { + // Option to delete this block but not blocks lower in the stack. + // Count the number of blocks that are nested in this block. + var descendantCount = block.getDescendants(true).length; + var nextBlock = block.getNextBlock(); + if (nextBlock) { + // Blocks in the current stack would survive this block's deletion. + descendantCount -= nextBlock.getDescendants(true).length; + } + var deleteOption = { + text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)), + enabled: true, + callback: function() { + Blockly.Events.setGroup(true); + block.dispose(true, true); + Blockly.Events.setGroup(false); + } + }; + return deleteOption; +}; + +/** + * Make a context menu option for showing help for the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockHelpOption = function(block) { + var url = goog.isFunction(block.helpUrl) ? block.helpUrl() : block.helpUrl; + var helpOption = { + enabled: !!url, + text: Blockly.Msg.HELP, + callback: function() { + block.showHelp_(); + } + }; + return helpOption; +}; + +/** + * Make a context menu option for duplicating the current block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockDuplicateOption = function(block) { + var enabled = true; + if (block.getDescendants().length > block.workspace.remainingCapacity()) { + enabled = false; + } + var duplicateOption = { + text: Blockly.Msg.DUPLICATE_BLOCK, + enabled: enabled, + callback: function() { + Blockly.duplicate_(block); + } + }; + return duplicateOption; +}; + +/** + * Make a context menu option for adding or removing comments on the current + * block. + * @param {!Blockly.BlockSvg} block The block where the right-click originated. + * @return {!Object} A menu option, containing text, enabled, and a callback. + * @package + */ +Blockly.ContextMenu.blockCommentOption = function(block) { + var commentOption = { + enabled: !goog.userAgent.IE + }; + // If there's already a comment, add an option to delete it. + if (block.comment) { + commentOption.text = Blockly.Msg.REMOVE_COMMENT; + commentOption.callback = function() { + block.setCommentText(null); + }; + } else { + // If there's no comment, add an option to create a comment. + commentOption.text = Blockly.Msg.ADD_COMMENT; + commentOption.callback = function() { + block.setCommentText(''); + }; + } + return commentOption; +}; diff --git a/core/css.js b/core/css.js index ed9859f..57c244d 100644 --- a/core/css.js +++ b/core/css.js @@ -24,6 +24,10 @@ */ 'use strict'; +/** + * @name Blockly.Css + * @namespace + */ goog.provide('Blockly.Css'); @@ -84,47 +88,24 @@ Blockly.Css.inject = function(hasCss, pathToMedia) { // Strip off any trailing slash (either Unix or Windows). Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); - // Inject CSS tag. + // Inject CSS tag at start of head. var cssNode = document.createElement('style'); - document.head.appendChild(cssNode); + document.head.insertBefore(cssNode, document.head.firstChild); + var cssTextNode = document.createTextNode(text); cssNode.appendChild(cssTextNode); Blockly.Css.styleSheet_ = cssNode.sheet; - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); }; /** * Set the cursor to be displayed when over something draggable. + * See See https://github.com/google/blockly/issues/981 for context. * @param {Blockly.Css.Cursor} cursor Enum. + * @deprecated April 2017. */ Blockly.Css.setCursor = function(cursor) { - if (Blockly.Css.currentCursor_ == cursor) { - return; - } - Blockly.Css.currentCursor_ = cursor; - var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto'; - // There are potentially hundreds of draggable objects. Changing their style - // properties individually is too slow, so change the CSS rule instead. - var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n'; - Blockly.Css.styleSheet_.deleteRule(0); - Blockly.Css.styleSheet_.insertRule(rule, 0); - // There is probably only one toolbox, so just change its style property. - var toolboxen = document.getElementsByClassName('blocklyToolboxDiv'); - for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) { - if (cursor == Blockly.Css.Cursor.DELETE) { - toolbox.style.cursor = url; - } else { - toolbox.style.cursor = ''; - } - } - // Set cursor on the whole document, so that rapid movements - // don't result in cursor changing to an arrow momentarily. - var html = document.body.parentNode; - if (cursor == Blockly.Css.Cursor.OPEN) { - html.style.cursor = ''; - } else { - html.style.cursor = url; - } + console.warn('Deprecated call to Blockly.Css.setCursor.' + + 'See https://github.com/google/blockly/issues/981 for context'); }; /** @@ -135,12 +116,52 @@ Blockly.Css.CONTENT = [ 'background-color: #fff;', 'outline: none;', 'overflow: hidden;', /* IE overflows by default. */ + 'position: absolute;', + 'display: block;', '}', '.blocklyWidgetDiv {', 'display: none;', 'position: absolute;', - 'z-index: 999;', + 'z-index: 99999;', /* big value for bootstrap3 compatibility */ + '}', + + '.injectionDiv {', + 'height: 100%;', + 'position: relative;', + 'overflow: hidden;', /* So blocks in drag surface disappear at edges */ + 'touch-action: none', + '}', + + '.blocklyNonSelectable {', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', + '}', + + '.blocklyWsDragSurface {', + 'display: none;', + 'position: absolute;', + 'top: 0;', + 'left: 0;', + '}', + /* Added as a separate rule with multiple classes to make it more specific + than a bootstrap rule that selects svg:root. See issue #1275 for context. + */ + '.blocklyWsDragSurface.blocklyOverflowVisible {', + 'overflow: visible;', + '}', + + '.blocklyBlockDragSurface {', + 'display: none;', + 'position: absolute;', + 'top: 0;', + 'left: 0;', + 'right: 0;', + 'bottom: 0;', + 'overflow: visible !important;', + 'z-index: 50;', /* Display below toolbox, but above everything else. */ '}', '.blocklyTooltipDiv {', @@ -151,10 +172,10 @@ Blockly.Css.CONTENT = [ 'display: none;', 'font-family: sans-serif;', 'font-size: 9pt;', - 'opacity: 0.9;', + 'opacity: .9;', 'padding: 2px;', 'position: absolute;', - 'z-index: 1000;', + 'z-index: 100000;', /* big value for bootstrap3 compatibility */ '}', '.blocklyResizeSE {', @@ -193,6 +214,51 @@ Blockly.Css.CONTENT = [ 'display: none;', '}', + '.blocklyDraggable {', + /* backup for browsers (e.g. IE11) that don't support grab */ + 'cursor: url("<<>>/handopen.cur"), auto;', + 'cursor: grab;', + 'cursor: -webkit-grab;', + '}', + + '.blocklyDragging {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + /* Changes cursor on mouse down. Not effective in Firefox because of + https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */ + '.blocklyDraggable:active {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + /* Change the cursor on the whole drag surface in case the mouse gets + ahead of block during a drag. This way the cursor is still a closed hand. + */ + '.blocklyBlockDragSurface .blocklyDraggable {', + /* backup for browsers (e.g. IE11) that don't support grabbing */ + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + + '.blocklyDragging.blocklyDraggingDelete {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + + '.blocklyToolboxDelete {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + + '.blocklyToolboxGrab {', + 'cursor: url("<<>>/handclosed.cur"), auto;', + 'cursor: grabbing;', + 'cursor: -webkit-grabbing;', + '}', + '.blocklyDragging>.blocklyPath,', '.blocklyDragging>.blocklyPathLight {', 'fill-opacity: .8;', @@ -244,13 +310,43 @@ Blockly.Css.CONTENT = [ 'fill: #000;', '}', + '.blocklyFlyout {', + 'position: absolute;', + 'z-index: 20;', + '}', + '.blocklyFlyoutButton {', + 'fill: #888;', + 'cursor: default;', + '}', + + '.blocklyFlyoutButtonShadow {', + 'fill: #666;', + '}', + + '.blocklyFlyoutButton:hover {', + 'fill: #aaa;', + '}', + + '.blocklyFlyoutLabel {', + 'cursor: default;', + '}', + + '.blocklyFlyoutLabelBackground {', + 'opacity: 0;', + '}', + + '.blocklyFlyoutLabelText {', + 'fill: #000;', + '}', + /* Don't allow users to select text. It gets annoying when trying to drag a block and selected text moves instead. */ - '.blocklySvg text {', + '.blocklySvg text, .blocklyBlockDragSurface text {', 'user-select: none;', '-moz-user-select: none;', + '-ms-user-select: none;', '-webkit-user-select: none;', 'cursor: inherit;', '}', @@ -322,16 +418,33 @@ Blockly.Css.CONTENT = [ 'fill-opacity: .8;', '}', + '.blocklyTransparentBackground {', + 'opacity: 0;', + '}', + + '.blocklyMainWorkspaceScrollbar {', + 'z-index: 20;', + '}', + + '.blocklyFlyoutScrollbar {', + 'z-index: 30;', + '}', + + '.blocklyScrollbarHorizontal, .blocklyScrollbarVertical {', + 'position: absolute;', + 'outline: none;', + '}', + '.blocklyScrollbarBackground {', 'opacity: 0;', '}', - '.blocklyScrollbarKnob {', + '.blocklyScrollbarHandle {', 'fill: #ccc;', '}', - '.blocklyScrollbarBackground:hover+.blocklyScrollbarKnob,', - '.blocklyScrollbarKnob:hover {', + '.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyScrollbarHandle:hover {', 'fill: #bbb;', '}', @@ -349,12 +462,12 @@ Blockly.Css.CONTENT = [ /* Darken flyout scrollbars due to being on a grey background. */ /* By contrast, workspace scrollbars are on a white background. */ - '.blocklyFlyout .blocklyScrollbarKnob {', + '.blocklyFlyout .blocklyScrollbarHandle {', 'fill: #bbb;', '}', - '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarKnob,', - '.blocklyFlyout .blocklyScrollbarKnob:hover {', + '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', + '.blocklyFlyout .blocklyScrollbarHandle:hover {', 'fill: #aaa;', '}', @@ -383,6 +496,7 @@ Blockly.Css.CONTENT = [ 'stroke: #f00;', 'stroke-width: 2;', 'stroke-linecap: round;', + 'pointer-events: none;', '}', '.blocklyContextMenu {', @@ -391,6 +505,9 @@ Blockly.Css.CONTENT = [ '.blocklyDropdownMenu {', 'padding: 0 !important;', + /* max-height value is same as the constant + * Blockly.FieldDropdown.MAX_MENU_HEIGHT defined in field_dropdown.js. */ + 'max-height: 300px !important;', '}', /* Override the default Closure URL. */ @@ -405,6 +522,12 @@ Blockly.Css.CONTENT = [ 'overflow-x: visible;', 'overflow-y: auto;', 'position: absolute;', + 'user-select: none;', + '-moz-user-select: none;', + '-ms-user-select: none;', + '-webkit-user-select: none;', + 'z-index: 70;', /* so blocks go under toolbox when dragging */ + '-webkit-tap-highlight-color: transparent;', /* issue #1345 */ '}', '.blocklyTreeRoot {', @@ -467,7 +590,7 @@ Blockly.Css.CONTENT = [ '}', '.blocklyTreeIconClosedRtl {', - 'background-position: 0px -1px;', + 'background-position: 0 -1px;', '}', '.blocklyTreeIconOpen {', @@ -479,7 +602,7 @@ Blockly.Css.CONTENT = [ '}', '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {', - 'background-position: 0px -17px;', + 'background-position: 0 -17px;', '}', '.blocklyTreeSelected>.blocklyTreeIconOpen {', @@ -499,6 +622,10 @@ Blockly.Css.CONTENT = [ 'vertical-align: middle;', '}', + '.blocklyToolboxDelete .blocklyTreeLabel {', + 'cursor: url("<<>>/handdelete.cur"), auto;', + '}', + '.blocklyTreeSelected .blocklyTreeLabel {', 'color: #fff;', '}', @@ -659,7 +786,6 @@ Blockly.Css.CONTENT = [ '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {', 'opacity: 0.3;', - '-moz-opacity: 0.3;', 'filter: alpha(opacity=30);', '}', diff --git a/core/dragged_connection_manager.js b/core/dragged_connection_manager.js new file mode 100644 index 0000000..c51d31e --- /dev/null +++ b/core/dragged_connection_manager.js @@ -0,0 +1,246 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Class that controls updates to connections during drags. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.DraggedConnectionManager'); + +goog.require('Blockly.RenderedConnection'); + +goog.require('goog.math.Coordinate'); + + +/** + * Class that controls updates to connections during drags. It is primarily + * responsible for finding the closest eligible connection and highlighting or + * unhiglighting it as needed during a drag. + * @param {!Blockly.BlockSvg} block The top block in the stack being dragged. + * @constructor + */ +Blockly.DraggedConnectionManager = function(block) { + Blockly.selected = block; + + /** + * The top block in the stack being dragged. + * Does not change during a drag. + * @type {!Blockly.Block} + * @private + */ + this.topBlock_ = block; + + /** + * The workspace on which these connections are being dragged. + * Does not change during a drag. + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = block.workspace; + + /** + * The connections on the dragging blocks that are available to connect to + * other blocks. This includes all open connections on the top block, as well + * as the last connection on the block stack. + * Does not change during a drag. + * @type {!Array.} + * @private + */ + this.availableConnections_ = this.initAvailableConnections_(); + + /** + * The connection that this block would connect to if released immediately. + * Updated on every mouse move. + * @type {Blockly.RenderedConnection} + * @private + */ + this.closestConnection_ = null; + + /** + * The connection that would connect to this.closestConnection_ if this block + * were released immediately. + * Updated on every mouse move. + * @type {Blockly.RenderedConnection} + * @private + */ + this.localConnection_ = null; + + /** + * The distance between this.closestConnection_ and this.localConnection_, + * in workspace units. + * Updated on every mouse move. + * @type {number} + * @private + */ + this.radiusConnection_ = 0; + + /** + * Whether the block would be deleted if it were dropped immediately. + * Updated on every mouse move. + * @type {boolean} + * @private + */ + this.wouldDeleteBlock_ = false; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.DraggedConnectionManager.prototype.dispose = function() { + this.topBlock_ = null; + this.workspace_ = null; + this.availableConnections_.length = 0; + this.closestConnection_ = null; + this.localConnection_ = null; +}; + +/** + * Return whether the block would be deleted if dropped immediately, based on + * information from the most recent move event. + * @return {boolean} true if the block would be deleted if dropped immediately. + * @package + */ +Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() { + return this.wouldDeleteBlock_; +}; + +/** + * Connect to the closest connection and render the results. + * This should be called at the end of a drag. + * @package + */ +Blockly.DraggedConnectionManager.prototype.applyConnections = function() { + if (this.closestConnection_) { + // Connect two blocks together. + this.localConnection_.connect(this.closestConnection_); + if (this.topBlock_.rendered) { + // Trigger a connection animation. + // Determine which connection is inferior (lower in the source stack). + var inferiorConnection = this.localConnection_.isSuperior() ? + this.closestConnection_ : this.localConnection_; + inferiorConnection.getSourceBlock().connectionUiEffect(); + // Bring the just-edited stack to the front. + var rootBlock = this.topBlock_.getRootBlock(); + rootBlock.bringToFront(); + } + this.removeHighlighting_(); + } +}; + +/** + * Update highlighted connections based on the most recent move location. + * @param {!goog.math.Coordinate} dxy Position relative to drag start, + * in workspace units. + * @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH}, + * {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}. + * @package + */ +Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea) { + var oldClosestConnection = this.closestConnection_; + var closestConnectionChanged = this.updateClosest_(dxy); + + if (closestConnectionChanged && oldClosestConnection) { + oldClosestConnection.unhighlight(); + } + + // Prefer connecting over dropping into the trash can, but prefer dragging to + // the toolbox over connecting to other blocks. + var wouldConnect = !!this.closestConnection_ && + deleteArea != Blockly.DELETE_AREA_TOOLBOX; + var wouldDelete = !!deleteArea && !this.topBlock_.getParent() && + this.topBlock_.isDeletable(); + this.wouldDeleteBlock_ = wouldDelete && !wouldConnect; + + // Get rid of highlighting so we don't sent mixed messages. + if (wouldDelete && this.closestConnection_) { + this.closestConnection_.unhighlight(); + this.closestConnection_ = null; + } + + if (!this.wouldDeleteBlock_ && closestConnectionChanged && + this.closestConnection_) { + this.addHighlighting_(); + } +}; + +/** + * Remove highlighting from the currently highlighted connection, if it exists. + * @private + */ +Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() { + if (this.closestConnection_) { + this.closestConnection_.unhighlight(); + } +}; + +/** + * Add highlighting to the closest connection, if it exists. + * @private + */ +Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() { + if (this.closestConnection_) { + this.closestConnection_.highlight(); + } +}; + +/** + * Populate the list of available connections on this block stack. This should + * only be called once, at the beginning of a drag. + * @return {!Array.} a list of available + * connections. + * @private + */ +Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() { + var available = this.topBlock_.getConnections_(false); + // Also check the last connection on this stack + var lastOnStack = this.topBlock_.lastConnectionInStack_(); + if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) { + available.push(lastOnStack); + } + return available; +}; + +/** + * Find the new closest connection, and update internal state in response. + * @param {!goog.math.Coordinate} dxy Position relative to the drag start, + * in workspace units. + * @return {boolean} Whether the closest connection has changed. + * @private + */ +Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) { + var oldClosestConnection = this.closestConnection_; + + this.closestConnection_ = null; + this.localConnection_ = null; + this.radiusConnection_ = Blockly.SNAP_RADIUS; + for (var i = 0; i < this.availableConnections_.length; i++) { + var myConnection = this.availableConnections_[i]; + var neighbour = myConnection.closest(this.radiusConnection_, dxy); + if (neighbour.connection) { + this.closestConnection_ = neighbour.connection; + this.localConnection_ = myConnection; + this.radiusConnection_ = neighbour.radius; + } + } + return oldClosestConnection != this.closestConnection_; +}; diff --git a/core/events.js b/core/events.js index bc4299e..6cf7744 100644 --- a/core/events.js +++ b/core/events.js @@ -24,8 +24,13 @@ */ 'use strict'; +/** + * Events fired as a result of actions in Blockly's editor. + * @namespace Blockly.Events + */ goog.provide('Blockly.Events'); +goog.require('goog.array'); goog.require('goog.math.Coordinate'); @@ -37,7 +42,7 @@ goog.require('goog.math.Coordinate'); Blockly.Events.group_ = ''; /** - * Sets whether events should be added to the undo stack. + * Sets whether the next event should be added to the undo stack. * @type {boolean} */ Blockly.Events.recordUndo = true; @@ -50,29 +55,71 @@ Blockly.Events.recordUndo = true; Blockly.Events.disabled_ = 0; /** - * Name of event that creates a block. + * Name of event that creates a block. Will be deprecated for BLOCK_CREATE. * @const */ Blockly.Events.CREATE = 'create'; /** - * Name of event that deletes a block. + * Name of event that creates a block. + * @const + */ +Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE; + +/** + * Name of event that deletes a block. Will be deprecated for BLOCK_DELETE. * @const */ Blockly.Events.DELETE = 'delete'; /** - * Name of event that changes a block. + * Name of event that deletes a block. + * @const + */ +Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE; + +/** + * Name of event that changes a block. Will be deprecated for BLOCK_CHANGE. * @const */ Blockly.Events.CHANGE = 'change'; /** - * Name of event that moves a block. + * Name of event that changes a block. + * @const + */ +Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE; + +/** + * Name of event that moves a block. Will be deprecated for BLOCK_MOVE. * @const */ Blockly.Events.MOVE = 'move'; +/** + * Name of event that moves a block. + * @const + */ +Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE; + +/** + * Name of event that creates a variable. + * @const + */ +Blockly.Events.VAR_CREATE = 'var_create'; + +/** + * Name of event that deletes a variable. + * @const + */ +Blockly.Events.VAR_DELETE = 'var_delete'; + +/** + * Name of event that renames a variable. + * @const + */ +Blockly.Events.VAR_RENAME = 'var_rename'; + /** * Name of event that records a UI change. * @const @@ -127,45 +174,42 @@ Blockly.Events.filter = function(queueIn, forward) { // Undo is merged in reverse order. queue.reverse(); } - // Merge duplicates. O(n^2), but n should be very small. - for (var i = 0, event1; event1 = queue[i]; i++) { - for (var j = i + 1, event2; event2 = queue[j]; j++) { - if (event1.type == event2.type && - event1.blockId == event2.blockId && - event1.workspaceId == event2.workspaceId) { - if (event1.type == Blockly.Events.MOVE) { - // Merge move events. - event1.newParentId = event2.newParentId; - event1.newInputName = event2.newInputName; - event1.newCoordinate = event2.newCoordinate; - queue.splice(j, 1); - j--; - } else if (event1.type == Blockly.Events.CHANGE && - event1.element == event2.element && - event1.name == event2.name) { - // Merge change events. - event1.newValue = event2.newValue; - queue.splice(j, 1); - j--; - } else if (event1.type == Blockly.Events.UI && - event2.element == 'click' && - (event1.element == 'commentOpen' || - event1.element == 'mutatorOpen' || - event1.element == 'warningOpen')) { - // Merge change events. - event1.newValue = event2.newValue; - queue.splice(j, 1); - j--; - } + var mergedQueue = []; + var hash = Object.create(null); + // Merge duplicates. + for (var i = 0, event; event = queue[i]; i++) { + if (!event.isNull()) { + var key = [event.type, event.blockId, event.workspaceId].join(' '); + var lastEvent = hash[key]; + if (!lastEvent) { + hash[key] = event; + mergedQueue.push(event); + } else if (event.type == Blockly.Events.MOVE) { + // Merge move events. + lastEvent.newParentId = event.newParentId; + lastEvent.newInputName = event.newInputName; + lastEvent.newCoordinate = event.newCoordinate; + } else if (event.type == Blockly.Events.CHANGE && + event.element == lastEvent.element && + event.name == lastEvent.name) { + // Merge change events. + lastEvent.newValue = event.newValue; + } else if (event.type == Blockly.Events.UI && + event.element == 'click' && + (lastEvent.element == 'commentOpen' || + lastEvent.element == 'mutatorOpen' || + lastEvent.element == 'warningOpen')) { + // Merge click events. + lastEvent.newValue = event.newValue; + } else { + // Collision: newer events should merge into this event to maintain order + hash[key] = event; + mergedQueue.push(event); } } } - // Remove null events. - for (var i = queue.length - 1; i >= 0; i--) { - if (queue[i].isNull()) { - queue.splice(i, 1); - } - } + // Filter out any events that have become null due to merging. + queue = mergedQueue.filter(function(e) { return !e.isNull(); }); if (!forward) { // Restore undo order. queue.reverse(); @@ -229,7 +273,7 @@ Blockly.Events.getGroup = function() { */ Blockly.Events.setGroup = function(state) { if (typeof state == 'boolean') { - Blockly.Events.group_ = state ? Blockly.genUid() : ''; + Blockly.Events.group_ = state ? Blockly.utils.genUid() : ''; } else { Blockly.Events.group_ = state; } @@ -266,11 +310,20 @@ Blockly.Events.fromJson = function(json, workspace) { event = new Blockly.Events.Delete(null); break; case Blockly.Events.CHANGE: - event = new Blockly.Events.Change(null); + event = new Blockly.Events.Change(null, '', '', '', ''); break; case Blockly.Events.MOVE: event = new Blockly.Events.Move(null); break; + case Blockly.Events.VAR_CREATE: + event = new Blockly.Events.VarCreate(null); + break; + case Blockly.Events.VAR_DELETE: + event = new Blockly.Events.VarDelete(null); + break; + case Blockly.Events.VAR_RENAME: + event = new Blockly.Events.VarRename(null, ''); + break; case Blockly.Events.UI: event = new Blockly.Events.Ui(null); break; @@ -284,13 +337,50 @@ Blockly.Events.fromJson = function(json, workspace) { /** * Abstract class for an event. - * @param {Blockly.Block} block The block. + * @param {Blockly.Block|Blockly.VariableModel} elem The block or variable. * @constructor */ -Blockly.Events.Abstract = function(block) { - if (block) { - this.blockId = block.id; - this.workspaceId = block.workspace.id; +Blockly.Events.Abstract = function(elem) { + /** + * The block id for the block this event pertains to, if appropriate for the + * event type. + * @type {string|undefined} + */ + this.blockId = undefined; + + /** + * The variable id for the variable this event pertains to. Only set in + * VarCreate, VarDelete, and VarRename events. + * @type {string|undefined} + */ + this.varId = undefined; + + /** + * The workspace identifier for this event. + * @type {string|undefined} + */ + this.workspaceId = undefined; + + /** + * The event group id for the group this event belongs to. Groups define + * events that should be treated as an single action from the user's + * perspective, and should be undone together. + * @type {string} + */ + this.group = undefined; + + /** + * Sets whether the event should be added to the undo stack. + * @type {boolean} + */ + this.recordUndo = undefined; + + if (elem instanceof Blockly.Block) { + this.blockId = elem.id; + this.workspaceId = elem.workspace.id; + } else if (elem instanceof Blockly.VariableModel) { + this.workspaceId = elem.workspace.id; + this.varId = elem.getId(); } this.group = Blockly.Events.group_; this.recordUndo = Blockly.Events.recordUndo; @@ -302,9 +392,14 @@ Blockly.Events.Abstract = function(block) { */ Blockly.Events.Abstract.prototype.toJson = function() { var json = { - 'type': this.type, - 'blockId': this.blockId + 'type': this.type }; + if (this.blockId) { + json['blockId'] = this.blockId; + } + if (this.varId) { + json['varId'] = this.varId; + } if (this.group) { json['group'] = this.group; } @@ -317,6 +412,7 @@ Blockly.Events.Abstract.prototype.toJson = function() { */ Blockly.Events.Abstract.prototype.fromJson = function(json) { this.blockId = json['blockId']; + this.varId = json['varId']; this.group = json['group']; }; @@ -332,10 +428,27 @@ Blockly.Events.Abstract.prototype.isNull = function() { * Run an event. * @param {boolean} forward True if run forward, false if run backward (undo). */ -Blockly.Events.Abstract.prototype.run = function(forward) { +Blockly.Events.Abstract.prototype.run = function( + /* eslint-disable no-unused-vars */ forward + /* eslint-enable no-unused-vars */) { // Defined by subclasses. }; +/** + * Get workspace the event belongs to. + * @return {Blockly.Workspace} The workspace the event belongs to. + * @throws {Error} if workspace is null. + * @private + */ +Blockly.Events.Abstract.prototype.getEventWorkspace_ = function() { + var workspace = Blockly.Workspace.getById(this.workspaceId); + if (!workspace) { + throw Error('Workspace is null. Event must have been generated from real' + + ' Blockly events.'); + } + return workspace; +}; + /** * Class for a block creation event. * @param {Blockly.Block} block The created block. Null for a blank event. @@ -347,11 +460,24 @@ Blockly.Events.Create = function(block) { return; // Blank event to be populated by fromJson. } Blockly.Events.Create.superClass_.constructor.call(this, block); - this.xml = Blockly.Xml.blockToDomWithXY(block); + + if (block.workspace.rendered) { + this.xml = Blockly.Xml.blockToDomWithXY(block); + } else { + this.xml = Blockly.Xml.blockToDom(block); + } this.ids = Blockly.Events.getDescendantIds_(block); }; goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract); +/** + * Class for a block creation event. + * @param {Blockly.Block} block The created block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockCreate = Blockly.Events.Create; + /** * Type of this event. * @type {string} @@ -384,7 +510,7 @@ Blockly.Events.Create.prototype.fromJson = function(json) { * @param {boolean} forward True if run forward, false if run backward (undo). */ Blockly.Events.Create.prototype.run = function(forward) { - var workspace = Blockly.Workspace.getById(this.workspaceId); + var workspace = this.getEventWorkspace_(); if (forward) { var xml = goog.dom.createDom('xml'); xml.appendChild(this.xml); @@ -393,10 +519,10 @@ Blockly.Events.Create.prototype.run = function(forward) { for (var i = 0, id; id = this.ids[i]; i++) { var block = workspace.getBlockById(id); if (block) { - block.dispose(false, true); + block.dispose(false, false); } else if (id == this.blockId) { // Only complain about root-level block. - console.warn("Can't uncreate non-existant block: " + id); + console.warn("Can't uncreate non-existent block: " + id); } } } @@ -416,11 +542,24 @@ Blockly.Events.Delete = function(block) { throw 'Connected blocks cannot be deleted.'; } Blockly.Events.Delete.superClass_.constructor.call(this, block); - this.oldXml = Blockly.Xml.blockToDomWithXY(block); + + if (block.workspace.rendered) { + this.oldXml = Blockly.Xml.blockToDomWithXY(block); + } else { + this.oldXml = Blockly.Xml.blockToDom(block); + } this.ids = Blockly.Events.getDescendantIds_(block); }; goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract); +/** + * Class for a block deletion event. + * @param {Blockly.Block} block The deleted block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockDelete = Blockly.Events.Delete; + /** * Type of this event. * @type {string} @@ -451,15 +590,15 @@ Blockly.Events.Delete.prototype.fromJson = function(json) { * @param {boolean} forward True if run forward, false if run backward (undo). */ Blockly.Events.Delete.prototype.run = function(forward) { - var workspace = Blockly.Workspace.getById(this.workspaceId); + var workspace = this.getEventWorkspace_(); if (forward) { for (var i = 0, id; id = this.ids[i]; i++) { var block = workspace.getBlockById(id); if (block) { - block.dispose(false, true); + block.dispose(false, false); } else if (id == this.blockId) { // Only complain about root-level block. - console.warn("Can't delete non-existant block: " + id); + console.warn("Can't delete non-existent block: " + id); } } } else { @@ -474,8 +613,8 @@ Blockly.Events.Delete.prototype.run = function(forward) { * @param {Blockly.Block} block The changed block. Null for a blank event. * @param {string} element One of 'field', 'comment', 'disabled', etc. * @param {?string} name Name of input or field affected, or null. - * @param {string} oldValue Previous value of element. - * @param {string} newValue New value of element. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. * @extends {Blockly.Events.Abstract} * @constructor */ @@ -491,6 +630,18 @@ Blockly.Events.Change = function(block, element, name, oldValue, newValue) { }; goog.inherits(Blockly.Events.Change, Blockly.Events.Abstract); +/** + * Class for a block change event. + * @param {Blockly.Block} block The changed block. Null for a blank event. + * @param {string} element One of 'field', 'comment', 'disabled', etc. + * @param {?string} name Name of input or field affected, or null. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockChange = Blockly.Events.Change; + /** * Type of this event. * @type {string} @@ -535,10 +686,10 @@ Blockly.Events.Change.prototype.isNull = function() { * @param {boolean} forward True if run forward, false if run backward (undo). */ Blockly.Events.Change.prototype.run = function(forward) { - var workspace = Blockly.Workspace.getById(this.workspaceId); + var workspace = this.getEventWorkspace_(); var block = workspace.getBlockById(this.blockId); if (!block) { - console.warn("Can't change non-existant block: " + this.blockId); + console.warn("Can't change non-existent block: " + this.blockId); return; } if (block.mutator) { @@ -550,9 +701,12 @@ Blockly.Events.Change.prototype.run = function(forward) { case 'field': var field = block.getField(this.name); if (field) { + // Run the validator for any side-effects it may have. + // The validator's opinion on validity is ignored. + field.callValidator(value); field.setValue(value); } else { - console.warn("Can't set non-existant field: " + this.name); + console.warn("Can't set non-existent field: " + this.name); } break; case 'comment': @@ -604,6 +758,15 @@ Blockly.Events.Move = function(block) { }; goog.inherits(Blockly.Events.Move, Blockly.Events.Abstract); + +/** + * Class for a block move event. Created before the move. + * @param {Blockly.Block} block The moved block. Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.BlockMove = Blockly.Events.Move; + /** * Type of this event. * @type {string} @@ -692,10 +855,10 @@ Blockly.Events.Move.prototype.isNull = function() { * @param {boolean} forward True if run forward, false if run backward (undo). */ Blockly.Events.Move.prototype.run = function(forward) { - var workspace = Blockly.Workspace.getById(this.workspaceId); + var workspace = this.getEventWorkspace_(); var block = workspace.getBlockById(this.blockId); if (!block) { - console.warn("Can't move non-existant block: " + this.blockId); + console.warn("Can't move non-existent block: " + this.blockId); return; } var parentId = forward ? this.newParentId : this.oldParentId; @@ -705,7 +868,7 @@ Blockly.Events.Move.prototype.run = function(forward) { if (parentId) { parentBlock = workspace.getBlockById(parentId); if (!parentBlock) { - console.warn("Can't connect to non-existant block: " + parentId); + console.warn("Can't connect to non-existent block: " + parentId); return; } } @@ -729,7 +892,7 @@ Blockly.Events.Move.prototype.run = function(forward) { if (parentConnection) { blockConnection.connect(parentConnection); } else { - console.warn("Can't connect to non-existant input: " + inputName); + console.warn("Can't connect to non-existent input: " + inputName); } } }; @@ -738,8 +901,8 @@ Blockly.Events.Move.prototype.run = function(forward) { * Class for a UI event. * @param {Blockly.Block} block The affected block. * @param {string} element One of 'selected', 'comment', 'mutator', etc. - * @param {string} oldValue Previous value of element. - * @param {string} newValue New value of element. + * @param {*} oldValue Previous value of element. + * @param {*} newValue New value of element. * @extends {Blockly.Events.Abstract} * @constructor */ @@ -780,3 +943,204 @@ Blockly.Events.Ui.prototype.fromJson = function(json) { this.element = json['element']; this.newValue = json['newValue']; }; + +/** + * Class for a variable creation event. + * @param {Blockly.VariableModel} variable The created variable. + * Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarCreate = function(variable) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarCreate.superClass_.constructor.call(this, variable); + this.varType = variable.type; + this.varName = variable.name; +}; +goog.inherits(Blockly.Events.VarCreate, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarCreate.prototype.type = Blockly.Events.VAR_CREATE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarCreate.prototype.toJson = function() { + var json = Blockly.Events.VarCreate.superClass_.toJson.call(this); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarCreate.prototype.fromJson = function(json) { + Blockly.Events.VarCreate.superClass_.fromJson.call(this, json); + this.varType = json['varType']; + this.varName = json['varName']; +}; + +/** + * Run a variable creation event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarCreate.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.createVariable(this.varName, this.varType, this.varId); + } else { + workspace.deleteVariableById(this.varId); + } +}; + +/** + * Class for a variable deletion event. + * @param {Blockly.VariableModel} variable The deleted variable. + * Null for a blank event. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarDelete = function(variable) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarDelete.superClass_.constructor.call(this, variable); + this.varType = variable.type; + this.varName = variable.name; +}; +goog.inherits(Blockly.Events.VarDelete, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarDelete.prototype.type = Blockly.Events.VAR_DELETE; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarDelete.prototype.toJson = function() { + var json = Blockly.Events.VarDelete.superClass_.toJson.call(this); + json['varType'] = this.varType; + json['varName'] = this.varName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarDelete.prototype.fromJson = function(json) { + Blockly.Events.VarDelete.superClass_.fromJson.call(this, json); + this.varType = json['varType']; + this.varName = json['varName']; +}; + +/** + * Run a variable deletion event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarDelete.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.deleteVariableById(this.varId); + } else { + workspace.createVariable(this.varName, this.varType, this.varId); + } +}; + +/** + * Class for a variable rename event. + * @param {Blockly.VariableModel} variable The renamed variable. + * Null for a blank event. + * @param {string} newName The new name the variable will be changed to. + * @extends {Blockly.Events.Abstract} + * @constructor + */ +Blockly.Events.VarRename = function(variable, newName) { + if (!variable) { + return; // Blank event to be populated by fromJson. + } + Blockly.Events.VarRename.superClass_.constructor.call(this, variable); + this.oldName = variable.name; + this.newName = newName; +}; +goog.inherits(Blockly.Events.VarRename, Blockly.Events.Abstract); + +/** + * Type of this event. + * @type {string} + */ +Blockly.Events.VarRename.prototype.type = Blockly.Events.VAR_RENAME; + +/** + * Encode the event as JSON. + * @return {!Object} JSON representation. + */ +Blockly.Events.VarRename.prototype.toJson = function() { + var json = Blockly.Events.VarRename.superClass_.toJson.call(this); + json['oldName'] = this.oldName; + json['newName'] = this.newName; + return json; +}; + +/** + * Decode the JSON event. + * @param {!Object} json JSON representation. + */ +Blockly.Events.VarRename.prototype.fromJson = function(json) { + Blockly.Events.VarRename.superClass_.fromJson.call(this, json); + this.oldName = json['oldName']; + this.newName = json['newName']; +}; + +/** + * Run a variable rename event. + * @param {boolean} forward True if run forward, false if run backward (undo). + */ +Blockly.Events.VarRename.prototype.run = function(forward) { + var workspace = this.getEventWorkspace_(); + if (forward) { + workspace.renameVariableById(this.varId, this.newName); + } else { + workspace.renameVariableById(this.varId, this.oldName); + } +}; + +/** + * Enable/disable a block depending on whether it is properly connected. + * Use this on applications where all blocks should be connected to a top block. + * Recommend setting the 'disable' option to 'false' in the config so that + * users don't try to reenable disabled orphan blocks. + * @param {!Blockly.Events.Abstract} event Custom data for event. + */ +Blockly.Events.disableOrphans = function(event) { + if (event.type == Blockly.Events.MOVE || + event.type == Blockly.Events.CREATE) { + var workspace = Blockly.Workspace.getById(event.workspaceId); + var block = workspace.getBlockById(event.blockId); + if (block) { + if (block.getParent() && !block.getParent().disabled) { + var children = block.getDescendants(); + for (var i = 0, child; child = children[i]; i++) { + child.setDisabled(false); + } + } else if ((block.outputConnection || block.previousConnection) && + !workspace.isDragging()) { + do { + block.setDisabled(true); + block = block.getNextBlock(); + } while (block); + } + } + } +}; diff --git a/core/extensions.js b/core/extensions.js new file mode 100644 index 0000000..016fc4a --- /dev/null +++ b/core/extensions.js @@ -0,0 +1,449 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Extensions are functions that help initialize blocks, usually + * adding dynamic behavior such as onchange handlers and mutators. These + * are applied using Block.applyExtension(), or the JSON "extensions" + * array attribute. + * @author Anm@anm.me (Andrew n marshall) + */ +'use strict'; + +/** + * @name Blockly.Extensions + * @namespace + **/ +goog.provide('Blockly.Extensions'); + +goog.require('Blockly.Mutator'); +goog.require('Blockly.utils'); +goog.require('goog.string'); + +/** + * The set of all registered extensions, keyed by extension name/id. + * @private + */ +Blockly.Extensions.ALL_ = {}; + +/** + * Registers a new extension function. Extensions are functions that help + * initialize blocks, usually adding dynamic behavior such as onchange + * handlers and mutators. These are applied using Block.applyExtension(), or + * the JSON "extensions" array attribute. + * @param {string} name The name of this extension. + * @param {Function} initFn The function to initialize an extended block. + * @throws {Error} if the extension name is empty, the extension is already + * registered, or extensionFn is not a function. + */ +Blockly.Extensions.register = function(name, initFn) { + if (!goog.isString(name) || goog.string.isEmptyOrWhitespace(name)) { + throw new Error('Error: Invalid extension name "' + name + '"'); + } + if (Blockly.Extensions.ALL_[name]) { + throw new Error('Error: Extension "' + name + '" is already registered.'); + } + if (!goog.isFunction(initFn)) { + throw new Error('Error: Extension "' + name + '" must be a function'); + } + Blockly.Extensions.ALL_[name] = initFn; +}; + +/** + * Registers a new extension function that adds all key/value of mixinObj. + * @param {string} name The name of this extension. + * @param {!Object} mixinObj The values to mix in. + * @throws {Error} if the extension name is empty or the extension is already + * registered. + */ +Blockly.Extensions.registerMixin = function(name, mixinObj) { + if (!goog.isObject(mixinObj)){ + throw new Error('Error: Mixin "' + name + '" must be a object'); + } + Blockly.Extensions.register(name, function() { + this.mixin(mixinObj); + }); +}; + +/** + * Registers a new extension function that adds a mutator to the block. + * At register time this performs some basic sanity checks on the mutator. + * The wrapper may also add a mutator dialog to the block, if both compose and + * decompose are defined on the mixin. + * @param {string} name The name of this mutator extension. + * @param {!Object} mixinObj The values to mix in. + * @param {(function())=} opt_helperFn An optional function to apply after + * mixing in the object. + * @param {Array.=} opt_blockList A list of blocks to appear in the + * flyout of the mutator dialog. + * @throws {Error} if the mutation is invalid or can't be applied to the block. + */ +Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn, + opt_blockList) { + var errorPrefix = 'Error when registering mutator "' + name + '": '; + + // Sanity check the mixin object before registering it. + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.domToMutation, 'domToMutation'); + Blockly.Extensions.checkHasFunction_( + errorPrefix, mixinObj.mutationToDom, 'mutationToDom'); + + var hasMutatorDialog = + Blockly.Extensions.checkMutatorDialog_(mixinObj, errorPrefix); + + if (opt_helperFn && !goog.isFunction(opt_helperFn)) { + throw new Error('Extension "' + name + '" is not a function'); + } + + // Sanity checks passed. + Blockly.Extensions.register(name, function() { + if (hasMutatorDialog) { + this.setMutator(new Blockly.Mutator(opt_blockList)); + } + // Mixin the object. + this.mixin(mixinObj); + + if (opt_helperFn) { + opt_helperFn.apply(this); + } + }); +}; + +/** + * Applies an extension method to a block. This should only be called during + * block construction. + * @param {string} name The name of the extension. + * @param {!Blockly.Block} block The block to apply the named extension to. + * @param {boolean} isMutator True if this extension defines a mutator. + * @throws {Error} if the extension is not found. + */ +Blockly.Extensions.apply = function(name, block, isMutator) { + var extensionFn = Blockly.Extensions.ALL_[name]; + if (!goog.isFunction(extensionFn)) { + throw new Error('Error: Extension "' + name + '" not found.'); + } + if (isMutator) { + // Fail early if the block already has mutation properties. + Blockly.Extensions.checkNoMutatorProperties_(name, block); + } else { + // Record the old properties so we can make sure they don't change after + // applying the extension. + var mutatorProperties = Blockly.Extensions.getMutatorProperties_(block); + } + extensionFn.apply(block); + + if (isMutator) { + var errorPrefix = 'Error after applying mutator "' + name + '": '; + Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block); + } else { + if (!Blockly.Extensions.mutatorPropertiesMatch_(mutatorProperties, block)) { + throw new Error('Error when applying extension "' + name + '": ' + + 'mutation properties changed when applying a non-mutator extension.'); + } + } +}; + +/** + * Check that the given value is a function. + * @param {string} errorPrefix The string to prepend to any error message. + * @param {*} func Function to check. + * @param {string} propertyName Which property to check. + * @throws {Error} if the property does not exist or is not a function. + * @private + */ +Blockly.Extensions.checkHasFunction_ = function(errorPrefix, func, + propertyName) { + if (!func) { + throw new Error(errorPrefix + + 'missing required property "' + propertyName + '"'); + } else if (typeof func != 'function') { + throw new Error(errorPrefix + + '" required property "' + propertyName + '" must be a function'); + } +}; + +/** + * Check that the given block does not have any of the four mutator properties + * defined on it. This function should be called before applying a mutator + * extension to a block, to make sure we are not overwriting properties. + * @param {string} mutationName The name of the mutation to reference in error + * messages. + * @param {!Blockly.Block} block The block to check. + * @throws {Error} if any of the properties already exist on the block. + * @private + */ +Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) { + var properties = Blockly.Extensions.getMutatorProperties_(block); + if (properties.length) { + throw new Error('Error: tried to apply mutation "' + mutationName + + '" to a block that already has mutator functions.' + + ' Block id: ' + block.id); + } +}; + +/** + * Check that the given object has both or neither of the functions required + * to have a mutator dialog. + * These functions are 'compose' and 'decompose'. If a block has one, it must + * have both. + * @param {!Object} object The object to check. + * @param {string} errorPrefix The string to prepend to any error message. + * @return {boolean} True if the object has both functions. False if it has + * neither function. + * @throws {Error} if the object has only one of the functions. + * @private + */ +Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) { + var hasCompose = object.compose !== undefined; + var hasDecompose = object.decompose !== undefined; + + if (hasCompose && hasDecompose) { + if (typeof object.compose != 'function') { + throw new Error(errorPrefix + 'compose must be a function.'); + } else if (typeof object.decompose != 'function') { + throw new Error(errorPrefix + 'decompose must be a function.'); + } + return true; + } else if (!hasCompose && !hasDecompose) { + return false; + } else { + throw new Error(errorPrefix + + 'Must have both or neither of "compose" and "decompose"'); + } +}; + +/** + * Check that a block has required mutator properties. This should be called + * after applying a mutation extension. + * @param {string} errorPrefix The string to prepend to any error message. + * @param {!Blockly.Block} block The block to inspect. + * @private + */ +Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix, + block) { + if (typeof block.domToMutation != 'function') { + throw new Error(errorPrefix + + 'Applying a mutator didn\'t add "domToMutation"'); + } + if (typeof block.mutationToDom != 'function') { + throw new Error(errorPrefix + + 'Applying a mutator didn\'t add "mutationToDom"'); + } + + // A block with a mutator isn't required to have a mutation dialog, but + // it should still have both or neither of compose and decompose. + Blockly.Extensions.checkMutatorDialog_(block, errorPrefix); +}; + +/** + * Get a list of values of mutator properties on the given block. + * @param {!Blockly.Block} block The block to inspect. + * @return {!Array.} a list with all of the defined properties, which + * should be functions, but may be anything other than undefined. + * @private + */ +Blockly.Extensions.getMutatorProperties_ = function(block) { + var result = []; + // List each function explicitly by reference to allow for renaming + // during compilation. + if (block.domToMutation !== undefined) { + result.push(block.domToMutation); + } + if (block.mutationToDom !== undefined) { + result.push(block.mutationToDom); + } + if (block.compose !== undefined) { + result.push(block.compose); + } + if (block.decompose !== undefined) { + result.push(block.decompose); + } + return result; +}; + +/** + * Check that the current mutator properties match a list of old mutator + * properties. This should be called after applying a non-mutator extension, + * to verify that the extension didn't change properties it shouldn't. + * @param {!Array.} oldProperties The old values to compare to. + * @param {!Blockly.Block} block The block to inspect for new values. + * @return {boolean} True if the property lists match. + * @private + */ +Blockly.Extensions.mutatorPropertiesMatch_ = function(oldProperties, block) { + var newProperties = Blockly.Extensions.getMutatorProperties_(block); + if (newProperties.length != oldProperties.length) { + return false; + } + for (var i = 0; i < newProperties.length; i++) { + if (oldProperties[i] != newProperties[i]) { + return false; + } + } + return true; +}; + +/** + * Builds an extension function that will map a dropdown value to a tooltip + * string. + * + * This method includes multiple checks to ensure tooltips, dropdown options, + * and message references are aligned. This aims to catch errors as early as + * possible, without requiring developers to manually test tooltips under each + * option. After the page is loaded, each tooltip text string will be checked + * for matching message keys in the internationalized string table. Deferring + * this until the page is loaded decouples loading dependencies. Later, upon + * loading the first block of any given type, the extension will validate every + * dropdown option has a matching tooltip in the lookupTable. Errors are + * reported as warnings in the console, and are never fatal. + * @param {string} dropdownName The name of the field whose value is the key + * to the lookup table. + * @param {!Object.} lookupTable The table of field values to + * tooltip text. + * @return {Function} The extension function. + */ +Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, + lookupTable) { + // List of block types already validated, to minimize duplicate warnings. + var blockTypesChecked = []; + + // Check the tooltip string messages for invalid references. + // Wait for load, in case Blockly.Msg is not yet populated. + // runAfterPageLoad() does not run in a Node.js environment due to lack of + // document object, in which case skip the validation. + if (typeof document == 'object') { // Relies on document.readyState + Blockly.utils.runAfterPageLoad(function() { + for (var key in lookupTable) { + // Will print warnings if reference is missing. + Blockly.utils.checkMessageReferences(lookupTable[key]); + } + }); + } + + /** + * The actual extension. + * @this {Blockly.Block} + */ + var extensionFn = function() { + if (this.type && blockTypesChecked.indexOf(this.type) === -1) { + Blockly.Extensions.checkDropdownOptionsInTable_( + this, dropdownName, lookupTable); + blockTypesChecked.push(this.type); + } + + this.setTooltip(function() { + var value = this.getFieldValue(dropdownName); + var tooltip = lookupTable[value]; + if (tooltip == null) { + if (blockTypesChecked.indexOf(this.type) === -1) { + // Warn for missing values on generated tooltips. + var warning = 'No tooltip mapping for value ' + value + + ' of field ' + dropdownName; + if (this.type != null) { + warning += (' of block type ' + this.type); + } + console.warn(warning + '.'); + } + } else { + tooltip = Blockly.utils.replaceMessageReferences(tooltip); + } + return tooltip; + }.bind(this)); + }; + return extensionFn; +}; + +/** + * Checks all options keys are present in the provided string lookup table. + * Emits console warnings when they are not. + * @param {!Blockly.Block} block The block containing the dropdown + * @param {string} dropdownName The name of the dropdown + * @param {!Object.} lookupTable The string lookup table + * @private + */ +Blockly.Extensions.checkDropdownOptionsInTable_ = function(block, dropdownName, + lookupTable) { + // Validate all dropdown options have values. + var dropdown = block.getField(dropdownName); + if (!dropdown.isOptionListDynamic()) { + var options = dropdown.getOptions(); + for (var i = 0; i < options.length; ++i) { + var optionKey = options[i][1]; // label, then value + if (lookupTable[optionKey] == null) { + console.warn('No tooltip mapping for value ' + optionKey + + ' of field ' + dropdownName + ' of block type ' + block.type); + } + } + } +}; + +/** + * Builds an extension function that will install a dynamic tooltip. The + * tooltip message should include the string '%1' and that string will be + * replaced with the value of the named field. + * @param {string} msgTemplate The template form to of the message text, with + * %1 placeholder. + * @param {string} fieldName The field with the replacement value. + * @returns {Function} The extension function. + */ +Blockly.Extensions.buildTooltipWithFieldValue = function(msgTemplate, + fieldName) { + // Check the tooltip string messages for invalid references. + // Wait for load, in case Blockly.Msg is not yet populated. + // runAfterPageLoad() does not run in a Node.js environment due to lack of + // document object, in which case skip the validation. + if (typeof document == 'object') { // Relies on document.readyState + Blockly.utils.runAfterPageLoad(function() { + // Will print warnings if reference is missing. + Blockly.utils.checkMessageReferences(msgTemplate); + }); + } + + /** + * The actual extension. + * @this {Blockly.Block} + */ + var extensionFn = function() { + this.setTooltip(function() { + return Blockly.utils.replaceMessageReferences(msgTemplate) + .replace('%1', this.getFieldValue(fieldName)); + }.bind(this)); + }; + return extensionFn; +}; + +/** + * Configures the tooltip to mimic the parent block when connected. Otherwise, + * uses the tooltip text at the time this extension is initialized. This takes + * advantage of the fact that all other values from JSON are initialized before + * extensions. + * @this {Blockly.Block} + * @private + */ +Blockly.Extensions.extensionParentTooltip_ = function() { + this.tooltipWhenNotConnected_ = this.tooltip; + this.setTooltip(function() { + var parent = this.getParent(); + return (parent && parent.getInputsInline() && parent.tooltip) || + this.tooltipWhenNotConnected_; + }.bind(this)); +}; +Blockly.Extensions.register('parent_tooltip_when_inline', + Blockly.Extensions.extensionParentTooltip_); diff --git a/core/field.js b/core/field.js index 305ecd6..66f457a 100644 --- a/core/field.js +++ b/core/field.js @@ -28,6 +28,8 @@ goog.provide('Blockly.Field'); +goog.require('Blockly.Gesture'); + goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.math.Size'); @@ -45,7 +47,7 @@ goog.require('goog.userAgent'); * @constructor */ Blockly.Field = function(text, opt_validator) { - this.size_ = new goog.math.Size(0, 25); + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); this.setValue(text); this.setValidator(opt_validator); }; @@ -68,7 +70,7 @@ Blockly.Field.cacheReference_ = 0; /** * Name of field. Unique within each block. * Static labels are usually unnamed. - * @type {string=} + * @type {string|undefined} */ Blockly.Field.prototype.name = undefined; @@ -135,40 +137,46 @@ Blockly.Field.prototype.init = function() { return; } // Build the DOM. - this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); + this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null); if (!this.visible_) { this.fieldGroup_.style.display = 'none'; } - this.borderRect_ = Blockly.createSvgElement('rect', - {'rx': 4, - 'ry': 4, - 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, - 'y': 0, - 'height': 16}, this.fieldGroup_, this.sourceBlock_.workspace); + this.borderRect_ = Blockly.utils.createSvgElement('rect', + { + 'rx': 4, + 'ry': 4, + 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, + 'y': 0, + 'height': 16 + }, this.fieldGroup_); /** @type {!Element} */ - this.textElement_ = Blockly.createSvgElement('text', + this.textElement_ = Blockly.utils.createSvgElement('text', {'class': 'blocklyText', 'y': this.size_.height - 12.5}, this.fieldGroup_); this.updateEditable(); this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); - this.mouseUpWrapper_ = - Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_); + this.mouseDownWrapper_ = + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); // Force a render. - this.updateTextNode_(); - if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, '', this.getValue())); - } + this.render_(); +}; + +/** + * Initializes the model of the field after it has been installed on a block. + * No-op by default. + */ +Blockly.Field.prototype.initModel = function() { }; /** * Dispose of all DOM objects belonging to this editable field. */ Blockly.Field.prototype.dispose = function() { - if (this.mouseUpWrapper_) { - Blockly.unbindEvent_(this.mouseUpWrapper_); - this.mouseUpWrapper_ = null; + if (this.mouseDownWrapper_) { + Blockly.unbindEvent_(this.mouseDownWrapper_); + this.mouseDownWrapper_ = null; } this.sourceBlock_ = null; goog.dom.removeNode(this.fieldGroup_); @@ -182,24 +190,32 @@ Blockly.Field.prototype.dispose = function() { * Add or remove the UI indicating if this field is editable or not. */ Blockly.Field.prototype.updateEditable = function() { - if (!this.EDITABLE || !this.sourceBlock_) { + var group = this.fieldGroup_; + if (!this.EDITABLE || !group) { return; } if (this.sourceBlock_.isEditable()) { - Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_), - 'blocklyEditableText'); - Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_), - 'blocklyNoNEditableText'); + Blockly.utils.addClass(group, 'blocklyEditableText'); + Blockly.utils.removeClass(group, 'blocklyNonEditableText'); this.fieldGroup_.style.cursor = this.CURSOR; } else { - Blockly.addClass_(/** @type {!Element} */ (this.fieldGroup_), - 'blocklyNonEditableText'); - Blockly.removeClass_(/** @type {!Element} */ (this.fieldGroup_), - 'blocklyEditableText'); + Blockly.utils.addClass(group, 'blocklyNonEditableText'); + Blockly.utils.removeClass(group, 'blocklyEditableText'); this.fieldGroup_.style.cursor = ''; } }; +/** + * Check whether this field is currently editable. Some fields are never + * editable (e.g. text labels). Those fields are not serialized to XML. Other + * fields may be editable, and therefore serialized, but may exist on + * non-editable blocks. + * @return {boolean} whether this field is editable and on an editable block + */ +Blockly.Field.prototype.isCurrentlyEditable = function() { + return this.EDITABLE && !!this.sourceBlock_ && this.sourceBlock_.isEditable(); +}; + /** * Gets whether this editable field is visible or not. * @return {boolean} True if visible. @@ -232,6 +248,50 @@ Blockly.Field.prototype.setValidator = function(handler) { this.validator_ = handler; }; +/** + * Gets the validation function for editable fields. + * @return {Function} Validation function, or null. + */ +Blockly.Field.prototype.getValidator = function() { + return this.validator_; +}; + +/** + * Validates a change. Does nothing. Subclasses may override this. + * @param {string} text The user's text. + * @return {string} No change needed. + */ +Blockly.Field.prototype.classValidator = function(text) { + return text; +}; + +/** + * Calls the validation function for this field, as well as all the validation + * function for the field's class and its parents. + * @param {string} text Proposed text. + * @return {?string} Revised text, or null if invalid. + */ +Blockly.Field.prototype.callValidator = function(text) { + var classResult = this.classValidator(text); + if (classResult === null) { + // Class validator rejects value. Game over. + return null; + } else if (classResult !== undefined) { + text = classResult; + } + var userValidator = this.getValidator(); + if (userValidator) { + var userResult = userValidator.call(this, text); + if (userResult === null) { + // User validator rejects value. Game over. + return null; + } else if (userResult !== undefined) { + text = userResult; + } + } + return text; +}; + /** * Gets the group element for this editable field. * Used for measuring the size and for positioning. @@ -247,31 +307,70 @@ Blockly.Field.prototype.getSvgRoot = function() { * @private */ Blockly.Field.prototype.render_ = function() { - if (this.visible_ && this.textElement_) { - var key = this.textElement_.textContent + '\n' + - this.textElement_.className.baseVal; - if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) { - var width = Blockly.Field.cacheWidths_[key]; - } else { - try { - var width = this.textElement_.getComputedTextLength(); - } catch (e) { - // MSIE 11 is known to throw "Unexpected call to method or property - // access." if Blockly is hidden. - var width = this.textElement_.textContent.length * 8; - } - if (Blockly.Field.cacheWidths_) { - Blockly.Field.cacheWidths_[key] = width; - } + if (!this.visible_) { + this.size_.width = 0; + return; + } + + // Replace the text. + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); + + this.updateWidth(); +}; + +/** + * Updates thw width of the field. This calls getCachedWidth which won't cache + * the approximated width on IE/Edge when `getComputedTextLength` fails. Once + * it eventually does succeed, the result will be cached. + **/ +Blockly.Field.prototype.updateWidth = function() { + var width = Blockly.Field.getCachedWidth(this.textElement_); + if (this.borderRect_) { + this.borderRect_.setAttribute('width', + width + Blockly.BlockSvg.SEP_SPACE_X); + } + this.size_.width = width; +}; + +/** + * Gets the width of a text element, caching it in the process. + * @param {!Element} textElement An SVG 'text' element. + * @return {number} Width of element. + */ +Blockly.Field.getCachedWidth = function(textElement) { + var key = textElement.textContent + '\n' + textElement.className.baseVal; + var width; + + // Return the cached width if it exists. + if (Blockly.Field.cacheWidths_) { + width = Blockly.Field.cacheWidths_[key]; + if (width) { + return width; } - if (this.borderRect_) { - this.borderRect_.setAttribute('width', - width + Blockly.BlockSvg.SEP_SPACE_X); + } + + // Attempt to compute fetch the width of the SVG text element. + try { + if (goog.userAgent.IE || goog.userAgent.EDGE) { + width = textElement.getBBox().width; + } else { + width = textElement.getComputedTextLength(); } - } else { - var width = 0; + } catch (e) { + // In other cases where we fail to geth the computed text. Instead, use an + // approximation and do not cache the result. At some later point in time + // when the block is inserted into the visible DOM, this method will be + // called again and, at that point in time, will not throw an exception. + return textElement.textContent.length * 8; } - this.size_.width = width; + + // Cache the computed width and return. + if (Blockly.Field.cacheWidths_) { + Blockly.Field.cacheWidths_[key] = width; + } + return width; }; /** @@ -308,16 +407,48 @@ Blockly.Field.prototype.getSize = function() { }; /** - * Returns the height and width of the field, - * accounting for the workspace scaling. - * @return {!goog.math.Size} Height and width. + * Returns the bounding box of the rendered field, accounting for workspace + * scaling. + * @return {!Object} An object with top, bottom, left, and right in pixels + * relative to the top left corner of the page (window coordinates). * @private */ Blockly.Field.prototype.getScaledBBox_ = function() { var bBox = this.borderRect_.getBBox(); - // Create new object, as getBBox can return an uneditable SVGRect in IE. - return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale, - bBox.height * this.sourceBlock_.workspace.scale); + var scaledHeight = bBox.height * this.sourceBlock_.workspace.scale; + var scaledWidth = bBox.width * this.sourceBlock_.workspace.scale; + var xy = this.getAbsoluteXY_(); + return { + top: xy.y, + bottom: xy.y + scaledHeight, + left: xy.x, + right: xy.x + scaledWidth + }; +}; + +/** + * Get the text from this field as displayed on screen. May differ from getText + * due to ellipsis, and other formatting. + * @return {string} Currently displayed text. + * @private + */ +Blockly.Field.prototype.getDisplayText_ = function() { + var text = this.text_; + if (!text) { + // Prevent the field from disappearing if empty. + return Blockly.Field.NBSP; + } + if (text.length > this.maxDisplayLength) { + // Truncate displayed string and add an ellipsis ('...'). + text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; + } + // Replace whitespace with non-breaking spaces so the text doesn't collapse. + text = text.replace(/\s/g, Blockly.Field.NBSP); + if (this.sourceBlock_.RTL) { + // The SVG is LTR, force text to be RTL. + text += '\u200F'; + } + return text; }; /** @@ -330,64 +461,43 @@ Blockly.Field.prototype.getText = function() { /** * Set the text in this field. Trigger a rerender of the source block. - * @param {*} text New text. + * @param {*} newText New text. */ -Blockly.Field.prototype.setText = function(text) { - if (text === null) { +Blockly.Field.prototype.setText = function(newText) { + if (newText === null) { // No change if null. return; } - text = String(text); - if (text === this.text_) { + newText = String(newText); + if (newText === this.text_) { // No change. return; } - this.text_ = text; - this.updateTextNode_(); - - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours_(); - } + this.text_ = newText; + this.forceRerender(); }; /** - * Update the text node of this field to display the current text. - * @private + * Force a rerender of the block that this field is installed on, which will + * rerender this field and adjust for any sizing changes. + * Other fields on the same block will not rerender, because their sizes have + * already been recorded. + * @package */ -Blockly.Field.prototype.updateTextNode_ = function() { - if (!this.textElement_) { - // Not rendered yet. - return; - } - var text = this.text_; - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; - } - // Empty the text element. - goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); - // Replace whitespace with non-breaking spaces so the text doesn't collapse. - text = text.replace(/\s/g, Blockly.Field.NBSP); - if (this.sourceBlock_.RTL && text) { - // The SVG is LTR, force text to be RTL. - text += '\u200F'; - } - if (!text) { - // Prevent the field from disappearing if empty. - text = Blockly.Field.NBSP; - } - var textNode = document.createTextNode(text); - this.textElement_.appendChild(textNode); - - // Cached width is obsolete. Clear it. +Blockly.Field.prototype.forceRerender = function() { + // Set width to 0 to force a rerender of this field. this.size_.width = 0; + + if (this.sourceBlock_ && this.sourceBlock_.rendered) { + this.sourceBlock_.render(); + this.sourceBlock_.bumpNeighbours_(); + } }; /** * By default there is no difference between the human-readable text and * the language-neutral values. Subclasses (such as dropdown) may define this. - * @return {string} Current text. + * @return {string} Current value. */ Blockly.Field.prototype.getValue = function() { return this.getText(); @@ -396,45 +506,37 @@ Blockly.Field.prototype.getValue = function() { /** * By default there is no difference between the human-readable text and * the language-neutral values. Subclasses (such as dropdown) may define this. - * @param {string} newText New text. + * @param {string} newValue New value. */ -Blockly.Field.prototype.setValue = function(newText) { - if (newText === null) { +Blockly.Field.prototype.setValue = function(newValue) { + if (newValue === null) { // No change if null. return; } - var oldText = this.getValue(); - if (oldText == newText) { + var oldValue = this.getValue(); + if (oldValue == newValue) { return; } if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, oldText, newText)); + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, newValue)); } - this.setText(newText); + this.setText(newValue); }; /** - * Handle a mouse up event on an editable field. - * @param {!Event} e Mouse up event. + * Handle a mouse down event on a field. + * @param {!Event} e Mouse down event. * @private */ -Blockly.Field.prototype.onMouseUp_ = function(e) { - if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) && - !goog.userAgent.isVersionOrHigher('537.51.2') && - e.layerX !== 0 && e.layerY !== 0) { - // Old iOS spawns a bogus event on the next touch after a 'prompt()' edit. - // Unlike the real events, these have a layerX and layerY set. +Blockly.Field.prototype.onMouseDown_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { + if (!this.sourceBlock_ || !this.sourceBlock_.workspace) { return; - } else if (Blockly.isRightButton(e)) { - // Right-click. - return; - } else if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Drag operation is concluding. Don't open the editor. - return; - } else if (this.sourceBlock_.isEditable()) { - // Non-abstract sub-classes must define a showEditor_ method. - this.showEditor_(); + } + var gesture = this.sourceBlock_.workspace.getGesture(e); + if (gesture) { + gesture.setStartField(this); } }; @@ -443,7 +545,9 @@ Blockly.Field.prototype.onMouseUp_ = function(e) { * @param {string|!Element} newTip Text for tooltip or a parent element to * link to for its tooltip. */ -Blockly.Field.prototype.setTooltip = function(newTip) { +Blockly.Field.prototype.setTooltip = function( + /* eslint-disable no-unused-vars */ newTip + /* eslint-enable no-unused-vars */) { // Non-abstract sub-classes may wish to implement this. See FieldLabel. }; diff --git a/core/field_angle.js b/core/field_angle.js index b5ca196..59384d0 100644 --- a/core/field_angle.js +++ b/core/field_angle.js @@ -33,7 +33,8 @@ goog.require('goog.userAgent'); /** * Class for an editable angle field. - * @param {string} text The initial content of the field. + * @param {(string|number)=} opt_value The initial content of the field. The + * value should cast to a number, and if it does not, '0' will be used. * @param {Function=} opt_validator An optional function that is called * to validate any constraints on what the user entered. Takes the new * text as an argument and returns the accepted text or null to abort @@ -41,42 +42,25 @@ goog.require('goog.userAgent'); * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldAngle = function(text, opt_validator) { - // Add degree symbol: "360°" (LTR) or "°360" (RTL) - this.symbol_ = Blockly.createSvgElement('tspan', {}, null); +Blockly.FieldAngle = function(opt_value, opt_validator) { + // Add degree symbol: '360°' (LTR) or '°360' (RTL) + this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null); this.symbol_.appendChild(document.createTextNode('\u00B0')); - Blockly.FieldAngle.superClass_.constructor.call(this, text, opt_validator); + opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0'; + Blockly.FieldAngle.superClass_.constructor.call( + this, opt_value, opt_validator); }; goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput); /** - * Sets a new change handler for angle field. - * @param {Function} handler New change handler, or null. + * Construct a FieldAngle from a JSON arg object. + * @param {!Object} options A JSON object with options (angle). + * @returns {!Blockly.FieldAngle} The new field instance. + * @package */ -Blockly.FieldAngle.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the angle validator. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = Blockly.FieldAngle.angleValidator.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldAngle.angleValidator; - } - Blockly.FieldAngle.superClass_.setValidator.call(this, wrappedHandler); +Blockly.FieldAngle.fromJson = function(options) { + return new Blockly.FieldAngle(options['angle']); }; /** @@ -119,13 +103,35 @@ Blockly.FieldAngle.OFFSET = 0; */ Blockly.FieldAngle.WRAP = 360; - /** * Radius of protractor circle. Slightly smaller than protractor size since * otherwise SVG crops off half the border at the edges. */ Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF - 1; +/** + * Adds degree symbol and recalculates width. + * Saves the computed width in a property. + * @private + */ +Blockly.FieldAngle.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } + + // Update textElement. + this.textElement_.textContent = this.getDisplayText_(); + + // Insert degree symbol. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.symbol_); + } + this.updateWidth(); +}; + /** * Clean up this FieldAngle, as well as the inherited FieldTextInput. * @return {!Function} Closure to call on destruction of the WidgetDiv. @@ -159,11 +165,11 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { Blockly.FieldAngle.superClass_.showEditor_.call(this, noFocus); var div = Blockly.WidgetDiv.DIV; if (!div.firstChild) { - // Mobile interface uses window.prompt. + // Mobile interface uses Blockly.prompt. return; } // Build the SVG DOM. - var svg = Blockly.createSvgElement('svg', { + var svg = Blockly.utils.createSvgElement('svg', { 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:html': 'http://www.w3.org/1999/xhtml', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', @@ -171,31 +177,37 @@ Blockly.FieldAngle.prototype.showEditor_ = function() { 'height': (Blockly.FieldAngle.HALF * 2) + 'px', 'width': (Blockly.FieldAngle.HALF * 2) + 'px' }, div); - var circle = Blockly.createSvgElement('circle', { + var circle = Blockly.utils.createSvgElement('circle', { 'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF, 'r': Blockly.FieldAngle.RADIUS, 'class': 'blocklyAngleCircle' }, svg); - this.gauge_ = Blockly.createSvgElement('path', + this.gauge_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyAngleGauge'}, svg); - this.line_ = Blockly.createSvgElement('line', - {'x1': Blockly.FieldAngle.HALF, - 'y1': Blockly.FieldAngle.HALF, - 'class': 'blocklyAngleLine'}, svg); + this.line_ = Blockly.utils.createSvgElement('line', { + 'x1': Blockly.FieldAngle.HALF, + 'y1': Blockly.FieldAngle.HALF, + 'class': 'blocklyAngleLine' + }, svg); // Draw markers around the edge. - for (var a = 0; a < 360; a += 15) { - Blockly.createSvgElement('line', { + for (var angle = 0; angle < 360; angle += 15) { + Blockly.utils.createSvgElement('line', { 'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS, 'y1': Blockly.FieldAngle.HALF, 'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS - - (a % 45 == 0 ? 10 : 5), + (angle % 45 == 0 ? 10 : 5), 'y2': Blockly.FieldAngle.HALF, 'class': 'blocklyAngleMarks', - 'transform': 'rotate(' + a + ',' + + 'transform': 'rotate(' + angle + ',' + Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')' }, svg); } svg.style.marginLeft = (15 - Blockly.FieldAngle.RADIUS) + 'px'; + + // The angle picker is different from other fields in that it updates on + // mousemove even if it's not in the middle of a drag. In future we may + // change this behavior. For now, using bindEvent_ instead of + // bindEventWithChecks_ allows it to work without a mousedown/touchstart. this.clickWrapper_ = Blockly.bindEvent_(svg, 'click', this, Blockly.WidgetDiv.hide); this.moveWrapper1_ = @@ -215,7 +227,7 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) { var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF; var angle = Math.atan(-dy / dx); if (isNaN(angle)) { - // This shouldn't happen, but let's not let this error propogate further. + // This shouldn't happen, but let's not let this error propagate further. return; } angle = goog.math.toDegrees(angle); @@ -234,7 +246,7 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) { angle = Math.round(angle / Blockly.FieldAngle.ROUND) * Blockly.FieldAngle.ROUND; } - angle = Blockly.FieldAngle.angleValidator(angle); + angle = this.callValidator(angle); Blockly.FieldTextInput.htmlInput_.value = angle; this.setValue(angle); this.validate_(); @@ -252,12 +264,6 @@ Blockly.FieldAngle.prototype.setText = function(text) { return; } this.updateGraph_(); - // Insert degree symbol. - if (this.sourceBlock_.RTL) { - this.textElement_.insertBefore(this.symbol_, this.textElement_.firstChild); - } else { - this.textElement_.appendChild(this.symbol_); - } // Cached width is obsolete. Clear it. this.size_.width = 0; }; @@ -304,17 +310,20 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() { * @param {string} text The user's text. * @return {?string} A string representing a valid angle, or null if invalid. */ -Blockly.FieldAngle.angleValidator = function(text) { - var n = Blockly.FieldTextInput.numberValidator(text); - if (n !== null) { - n = n % 360; - if (n < 0) { - n += 360; - } - if (n > Blockly.FieldAngle.WRAP) { - n -= 360; - } - n = String(n); +Blockly.FieldAngle.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + var n = parseFloat(text || 0); + if (isNaN(n)) { + return null; + } + n = n % 360; + if (n < 0) { + n += 360; + } + if (n > Blockly.FieldAngle.WRAP) { + n -= 360; } - return n; + return String(n); }; diff --git a/core/field_checkbox.js b/core/field_checkbox.js index fb46e0a..d723bab 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -46,6 +46,16 @@ Blockly.FieldCheckbox = function(state, opt_validator) { }; goog.inherits(Blockly.FieldCheckbox, Blockly.Field); +/** + * Construct a FieldCheckbox from a JSON arg object. + * @param {!Object} options A JSON object with options (checked). + * @returns {!Blockly.FieldCheckbox} The new field instance. + * @package + */ +Blockly.FieldCheckbox.fromJson = function(options) { + return new Blockly.FieldCheckbox(options['checked'] ? 'TRUE' : 'FALSE'); +}; + /** * Character for the checkmark. */ @@ -67,7 +77,7 @@ Blockly.FieldCheckbox.prototype.init = function() { Blockly.FieldCheckbox.superClass_.init.call(this); // The checkbox doesn't use the inherited text element. // Instead it uses a custom checkmark element that is either visible or not. - this.checkElement_ = Blockly.createSvgElement('text', + this.checkElement_ = Blockly.utils.createSvgElement('text', {'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14}, this.fieldGroup_); var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR); @@ -84,14 +94,16 @@ Blockly.FieldCheckbox.prototype.getValue = function() { }; /** - * Set the checkbox to be checked if strBool is 'TRUE', unchecks otherwise. - * @param {string} strBool New state. + * Set the checkbox to be checked if newBool is 'TRUE' or true, + * unchecks otherwise. + * @param {string|boolean} newBool New state. */ -Blockly.FieldCheckbox.prototype.setValue = function(strBool) { - var newState = (strBool == 'TRUE'); +Blockly.FieldCheckbox.prototype.setValue = function(newBool) { + var newState = (typeof newBool == 'string') ? + (newBool.toUpperCase() == 'TRUE') : !!newBool; if (this.state_ !== newState) { if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this.sourceBlock_, 'field', this.name, this.state_, newState)); } this.state_ = newState; @@ -107,12 +119,9 @@ Blockly.FieldCheckbox.prototype.setValue = function(strBool) { */ Blockly.FieldCheckbox.prototype.showEditor_ = function() { var newState = !this.state_; - if (this.sourceBlock_ && this.validator_) { + if (this.sourceBlock_) { // Call any validation function, and allow it to override. - var override = this.validator_(newState); - if (override !== undefined) { - newState = override; - } + newState = this.callValidator(newState); } if (newState !== null) { this.setValue(String(newState).toUpperCase()); diff --git a/core/field_colour.js b/core/field_colour.js index 65346ee..77632c1 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -27,6 +27,8 @@ goog.provide('Blockly.FieldColour'); goog.require('Blockly.Field'); +goog.require('Blockly.utils'); + goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.style'); @@ -50,6 +52,16 @@ Blockly.FieldColour = function(colour, opt_validator) { }; goog.inherits(Blockly.FieldColour, Blockly.Field); +/** + * Construct a FieldColour from a JSON arg object. + * @param {!Object} options A JSON object with options (colour). + * @returns {!Blockly.FieldColour} The new field instance. + * @package + */ +Blockly.FieldColour.fromJson = function(options) { + return new Blockly.FieldColour(options['colour']); +}; + /** * By default use the global constants for colours. * @type {Array.} @@ -101,7 +113,7 @@ Blockly.FieldColour.prototype.getValue = function() { Blockly.FieldColour.prototype.setValue = function(colour) { if (this.sourceBlock_ && Blockly.Events.isEnabled() && this.colour_ != colour) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this.sourceBlock_, 'field', this.name, this.colour_, colour)); } this.colour_ = colour; @@ -166,45 +178,18 @@ Blockly.FieldColour.prototype.setColumns = function(columns) { Blockly.FieldColour.prototype.showEditor_ = function() { Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, Blockly.FieldColour.widgetDispose_); - // Create the palette using Closure. - var picker = new goog.ui.ColorPicker(); - picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS); - picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS); - // Position the palette to line up with the field. - // Record windowSize and scrollOffset before adding the palette. - var windowSize = goog.dom.getViewportSize(); - var scrollOffset = goog.style.getViewportPageOffset(document); - var xy = this.getAbsoluteXY_(); - var borderBBox = this.getScaledBBox_(); - var div = Blockly.WidgetDiv.DIV; - picker.render(div); - picker.setSelectedColor(this.getValue()); - // Record paletteSize after adding the palette. + // Record viewport dimensions before adding the widget. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getScaledBBox_(); + + // Create and add the colour picker, then record the size. + var picker = this.createWidget_(); var paletteSize = goog.style.getSize(picker.getElement()); - // Flip the palette vertically if off the bottom. - if (xy.y + paletteSize.height + borderBBox.height >= - windowSize.height + scrollOffset.y) { - xy.y -= paletteSize.height - 1; - } else { - xy.y += borderBBox.height - 1; - } - if (this.sourceBlock_.RTL) { - xy.x += borderBBox.width; - xy.x -= paletteSize.width; - // Don't go offscreen left. - if (xy.x < scrollOffset.x) { - xy.x = scrollOffset.x; - } - } else { - // Don't go offscreen right. - if (xy.x > windowSize.width + scrollOffset.x - paletteSize.width) { - xy.x = windowSize.width + scrollOffset.x - paletteSize.width; - } - } - Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, - this.sourceBlock_.RTL); + // Position the picker to line up with the field. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, paletteSize, + this.sourceBlock_.RTL); // Configure event handler. var thisField = this; @@ -213,12 +198,9 @@ Blockly.FieldColour.prototype.showEditor_ = function() { function(event) { var colour = event.target.getSelectedColor() || '#000000'; Blockly.WidgetDiv.hide(); - if (thisField.sourceBlock_ && thisField.validator_) { + if (thisField.sourceBlock_) { // Call any validation function, and allow it to override. - var override = thisField.validator_(colour); - if (override !== undefined) { - colour = override; - } + colour = thisField.callValidator(colour); } if (colour !== null) { thisField.setValue(colour); @@ -226,6 +208,22 @@ Blockly.FieldColour.prototype.showEditor_ = function() { }); }; +/** + * Create a color picker widget and render it inside the widget div. + * @return {!goog.ui.ColorPicker} The newly created color picker. + * @private + */ +Blockly.FieldColour.prototype.createWidget_ = function() { + // Create the palette using Closure. + var picker = new goog.ui.ColorPicker(); + picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS); + picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setSelectedColor(this.getValue()); + return picker; +}; + /** * Hide the colour palette. * @private @@ -234,4 +232,5 @@ Blockly.FieldColour.widgetDispose_ = function() { if (Blockly.FieldColour.changeEventKey_) { goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_); } + Blockly.Events.setGroup(false); }; diff --git a/core/field_date.js b/core/field_date.js index e2a203a..53ced0e 100644 --- a/core/field_date.js +++ b/core/field_date.js @@ -27,7 +27,10 @@ goog.provide('Blockly.FieldDate'); goog.require('Blockly.Field'); +goog.require('Blockly.utils'); + goog.require('goog.date'); +goog.require('goog.date.DateTime'); goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.i18n.DateTimeSymbols'); @@ -56,6 +59,16 @@ Blockly.FieldDate = function(date, opt_validator) { }; goog.inherits(Blockly.FieldDate, Blockly.Field); +/** + * Construct a FieldDate from a JSON arg object. + * @param {!Object} options A JSON object with options (date). + * @returns {!Blockly.FieldDate} The new field instance. + * @package + */ +Blockly.FieldDate.fromJson = function(options) { + return new Blockly.FieldDate(options['date']); +}; + /** * Mouse cursor style when over the hotspot that initiates the editor. */ @@ -82,11 +95,11 @@ Blockly.FieldDate.prototype.getValue = function() { * @param {string} date The new date. */ Blockly.FieldDate.prototype.setValue = function(date) { - if (this.sourceBlock_ && this.validator_) { - var validated = this.validator_(date); + if (this.sourceBlock_) { + var validated = this.callValidator(date); // If the new date is invalid, validation returns null. // In this case we still want to display the illegal result. - if (validated !== null && validated !== undefined) { + if (validated !== null) { date = validated; } } @@ -101,46 +114,18 @@ Blockly.FieldDate.prototype.setValue = function(date) { Blockly.FieldDate.prototype.showEditor_ = function() { Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, Blockly.FieldDate.widgetDispose_); - // Create the date picker using Closure. - Blockly.FieldDate.loadLanguage_(); - var picker = new goog.ui.DatePicker(); - picker.setAllowNone(false); - picker.setShowWeekNum(false); - // Position the picker to line up with the field. - // Record windowSize and scrollOffset before adding the picker. - var windowSize = goog.dom.getViewportSize(); - var scrollOffset = goog.style.getViewportPageOffset(document); - var xy = this.getAbsoluteXY_(); - var borderBBox = this.getScaledBBox_(); - var div = Blockly.WidgetDiv.DIV; - picker.render(div); - picker.setDate(goog.date.fromIsoString(this.getValue())); - // Record pickerSize after adding the date picker. + // Record viewport dimensions before adding the picker. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getScaledBBox_(); + + // Create and add the date picker, then record the size. + var picker = this.createWidget_(); var pickerSize = goog.style.getSize(picker.getElement()); - // Flip the picker vertically if off the bottom. - if (xy.y + pickerSize.height + borderBBox.height >= - windowSize.height + scrollOffset.y) { - xy.y -= pickerSize.height - 1; - } else { - xy.y += borderBBox.height - 1; - } - if (this.sourceBlock_.RTL) { - xy.x += borderBBox.width; - xy.x -= pickerSize.width; - // Don't go offscreen left. - if (xy.x < scrollOffset.x) { - xy.x = scrollOffset.x; - } - } else { - // Don't go offscreen right. - if (xy.x > windowSize.width + scrollOffset.x - pickerSize.width) { - xy.x = windowSize.width + scrollOffset.x - pickerSize.width; - } - } - Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, - this.sourceBlock_.RTL); + // Position the picker to line up with the field. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, pickerSize, + this.sourceBlock_.RTL); // Configure event handler. var thisField = this; @@ -149,17 +134,31 @@ Blockly.FieldDate.prototype.showEditor_ = function() { function(event) { var date = event.date ? event.date.toIsoString(true) : ''; Blockly.WidgetDiv.hide(); - if (thisField.sourceBlock_ && thisField.validator_) { + if (thisField.sourceBlock_) { // Call any validation function, and allow it to override. - var override = thisField.validator_(date); - if (override !== undefined) { - date = override; - } + date = thisField.callValidator(date); } thisField.setValue(date); }); }; +/** + * Create a date picker widget and render it inside the widget div. + * @return {!goog.ui.DatePicker} The newly created date picker. + * @private + */ +Blockly.FieldDate.prototype.createWidget_ = function() { + // Create the date picker using Closure. + Blockly.FieldDate.loadLanguage_(); + var picker = new goog.ui.DatePicker(); + picker.setAllowNone(false); + picker.setShowWeekNum(false); + var div = Blockly.WidgetDiv.DIV; + picker.render(div); + picker.setDate(goog.date.DateTime.fromIsoString(this.getValue())); + return picker; +}; + /** * Hide the date picker. * @private @@ -168,6 +167,7 @@ Blockly.FieldDate.widgetDispose_ = function() { if (Blockly.FieldDate.changeEventKey_) { goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_); } + Blockly.Events.setGroup(false); }; /** @@ -193,14 +193,14 @@ Blockly.FieldDate.loadLanguage_ = function() { */ Blockly.FieldDate.CSS = [ /* Copied from: goog/css/datepicker.css */ - /* + /** * Copyright 2009 The Closure Library Authors. All Rights Reserved. * * Use of this source code is governed by the Apache License, Version 2.0. * See the COPYING file for details. */ - /* + /** * Standard styling for a goog.ui.DatePicker. * * @author arv@google.com (Erik Arvidsson) diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 294682f..3ecca22 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -29,6 +29,9 @@ goog.provide('Blockly.FieldDropdown'); goog.require('Blockly.Field'); +goog.require('Blockly.utils'); +goog.require('Blockly.utils.uiMenu'); + goog.require('goog.dom'); goog.require('goog.events'); goog.require('goog.style'); @@ -39,8 +42,8 @@ goog.require('goog.userAgent'); /** * Class for an editable dropdown field. - * @param {(!Array.>|!Function)} menuGenerator An array of - * options for a dropdown list, or a function which generates these options. + * @param {(!Array.|!Function)} menuGenerator An array of options + * for a dropdown list, or a function which generates these options. * @param {Function=} opt_validator A function that is executed when a new * option is selected, with the newly selected value as its sole argument. * If it returns a value, that value (which must be one of the options) will @@ -52,7 +55,7 @@ goog.require('goog.userAgent'); Blockly.FieldDropdown = function(menuGenerator, opt_validator) { this.menuGenerator_ = menuGenerator; this.trimOptions_(); - var firstTuple = this.getOptions_()[0]; + var firstTuple = this.getOptions()[0]; // Call parent's constructor. Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1], @@ -61,10 +64,26 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator) { goog.inherits(Blockly.FieldDropdown, Blockly.Field); /** - * Horizontal distance that a checkmark ovehangs the dropdown. + * Construct a FieldDropdown from a JSON arg object. + * @param {!Object} options A JSON object with options (options). + * @returns {!Blockly.FieldDropdown} The new field instance. + * @package + */ +Blockly.FieldDropdown.fromJson = function(options) { + return new Blockly.FieldDropdown(options['options']); +}; + +/** + * Horizontal distance that a checkmark overhangs the dropdown. */ Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25; +/** + * Maximum height of the dropdown menu,it's also referenced in css.js as + * part of .blocklyDropdownMenu. + */ +Blockly.FieldDropdown.MAX_MENU_HEIGHT = 300; + /** * Android can't (in 2014) display "▾", so use "▼" instead. */ @@ -75,6 +94,28 @@ Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE'; */ Blockly.FieldDropdown.prototype.CURSOR = 'default'; +/** + * Language-neutral currently selected string or image object. + * @type {string|!Object} + * @private + */ +Blockly.FieldDropdown.prototype.value_ = ''; + +/** + * SVG image element if currently selected option is an image, or null. + * @type {SVGElement} + * @private + */ +Blockly.FieldDropdown.prototype.imageElement_ = null; + +/** + * Object with src, height, width, and alt attributes if currently selected + * option is an image, or null. + * @type {Object} + * @private + */ +Blockly.FieldDropdown.prototype.imageJson_ = null; + /** * Install this dropdown on a block. */ @@ -84,16 +125,12 @@ Blockly.FieldDropdown.prototype.init = function() { return; } // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL) - this.arrow_ = Blockly.createSvgElement('tspan', {}, null); - this.arrow_.appendChild(document.createTextNode( - this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' : - ' ' + Blockly.FieldDropdown.ARROW_CHAR)); + this.arrow_ = Blockly.utils.createSvgElement('tspan', {}, null); + this.arrow_.appendChild(document.createTextNode(this.sourceBlock_.RTL ? + Blockly.FieldDropdown.ARROW_CHAR + ' ' : + ' ' + Blockly.FieldDropdown.ARROW_CHAR)); Blockly.FieldDropdown.superClass_.init.call(this); - // Force a reset of the text to add the arrow. - var text = this.text_; - this.text_ = null; - this.setText(text); }; /** @@ -102,97 +139,184 @@ Blockly.FieldDropdown.prototype.init = function() { */ Blockly.FieldDropdown.prototype.showEditor_ = function() { Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null); + var menu = this.createMenu_(); + this.addEventListeners_(menu); + this.positionMenu_(menu); +}; + +/** + * Add event listeners for actions on the items in the dropdown menu. + * @param {!goog.ui.Menu} menu The menu to add listeners to. + * @private + */ +Blockly.FieldDropdown.prototype.addEventListeners_ = function(menu) { + this.addActionListener_(menu); + this.addTouchStartListener_(menu); + this.addTouchEndListener_(menu); +}; + +/** + * Add a listener for mouse and keyboard events in the menu and its items. + * @param {!goog.ui.Menu} menu The menu to add listeners to. + * @private + */ +Blockly.FieldDropdown.prototype.addActionListener_ = function(menu) { var thisField = this; function callback(e) { + var menu = this; var menuItem = e.target; if (menuItem) { - var value = menuItem.getValue(); - if (thisField.sourceBlock_ && thisField.validator_) { - // Call any validation function, and allow it to override. - var override = thisField.validator_(value); - if (override !== undefined) { - value = override; - } - } - if (value !== null) { - thisField.setValue(value); - } + thisField.onItemSelected(menu, menuItem); } Blockly.WidgetDiv.hideIfOwner(thisField); - } - - var menu = new goog.ui.Menu(); - menu.setRightToLeft(this.sourceBlock_.RTL); - var options = this.getOptions_(); - for (var x = 0; x < options.length; x++) { - var text = options[x][0]; // Human-readable text. - var value = options[x][1]; // Language-neutral value. - var menuItem = new goog.ui.MenuItem(text); - menuItem.setRightToLeft(this.sourceBlock_.RTL); - menuItem.setValue(value); - menuItem.setCheckable(true); - menu.addChild(menuItem, true); - menuItem.setChecked(value == this.value_); + Blockly.Events.setGroup(false); } // Listen for mouse/keyboard events. goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); +}; + +/** + * Add a listener for touch start events on menu items. + * @param {!goog.ui.Menu} menu The menu to add the listener to. + * @private + */ +Blockly.FieldDropdown.prototype.addTouchStartListener_ = function(menu) { // Listen for touch events (why doesn't Closure handle this already?). - function callbackTouchStart(e) { + function callback(e) { var control = this.getOwnerControl(/** @type {Node} */ (e.target)); // Highlight the menu item. control.handleMouseDown(e); } + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHSTART, callback); +}; + +/** + * Add a listener for touch end events on menu items. + * @param {!goog.ui.Menu} menu The menu to add the listener to. + * @private + */ +Blockly.FieldDropdown.prototype.addTouchEndListener_ = function(menu) { + // Listen for touch events (why doesn't Closure handle this already?). function callbackTouchEnd(e) { var control = this.getOwnerControl(/** @type {Node} */ (e.target)); // Activate the menu item. control.performActionInternal(e); } - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART, - callbackTouchStart); - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND, - callbackTouchEnd); - - // Record windowSize and scrollOffset before adding menu. - var windowSize = goog.dom.getViewportSize(); - var scrollOffset = goog.style.getViewportPageOffset(document); - var xy = this.getAbsoluteXY_(); - var borderBBox = this.getScaledBBox_(); + menu.getHandler().listen( + menu.getElement(), goog.events.EventType.TOUCHEND, callbackTouchEnd); +}; + +/** + * Create and populate the menu and menu items for this dropdown, based on + * the options list. + * @return {!goog.ui.Menu} The populated dropdown menu. + * @private + */ +Blockly.FieldDropdown.prototype.createMenu_ = function() { + var menu = new goog.ui.Menu(); + menu.setRightToLeft(this.sourceBlock_.RTL); + var options = this.getOptions(); + for (var i = 0; i < options.length; i++) { + var content = options[i][0]; // Human-readable text or image. + var value = options[i][1]; // Language-neutral value. + if (typeof content == 'object') { + // An image, not text. + var image = new Image(content['width'], content['height']); + image.src = content['src']; + image.alt = content['alt'] || ''; + content = image; + } + var menuItem = new goog.ui.MenuItem(content); + menuItem.setRightToLeft(this.sourceBlock_.RTL); + menuItem.setValue(value); + menuItem.setCheckable(true); + menu.addChild(menuItem, true); + menuItem.setChecked(value == this.value_); + } + return menu; +}; + +/** + * Place the menu correctly on the screen, taking into account the dimensions + * of the menu and the dimensions of the screen so that it doesn't run off any + * edges. + * @param {!goog.ui.Menu} menu The menu to position. + * @private + */ +Blockly.FieldDropdown.prototype.positionMenu_ = function(menu) { + // Record viewport dimensions before adding the dropdown. + var viewportBBox = Blockly.utils.getViewportBBox(); + var anchorBBox = this.getAnchorDimensions_(); + + this.createWidget_(menu); + var menuSize = Blockly.utils.uiMenu.getSize(menu); + + if (menuSize.height > Blockly.FieldDropdown.MAX_MENU_HEIGHT) { + menuSize.height = Blockly.FieldDropdown.MAX_MENU_HEIGHT; + } + + if (this.sourceBlock_.RTL) { + Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize); + } + // Position the menu. + Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, + this.sourceBlock_.RTL); + // Calling menuDom.focus() has to wait until after the menu has been placed + // correctly. Otherwise it will cause a page scroll to get the misplaced menu + // in view. See issue #1329. + menu.getElement().focus(); +}; + +/** + * Create and render the menu widget inside Blockly's widget div. + * @param {!goog.ui.Menu} menu The menu to add to the widget div. + * @private + */ +Blockly.FieldDropdown.prototype.createWidget_ = function(menu) { var div = Blockly.WidgetDiv.DIV; menu.render(div); - var menuDom = menu.getElement(); - Blockly.addClass_(menuDom, 'blocklyDropdownMenu'); - // Record menuSize after adding menu. - var menuSize = goog.style.getSize(menuDom); - // Recalculate height for the total content, not only box height. - menuSize.height = menuDom.scrollHeight; + Blockly.utils.addClass(menu.getElement(), 'blocklyDropdownMenu'); + // Enable autofocus after the initial render to avoid issue #1329. + menu.setAllowAutoFocus(true); +}; - // Position the menu. - // Flip menu vertically if off the bottom. - if (xy.y + menuSize.height + borderBBox.height >= - windowSize.height + scrollOffset.y) { - xy.y -= menuSize.height + 2; - } else { - xy.y += borderBBox.height; - } +/** + * Returns the coordinates of the anchor rectangle for the widget div. + * On a FieldDropdown we take the top-left corner of the field, then adjust for + * the size of the checkmark that is displayed next to the currently selected + * item. This means that the item text will be positioned directly under the + * field text, rather than offset slightly. + * @returns {!Object} The bounding rectangle of the anchor, in window + * coordinates. + * @private + */ +Blockly.FieldDropdown.prototype.getAnchorDimensions_ = function() { + var boundingBox = this.getScaledBBox_(); if (this.sourceBlock_.RTL) { - xy.x += borderBBox.width; - xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG; - // Don't go offscreen left. - if (xy.x < scrollOffset.x + menuSize.width) { - xy.x = scrollOffset.x + menuSize.width; - } + boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG; } else { - xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG; - // Don't go offscreen right. - if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) { - xy.x = windowSize.width + scrollOffset.x - menuSize.width; - } + boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG; + } + + return boundingBox; +}; + +/** + * Handle the selection of an item in the dropdown menu. + * @param {!goog.ui.Menu} menu The Menu component clicked. + * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu. + */ +Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) { + var value = menuItem.getValue(); + if (this.sourceBlock_) { + // Call any validation function, and allow it to override. + value = this.callValidator(value); + } + if (value !== null) { + this.setValue(value); } - Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, - this.sourceBlock_.RTL); - menu.setAllowAutoFocus(true); - menuDom.focus(); }; /** @@ -204,13 +328,33 @@ Blockly.FieldDropdown.prototype.trimOptions_ = function() { this.prefixField = null; this.suffixField = null; var options = this.menuGenerator_; - if (!goog.isArray(options) || options.length < 2) { + if (!goog.isArray(options)) { return; } - var strings = options.map(function(t) {return t[0];}); - var shortest = Blockly.shortestStringLength(strings); - var prefixLength = Blockly.commonWordPrefix(strings, shortest); - var suffixLength = Blockly.commonWordSuffix(strings, shortest); + var hasImages = false; + + // Localize label text and image alt text. + for (var i = 0; i < options.length; i++) { + var label = options[i][0]; + if (typeof label == 'string') { + options[i][0] = Blockly.utils.replaceMessageReferences(label); + } else { + if (label.alt != null) { + options[i][0].alt = Blockly.utils.replaceMessageReferences(label.alt); + } + hasImages = true; + } + } + if (hasImages || options.length < 2) { + return; // Do nothing if too few items or at least one label is an image. + } + var strings = []; + for (var i = 0; i < options.length; i++) { + strings.push(options[i][0]); + } + var shortest = Blockly.utils.shortestStringLength(strings); + var prefixLength = Blockly.utils.commonWordPrefix(strings, shortest); + var suffixLength = Blockly.utils.commonWordSuffix(strings, shortest); if (!prefixLength && !suffixLength) { return; } @@ -224,24 +368,46 @@ Blockly.FieldDropdown.prototype.trimOptions_ = function() { if (suffixLength) { this.suffixField = strings[0].substr(1 - suffixLength); } - // Remove the prefix and suffix from the options. + + this.menuGenerator_ = Blockly.FieldDropdown.applyTrim_(options, prefixLength, + suffixLength); +}; + +/** + * Use the calculated prefix and suffix lengths to trim all of the options in + * the given array. + * @param {!Array.} options Array of option tuples: + * (human-readable text or image, language-neutral name). + * @param {number} prefixLength The length of the common prefix. + * @param {number} suffixLength The length of the common suffix + * @return {!Array.} A new array with all of the option text trimmed. + */ +Blockly.FieldDropdown.applyTrim_ = function(options, prefixLength, suffixLength) { var newOptions = []; - for (var x = 0; x < options.length; x++) { - var text = options[x][0]; - var value = options[x][1]; + // Remove the prefix and suffix from the options. + for (var i = 0; i < options.length; i++) { + var text = options[i][0]; + var value = options[i][1]; text = text.substring(prefixLength, text.length - suffixLength); - newOptions[x] = [text, value]; + newOptions[i] = [text, value]; } - this.menuGenerator_ = newOptions; + return newOptions; +}; + +/** + * @return {boolean} True if the option list is generated by a function. + * Otherwise false. + */ +Blockly.FieldDropdown.prototype.isOptionListDynamic = function() { + return goog.isFunction(this.menuGenerator_); }; /** * Return a list of the options for this dropdown. - * @return {!Array.>} Array of option tuples: - * (human-readable text, language-neutral name). - * @private + * @return {!Array.} Array of option tuples: + * (human-readable text or image, language-neutral name). */ -Blockly.FieldDropdown.prototype.getOptions_ = function() { +Blockly.FieldDropdown.prototype.getOptions = function() { if (goog.isFunction(this.menuGenerator_)) { return this.menuGenerator_.call(this); } @@ -265,52 +431,128 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) { return; // No change if null. } if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( this.sourceBlock_, 'field', this.name, this.value_, newValue)); } this.value_ = newValue; // Look up and display the human-readable text. - var options = this.getOptions_(); - for (var x = 0; x < options.length; x++) { + var options = this.getOptions(); + for (var i = 0; i < options.length; i++) { // Options are tuples of human-readable text and language-neutral values. - if (options[x][1] == newValue) { - this.setText(options[x][0]); + if (options[i][1] == newValue) { + var content = options[i][0]; + if (typeof content == 'object') { + this.imageJson_ = content; + this.text_ = content.alt; + } else { + this.imageJson_ = null; + this.text_ = content; + } + // Always rerender if either the value or the text has changed. + this.forceRerender(); return; } } // Value not found. Add it, maybe it will become valid once set // (like variable names). - this.setText(newValue); + this.text_ = newValue; + this.forceRerender(); }; /** - * Set the text in this field. Trigger a rerender of the source block. - * @param {?string} text New text. + * Draws the border with the correct width. + * @private */ -Blockly.FieldDropdown.prototype.setText = function(text) { +Blockly.FieldDropdown.prototype.render_ = function() { + if (!this.visible_) { + this.size_.width = 0; + return; + } if (this.sourceBlock_ && this.arrow_) { // Update arrow's colour. this.arrow_.style.fill = this.sourceBlock_.getColour(); } - if (text === null || text === this.text_) { - // No change if null. - return; + goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); + goog.dom.removeNode(this.imageElement_); + this.imageElement_ = null; + + if (this.imageJson_) { + this.renderSelectedImage_(); + } else { + this.renderSelectedText_(); } - this.text_ = text; - this.updateTextNode_(); + this.borderRect_.setAttribute('height', this.size_.height - 9); + this.borderRect_.setAttribute('width', + this.size_.width + Blockly.BlockSvg.SEP_SPACE_X); +}; - if (this.textElement_) { - // Insert dropdown arrow. - if (this.sourceBlock_.RTL) { - this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); - } else { - this.textElement_.appendChild(this.arrow_); - } +/** + * Renders the selected option, which must be an image. + * @private + */ +Blockly.FieldDropdown.prototype.renderSelectedImage_ = function() { + // Image option is selected. + this.imageElement_ = Blockly.utils.createSvgElement('image', + { + 'y': 5, + 'height': this.imageJson_.height + 'px', + 'width': this.imageJson_.width + 'px' + }, this.fieldGroup_); + this.imageElement_.setAttributeNS( + 'http://www.w3.org/1999/xlink', 'xlink:href', this.imageJson_.src); + // Insert dropdown arrow. + this.textElement_.appendChild(this.arrow_); + var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); + this.size_.height = Number(this.imageJson_.height) + 19; + this.size_.width = Number(this.imageJson_.width) + arrowWidth; + if (this.sourceBlock_.RTL) { + this.imageElement_.setAttribute('x', arrowWidth); + this.textElement_.setAttribute('x', -1); + } else { + this.textElement_.setAttribute('text-anchor', 'end'); + this.textElement_.setAttribute('x', this.size_.width + 1); + } +}; + +/** + * Renders the selected option, which must be text. + * @private + */ +Blockly.FieldDropdown.prototype.renderSelectedText_ = function() { + // Text option is selected. + // Replace the text. + var textNode = document.createTextNode(this.getDisplayText_()); + this.textElement_.appendChild(textNode); + // Insert dropdown arrow. + if (this.sourceBlock_.RTL) { + this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); + } else { + this.textElement_.appendChild(this.arrow_); } + this.textElement_.setAttribute('text-anchor', 'start'); + this.textElement_.setAttribute('x', 0); + + this.size_.height = Blockly.BlockSvg.MIN_BLOCK_Y; + this.size_.width = Blockly.Field.getCachedWidth(this.textElement_); +}; - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours_(); +/** + * Updates the width of the field. Overrides field.prototype.updateWidth to + * deal with image selections on IE and Edge. If the selected item is not an + * image, or if the browser is not IE / Edge, this simply calls the parent + * implementation. + */ +Blockly.FieldDropdown.prototype.updateWidth = function() { + if (this.imageJson_ && (goog.userAgent.IE || goog.userAgent.EDGE)) { + // Recalculate the full width. + var arrowWidth = Blockly.Field.getCachedWidth(this.arrow_); + var width = Number(this.imageJson_.width) + arrowWidth + Blockly.BlockSvg.SEP_SPACE_X; + if (this.borderRect_) { + this.borderRect_.setAttribute('width', width); + } + this.size_.width = width; + } else { + Blockly.Field.prototype.updateWidth.call(this); } }; diff --git a/core/field_image.js b/core/field_image.js index 71d8052..eaa8e99 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -19,7 +19,7 @@ */ /** - * @fileoverview Image field. Used for titles, labels, etc. + * @fileoverview Image field. Used for pictures, icons, etc. * @author fraser@google.com (Neil Fraser) */ 'use strict'; @@ -33,16 +33,19 @@ goog.require('goog.userAgent'); /** - * Class for an image. + * Class for an image on a block. * @param {string} src The URL of the image. * @param {number} width Width of the image. * @param {number} height Height of the image. * @param {string=} opt_alt Optional alt text for when block is collapsed. + * @param {Function=} opt_onClick Optional function to be called when the image + * is clicked. If opt_onClick is defined, opt_alt must also be defined. * @extends {Blockly.Field} * @constructor */ -Blockly.FieldImage = function(src, width, height, opt_alt) { +Blockly.FieldImage = function(src, width, height, opt_alt, opt_onClick) { this.sourceBlock_ = null; + // Ensure height and width are numbers. Strings are bad at math. this.height_ = Number(height); this.width_ = Number(width); @@ -50,15 +53,29 @@ Blockly.FieldImage = function(src, width, height, opt_alt) { this.height_ + 2 * Blockly.BlockSvg.INLINE_PADDING_Y); this.text_ = opt_alt || ''; this.setValue(src); + + if (typeof opt_onClick == 'function') { + this.clickHandler_ = opt_onClick; + } }; goog.inherits(Blockly.FieldImage, Blockly.Field); /** - * Rectangular mask used by Firefox. - * @type {Element} - * @private + * Construct a FieldImage from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (src, width, height, and + * alt). + * @returns {!Blockly.FieldImage} The new field instance. + * @package */ -Blockly.FieldImage.prototype.rectElement_ = null; +Blockly.FieldImage.fromJson = function(options) { + var src = Blockly.utils.replaceMessageReferences(options['src']); + var width = Number(Blockly.utils.replaceMessageReferences(options['width'])); + var height = + Number(Blockly.utils.replaceMessageReferences(options['height'])); + var alt = Blockly.utils.replaceMessageReferences(options['alt']); + return new Blockly.FieldImage(src, width, height, alt); +}; /** * Editable fields are saved by the XML renderer, non-editable fields are not. @@ -75,32 +92,26 @@ Blockly.FieldImage.prototype.init = function() { } // Build the DOM. /** @type {SVGElement} */ - this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); + this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null); if (!this.visible_) { this.fieldGroup_.style.display = 'none'; } /** @type {SVGElement} */ - this.imageElement_ = Blockly.createSvgElement('image', - {'height': this.height_ + 'px', - 'width': this.width_ + 'px'}, this.fieldGroup_); + this.imageElement_ = Blockly.utils.createSvgElement( + 'image', + { + 'height': this.height_ + 'px', + 'width': this.width_ + 'px' + }, + this.fieldGroup_); this.setValue(this.src_); - if (goog.userAgent.GECKO) { - /** - * Due to a Firefox bug which eats mouse events on image elements, - * a transparent rectangle needs to be placed on top of the image. - * @type {SVGElement} - */ - this.rectElement_ = Blockly.createSvgElement('rect', - {'height': this.height_ + 'px', - 'width': this.width_ + 'px', - 'fill-opacity': 0}, this.fieldGroup_); - } this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); // Configure the field to be transparent with respect to tooltips. - var topElement = this.rectElement_ || this.imageElement_; - topElement.tooltip = this.sourceBlock_; - Blockly.Tooltip.bindMouseEvents(topElement); + this.setTooltip(this.sourceBlock_); + Blockly.Tooltip.bindMouseEvents(this.imageElement_); + + this.maybeAddClickHandler_(); }; /** @@ -110,7 +121,19 @@ Blockly.FieldImage.prototype.dispose = function() { goog.dom.removeNode(this.fieldGroup_); this.fieldGroup_ = null; this.imageElement_ = null; - this.rectElement_ = null; +}; + +/** + * Bind events for a mouse down on the image, but only if a click handler has + * been defined. + * @private + */ +Blockly.FieldImage.prototype.maybeAddClickHandler_ = function() { + if (this.clickHandler_) { + this.mouseDownWrapper_ = + Blockly.bindEventWithChecks_( + this.fieldGroup_, 'mousedown', this, this.onMouseDown_); + } }; /** @@ -119,8 +142,7 @@ Blockly.FieldImage.prototype.dispose = function() { * link to for its tooltip. */ Blockly.FieldImage.prototype.setTooltip = function(newTip) { - var topElement = this.rectElement_ || this.imageElement_; - topElement.tooltip = newTip; + this.imageElement_.tooltip = newTip; }; /** @@ -145,7 +167,7 @@ Blockly.FieldImage.prototype.setValue = function(src) { this.src_ = src; if (this.imageElement_) { this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink', - 'xlink:href', goog.isString(src) ? src : ''); + 'xlink:href', src || ''); } }; @@ -169,3 +191,28 @@ Blockly.FieldImage.prototype.setText = function(alt) { Blockly.FieldImage.prototype.render_ = function() { // NOP }; + +/** + * Images are fixed width, no need to render even if forced. + */ +Blockly.FieldImage.prototype.forceRerender = function() { + // NOP +}; + +/** + * Images are fixed width, no need to update. + * @private + */ +Blockly.FieldImage.prototype.updateWidth = function() { + // NOP +}; + +/** + * If field click is called, and click handler defined, + * call the handler. + */ +Blockly.FieldImage.prototype.showEditor_ = function() { + if (this.clickHandler_) { + this.clickHandler_(this); + } +}; diff --git a/core/field_instance.js b/core/field_instance.js deleted file mode 100644 index 19c5095..0000000 --- a/core/field_instance.js +++ /dev/null @@ -1,219 +0,0 @@ -/** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** - * @fileoverview Instances input field. - */ -'use strict'; - -goog.provide('Blockly.FieldInstance'); - -goog.require('Blockly.FieldDropdown'); -goog.require('Blockly.Instances'); -goog.require('Blockly.Msg'); -goog.require('Blockly.utils'); -goog.require('goog.string'); - - -/** - * Class for a specific type of instances' dropdown field. - * @param {?string} instanceName The default name for the instance. If null, - * a unique instance name will be generated. - * @param {!string} instanceType The type of instances for the dropdown. - * @param {boolean} uniqueName - * @param {boolean=} opt_lockNew Indicates a special case in which this - * dropdown can only rename the current name and each new block will always - * have a unique name. - * @param {boolean=} opt_lockRename - * @param {Function=} opt_validator A function that is executed when a new - * option is selected. Its sole argument is the new option value. - * @extends {Blockly.FieldDropdown} - * @constructor - */ -Blockly.FieldInstance = function( - instanceType, instanceName, uniqueName, opt_lockNew, opt_lockRename, - opt_editDropdownData, opt_validator) { - Blockly.FieldInstance.superClass_.constructor.call(this, - this.dropdownCreate, opt_validator); - - this.instanceType_ = instanceType; - this.setValue(instanceName); - this.uniqueName_ = (uniqueName === true); - this.lockNew_ = (opt_lockNew === true); - this.lockRename_ = (opt_lockRename === true); - this.editDropdownData = (opt_editDropdownData instanceof Function) ? - opt_editDropdownData : null; -}; -goog.inherits(Blockly.FieldInstance, Blockly.FieldDropdown); - -/** - * Sets a new change handler for instance field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldInstance.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the instance rename handler. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = Blockly.FieldInstance.dropdownChange.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldInstance.dropdownChange; - } - Blockly.FieldInstance.superClass_.setValidator.call(this, wrappedHandler); -}; - -/** - * Install this dropdown on a block. - */ -Blockly.FieldInstance.prototype.init = function() { - // Nothing to do if the dropdown has already been initialised once. - if (this.fieldGroup_) return; - - Blockly.FieldInstance.superClass_.init.call(this); - - var workspace = this.sourceBlock_.isInFlyout ? - this.sourceBlock_.workspace.targetWorkspace : this.sourceBlock_.workspace; - - if (!this.getValue()) { - // Instances without names get uniquely named for this workspace. - this.setValue(Blockly.Instances.generateUniqueName(workspace)); - } else { - if (this.uniqueName_) { - // Ensure the given name is unique in the workspace, but not in flyout - if (!this.sourceBlock_.isInFlyout) { - this.setValue( - Blockly.Instances.convertToUniqueNameBlock( - this.getValue(), this.sourceBlock_)); - } - } else { - // Pick an existing name from the workspace if any exists - var existingName = - Blockly.Instances.getAnyInstanceOf(this.instanceType_ , workspace); - if (existingName) this.setValue(existingName); - } - } -}; - -/** - * Get the instance's name (use a variableDB to convert into a real name). - * Unlike a regular dropdown, instances are literal and have no neutral value. - * @return {string} Current text. - */ -Blockly.FieldInstance.prototype.getValue = function() { - return this.getText(); -}; - -Blockly.FieldInstance.prototype.getInstanceTypeValue = function(instanceType) { - return (instanceType === this.instanceType_) ? this.getText() : undefined; -}; - -/** - * Set the instance name. - * @param {string} newValue New text. - */ -Blockly.FieldInstance.prototype.setValue = function(newValue) { - if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, this.value_, newValue)); - } - this.value_ = newValue; - this.setText(newValue); -}; - -/** - * Return a sorted list of instance names for instance dropdown menus. - * If editDropdownData has been defined it passes this list to the - * @return {!Array.} Array of instance names. - */ -Blockly.FieldInstance.prototype.dropdownCreate = function() { - if (this.sourceBlock_ && this.sourceBlock_.workspace) { - var instanceList = - Blockly.Instances.allInstancesOf(this.instanceType_, - this.sourceBlock_.workspace); - } else { - var instanceList = []; - } - // Ensure that the currently selected instance is an option. - var name = this.getText(); - if (name && instanceList.indexOf(name) == -1) { - instanceList.push(name); - } - instanceList.sort(goog.string.caseInsensitiveCompare); - if (!this.lockRename_) { - instanceList.push(Blockly.Msg.RENAME_INSTANCE); - } - if (!this.lockNew_) { - instanceList.push(Blockly.Msg.NEW_INSTANCE); - } - - // If configured, pass data to external handler for additional processing - var options = this.editDropdownData ? this.editDropdownData(options) : []; - - // Instances are not language specific, so use for display and internal name - for (var i = 0; i < instanceList.length; i++) { - options[i] = [instanceList[i], instanceList[i]]; - } - - return options; -}; - -/** - * Event handler for a change in instance name. - * Special case the 'New instance...' and 'Rename instance...' options. - * In both of these special cases, prompt the user for a new name. - * @param {string} text The selected dropdown menu option. - * @return {null|undefined|string} An acceptable new instance name, or null if - * change is to be either aborted (cancel button) or has been already - * handled (rename), or undefined if an existing instance was chosen. - * @this {!Blockly.FieldInstance} - */ -Blockly.FieldInstance.dropdownChange = function(text) { - function promptName(promptText, defaultText, callback) { - Blockly.hideChaff(); - var newVar = Blockly.prompt(promptText, defaultText, function(newVar) { - // Merge runs of whitespace. Strip leading and trailing whitespace. - // Beyond this, all names are legal. - if (newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - if (newVar == Blockly.Msg.RENAME_INSTANCE || - newVar == Blockly.Msg.NEW_INSTANCE) { - newVar = null; // Ok, not ALL names are legal... - } - } - callback(newVar); - }); - } - var workspace = this.sourceBlock_.workspace; - if (text == Blockly.Msg.RENAME_INSTANCE) { - var oldInstance = this.getText(); - var thisFieldInstance = this; - var callbackRename = function(text) { - if (text) { - Blockly.Instances.renameInstance( - oldInstance, text, thisFieldInstance.instanceType_, workspace); - } - }; - promptName(Blockly.Msg.RENAME_INSTANCE_TITLE.replace('%1', oldInstance), - oldInstance, callbackRename); - return null; - } else if (text == Blockly.Msg.NEW_INSTANCE) { - //TODO: this return needs to be replaced with an asynchronous callback - return Blockly.Instances.generateUniqueName(workspace); - } - return undefined; -}; diff --git a/core/field_label.js b/core/field_label.js index cb5fa7d..039f73a 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -46,6 +46,18 @@ Blockly.FieldLabel = function(text, opt_class) { }; goog.inherits(Blockly.FieldLabel, Blockly.Field); +/** + * Construct a FieldLabel from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, and class). + * @returns {!Blockly.FieldLabel} The new field instance. + * @package + */ +Blockly.FieldLabel.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + return new Blockly.FieldLabel(text, options['class']); +}; + /** * Editable fields are saved by the XML renderer, non-editable fields are not. */ @@ -60,10 +72,10 @@ Blockly.FieldLabel.prototype.init = function() { return; } // Build the DOM. - this.textElement_ = Blockly.createSvgElement('text', + this.textElement_ = Blockly.utils.createSvgElement('text', {'class': 'blocklyText', 'y': this.size_.height - 5}, null); if (this.class_) { - Blockly.addClass_(this.textElement_, this.class_); + Blockly.utils.addClass(this.textElement_, this.class_); } if (!this.visible_) { this.textElement_.style.display = 'none'; @@ -74,7 +86,7 @@ Blockly.FieldLabel.prototype.init = function() { this.textElement_.tooltip = this.sourceBlock_; Blockly.Tooltip.bindMouseEvents(this.textElement_); // Force a render. - this.updateTextNode_(); + this.render_(); }; /** diff --git a/core/field_number.js b/core/field_number.js index eb92291..ca90d4d 100644 --- a/core/field_number.js +++ b/core/field_number.js @@ -27,10 +27,16 @@ goog.provide('Blockly.FieldNumber'); goog.require('Blockly.FieldTextInput'); +goog.require('goog.math'); + /** * Class for an editable number field. - * @param {string} text The initial content of the field. + * @param {(string|number)=} opt_value The initial content of the field. The value + * should cast to a number, and if it does not, '0' will be used. + * @param {(string|number)=} opt_min Minimum value. + * @param {(string|number)=} opt_max Maximum value. + * @param {(string|number)=} opt_precision Precision for value. * @param {Function=} opt_validator An optional function that is called * to validate any constraints on what the user entered. Takes the new * text as an argument and returns either the accepted text, a replacement @@ -38,8 +44,73 @@ goog.require('Blockly.FieldTextInput'); * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldNumber = function(text, opt_validator) { - Blockly.FieldNumber.superClass_.constructor.call(this, text, - opt_validator); +Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision, + opt_validator) { + opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0'; + Blockly.FieldNumber.superClass_.constructor.call( + this, opt_value, opt_validator); + this.setConstraints(opt_min, opt_max, opt_precision); }; goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput); + +/** + * Construct a FieldNumber from a JSON arg object. + * @param {!Object} options A JSON object with options (value, min, max, and + * precision). + * @returns {!Blockly.FieldNumber} The new field instance. + * @package + */ +Blockly.FieldNumber.fromJson = function(options) { + return new Blockly.FieldNumber(options['value'], + options['min'], options['max'], options['precision']); +}; + +/** + * Set the maximum, minimum and precision constraints on this field. + * Any of these properties may be undefiend or NaN to be disabled. + * Setting precision (usually a power of 10) enforces a minimum step between + * values. That is, the user's value will rounded to the closest multiple of + * precision. The least significant digit place is inferred from the precision. + * Integers values can be enforces by choosing an integer precision. + * @param {number|string|undefined} min Minimum value. + * @param {number|string|undefined} max Maximum value. + * @param {number|string|undefined} precision Precision for value. + */ +Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) { + precision = parseFloat(precision); + this.precision_ = isNaN(precision) ? 0 : precision; + min = parseFloat(min); + this.min_ = isNaN(min) ? -Infinity : min; + max = parseFloat(max); + this.max_ = isNaN(max) ? Infinity : max; + this.setValue(this.callValidator(this.getValue())); +}; + +/** + * Ensure that only a number in the correct range may be entered. + * @param {string} text The user's text. + * @return {?string} A string representing a valid number, or null if invalid. + */ +Blockly.FieldNumber.prototype.classValidator = function(text) { + if (text === null) { + return null; + } + text = String(text); + // TODO: Handle cases like 'ten', '1.203,14', etc. + // 'O' is sometimes mistaken for '0' by inexperienced users. + text = text.replace(/O/ig, '0'); + // Strip out thousands separators. + text = text.replace(/,/g, ''); + var n = parseFloat(text || 0); + if (isNaN(n)) { + // Invalid number. + return null; + } + // Round to nearest multiple of precision. + if (this.precision_ && isFinite(n)) { + n = Math.round(n / this.precision_) * this.precision_; + } + // Get the value in range. + n = goog.math.clamp(n, this.min_, this.max_); + return String(n); +}; diff --git a/core/field_textinput.js b/core/field_textinput.js index 04e6238..a8490ab 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -30,6 +30,7 @@ goog.require('Blockly.Field'); goog.require('Blockly.Msg'); goog.require('goog.asserts'); goog.require('goog.dom'); +goog.require('goog.dom.TagName'); goog.require('goog.userAgent'); @@ -49,11 +50,36 @@ Blockly.FieldTextInput = function(text, opt_validator) { }; goog.inherits(Blockly.FieldTextInput, Blockly.Field); +/** + * Construct a FieldTextInput from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, class, and + * spellcheck). + * @returns {!Blockly.FieldTextInput} The new field instance. + * @package + */ +Blockly.FieldTextInput.fromJson = function(options) { + var text = Blockly.utils.replaceMessageReferences(options['text']); + var field = new Blockly.FieldTextInput(text, options['class']); + if (typeof options['spellcheck'] === 'boolean') { + field.setSpellcheck(options['spellcheck']); + } + return field; +}; + /** * Point size of text. Should match blocklyText's font-size in CSS. */ Blockly.FieldTextInput.FONTSIZE = 11; +/** + * The HTML input element for the user to type, or null if no FieldTextInput + * editor is currently open. + * @type {HTMLInputElement} + * @private + */ +Blockly.FieldTextInput.htmlInput_ = null; + /** * Mouse cursor style when over the hotspot that initiates the editor. */ @@ -74,23 +100,44 @@ Blockly.FieldTextInput.prototype.dispose = function() { }; /** - * Set the text in this field. - * @param {?string} text New text. + * Set the value of this field. + * @param {?string} newValue New value. * @override */ -Blockly.FieldTextInput.prototype.setValue = function(text) { - if (text === null) { +Blockly.FieldTextInput.prototype.setValue = function(newValue) { + if (newValue === null) { return; // No change if null. } - if (this.sourceBlock_ && this.validator_) { - var validated = this.validator_(text); - // If the new text is invalid, validation returns null. + if (this.sourceBlock_) { + var validated = this.callValidator(newValue); + // If the new value is invalid, validation returns null. // In this case we still want to display the illegal result. - if (validated !== null && validated !== undefined) { - text = validated; + if (validated !== null) { + newValue = validated; } } - Blockly.Field.prototype.setValue.call(this, text); + Blockly.Field.prototype.setValue.call(this, newValue); +}; + +/** + * Set the text in this field and fire a change event. + * @param {*} newText New text. + */ +Blockly.FieldTextInput.prototype.setText = function(newText) { + if (newText === null) { + // No change if null. + return; + } + newText = String(newText); + if (newText === this.text_) { + // No change. + return; + } + if (this.sourceBlock_ && Blockly.Events.isEnabled()) { + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, this.text_, newText)); + } + Blockly.Field.prototype.setText.call(this, newText); }; /** @@ -112,28 +159,46 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { var quietInput = opt_quietInput || false; if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID || goog.userAgent.IPAD)) { - // Mobile browsers have issues with in-line textareas (focus & keyboards). - var newValue = Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_); - if (this.sourceBlock_ && this.validator_) { - var override = this.validator_(newValue); - if (override !== undefined) { - newValue = override; - } - } - this.setValue(newValue); - return; + this.showPromptEditor_(); + } else { + this.showInlineEditor_(quietInput); } +}; + +/** + * Create and show a text input editor that is a prompt (usually a popup). + * Mobile browsers have issues with in-line textareas (focus and keyboards). + * @private + */ +Blockly.FieldTextInput.prototype.showPromptEditor_ = function() { + var fieldText = this; + Blockly.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_, + function(newValue) { + if (fieldText.sourceBlock_) { + newValue = fieldText.callValidator(newValue); + } + fieldText.setValue(newValue); + }); +}; +/** + * Create and show a text input editor that sits directly over the text input. + * @param {boolean} quietInput True if editor should be created without + * focus. + * @private + */ +Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_()); var div = Blockly.WidgetDiv.DIV; // Create the input. - var htmlInput = goog.dom.createDom('input', 'blocklyHtmlInput'); + var htmlInput = + goog.dom.createDom(goog.dom.TagName.INPUT, 'blocklyHtmlInput'); htmlInput.setAttribute('spellcheck', this.spellcheck_); var fontSize = (Blockly.FieldTextInput.FONTSIZE * this.workspace_.scale) + 'pt'; div.style.fontSize = fontSize; htmlInput.style.fontSize = fontSize; - /** @type {!HTMLInputElement} */ + Blockly.FieldTextInput.htmlInput_ = htmlInput; div.appendChild(htmlInput); @@ -146,19 +211,45 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { htmlInput.select(); } + this.bindEvents_(htmlInput); +}; + +/** + * Bind handlers for user input on this field and size changes on the workspace. + * @param {!HTMLInputElement} htmlInput The htmlInput created in showEditor, to + * which event handlers will be bound. + * @private + */ +Blockly.FieldTextInput.prototype.bindEvents_ = function(htmlInput) { // Bind to keydown -- trap Enter without IME and Esc to hide. htmlInput.onKeyDownWrapper_ = - Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); + Blockly.bindEventWithChecks_( + htmlInput, 'keydown', this, this.onHtmlInputKeyDown_); // Bind to keyup -- trap Enter; resize after every keystroke. htmlInput.onKeyUpWrapper_ = - Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_); + Blockly.bindEventWithChecks_( + htmlInput, 'keyup', this, this.onHtmlInputChange_); // Bind to keyPress -- repeatedly resize when holding down a key. htmlInput.onKeyPressWrapper_ = - Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_); + Blockly.bindEventWithChecks_( + htmlInput, 'keypress', this, this.onHtmlInputChange_); htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this); this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_); }; +/** + * Unbind handlers for user input and workspace size changes. + * @param {!HTMLInputElement} htmlInput The html for this text input. + * @private + */ +Blockly.FieldTextInput.prototype.unbindEvents_ = function(htmlInput) { + Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_); + Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_); + this.workspace_.removeChangeListener( + htmlInput.onWorkspaceChangeWrapper_); +}; + /** * Handle key down to the editor. * @param {!Event} e Keyboard event. @@ -184,7 +275,8 @@ Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) { * @param {!Event} e Keyboard event. * @private */ -Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) { +Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { var htmlInput = Blockly.FieldTextInput.htmlInput_; // Update source block. var text = htmlInput.value; @@ -198,6 +290,7 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) { this.sourceBlock_.render(); } this.resizeEditor_(); + Blockly.svgResize(this.sourceBlock_.workspace); }; /** @@ -209,13 +302,13 @@ Blockly.FieldTextInput.prototype.validate_ = function() { var valid = true; goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_); var htmlInput = Blockly.FieldTextInput.htmlInput_; - if (this.sourceBlock_ && this.validator_) { - valid = this.validator_(htmlInput.value); + if (this.sourceBlock_) { + valid = this.callValidator(htmlInput.value); } if (valid === null) { - Blockly.addClass_(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput'); } else { - Blockly.removeClass_(htmlInput, 'blocklyInvalidInput'); + Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput'); } }; @@ -225,17 +318,15 @@ Blockly.FieldTextInput.prototype.validate_ = function() { */ Blockly.FieldTextInput.prototype.resizeEditor_ = function() { var div = Blockly.WidgetDiv.DIV; - var bBox = this.fieldGroup_.getBBox(); - div.style.width = bBox.width * this.workspace_.scale + 'px'; - div.style.height = bBox.height * this.workspace_.scale + 'px'; - var xy = this.getAbsoluteXY_(); + var bBox = this.getScaledBBox_(); + div.style.width = bBox.right - bBox.left + 'px'; + div.style.height = bBox.bottom - bBox.top + 'px'; + // In RTL mode block fields and LTR input fields the left edge moves, // whereas the right edge is fixed. Reposition the editor. - if (this.sourceBlock_.RTL) { - var borderBBox = this.getScaledBBox_(); - xy.x += borderBBox.width; - xy.x -= div.offsetWidth; - } + var x = this.sourceBlock_.RTL ? bBox.right - div.offsetWidth : bBox.left; + var xy = new goog.math.Coordinate(x, bBox.top); + // Shift by a few pixels to line up exactly. xy.y += 1; if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) { @@ -262,25 +353,12 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { return function() { var htmlInput = Blockly.FieldTextInput.htmlInput_; // Save the edit (if it validates). - var text = htmlInput.value; - if (thisField.sourceBlock_ && thisField.validator_) { - var text1 = thisField.validator_(text); - if (text1 === null) { - // Invalid edit. - text = htmlInput.defaultValue; - } else if (text1 !== undefined) { - // Validation function has changed the text. - text = text1; - } - } - thisField.setValue(text); - thisField.sourceBlock_.rendered && thisField.sourceBlock_.render(); - Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_); - Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_); - Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_); - thisField.workspace_.removeChangeListener( - htmlInput.onWorkspaceChangeWrapper_); + thisField.maybeSaveEdit_(); + + thisField.unbindEvents_(htmlInput); Blockly.FieldTextInput.htmlInput_ = null; + Blockly.Events.setGroup(false); + // Delete style properties. var style = Blockly.WidgetDiv.DIV.style; style.width = 'auto'; @@ -289,12 +367,35 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { }; }; +Blockly.FieldTextInput.prototype.maybeSaveEdit_ = function() { + var htmlInput = Blockly.FieldTextInput.htmlInput_; + // Save the edit (if it validates). + var text = htmlInput.value; + if (this.sourceBlock_) { + var text1 = this.callValidator(text); + if (text1 === null) { + // Invalid edit. + text = htmlInput.defaultValue; + } else { + // Validation function has changed the text. + text = text1; + if (this.onFinishEditing_) { + this.onFinishEditing_(text); + } + } + } + this.setText(text); + this.sourceBlock_.rendered && this.sourceBlock_.render(); +}; + /** * Ensure that only a number may be entered. * @param {string} text The user's text. * @return {?string} A string representing a valid number, or null if invalid. */ Blockly.FieldTextInput.numberValidator = function(text) { + console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' + + 'Use Blockly.FieldNumber instead.'); if (text === null) { return null; } diff --git a/core/field_variable.js b/core/field_variable.js index 51a85ce..333fee2 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -28,8 +28,9 @@ goog.provide('Blockly.FieldVariable'); goog.require('Blockly.FieldDropdown'); goog.require('Blockly.Msg'); +goog.require('Blockly.VariableModel'); goog.require('Blockly.Variables'); -goog.require('Blockly.utils'); +goog.require('goog.asserts'); goog.require('goog.string'); @@ -39,47 +40,46 @@ goog.require('goog.string'); * a unique variable name will be generated. * @param {Function=} opt_validator A function that is executed when a new * option is selected. Its sole argument is the new option value. + * @param {Array.=} opt_variableTypes A list of the types of variables + * to include in the dropdown. + * @param {string=} opt_defaultType The type of variable to create if this + * field's value is not explicitly set. Defaults to ''. * @extends {Blockly.FieldDropdown} * @constructor */ -Blockly.FieldVariable = function(varname, opt_validator) { - Blockly.FieldVariable.superClass_.constructor.call(this, - this.dropdownCreate, opt_validator); - this.setValue(varname || ''); +Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes, + opt_defaultType) { + // The FieldDropdown constructor would call setValue, which might create a + // spurious variable. Just do the relevant parts of the constructor. + this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate; + this.size_ = new goog.math.Size(0, Blockly.BlockSvg.MIN_BLOCK_Y); + this.setValidator(opt_validator); + this.defaultVariableName = (varname || ''); + + this.setTypes_(opt_variableTypes, opt_defaultType); + this.value_ = null; }; goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown); /** - * Sets a new change handler for angle field. - * @param {Function} handler New change handler, or null. - */ -Blockly.FieldVariable.prototype.setValidator = function(handler) { - var wrappedHandler; - if (handler) { - // Wrap the user's change handler together with the variable rename handler. - wrappedHandler = function(value) { - var v1 = handler.call(this, value); - if (v1 === null) { - var v2 = v1; - } else { - if (v1 === undefined) { - v1 = value; - } - var v2 = Blockly.FieldVariable.dropdownChange.call(this, v1); - if (v2 === undefined) { - v2 = v1; - } - } - return v2 === value ? undefined : v2; - }; - } else { - wrappedHandler = Blockly.FieldVariable.dropdownChange; - } - Blockly.FieldVariable.superClass_.setValidator.call(this, wrappedHandler); + * Construct a FieldVariable from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (variable, + * variableTypes, and defaultType). + * @returns {!Blockly.FieldVariable} The new field instance. + * @package + */ +Blockly.FieldVariable.fromJson = function(options) { + var varname = Blockly.utils.replaceMessageReferences(options['variable']); + var variableTypes = options['variableTypes']; + var defaultType = options['defaultType']; + return new Blockly.FieldVariable(varname, null, variableTypes, defaultType); }; /** - * Install this dropdown on a block. + * Initialize everything needed to render this field. This includes making sure + * that the field's value is valid. + * @public */ Blockly.FieldVariable.prototype.init = function() { if (this.fieldGroup_) { @@ -87,116 +87,268 @@ Blockly.FieldVariable.prototype.init = function() { return; } Blockly.FieldVariable.superClass_.init.call(this); - if (!this.getValue()) { - // Variables without names get uniquely named for this workspace. - var workspace = - this.sourceBlock_.isInFlyout ? - this.sourceBlock_.workspace.targetWorkspace : - this.sourceBlock_.workspace; - this.setValue(Blockly.Variables.generateUniqueName(workspace)); + + // TODO (1010): Change from init/initModel to initView/initModel + this.initModel(); +}; + +/** + * Initialize the model for this field if it has not already been initialized. + * If the value has not been set to a variable by the first render, we make up a + * variable rather than let the value be invalid. + * @package + */ +Blockly.FieldVariable.prototype.initModel = function() { + if (this.variable_) { + return; // Initialization already happened. + } + this.workspace_ = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getOrCreateVariablePackage( + this.workspace_, null, this.defaultVariableName, this.defaultType_); + + // Don't fire a change event for this setValue. It would have null as the + // old value, which is not valid. + Blockly.Events.disable(); + try { + this.setValue(variable.getId()); + } finally { + Blockly.Events.enable(); } }; /** - * Get the variable's name (use a variableDB to convert into a real name). - * Unline a regular dropdown, variables are literal and have no neutral value. - * @return {string} Current text. + * Dispose of this field. + * @public + */ +Blockly.FieldVariable.dispose = function() { + Blockly.FieldVariable.superClass_.dispose.call(this); + this.workspace_ = null; + this.variableMap_ = null; +}; + +/** + * Attach this field to a block. + * @param {!Blockly.Block} block The block containing this field. + */ +Blockly.FieldVariable.prototype.setSourceBlock = function(block) { + goog.asserts.assert(!block.isShadow(), + 'Variable fields are not allowed to exist on shadow blocks.'); + Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block); +}; + +/** + * Get the variable's ID. + * @return {string} Current variable's ID. */ Blockly.FieldVariable.prototype.getValue = function() { - return this.getText(); + return this.variable_ ? this.variable_.getId() : null; +}; + +/** + * Get the text from this field, which is the selected variable's name. + * @return {string} The selected variable's name, or the empty string if no + * variable is selected. + */ +Blockly.FieldVariable.prototype.getText = function() { + return this.variable_ ? this.variable_.name : ''; +}; + +/** + * Get the variable model for the selected variable. + * Not guaranteed to be in the variable map on the workspace (e.g. if accessed + * after the variable has been deleted). + * @return {?Blockly.VariableModel} the selected variable, or null if none was + * selected. + * @package + */ +Blockly.FieldVariable.prototype.getVariable = function() { + return this.variable_; }; /** - * Set the variable name. - * @param {string} newValue New text. + * Set the variable ID. + * @param {string} id New variable ID, which must reference an existing + * variable. */ -Blockly.FieldVariable.prototype.setValue = function(newValue) { +Blockly.FieldVariable.prototype.setValue = function(id) { + var workspace = this.sourceBlock_.workspace; + var variable = Blockly.Variables.getVariable(workspace, id); + + if (!variable) { + throw new Error('Variable id doesn\'t point to a real variable! ID was ' + + id); + } + // Type checks! + var type = variable.type; + if (!this.typeIsAllowed_(type)) { + throw new Error('Variable type doesn\'t match this field! Type was ' + + type); + } if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, this.value_, newValue)); + var oldValue = this.variable_ ? this.variable_.getId() : null; + Blockly.Events.fire(new Blockly.Events.BlockChange( + this.sourceBlock_, 'field', this.name, oldValue, id)); + } + this.variable_ = variable; + this.value_ = id; + this.setText(variable.name); +}; + +/** + * Check whether the given variable type is allowed on this field. + * @param {string} type The type to check. + * @return {boolean} True if the type is in the list of allowed types. + * @private + */ +Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) { + var typeList = this.getVariableTypes_(); + if (!typeList) { + return true; // If it's null, all types are valid. + } + for (var i = 0; i < typeList.length; i++) { + if (type == typeList[i]) { + return true; + } } - this.value_ = newValue; - this.setText(newValue); + return false; +}; + +/** + * Return a list of variable types to include in the dropdown. + * @return {!Array.} Array of variable types. + * @throws {Error} if variableTypes is an empty array. + * @private + */ +Blockly.FieldVariable.prototype.getVariableTypes_ = function() { + // TODO (#1513): Try to avoid calling this every time the field is edited. + var variableTypes = this.variableTypes; + if (variableTypes === null) { + // If variableTypes is null, return all variable types. + if (this.sourceBlock_) { + var workspace = this.sourceBlock_.workspace; + return workspace.getVariableTypes(); + } + } + variableTypes = variableTypes || ['']; + if (variableTypes.length == 0) { + // Throw an error if variableTypes is an empty list. + var name = this.getText(); + throw new Error('\'variableTypes\' of field variable ' + + name + ' was an empty list'); + } + return variableTypes; +}; + +/** + * Parse the optional arguments representing the allowed variable types and the + * default variable type. + * @param {Array.=} opt_variableTypes A list of the types of variables + * to include in the dropdown. If null or undefined, variables of all types + * will be displayed in the dropdown. + * @param {string=} opt_defaultType The type of the variable to create if this + * field's value is not explicitly set. Defaults to ''. + * @private + */ +Blockly.FieldVariable.prototype.setTypes_ = function(opt_variableTypes, + opt_defaultType) { + // If you expected that the default type would be the same as the only entry + // in the variable types array, tell the Blockly team by commenting on #1499. + var defaultType = opt_defaultType || ''; + // Set the allowable variable types. Null means all types on the workspace. + if (opt_variableTypes == null || opt_variableTypes == undefined) { + var variableTypes = null; + } else if (Array.isArray(opt_variableTypes)) { + var variableTypes = opt_variableTypes; + // Make sure the default type is valid. + var isInArray = false; + for (var i = 0; i < variableTypes.length; i++) { + if (variableTypes[i] == defaultType) { + isInArray = true; + } + } + if (!isInArray) { + throw new Error('Invalid default type \'' + defaultType + '\' in ' + + 'the definition of a FieldVariable'); + } + } else { + throw new Error('\'variableTypes\' was not an array in the definition of ' + + 'a FieldVariable'); + } + // Only update the field once all checks pass. + this.defaultType_ = defaultType; + this.variableTypes = variableTypes; }; /** * Return a sorted list of variable names for variable dropdown menus. * Include a special option at the end for creating a new variable name. * @return {!Array.} Array of variable names. - * @this {!Blockly.FieldVariable} + * @this {Blockly.FieldVariable} */ -Blockly.FieldVariable.prototype.dropdownCreate = function() { - if (this.sourceBlock_ && this.sourceBlock_.workspace) { - var variableList = - Blockly.Variables.allVariables(this.sourceBlock_.workspace); - } else { - var variableList = []; +Blockly.FieldVariable.dropdownCreate = function() { + if (!this.variable_) { + throw new Error('Tried to call dropdownCreate on a variable field with no' + + ' variable selected.'); } - // Ensure that the currently selected variable is an option. + var variableModelList = []; var name = this.getText(); - if (name && variableList.indexOf(name) == -1) { - variableList.push(name); - } - variableList.sort(goog.string.caseInsensitiveCompare); - variableList.push(Blockly.Msg.RENAME_VARIABLE); - variableList.push(Blockly.Msg.NEW_VARIABLE); - // Variables are not language-specific, use the name as both the user-facing - // text and the internal representation. + var workspace = null; + if (this.sourceBlock_) { + workspace = this.sourceBlock_.workspace; + } + if (workspace) { + var variableTypes = this.getVariableTypes_(); + var variableModelList = []; + // Get a copy of the list, so that adding rename and new variable options + // doesn't modify the workspace's list. + for (var i = 0; i < variableTypes.length; i++) { + var variableType = variableTypes[i]; + var variables = workspace.getVariablesOfType(variableType); + variableModelList = variableModelList.concat(variables); + } + } + variableModelList.sort(Blockly.VariableModel.compareByName); + var options = []; - for (var x = 0; x < variableList.length; x++) { - options[x] = [variableList[x], variableList[x]]; + for (var i = 0; i < variableModelList.length; i++) { + // Set the UUID as the internal representation of the variable. + options[i] = [variableModelList[i].name, variableModelList[i].getId()]; } + options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]); + if (Blockly.Msg.DELETE_VARIABLE) { + options.push( + [ + Blockly.Msg.DELETE_VARIABLE.replace('%1', name), + Blockly.DELETE_VARIABLE_ID + ] + ); + } + return options; }; /** - * Event handler for a change in variable name. - * Special case the 'New variable...' and 'Rename variable...' options. - * In both of these special cases, prompt the user for a new name. - * @param {string} text The selected dropdown menu option. - * @return {null|undefined|string} An acceptable new variable name, or null if - * change is to be either aborted (cancel button) or has been already - * handled (rename), or undefined if an existing variable was chosen. - * @this {!Blockly.FieldVariable} - */ -Blockly.FieldVariable.dropdownChange = function(text) { - function promptName(promptText, defaultText, callback) { - Blockly.hideChaff(); - var newVar = Blockly.prompt(promptText, defaultText, function(newVar) { - // Merge runs of whitespace. Strip leading and trailing whitespace. - // Beyond this, all names are legal. - if (newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - if (newVar == Blockly.Msg.RENAME_VARIABLE || - newVar == Blockly.Msg.NEW_VARIABLE) { - // Ok, not ALL names are legal... - newVar = null; - } - } - callback(newVar); - }); - } - var workspace = this.sourceBlock_.workspace; - if (text == Blockly.Msg.RENAME_VARIABLE) { - var oldVar = this.getText(); - promptName(Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', oldVar), - oldVar, function(text) { - if (text) { - Blockly.Variables.renameVariable(oldVar, text, workspace); - } - }); - return null; - } else if (text == Blockly.Msg.NEW_VARIABLE) { - /*promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '', function(text) { - // Since variables are case-insensitive, ensure that if the new variable - // matches with an existing variable, the new case prevails throughout. - if (text) { - Blockly.Variables.renameVariable(text, text, workspace); - //TODO: need to add here what too do with the newly created variable - } - });*/ - //TODO: this return variable needs to be made redundant - return Blockly.Variables.generateUniqueName(workspace); + * Handle the selection of an item in the variable dropdown menu. + * Special case the 'Rename variable...' and 'Delete variable...' options. + * In the rename case, prompt the user for a new name. + * @param {!goog.ui.Menu} menu The Menu component clicked. + * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu. + */ +Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) { + var id = menuItem.getValue(); + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + var workspace = this.sourceBlock_.workspace; + if (id == Blockly.RENAME_VARIABLE_ID) { + // Rename variable. + Blockly.Variables.renameVariable(workspace, this.variable_); + return; + } else if (id == Blockly.DELETE_VARIABLE_ID) { + // Delete variable. + workspace.deleteVariableById(this.variable_.getId()); + return; + } + + // TODO (#1529): Call any validation function, and allow it to override. } - return undefined; + this.setValue(id); }; diff --git a/core/flyout.js b/core/flyout.js deleted file mode 100644 index b527087..0000000 --- a/core/flyout.js +++ /dev/null @@ -1,1136 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2011 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 Flyout tray containing blocks which may be created. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Flyout'); - -goog.require('Blockly.Block'); -goog.require('Blockly.Comment'); -goog.require('Blockly.Events'); -goog.require('Blockly.WorkspaceSvg'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.math.Rect'); -goog.require('goog.userAgent'); - - -/** - * Class for a flyout. - * @param {!Object} workspaceOptions Dictionary of options for the workspace. - * @constructor - */ -Blockly.Flyout = function(workspaceOptions) { - workspaceOptions.getMetrics = this.getMetrics_.bind(this); - workspaceOptions.setMetrics = this.setMetrics_.bind(this); - /** - * @type {!Blockly.Workspace} - * @private - */ - this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); - this.workspace_.isFlyout = true; - - /** - * Is RTL vs LTR. - * @type {boolean} - */ - this.RTL = !!workspaceOptions.RTL; - - /** - * Flyout should be laid out horizontally vs vertically. - * @type {boolean} - * @private - */ - this.horizontalLayout_ = workspaceOptions.horizontalLayout; - - /** - * Position of the toolbox and flyout relative to the workspace. - * @type {number} - * @private - */ - this.toolboxPosition_ = workspaceOptions.toolboxPosition; - - /** - * Opaque data that can be passed to Blockly.unbindEvent_. - * @type {!Array.} - * @private - */ - this.eventWrappers_ = []; - - /** - * List of background buttons that lurk behind each block to catch clicks - * landing in the blocks' lakes and bays. - * @type {!Array.} - * @private - */ - this.buttons_ = []; - - /** - * List of event listeners. - * @type {!Array.} - * @private - */ - this.listeners_ = []; - - /** - * List of blocks that should always be disabled. - * @type {!Array.} - * @private - */ - this.permanentlyDisabled_ = []; -}; - -/** - * Does the flyout automatically close when a block is created? - * @type {boolean} - */ -Blockly.Flyout.prototype.autoClose = true; - -/** - * Corner radius of the flyout background. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.CORNER_RADIUS = 8; - -/** - * Margin around the edges of the blocks in the flyout. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS; - -/** - * Top/bottom padding between scrollbar and edge of flyout background. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2; - -/** - * Width of flyout. - * @type {number} - * @private - */ -Blockly.Flyout.prototype.width_ = 0; - -/** - * Height of flyout. - * @type {number} - * @private - */ -Blockly.Flyout.prototype.height_ = 0; - -/** - * Creates the flyout's DOM. Only needs to be called once. - * @return {!Element} The flyout's SVG group. - */ -Blockly.Flyout.prototype.createDom = function() { - /* - - - - - */ - this.svgGroup_ = Blockly.createSvgElement('g', - {'class': 'blocklyFlyout'}, null); - this.svgBackground_ = Blockly.createSvgElement('path', - {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); - this.svgGroup_.appendChild(this.workspace_.createDom()); - return this.svgGroup_; -}; - -/** - * Initializes the flyout. - * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create - * new blocks. - */ -Blockly.Flyout.prototype.init = function(targetWorkspace) { - this.targetWorkspace_ = targetWorkspace; - this.workspace_.targetWorkspace = targetWorkspace; - // Add scrollbar. - this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, - this.horizontalLayout_, false); - - this.hide(); - - Array.prototype.push.apply(this.eventWrappers_, - Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.wheel_)); - if (!this.autoClose) { - this.filterWrapper_ = this.filterForCapacity_.bind(this); - this.targetWorkspace_.addChangeListener(this.filterWrapper_); - } - // Dragging the flyout up and down. - Array.prototype.push.apply(this.eventWrappers_, - Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_)); -}; - -/** - * Dispose of this flyout. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Flyout.prototype.dispose = function() { - this.hide(); - Blockly.unbindEvent_(this.eventWrappers_); - if (this.filterWrapper_) { - this.targetWorkspace_.removeChangeListener(this.filterWrapper_); - this.filterWrapper_ = null; - } - if (this.scrollbar_) { - this.scrollbar_.dispose(); - this.scrollbar_ = null; - } - if (this.workspace_) { - this.workspace_.targetWorkspace = null; - this.workspace_.dispose(); - this.workspace_ = null; - } - if (this.svgGroup_) { - goog.dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; - } - this.svgBackground_ = null; - this.targetWorkspace_ = null; -}; - -/** - * Get the width of the flyout. - * @return {number} The width of the flyout. - */ -Blockly.Flyout.prototype.getWidth = function() { - return this.width_; -}; - -/** - * Get the height of the flyout. - * @return {number} The width of the flyout. - */ -Blockly.Flyout.prototype.getHeight = function() { - return this.height_; -}; - -/** - * Return an object with all the metrics required to size scrollbars for the - * flyout. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the contents, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .absoluteTop: Top-edge of view. - * .viewLeft: Offset of the left edge of visible rectangle from parent, - * .contentLeft: Offset of the left-most content from the x=0 coordinate, - * .absoluteLeft: Left-edge of view. - * @return {Object} Contains size and position metrics of the flyout. - * @private - */ -Blockly.Flyout.prototype.getMetrics_ = function() { - if (!this.isVisible()) { - // Flyout is hidden. - return null; - } - - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - var optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - var absoluteTop = this.SCROLLBAR_PADDING; - var absoluteLeft = this.SCROLLBAR_PADDING; - if (this.horizontalLayout_) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - absoluteTop = 0; - } - var viewHeight = this.height_; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - viewHeight += this.MARGIN - this.SCROLLBAR_PADDING; - } - var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; - } else { - absoluteLeft = 0; - var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; - var viewWidth = this.width_; - if (!this.RTL) { - viewWidth -= this.SCROLLBAR_PADDING; - } - } - - var metrics = { - viewHeight: viewHeight, - viewWidth: viewWidth, - contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, - contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, - viewTop: -this.workspace_.scrollY, - viewLeft: -this.workspace_.scrollX, - contentTop: 0, // TODO: #349 - contentLeft: 0, // TODO: #349 - absoluteTop: absoluteTop, - absoluteLeft: absoluteLeft - }; - return metrics; -}; - -/** - * Sets the translation of the flyout to match the scrollbars. - * @param {!Object} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. - * @private - */ -Blockly.Flyout.prototype.setMetrics_ = function(xyRatio) { - var metrics = this.getMetrics_(); - // This is a fix to an apparent race condition. - if (!metrics) { - return; - } - if (!this.horizontalLayout_ && goog.isNumber(xyRatio.y)) { - this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y; - } else if (this.horizontalLayout_ && goog.isNumber(xyRatio.x)) { - this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x; - } - - this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, - this.workspace_.scrollY + metrics.absoluteTop); -}; - -/** - * Move the flyout to the edge of the workspace. - */ -Blockly.Flyout.prototype.position = function() { - if (!this.isVisible()) { - return; - } - var metrics = this.targetWorkspace_.getMetrics(); - if (!metrics) { - // Hidden components will return null. - return; - } - var edgeWidth = this.horizontalLayout_ ? metrics.viewWidth : this.width_; - edgeWidth -= this.CORNER_RADIUS; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - edgeWidth *= -1; - } - - this.setBackgroundPath_(edgeWidth, - this.horizontalLayout_ ? this.height_ : metrics.viewHeight); - - var x = metrics.absoluteLeft; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - x += metrics.viewWidth; - x -= this.width_; - } - - var y = metrics.absoluteTop; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - y += metrics.viewHeight; - y -= this.height_; - } - - this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); - - // Record the height for Blockly.Flyout.getMetrics_, or width if the layout is - // horizontal. - if (this.horizontalLayout_) { - this.width_ = metrics.viewWidth; - } else { - this.height_ = metrics.viewHeight; - } - - // Update the scrollbar (if one exists). - if (this.scrollbar_) { - this.scrollbar_.resize(); - } -}; - -/** - * Create and set the path for the visible boundaries of the flyout. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) { - if (this.horizontalLayout_) { - this.setBackgroundPathHorizontal_(width, height); - } else { - this.setBackgroundPathVertical_(width, height); - } -}; - -/** - * Create and set the path for the visible boundaries of the flyout in vertical - * mode. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) { - var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT; - // Decide whether to start on the left or right. - var path = ['M ' + (atRight ? this.width_ : 0) + ',0']; - // Top. - path.push('h', width); - // Rounded corner. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - atRight ? 0 : 1, - atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, - this.CORNER_RADIUS); - // Side closest to workspace. - path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2)); - // Rounded corner. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - atRight ? 0 : 1, - atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, - this.CORNER_RADIUS); - // Bottom. - path.push('h', -width); - path.push('z'); - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Create and set the path for the visible boundaries of the flyout in - * horizontal mode. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = - function(width, height) { - var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; - // Start at top left. - var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; - - if (atTop) { - // Top. - path.push('h', width + this.CORNER_RADIUS); - // Right. - path.push('v', height); - // Bottom. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('h', -1 * (width - this.CORNER_RADIUS)); - // Left. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('z'); - } else { - // Top. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('h', width - this.CORNER_RADIUS); - // Right. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('v', height - this.CORNER_RADIUS); - // Bottom. - path.push('h', -width - this.CORNER_RADIUS); - // Left. - path.push('z'); - } - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Scroll the flyout to the top. - */ -Blockly.Flyout.prototype.scrollToStart = function() { - this.scrollbar_.set((this.horizontalLayout_ && this.RTL) ? Infinity : 0); -}; - -/** - * Scroll the flyout. - * @param {!Event} e Mouse wheel scroll event. - * @private - */ -Blockly.Flyout.prototype.wheel_ = function(e) { - var delta = this.horizontalLayout_ ? e.deltaX : e.deltaY; - - if (delta) { - if (goog.userAgent.GECKO) { - // Firefox's deltas are a tenth that of Chrome/Safari. - delta *= 10; - } - var metrics = this.getMetrics_(); - var pos = this.horizontalLayout_ ? metrics.viewLeft + delta : - metrics.viewTop + delta; - var limit = this.horizontalLayout_ ? - metrics.contentWidth - metrics.viewWidth : - metrics.contentHeight - metrics.viewHeight; - pos = Math.min(pos, limit); - pos = Math.max(pos, 0); - this.scrollbar_.set(pos); - } - - // Don't scroll the page. - e.preventDefault(); - // Don't propagate mousewheel event (zooming). - e.stopPropagation(); -}; - -/** - * Is the flyout visible? - * @return {boolean} True if visible. - */ -Blockly.Flyout.prototype.isVisible = function() { - return this.svgGroup_ && this.svgGroup_.style.display == 'block'; -}; - -/** - * Hide and empty the flyout. - */ -Blockly.Flyout.prototype.hide = function() { - if (!this.isVisible()) { - return; - } - this.svgGroup_.style.display = 'none'; - // Delete all the event listeners. - for (var x = 0, listen; listen = this.listeners_[x]; x++) { - Blockly.unbindEvent_(listen); - } - this.listeners_.length = 0; - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); - this.reflowWrapper_ = null; - } - // Do NOT delete the blocks here. Wait until Flyout.show. - // https://neil.fraser.name/news/2014/08/09/ -}; - -/** - * Show and populate the flyout. - * @param {!Array|string} xmlList List of blocks to show. - * Variables and procedures have a custom set of blocks. - */ -Blockly.Flyout.prototype.show = function(xmlList) { - this.hide(); - this.clearOldBlocks_(); - - if (xmlList == Blockly.Variables.NAME_TYPE) { - // Special category for variables. - xmlList = - Blockly.Variables.flyoutCategory(this.workspace_.targetWorkspace); - } else if (xmlList == Blockly.Procedures.NAME_TYPE) { - // Special category for procedures. - xmlList = - Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace); - } - - this.svgGroup_.style.display = 'block'; - // Create the blocks to be shown in this flyout. - var blocks = []; - var gaps = []; - this.permanentlyDisabled_.length = 0; - for (var i = 0, xml; xml = xmlList[i]; i++) { - if (xml.tagName && xml.tagName.toUpperCase() == 'BLOCK') { - var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_); - if (curBlock.disabled) { - // Record blocks that were initially disabled. - // Do not enable these blocks as a result of capacity filtering. - this.permanentlyDisabled_.push(curBlock); - } - blocks.push(curBlock); - var gap = parseInt(xml.getAttribute('gap'), 10); - gaps.push(isNaN(gap) ? this.MARGIN * 3 : gap); - } - } - - this.layoutBlocks_(blocks, gaps); - - // IE 11 is an incompetant browser that fails to fire mouseout events. - // When the mouse is over the background, deselect all blocks. - var deselectAll = function(e) { - var topBlocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = topBlocks[i]; i++) { - block.removeSelect(); - } - }; - this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', - this, deselectAll)); - - if (this.horizontalLayout_) { - this.height_ = 0; - } else { - this.width_ = 0; - } - this.reflow(); - - this.offsetHorizontalRtlBlocks(this.workspace_.getTopBlocks(false)); - this.filterForCapacity_(); - - // Fire a resize event to update the flyout's scrollbar. - Blockly.svgResize(this.workspace_); - this.reflowWrapper_ = this.reflow.bind(this); - this.workspace_.addChangeListener(this.reflowWrapper_); -}; - -/** - * Lay out the blocks in the flyout. - * @param {!Array.} blocks The blocks to lay out. - * @param {!Array.} gaps The visible gaps between blocks. - * @private - */ -Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) { - var margin = this.MARGIN * this.workspace_.scale; - var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; - var cursorY = margin; - for (var i = 0, block; block = blocks[i]; i++) { - var allBlocks = block.getDescendants(); - for (var j = 0, child; child = allBlocks[j]; j++) { - // Mark blocks as being inside a flyout. This is used to detect and - // prevent the closure of the flyout if the user right-clicks on such a - // block. - child.isInFlyout = true; - } - block.render(); - var root = block.getSvgRoot(); - var blockHW = block.getHeightWidth(); - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - if (this.horizontalLayout_) { - cursorX += tab; - } - block.moveBy((this.horizontalLayout_ && this.RTL) ? -cursorX : cursorX, - cursorY); - if (this.horizontalLayout_) { - cursorX += (blockHW.width + gaps[i] - tab); - } else { - cursorY += blockHW.height + gaps[i]; - } - - // Create an invisible rectangle under the block to act as a button. Just - // using the block as a button is poor, since blocks have holes in them. - var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null); - rect.tooltip = block; - Blockly.Tooltip.bindMouseEvents(rect); - // Add the rectangles under the blocks, so that the blocks' tooltips work. - this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); - block.flyoutRect_ = rect; - this.buttons_[i] = rect; - - this.addBlockListeners_(root, block, rect); - } -}; - -/** - * Delete blocks and background buttons from a previous showing of the flyout. - * @private - */ -Blockly.Flyout.prototype.clearOldBlocks_ = function() { - // Delete any blocks from a previous showing. - var oldBlocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = oldBlocks[i]; i++) { - if (block.workspace == this.workspace_) { - block.dispose(false, false); - } - } - // Delete any background buttons from a previous showing. - for (var j = 0, rect; rect = this.buttons_[j]; j++) { - goog.dom.removeNode(rect); - } - this.buttons_.length = 0; -}; - -/** - * Add listeners to a block that has been added to the flyout. - * @param {!Element} root The root node of the SVG group the block is in. - * @param {!Blockly.Block} block The block to add listeners for. - * @param {!Element} rect The invisible rectangle under the block that acts as - * a button for that block. - * @private - */ -Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { - if (this.autoClose) { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.createBlockFunc_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.createBlockFunc_(block))); - } else { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.blockMouseDown_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.blockMouseDown_(block))); - } - this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, - block.removeSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, - block.removeSelect)); -}; - -/** - * Handle a mouse-down on an SVG block in a non-closing flyout. - * @param {!Blockly.Block} block The flyout block to copy. - * @return {!Function} Function to call when block is clicked. - * @private - */ -Blockly.Flyout.prototype.blockMouseDown_ = function(block) { - var flyout = this; - return function(e) { - Blockly.terminateDrag_(); - Blockly.hideChaff(); - if (Blockly.isRightButton(e)) { - // Right-click. - block.showContextMenu_(e); - } else { - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - // Record the current mouse position. - Blockly.Flyout.startDownEvent_ = e; - Blockly.Flyout.startBlock_ = block; - Blockly.Flyout.startFlyout_ = flyout; - Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, flyout.onMouseUp_); - Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, flyout.onMouseMoveBlock_); - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - }; -}; - -/** - * Mouse down on the flyout background. Start a vertical scroll drag. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Flyout.prototype.onMouseDown_ = function(e) { - if (Blockly.isRightButton(e)) { - return; - } - Blockly.hideChaff(true); - Blockly.Flyout.terminateDrag_(); - this.startDragMouseY_ = e.clientY; - this.startDragMouseX_ = e.clientX; - Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove', - this, this.onMouseMove_); - Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, 'mouseup', - this, Blockly.Flyout.terminateDrag_); - // This event has been handled. No need to bubble up to the document. - e.preventDefault(); - e.stopPropagation(); -}; - -/** - * Handle a mouse-up anywhere in the SVG pane. Is only registered when a - * block is clicked. We can't use mouseUp on the block since a fast-moving - * cursor can briefly escape the block before it catches up. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.Flyout.prototype.onMouseUp_ = function(e) { - if (Blockly.dragMode_ != Blockly.DRAG_FREE && - !Blockly.WidgetDiv.isVisible()) { - Blockly.Events.fire( - new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click', - undefined, undefined)); - } - Blockly.terminateDrag_(); -}; - -/** - * Handle a mouse-move to vertically drag the flyout. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Flyout.prototype.onMouseMove_ = function(e) { - var metrics = this.getMetrics_(); - if (this.horizontalLayout_) { - if (metrics.contentWidth - metrics.viewWidth < 0) { - return; - } - var dx = e.clientX - this.startDragMouseX_; - this.startDragMouseX_ = e.clientX; - var x = metrics.viewLeft - dx; - x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth); - this.scrollbar_.set(x); - } else { - if (metrics.contentHeight - metrics.viewHeight < 0) { - return; - } - var dy = e.clientY - this.startDragMouseY_; - this.startDragMouseY_ = e.clientY; - var y = metrics.viewTop - dy; - y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight); - this.scrollbar_.set(y); - } -}; - -/** - * Mouse button is down on a block in a non-closing flyout. Create the block - * if the mouse moves beyond a small radius. This allows one to play with - * fields without instantiating blocks that instantly self-destruct. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) { - if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && - e.button == 0) { - /* HACK: - Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events - on certain touch actions. Ignore events with these signatures. - This may result in a one-pixel blind spot in other browsers, - but this shouldn't be noticable. */ - e.stopPropagation(); - return; - } - var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX; - var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY; - // Still dragging within the sticky DRAG_RADIUS. - if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { - // Create the block. - Blockly.Flyout.startFlyout_.createBlockFunc_(Blockly.Flyout.startBlock_)( - Blockly.Flyout.startDownEvent_); - } -}; - -/** - * Create a copy of this block on the workspace. - * @param {!Blockly.Block} originBlock The flyout block to copy. - * @return {!Function} Function to call when block is clicked. - * @private - */ -Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) { - var flyout = this; - return function(e) { - if (Blockly.isRightButton(e)) { - // Right-click. Don't create a block, let the context menu show. - return; - } - if (originBlock.disabled) { - // Beyond capacity. - return; - } - Blockly.Events.disable(); - var block = flyout.placeNewBlock_(originBlock); - Blockly.Events.enable(); - if (Blockly.Events.isEnabled()) { - Blockly.Events.setGroup(true); - Blockly.Events.fire(new Blockly.Events.Create(block)); - } - if (flyout.autoClose) { - flyout.hide(); - } else { - flyout.filterForCapacity_(); - } - // Start a dragging operation on the new block. - block.onMouseDown_(e); - Blockly.dragMode_ = Blockly.DRAG_FREE; - block.setDragging_(true); - }; -}; - -/** - * Copy a block from the flyout to the workspace and position it correctly. - * @param {!Blockly.Block} originBlock The flyout block to copy.. - * @return {!Blockly.Block} The new block in the main workspace. - * @private - */ -Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) { - var targetWorkspace = this.targetWorkspace_; - var svgRootOld = originBlock.getSvgRoot(); - if (!svgRootOld) { - throw 'originBlock is not rendered.'; - } - // Figure out where the original block is on the screen, relative to the upper - // left corner of the main workspace. - var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace); - // Take into account that the flyout might have been scrolled horizontally - // (separately from the main workspace). - // Generally a no-op in vertical mode but likely to happen in horizontal - // mode. - var scrollX = this.workspace_.scrollX; - var scale = this.workspace_.scale; - xyOld.x += scrollX / scale - scrollX; - // If the flyout is on the right side, (0, 0) in the flyout is offset to - // the right of (0, 0) in the main workspace. Add an offset to take that - // into account. - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - scrollX = targetWorkspace.getMetrics().viewWidth - this.width_; - scale = targetWorkspace.scale; - // Scale the scroll (getSvgXY_ did not do this). - xyOld.x += scrollX / scale - scrollX; - } - - // Take into account that the flyout might have been scrolled vertically - // (separately from the main workspace). - // Generally a no-op in horizontal mode but likely to happen in vertical - // mode. - var scrollY = this.workspace_.scrollY; - scale = this.workspace_.scale; - xyOld.y += scrollY / scale - scrollY; - // If the flyout is on the bottom, (0, 0) in the flyout is offset to be below - // (0, 0) in the main workspace. Add an offset to take that into account. - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - scrollY = targetWorkspace.getMetrics().viewHeight - this.height_; - scale = targetWorkspace.scale; - xyOld.y += scrollY / scale - scrollY; - } - - // Create the new block by cloning the block in the flyout (via XML). - var xml = Blockly.Xml.blockToDom(originBlock); - var block = Blockly.Xml.domToBlock(xml, targetWorkspace); - var svgRootNew = block.getSvgRoot(); - if (!svgRootNew) { - throw 'block is not rendered.'; - } - // Figure out where the new block got placed on the screen, relative to the - // upper left corner of the workspace. This may not be the same as the - // original block because the flyout's origin may not be the same as the - // main workspace's origin. - var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace); - // Scale the scroll (getSvgXY_ did not do this). - xyNew.x += - targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX; - xyNew.y += - targetWorkspace.scrollY / targetWorkspace.scale - targetWorkspace.scrollY; - // If the flyout is collapsible and the workspace can't be scrolled. - if (targetWorkspace.toolbox_ && !targetWorkspace.scrollbar) { - xyNew.x += targetWorkspace.toolbox_.getWidth() / targetWorkspace.scale; - xyNew.y += targetWorkspace.toolbox_.getHeight() / targetWorkspace.scale; - } - - // Move the new block to where the old block is. - block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y); - return block; -}; - -/** - * Filter the blocks on the flyout to disable the ones that are above the - * capacity limit. - * @private - */ -Blockly.Flyout.prototype.filterForCapacity_ = function() { - var remainingCapacity = this.targetWorkspace_.remainingCapacity(); - var blocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - if (this.permanentlyDisabled_.indexOf(block) == -1) { - var allBlocks = block.getDescendants(); - block.setDisabled(allBlocks.length > remainingCapacity); - } - } -}; - -/** - * Return the deletion rectangle for this flyout. - * @return {goog.math.Rect} Rectangle in which to delete. - */ -Blockly.Flyout.prototype.getClientRect = function() { - var flyoutRect = this.svgGroup_.getBoundingClientRect(); - // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout - // area are still deleted. Must be larger than the largest screen size, - // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). - var BIG_NUM = 1000000000; - var x = flyoutRect.left; - var y = flyoutRect.top; - var width = flyoutRect.width; - var height = flyoutRect.height; - - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2, - BIG_NUM + height); - } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2, - BIG_NUM + height); - } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width, - BIG_NUM * 2); - } else { // Right - return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2); - } -}; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.Flyout.terminateDrag_ = function() { - if (Blockly.Flyout.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_); - Blockly.Flyout.onMouseUpWrapper_ = null; - } - if (Blockly.Flyout.onMouseMoveBlockWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_); - Blockly.Flyout.onMouseMoveBlockWrapper_ = null; - } - if (Blockly.Flyout.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_); - Blockly.Flyout.onMouseMoveWrapper_ = null; - } - if (Blockly.Flyout.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_); - Blockly.Flyout.onMouseUpWrapper_ = null; - } - Blockly.Flyout.startDownEvent_ = null; - Blockly.Flyout.startBlock_ = null; - Blockly.Flyout.startFlyout_ = null; -}; - -/** - * Compute height of flyout. Position button under each block. - * For RTL: Lay out the blocks right-aligned. - * @param {!Array} blocks The blocks to reflow. - */ -Blockly.Flyout.prototype.reflowHorizontal = function(blocks) { - this.workspace_.scale = this.targetWorkspace_.scale; - var flyoutHeight = 0; - for (var i = 0, block; block = blocks[i]; i++) { - flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); - } - flyoutHeight += this.MARGIN * 1.5; - flyoutHeight *= this.workspace_.scale; - flyoutHeight += Blockly.Scrollbar.scrollbarThickness; - if (this.height_ != flyoutHeight) { - for (var i = 0, block; block = blocks[i]; i++) { - var blockHW = block.getHeightWidth(); - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('width', blockHW.width); - block.flyoutRect_.setAttribute('height', blockHW.height); - // Rectangles behind blocks with output tabs are shifted a bit. - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - var blockXY = block.getRelativeToSurfaceXY(); - block.flyoutRect_.setAttribute('y', blockXY.y); - block.flyoutRect_.setAttribute('x', - this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); - // For hat blocks we want to shift them down by the hat height - // since the y coordinate is the corner, not the top of the hat. - var hatOffset = - block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; - if (hatOffset) { - block.moveBy(0, hatOffset); - } - block.flyoutRect_.setAttribute('y', blockXY.y); - } - } - // Record the height for .getMetrics_ and .position. - this.height_ = flyoutHeight; - Blockly.asyncSvgResize(this.workspace_); - } -}; - -/** - * Compute width of flyout. Position button under each block. - * For RTL: Lay out the blocks right-aligned. - * @param {!Array} blocks The blocks to reflow. - */ -Blockly.Flyout.prototype.reflowVertical = function(blocks) { - this.workspace_.scale = this.targetWorkspace_.scale; - var flyoutWidth = 0; - for (var i = 0, block; block = blocks[i]; i++) { - var width = block.getHeightWidth().width; - if (block.outputConnection) { - width -= Blockly.BlockSvg.TAB_WIDTH; - } - flyoutWidth = Math.max(flyoutWidth, width); - } - flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH; - flyoutWidth *= this.workspace_.scale; - flyoutWidth += Blockly.Scrollbar.scrollbarThickness; - if (this.width_ != flyoutWidth) { - for (var i = 0, block; block = blocks[i]; i++) { - var blockHW = block.getHeightWidth(); - if (this.RTL) { - // With the flyoutWidth known, right-align the blocks. - var oldX = block.getRelativeToSurfaceXY().x; - var dx = flyoutWidth - this.MARGIN; - dx /= this.workspace_.scale; - dx -= Blockly.BlockSvg.TAB_WIDTH; - block.moveBy(dx - oldX, 0); - } - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('width', blockHW.width); - block.flyoutRect_.setAttribute('height', blockHW.height); - // Blocks with output tabs are shifted a bit. - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - var blockXY = block.getRelativeToSurfaceXY(); - block.flyoutRect_.setAttribute('x', - this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); - // For hat blocks we want to shift them down by the hat height - // since the y coordinate is the corner, not the top of the hat. - var hatOffset = - block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; - if (hatOffset) { - block.moveBy(0, hatOffset); - } - block.flyoutRect_.setAttribute('y', blockXY.y); - } - } - // Record the width for .getMetrics_ and .position. - this.width_ = flyoutWidth; - Blockly.asyncSvgResize(this.workspace_); - } -}; - -/** - * Reflow blocks and their buttons. - */ -Blockly.Flyout.prototype.reflow = function() { - var blocks = this.workspace_.getTopBlocks(false); - if (this.horizontalLayout_) { - this.reflowHorizontal(blocks); - } else { - this.reflowVertical(blocks); - } -}; - -/** - * In the horizontal RTL case all of the blocks will be laid out to the left of - * the origin, but we won't know how big the workspace is until the layout pass - * is done. - * Now that it's done, shunt all the blocks to be right of the origin. - * @param {!Array} blocks The blocks to reposition. - */ -Blockly.Flyout.prototype.offsetHorizontalRtlBlocks = function(blocks) { - if (this.horizontalLayout_ && this.RTL) { - // We don't know this workspace's view width yet. - this.position(); - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - var offset = Math.max(-optionBox.x + this.MARGIN, - this.width_ / this.workspace_.scale); - - for (var i = 0, block; block = blocks[i]; i++) { - block.moveBy(offset, 0); - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('x', - offset + Number(block.flyoutRect_.getAttribute('x'))); - } - } - } -}; diff --git a/core/flyout_base.js b/core/flyout_base.js new file mode 100644 index 0000000..d845ae8 --- /dev/null +++ b/core/flyout_base.js @@ -0,0 +1,804 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2011 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 Flyout tray containing blocks which may be created. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Flyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.Gesture'); +goog.require('Blockly.Touch'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @constructor + */ +Blockly.Flyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + /** + * @type {!Blockly.Workspace} + * @private + */ + this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); + this.workspace_.isFlyout = true; + + /** + * Is RTL vs LTR. + * @type {boolean} + */ + this.RTL = !!workspaceOptions.RTL; + + /** + * Position of the toolbox and flyout relative to the workspace. + * @type {number} + * @private + */ + this.toolboxPosition_ = workspaceOptions.toolboxPosition; + + /** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {!Array.} + * @private + */ + this.eventWrappers_ = []; + + /** + * List of background mats that lurk behind each block to catch clicks + * landing in the blocks' lakes and bays. + * @type {!Array.} + * @private + */ + this.mats_ = []; + + /** + * List of visible buttons. + * @type {!Array.} + * @private + */ + this.buttons_ = []; + + /** + * List of event listeners. + * @type {!Array.} + * @private + */ + this.listeners_ = []; + + /** + * List of blocks that should always be disabled. + * @type {!Array.} + * @private + */ + this.permanentlyDisabled_ = []; +}; + +/** + * Does the flyout automatically close when a block is created? + * @type {boolean} + */ +Blockly.Flyout.prototype.autoClose = true; + +/** + * Whether the flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.isVisible_ = false; + +/** + * Whether the workspace containing this flyout is visible. + * @type {boolean} + * @private + */ +Blockly.Flyout.prototype.containerVisible_ = true; + +/** + * Corner radius of the flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.CORNER_RADIUS = 8; + +/** + * Margin around the edges of the blocks in the flyout. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS; + +// TODO: Move GAP_X and GAP_Y to their appropriate files. + +/** + * Gap between items in horizontal flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Gap between items in vertical flyouts. Can be overridden with the "sep" + * element. + * @const {number} + */ +Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3; + +/** + * Top/bottom padding between scrollbar and edge of flyout background. + * @type {number} + * @const + */ +Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2; + +/** + * Width of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.width_ = 0; + +/** + * Height of flyout. + * @type {number} + * @private + */ +Blockly.Flyout.prototype.height_ = 0; + +/** + * Range of a drag angle from a flyout considered "dragging toward workspace". + * Drags that are within the bounds of this many degrees from the orthogonal + * line to the flyout edge are considered to be "drags toward the workspace". + * Example: + * Flyout Edge Workspace + * [block] / <-within this angle, drags "toward workspace" | + * [block] ---- orthogonal to flyout boundary ---- | + * [block] \ | + * The angle is given in degrees from the orthogonal. + * + * This is used to know when to create a new block and when to scroll the + * flyout. Setting it to 360 means that all drags create a new block. + * @type {number} + * @private +*/ +Blockly.Flyout.prototype.dragAngleRange_ = 70; + +/** + * Creates the flyout's DOM. Only needs to be called once. The flyout can + * either exist as its own svg element or be a g element nested inside a + * separate svg element. + * @param {string} tagName The type of tag to put the flyout in. This + * should be or . + * @return {!Element} The flyout's SVG group. + */ +Blockly.Flyout.prototype.createDom = function(tagName) { + /* + + + + + */ + // Setting style to display:none to start. The toolbox and flyout + // hide/show code will set up proper visibility and size later. + this.svgGroup_ = Blockly.utils.createSvgElement(tagName, + {'class': 'blocklyFlyout', 'style': 'display: none'}, null); + this.svgBackground_ = Blockly.utils.createSvgElement('path', + {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); + this.svgGroup_.appendChild(this.workspace_.createDom()); + return this.svgGroup_; +}; + +/** + * Initializes the flyout. + * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create + * new blocks. + */ +Blockly.Flyout.prototype.init = function(targetWorkspace) { + this.targetWorkspace_ = targetWorkspace; + this.workspace_.targetWorkspace = targetWorkspace; + // Add scrollbar. + this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, + this.horizontalLayout_, false, 'blocklyFlyoutScrollbar'); + + this.hide(); + + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, this.wheel_)); + if (!this.autoClose) { + this.filterWrapper_ = this.filterForCapacity_.bind(this); + this.targetWorkspace_.addChangeListener(this.filterWrapper_); + } + + // Dragging the flyout up and down. + Array.prototype.push.apply(this.eventWrappers_, + Blockly.bindEventWithChecks_( + this.svgBackground_, 'mousedown', this, this.onMouseDown_)); + + // A flyout connected to a workspace doesn't have its own current gesture. + this.workspace_.getGesture = + this.targetWorkspace_.getGesture.bind(this.targetWorkspace_); + + // Get variables from the main workspace rather than the target workspace. + this.workspace_.variableMap_ = this.targetWorkspace_.getVariableMap(); + + this.workspace_.createPotentialVariableMap(); +}; + +/** + * Dispose of this flyout. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Flyout.prototype.dispose = function() { + this.hide(); + Blockly.unbindEvent_(this.eventWrappers_); + if (this.filterWrapper_) { + this.targetWorkspace_.removeChangeListener(this.filterWrapper_); + this.filterWrapper_ = null; + } + if (this.scrollbar_) { + this.scrollbar_.dispose(); + this.scrollbar_ = null; + } + if (this.workspace_) { + this.workspace_.targetWorkspace = null; + this.workspace_.dispose(); + this.workspace_ = null; + } + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.svgBackground_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Get the width of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getWidth = function() { + return this.width_; +}; + +/** + * Get the height of the flyout. + * @return {number} The width of the flyout. + */ +Blockly.Flyout.prototype.getHeight = function() { + return this.height_; +}; + +/** + * Get the workspace inside the flyout. + * @return {!Blockly.WorkspaceSvg} The workspace inside the flyout. + * @package + */ +Blockly.Flyout.prototype.getWorkspace = function() { + return this.workspace_; +}; + +/** + * Is the flyout visible? + * @return {boolean} True if visible. + */ +Blockly.Flyout.prototype.isVisible = function() { + return this.isVisible_; +}; + +/** + * Set whether the flyout is visible. A value of true does not necessarily mean + * that the flyout is shown. It could be hidden because its container is hidden. + * @param {boolean} visible True if visible. + */ +Blockly.Flyout.prototype.setVisible = function(visible) { + var visibilityChanged = (visible != this.isVisible()); + + this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Set whether this flyout's container is visible. + * @param {boolean} visible Whether the container is visible. + */ +Blockly.Flyout.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Update the display property of the flyout based whether it thinks it should + * be visible and whether its containing workspace is visible. + * @private + */ +Blockly.Flyout.prototype.updateDisplay_ = function() { + var show = true; + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + this.svgGroup_.style.display = show ? 'block' : 'none'; + // Update the scrollbar's visiblity too since it should mimic the + // flyout's visibility. + this.scrollbar_.setContainerVisible(show); +}; + +/** + * Update the view based on coordinates calculated in position(). + * @param {number} width The computed width of the flyout's SVG group + * @param {number} height The computed height of the flyout's SVG group. + * @param {number} x The computed x origin of the flyout's SVG group. + * @param {number} y The computed y origin of the flyout's SVG group. + * @private + */ +Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) { + this.svgGroup_.setAttribute("width", width); + this.svgGroup_.setAttribute("height", height); + var transform = 'translate(' + x + 'px,' + y + 'px)'; + Blockly.utils.setCssTransform(this.svgGroup_, transform); + + // Update the scrollbar (if one exists). + if (this.scrollbar_) { + // Set the scrollbars origin to be the top left of the flyout. + this.scrollbar_.setOrigin(x, y); + this.scrollbar_.resize(); + } +}; + +/** + * Hide and empty the flyout. + */ +Blockly.Flyout.prototype.hide = function() { + if (!this.isVisible()) { + return; + } + this.setVisible(false); + // Delete all the event listeners. + for (var x = 0, listen; listen = this.listeners_[x]; x++) { + Blockly.unbindEvent_(listen); + } + this.listeners_.length = 0; + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + this.reflowWrapper_ = null; + } + // Do NOT delete the blocks here. Wait until Flyout.show. + // https://neil.fraser.name/news/2014/08/09/ +}; + +/** + * Show and populate the flyout. + * @param {!Array|string} xmlList List of blocks to show. + * Variables and procedures have a custom set of blocks. + */ +Blockly.Flyout.prototype.show = function(xmlList) { + this.workspace_.setResizesEnabled(false); + this.hide(); + this.clearOldBlocks_(); + + // Handle dynamic categories, represented by a name instead of a list of XML. + // Look up the correct category generation function and call that to get a + // valid XML list. + if (typeof xmlList == 'string') { + var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback( + xmlList); + goog.asserts.assert(goog.isFunction(fnToApply), + 'Couldn\'t find a callback function when opening a toolbox category.'); + xmlList = fnToApply(this.workspace_.targetWorkspace); + goog.asserts.assert(goog.isArray(xmlList), + 'The result of a toolbox category callback must be an array.'); + } + + this.setVisible(true); + // Create the blocks to be shown in this flyout. + var contents = []; + var gaps = []; + this.permanentlyDisabled_.length = 0; + for (var i = 0, xml; xml = xmlList[i]; i++) { + if (xml.tagName) { + var tagName = xml.tagName.toUpperCase(); + var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y; + if (tagName == 'BLOCK') { + var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_); + if (curBlock.disabled) { + // Record blocks that were initially disabled. + // Do not enable these blocks as a result of capacity filtering. + this.permanentlyDisabled_.push(curBlock); + } + contents.push({type: 'block', block: curBlock}); + var gap = parseInt(xml.getAttribute('gap'), 10); + gaps.push(isNaN(gap) ? default_gap : gap); + } else if (xml.tagName.toUpperCase() == 'SEP') { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // This overwrites the gap attribute on the previous block. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseInt(xml.getAttribute('gap'), 10); + // Ignore gaps before the first block. + if (!isNaN(newGap) && gaps.length > 0) { + gaps[gaps.length - 1] = newGap; + } else { + gaps.push(default_gap); + } + } else if (tagName == 'BUTTON' || tagName == 'LABEL') { + // Labels behave the same as buttons, but are styled differently. + var isLabel = tagName == 'LABEL'; + var curButton = new Blockly.FlyoutButton(this.workspace_, + this.targetWorkspace_, xml, isLabel); + contents.push({type: 'button', button: curButton}); + gaps.push(default_gap); + } + } + } + + this.layout_(contents, gaps); + + // IE 11 is an incompetent browser that fails to fire mouseout events. + // When the mouse is over the background, deselect all blocks. + var deselectAll = function() { + var topBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = topBlocks[i]; i++) { + block.removeSelect(); + } + }; + + this.listeners_.push(Blockly.bindEventWithChecks_(this.svgBackground_, + 'mouseover', this, deselectAll)); + + if (this.horizontalLayout_) { + this.height_ = 0; + } else { + this.width_ = 0; + } + this.workspace_.setResizesEnabled(true); + this.reflow(); + + this.filterForCapacity_(); + + // Correctly position the flyout's scrollbar when it opens. + this.position(); + + this.reflowWrapper_ = this.reflow.bind(this); + this.workspace_.addChangeListener(this.reflowWrapper_); +}; + +/** + * Delete blocks, mats and buttons from a previous showing of the flyout. + * @private + */ +Blockly.Flyout.prototype.clearOldBlocks_ = function() { + // Delete any blocks from a previous showing. + var oldBlocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = oldBlocks[i]; i++) { + if (block.workspace == this.workspace_) { + block.dispose(false, false); + } + } + // Delete any mats from a previous showing. + for (var j = 0; j < this.mats_.length; j++) { + var rect = this.mats_[j]; + if (rect) { + goog.dom.removeNode(rect); + } + } + this.mats_.length = 0; + // Delete any buttons from a previous showing. + for (var i = 0, button; button = this.buttons_[i]; i++) { + button.dispose(); + } + this.buttons_.length = 0; + + // Clear potential variables from the previous showing. + this.workspace_.getPotentialVariableMap().clear(); +}; + +/** + * Add listeners to a block that has been added to the flyout. + * @param {!Element} root The root node of the SVG group the block is in. + * @param {!Blockly.Block} block The block to add listeners for. + * @param {!Element} rect The invisible rectangle under the block that acts as + * a mat for that block. + * @private + */ +Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { + this.listeners_.push(Blockly.bindEventWithChecks_(root, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEventWithChecks_(rect, 'mousedown', null, + this.blockMouseDown_(block))); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, + block.removeSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, + block.removeSelect)); +}; + +/** + * Handle a mouse-down on an SVG block in a non-closing flyout. + * @param {!Blockly.Block} block The flyout block to copy. + * @return {!Function} Function to call when block is clicked. + * @private + */ +Blockly.Flyout.prototype.blockMouseDown_ = function(block) { + var flyout = this; + return function(e) { + var gesture = flyout.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.setStartBlock(block); + gesture.handleFlyoutStart(e, flyout); + } + }; +}; + +/** + * Mouse down on the flyout background. Start a vertical scroll drag. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.Flyout.prototype.onMouseDown_ = function(e) { + var gesture = this.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.handleFlyoutStart(e, this); + } +}; + +/** + * Create a copy of this block on the workspace. + * @param {!Blockly.BlockSvg} originalBlock The block to copy from the flyout. + * @return {Blockly.BlockSvg} The newly created block, or null if something + * went wrong with deserialization. + * @package + */ +Blockly.Flyout.prototype.createBlock = function(originalBlock) { + var newBlock = null; + Blockly.Events.disable(); + var variablesBeforeCreation = this.targetWorkspace_.getAllVariables(); + this.targetWorkspace_.setResizesEnabled(false); + try { + newBlock = this.placeNewBlock_(originalBlock); + // Close the flyout. + Blockly.hideChaff(); + } finally { + Blockly.Events.enable(); + } + + var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace_, + variablesBeforeCreation); + + if (Blockly.Events.isEnabled()) { + Blockly.Events.setGroup(true); + Blockly.Events.fire(new Blockly.Events.Create(newBlock)); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } + } + if (this.autoClose) { + this.hide(); + } else { + this.filterForCapacity_(); + } + return newBlock; +}; + +/** + * Initialize the given button: move it to the correct location, + * add listeners, etc. + * @param {!Blockly.FlyoutButton} button The button to initialize and place. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @private + */ +Blockly.Flyout.prototype.initFlyoutButton_ = function(button, x, y) { + var buttonSvg = button.createDom(); + button.moveTo(x, y); + button.show(); + // Clicking on a flyout button or label is a lot like clicking on the + // flyout background. + this.listeners_.push( + Blockly.bindEventWithChecks_( + buttonSvg, 'mousedown', this, this.onMouseDown_)); + + this.buttons_.push(button); +}; + +/** + * Create and place a rectangle corresponding to the given block. + * @param {!Blockly.Block} block The block to associate the rect to. + * @param {number} x The x position of the cursor during this layout pass. + * @param {number} y The y position of the cursor during this layout pass. + * @param {!{height: number, width: number}} blockHW The height and width of the + * block. + * @param {number} index The index into the mats list where this rect should be + * placed. + * @return {!SVGElement} Newly created SVG element for the rectangle behind the + * block. + * @private + */ +Blockly.Flyout.prototype.createRect_ = function(block, x, y, blockHW, index) { + // Create an invisible rectangle under the block to act as a button. Just + // using the block as a button is poor, since blocks have holes in them. + var rect = Blockly.utils.createSvgElement('rect', + { + 'fill-opacity': 0, + 'x': x, + 'y': y, + 'height': blockHW.height, + 'width': blockHW.width + }, null); + rect.tooltip = block; + Blockly.Tooltip.bindMouseEvents(rect); + // Add the rectangles under the blocks, so that the blocks' tooltips work. + this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); + + block.flyoutRect_ = rect; + this.mats_[index] = rect; + return rect; +}; + +/** + * Move a rectangle to sit exactly behind a block, taking into account tabs, + * hats, and any other protrusions we invent. + * @param {!SVGElement} rect The rectangle to move directly behind the block. + * @param {!Blockly.BlockSvg} block The block the rectangle should be behind. + * @private + */ +Blockly.Flyout.prototype.moveRectToBlock_ = function(rect, block) { + var blockHW = block.getHeightWidth(); + rect.setAttribute('width', blockHW.width); + rect.setAttribute('height', blockHW.height); + + // For hat blocks we want to shift them down by the hat height + // since the y coordinate is the corner, not the top of the hat. + var hatOffset = + block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; + if (hatOffset) { + block.moveBy(0, hatOffset); + } + + // Blocks with output tabs are shifted a bit. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + var blockXY = block.getRelativeToSurfaceXY(); + rect.setAttribute('y', blockXY.y); + rect.setAttribute('x', + this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); +}; + +/** + * Filter the blocks on the flyout to disable the ones that are above the + * capacity limit. For instance, if the user may only place two more blocks on + * the workspace, an "a + b" block that has two shadow blocks would be disabled. + * @private + */ +Blockly.Flyout.prototype.filterForCapacity_ = function() { + var remainingCapacity = this.targetWorkspace_.remainingCapacity(); + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (this.permanentlyDisabled_.indexOf(block) == -1) { + var allBlocks = block.getDescendants(); + block.setDisabled(allBlocks.length > remainingCapacity); + } + } +}; + +/** + * Reflow blocks and their mats. + */ +Blockly.Flyout.prototype.reflow = function() { + if (this.reflowWrapper_) { + this.workspace_.removeChangeListener(this.reflowWrapper_); + } + this.reflowInternal_(); + if (this.reflowWrapper_) { + this.workspace_.addChangeListener(this.reflowWrapper_); + } +}; + +/** + * @return {boolean} True if this flyout may be scrolled with a scrollbar or by + * dragging. + * @package + */ +Blockly.Flyout.prototype.isScrollable = function() { + return this.scrollbar_ ? this.scrollbar_.isVisible() : false; +}; + +/** + * Copy a block from the flyout to the workspace and position it correctly. + * @param {!Blockly.Block} oldBlock The flyout block to copy. + * @return {!Blockly.Block} The new block in the main workspace. + * @private + */ +Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) { + var targetWorkspace = this.targetWorkspace_; + var svgRootOld = oldBlock.getSvgRoot(); + if (!svgRootOld) { + throw 'oldBlock is not rendered.'; + } + + // Create the new block by cloning the block in the flyout (via XML). + var xml = Blockly.Xml.blockToDom(oldBlock); + // The target workspace would normally resize during domToBlock, which will + // lead to weird jumps. Save it for terminateDrag. + targetWorkspace.setResizesEnabled(false); + + // Using domToBlock instead of domToWorkspace means that the new block will be + // placed at position (0, 0) in main workspace units. + var block = Blockly.Xml.domToBlock(xml, targetWorkspace); + var svgRootNew = block.getSvgRoot(); + if (!svgRootNew) { + throw 'block is not rendered.'; + } + + // The offset in pixels between the main workspace's origin and the upper left + // corner of the injection div. + var mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels(); + + // The offset in pixels between the flyout workspace's origin and the upper + // left corner of the injection div. + var flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels(); + + // The position of the old block in flyout workspace coordinates. + var oldBlockPosWs = oldBlock.getRelativeToSurfaceXY(); + + // The position of the old block in pixels relative to the flyout + // workspace's origin. + var oldBlockPosPixels = oldBlockPosWs.scale(this.workspace_.scale); + + // The position of the old block in pixels relative to the upper left corner + // of the injection div. + var oldBlockOffsetPixels = goog.math.Coordinate.sum(flyoutOffsetPixels, + oldBlockPosPixels); + + // The position of the old block in pixels relative to the origin of the + // main workspace. + var finalOffsetPixels = goog.math.Coordinate.difference(oldBlockOffsetPixels, + mainOffsetPixels); + + // The position of the old block in main workspace coordinates. + var finalOffsetMainWs = finalOffsetPixels.scale(1 / targetWorkspace.scale); + + block.moveBy(finalOffsetMainWs.x, finalOffsetMainWs.y); + return block; +}; diff --git a/core/flyout_button.js b/core/flyout_button.js new file mode 100644 index 0000000..13ce2b1 --- /dev/null +++ b/core/flyout_button.js @@ -0,0 +1,263 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Class for a button in the flyout. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FlyoutButton'); + +goog.require('goog.dom'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a button in the flyout. + * @param {!Blockly.WorkspaceSvg} workspace The workspace in which to place this + * button. + * @param {!Blockly.WorkspaceSvg} targetWorkspace The flyout's target workspace. + * @param {!Element} xml The XML specifying the label/button. + * @param {boolean} isLabel Whether this button should be styled as a label. + * @constructor + */ +Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) { + // Labels behave the same as buttons, but are styled differently. + + /** + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * @type {!Blockly.Workspace} + * @private + */ + this.targetWorkspace_ = targetWorkspace; + + /** + * @type {string} + * @private + */ + this.text_ = xml.getAttribute('text'); + + /** + * @type {!goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); + + /** + * Whether this button should be styled as a label. + * @type {boolean} + * @private + */ + this.isLabel_ = isLabel; + + /** + * Function to call when this button is clicked. + * @type {function(!Blockly.FlyoutButton)} + * @private + */ + this.callback_ = null; + + var callbackKey = xml.getAttribute('callbackKey'); + if (this.isLabel_ && callbackKey) { + console.warn('Labels should not have callbacks. Label text: ' + this.text_); + } else if (!this.isLabel_ && + !(callbackKey && targetWorkspace.getButtonCallback(callbackKey))) { + console.warn('Buttons should have callbacks. Button text: ' + this.text_); + } else { + this.callback_ = targetWorkspace.getButtonCallback(callbackKey); + } + + /** + * If specified, a CSS class to add to this button. + * @type {?string} + * @private + */ + this.cssClass_ = xml.getAttribute('web-class') || null; +}; + +/** + * The margin around the text in the button. + */ +Blockly.FlyoutButton.MARGIN = 5; + +/** + * The width of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.width = 0; + +/** + * The height of the button's rect. + * @type {number} + */ +Blockly.FlyoutButton.prototype.height = 0; + +/** + * Opaque data that can be passed to Blockly.unbindEvent_. + * @type {Array.} + * @private + */ +Blockly.FlyoutButton.prototype.onMouseUpWrapper_ = null; + +/** + * Create the button elements. + * @return {!Element} The button's SVG group. + */ +Blockly.FlyoutButton.prototype.createDom = function() { + var cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'; + if (this.cssClass_) { + cssClass += ' ' + this.cssClass_; + } + + this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': cssClass}, + this.workspace_.getCanvas()); + + if (!this.isLabel_) { + // Shadow rectangle (light source does not mirror in RTL). + var shadow = Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyFlyoutButtonShadow', + 'rx': 4, 'ry': 4, 'x': 1, 'y': 1 + }, + this.svgGroup_); + } + // Background rectangle. + var rect = Blockly.utils.createSvgElement('rect', + { + 'class': this.isLabel_ ? + 'blocklyFlyoutLabelBackground' : 'blocklyFlyoutButtonBackground', + 'rx': 4, 'ry': 4 + }, + this.svgGroup_); + + var svgText = Blockly.utils.createSvgElement('text', + { + 'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText', + 'x': 0, + 'y': 0, + 'text-anchor': 'middle' + }, + this.svgGroup_); + svgText.textContent = this.text_; + + this.width = Blockly.Field.getCachedWidth(svgText); + this.height = 20; // Can't compute it :( + + if (!this.isLabel_) { + this.width += 2 * Blockly.FlyoutButton.MARGIN; + shadow.setAttribute('width', this.width); + shadow.setAttribute('height', this.height); + } + rect.setAttribute('width', this.width); + rect.setAttribute('height', this.height); + + svgText.setAttribute('x', this.width / 2); + svgText.setAttribute('y', this.height - Blockly.FlyoutButton.MARGIN); + + this.updateTransform_(); + + this.mouseUpWrapper_ = Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup', + this, this.onMouseUp_); + return this.svgGroup_; +}; + +/** + * Correctly position the flyout button and make it visible. + */ +Blockly.FlyoutButton.prototype.show = function() { + this.updateTransform_(); + this.svgGroup_.setAttribute('display', 'block'); +}; + +/** + * Update SVG attributes to match internal state. + * @private + */ +Blockly.FlyoutButton.prototype.updateTransform_ = function() { + this.svgGroup_.setAttribute('transform', + 'translate(' + this.position_.x + ',' + this.position_.y + ')'); +}; + +/** + * Move the button to the given x, y coordinates. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + */ +Blockly.FlyoutButton.prototype.moveTo = function(x, y) { + this.position_.x = x; + this.position_.y = y; + this.updateTransform_(); +}; + +/** + * Location of the button. + * @return {!goog.math.Coordinate} x, y coordinates. + * @package + */ +Blockly.FlyoutButton.prototype.getPosition = function() { + return this.position_; +}; + +/** + * Get the button's target workspace. + * @return {!Blockly.WorkspaceSvg} The target workspace of the flyout where this + * button resides. + */ +Blockly.FlyoutButton.prototype.getTargetWorkspace = function() { + return this.targetWorkspace_; +}; + +/** + * Dispose of this button. + */ +Blockly.FlyoutButton.prototype.dispose = function() { + if (this.onMouseUpWrapper_) { + Blockly.unbindEvent_(this.onMouseUpWrapper_); + } + if (this.svgGroup_) { + goog.dom.removeNode(this.svgGroup_); + this.svgGroup_ = null; + } + this.workspace_ = null; + this.targetWorkspace_ = null; +}; + +/** + * Do something when the button is clicked. + * @param {!Event} e Mouse up event. + * @private + */ +Blockly.FlyoutButton.prototype.onMouseUp_ = function(e) { + var gesture = this.targetWorkspace_.getGesture(e); + if (gesture) { + gesture.cancel(); + } + + // Call the callback registered to this button. + if (this.callback_) { + this.callback_(this); + } +}; diff --git a/core/flyout_dragger.js b/core/flyout_dragger.js new file mode 100644 index 0000000..c3909ea --- /dev/null +++ b/core/flyout_dragger.js @@ -0,0 +1,83 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a flyout visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.FlyoutDragger'); + +goog.require('Blockly.WorkspaceDragger'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a flyout dragger. It moves a flyout workspace around when it is + * being dragged by a mouse or touch. + * Note that the workspace itself manages whether or not it has a drag surface + * and how to do translations based on that. This simply passes the right + * commands based on events. + * @param {!Blockly.Flyout} flyout The flyout to drag. + * @constructor + */ +Blockly.FlyoutDragger = function(flyout) { + Blockly.FlyoutDragger.superClass_.constructor.call(this, + flyout.getWorkspace()); + + /** + * The scrollbar to update to move the flyout. + * Unlike the main workspace, the flyout has only one scrollbar, in either the + * horizontal or the vertical direction. + * @type {!Blockly.Scrollbar} + * @private + */ + this.scrollbar_ = flyout.scrollbar_; + + /** + * Whether the flyout scrolls horizontally. If false, the flyout scrolls + * vertically. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = flyout.horizontalLayout_; +}; +goog.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger); + +/** + * Move the appropriate scrollbar to drag the flyout. + * Since flyouts only scroll in one direction at a time, this will discard one + * of the calculated values. + * x and y are in pixels. + * @param {number} x The new x position to move the scrollbar to. + * @param {number} y The new y position to move the scrollbar to. + * @private + */ +Blockly.FlyoutDragger.prototype.updateScroll_ = function(x, y) { + // Move the scrollbar and the flyout will scroll automatically. + if (this.horizontalLayout_) { + this.scrollbar_.set(x); + } else { + this.scrollbar_.set(y); + } +}; diff --git a/core/flyout_horizontal.js b/core/flyout_horizontal.js new file mode 100644 index 0000000..7268e9b --- /dev/null +++ b/core/flyout_horizontal.js @@ -0,0 +1,381 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Horizontal flyout tray containing blocks which may be created. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.HorizontalFlyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.Flyout'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @extends {Blockly.Flyout} + * @constructor + */ +Blockly.HorizontalFlyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions); + /** + * Flyout should be laid out horizontally. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = true; +}; +goog.inherits(Blockly.HorizontalFlyout, Blockly.Flyout); + +/** + * Return an object with all the metrics required to size scrollbars for the + * flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the contents, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .absoluteTop: Top-edge of view. + * .viewLeft: Offset of the left edge of visible rectangle from parent, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteLeft: Left-edge of view. + * @return {Object} Contains size and position metrics of the flyout. + * @private + */ +Blockly.HorizontalFlyout.prototype.getMetrics_ = function() { + if (!this.isVisible()) { + // Flyout is hidden. + return null; + } + + try { + var optionBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + var optionBox = {height: 0, y: 0, width: 0, x: 0}; + } + + var absoluteTop = this.SCROLLBAR_PADDING; + var absoluteLeft = this.SCROLLBAR_PADDING; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + absoluteTop = 0; + } + var viewHeight = this.height_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + viewHeight -= this.SCROLLBAR_PADDING; + } + var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; + + var metrics = { + viewHeight: viewHeight, + viewWidth: viewWidth, + contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, + contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, + viewTop: -this.workspace_.scrollY, + viewLeft: -this.workspace_.scrollX, + contentTop: optionBox.y, + contentLeft: optionBox.x, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft + }; + return metrics; +}; + +/** + * Sets the translation of the flyout to match the scrollbars. + * @param {!Object} xyRatio Contains a y property which is a float + * between 0 and 1 specifying the degree of scrolling and a + * similar x property. + * @private + */ +Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) { + var metrics = this.getMetrics_(); + // This is a fix to an apparent race condition. + if (!metrics) { + return; + } + + if (goog.isNumber(xyRatio.x)) { + this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x; + } + + this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, + this.workspace_.scrollY + metrics.absoluteTop); +}; + +/** + * Move the flyout to the edge of the workspace. + */ +Blockly.HorizontalFlyout.prototype.position = function() { + if (!this.isVisible()) { + return; + } + var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return; + } + // Record the width for Blockly.Flyout.getMetrics_. + this.width_ = targetWorkspaceMetrics.viewWidth; + + var edgeWidth = targetWorkspaceMetrics.viewWidth - 2 * this.CORNER_RADIUS; + var edgeHeight = this.height_ - this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + var x = targetWorkspaceMetrics.absoluteLeft; + var y = targetWorkspaceMetrics.absoluteTop; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + y += (targetWorkspaceMetrics.viewHeight - this.height_); + } + this.positionAt_(this.width_, this.height_, x, y); +}; + +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width, + height) { + var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; + // Start at top left. + var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; + + if (atTop) { + // Top. + path.push('h', width + 2 * this.CORNER_RADIUS); + // Right. + path.push('v', height); + // Bottom. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('h', -1 * width); + // Left. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + -this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('z'); + } else { + // Top. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, -this.CORNER_RADIUS); + path.push('h', width); + // Right. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, + this.CORNER_RADIUS, this.CORNER_RADIUS); + path.push('v', height); + // Bottom. + path.push('h', -width - 2 * this.CORNER_RADIUS); + // Left. + path.push('z'); + } + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Scroll the flyout to the top. + */ +Blockly.HorizontalFlyout.prototype.scrollToStart = function() { + this.scrollbar_.set(this.RTL ? Infinity : 0); +}; + +/** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @private + */ +Blockly.HorizontalFlyout.prototype.wheel_ = function(e) { + var delta = e.deltaX; + + if (delta) { + // Firefox's mouse wheel deltas are a tenth that of Chrome/Safari. + // DeltaMode is 1 for a mouse wheel, but not for a trackpad scroll event + if (goog.userAgent.GECKO && (e.deltaMode === 1)) { + delta *= 10; + } + // TODO: #1093 + var metrics = this.getMetrics_(); + var pos = metrics.viewLeft + delta; + var limit = metrics.contentWidth - metrics.viewWidth; + pos = Math.min(pos, limit); + pos = Math.max(pos, 0); + this.scrollbar_.set(pos); + // When the flyout moves from a wheel event, hide WidgetDiv. + Blockly.WidgetDiv.hide(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array.} contents The blocks and buttons to lay out. + * @param {!Array.} gaps The visible gaps between blocks. + * @private + */ +Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) { + this.workspace_.scale = this.targetWorkspace_.scale; + var margin = this.MARGIN; + var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; + var cursorY = margin; + if (this.RTL) { + contents = contents.reverse(); + } + + for (var i = 0, item; item = contents[i]; i++) { + if (item.type == 'block') { + var block = item.block; + var allBlocks = block.getDescendants(); + for (var j = 0, child; child = allBlocks[j]; j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such a + // block. + child.isInFlyout = true; + } + block.render(); + var root = block.getSvgRoot(); + var blockHW = block.getHeightWidth(); + + // Figure out where to place the block. + var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; + if (this.RTL) { + var moveX = cursorX + blockHW.width; + } else { + var moveX = cursorX + tab; + } + block.moveBy(moveX, cursorY); + + var rect = this.createRect_(block, moveX, cursorY, blockHW, i); + cursorX += (blockHW.width + gaps[i]); + + this.addBlockListeners_(root, block, rect); + } else if (item.type == 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorX += (item.button.width + gaps[i]); + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} true if the drag is toward the workspace. + * @package + */ +Blockly.HorizontalFlyout.prototype.isDragTowardWorkspace = function( + currentDragDeltaXY) { + var dx = currentDragDeltaXY.x; + var dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var range = this.dragAngleRange_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + // Horizontal at top. + if (dragDirection < 90 + range && dragDirection > 90 - range) { + return true; + } + } else { + // Horizontal at bottom. + if (dragDirection > -90 - range && dragDirection < -90 + range) { + return true; + } + } + return false; +}; + +/** + * Return the deletion rectangle for this flyout in viewport coordinates. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.HorizontalFlyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout + // area are still deleted. Must be larger than the largest screen size, + // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). + var BIG_NUM = 1000000000; + var y = flyoutRect.top; + var height = flyoutRect.height; + + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { + return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2, + BIG_NUM + height); + } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { + return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2, + BIG_NUM + height); + } + // TODO: Else throw error (should never happen). +}; + +/** + * Compute height of flyout. Position mat under each block. + * For RTL: Lay out the blocks right-aligned. + * @private + */ +Blockly.HorizontalFlyout.prototype.reflowInternal_ = function() { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutHeight = 0; + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); + } + flyoutHeight += this.MARGIN * 1.5; + flyoutHeight *= this.workspace_.scale; + flyoutHeight += Blockly.Scrollbar.scrollbarThickness; + + if (this.height_ != flyoutHeight) { + for (var i = 0, block; block = blocks[i]; i++) { + if (block.flyoutRect_) { + this.moveRectToBlock_(block.flyoutRect_, block); + } + } + // Record the height for .getMetrics_ and .position. + this.height_ = flyoutHeight; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; diff --git a/core/flyout_vertical.js b/core/flyout_vertical.js new file mode 100644 index 0000000..5559c43 --- /dev/null +++ b/core/flyout_vertical.js @@ -0,0 +1,379 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Layout code for a vertical variant of the flyout. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.VerticalFlyout'); + +goog.require('Blockly.Block'); +goog.require('Blockly.Events'); +goog.require('Blockly.Flyout'); +goog.require('Blockly.FlyoutButton'); +goog.require('Blockly.utils'); +goog.require('Blockly.WorkspaceSvg'); +goog.require('goog.dom'); +goog.require('goog.events'); +goog.require('goog.math.Rect'); +goog.require('goog.userAgent'); + + +/** + * Class for a flyout. + * @param {!Object} workspaceOptions Dictionary of options for the workspace. + * @extends {Blockly.Flyout} + * @constructor + */ +Blockly.VerticalFlyout = function(workspaceOptions) { + workspaceOptions.getMetrics = this.getMetrics_.bind(this); + workspaceOptions.setMetrics = this.setMetrics_.bind(this); + + Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions); + /** + * Flyout should be laid out vertically. + * @type {boolean} + * @private + */ + this.horizontalLayout_ = false; +}; +goog.inherits(Blockly.VerticalFlyout, Blockly.Flyout); + +/** + * Return an object with all the metrics required to size scrollbars for the + * flyout. The following properties are computed: + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the contents, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .absoluteTop: Top-edge of view. + * .viewLeft: Offset of the left edge of visible rectangle from parent, + * .contentLeft: Offset of the left-most content from the x=0 coordinate, + * .absoluteLeft: Left-edge of view. + * @return {Object} Contains size and position metrics of the flyout. + * @private + */ +Blockly.VerticalFlyout.prototype.getMetrics_ = function() { + if (!this.isVisible()) { + // Flyout is hidden. + return null; + } + + try { + var optionBox = this.workspace_.getCanvas().getBBox(); + } catch (e) { + // Firefox has trouble with hidden elements (Bug 528969). + var optionBox = {height: 0, y: 0, width: 0, x: 0}; + } + + // Padding for the end of the scrollbar. + var absoluteTop = this.SCROLLBAR_PADDING; + var absoluteLeft = 0; + + var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; + var viewWidth = this.width_; + if (!this.RTL) { + viewWidth -= this.SCROLLBAR_PADDING; + } + + var metrics = { + viewHeight: viewHeight, + viewWidth: viewWidth, + contentHeight: optionBox.height * this.workspace_.scale + 2 * this.MARGIN, + contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN, + viewTop: -this.workspace_.scrollY + optionBox.y, + viewLeft: -this.workspace_.scrollX, + contentTop: optionBox.y, + contentLeft: optionBox.x, + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft + }; + return metrics; +}; + +/** + * Sets the translation of the flyout to match the scrollbars. + * @param {!Object} xyRatio Contains a y property which is a float + * between 0 and 1 specifying the degree of scrolling and a + * similar x property. + * @private + */ +Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) { + var metrics = this.getMetrics_(); + // This is a fix to an apparent race condition. + if (!metrics) { + return; + } + if (goog.isNumber(xyRatio.y)) { + this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y; + } + this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, + this.workspace_.scrollY + metrics.absoluteTop); +}; + +/** + * Move the flyout to the edge of the workspace. + */ +Blockly.VerticalFlyout.prototype.position = function() { + if (!this.isVisible()) { + return; + } + var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); + if (!targetWorkspaceMetrics) { + // Hidden components will return null. + return; + } + // Record the height for Blockly.Flyout.getMetrics_ + this.height_ = targetWorkspaceMetrics.viewHeight; + + var edgeWidth = this.width_ - this.CORNER_RADIUS; + var edgeHeight = targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS; + this.setBackgroundPath_(edgeWidth, edgeHeight); + + var y = targetWorkspaceMetrics.absoluteTop; + var x = targetWorkspaceMetrics.absoluteLeft; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { + x += (targetWorkspaceMetrics.viewWidth - this.width_); + } + this.positionAt_(this.width_, this.height_, x, y); +}; + +/** + * Create and set the path for the visible boundaries of the flyout. + * @param {number} width The width of the flyout, not including the + * rounded corners. + * @param {number} height The height of the flyout, not including + * rounded corners. + * @private + */ +Blockly.VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) { + var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT; + var totalWidth = width + this.CORNER_RADIUS; + + // Decide whether to start on the left or right. + var path = ['M ' + (atRight ? totalWidth : 0) + ',0']; + // Top. + path.push('h', atRight ? -width : width); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Side closest to workspace. + path.push('v', Math.max(0, height)); + // Rounded corner. + path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, + atRight ? 0 : 1, + atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, + this.CORNER_RADIUS); + // Bottom. + path.push('h', atRight ? width : -width); + path.push('z'); + this.svgBackground_.setAttribute('d', path.join(' ')); +}; + +/** + * Scroll the flyout to the top. + */ +Blockly.VerticalFlyout.prototype.scrollToStart = function() { + this.scrollbar_.set(0); +}; + +/** + * Scroll the flyout. + * @param {!Event} e Mouse wheel scroll event. + * @private + */ +Blockly.VerticalFlyout.prototype.wheel_ = function(e) { + var delta = e.deltaY; + + if (delta) { + if (goog.userAgent.GECKO) { + // Firefox's deltas are a tenth that of Chrome/Safari. + delta *= 10; + } + var metrics = this.getMetrics_(); + var pos = (metrics.viewTop - metrics.contentTop) + delta; + var limit = metrics.contentHeight - metrics.viewHeight; + pos = Math.min(pos, limit); + pos = Math.max(pos, 0); + this.scrollbar_.set(pos); + // When the flyout moves from a wheel event, hide WidgetDiv. + Blockly.WidgetDiv.hide(); + } + + // Don't scroll the page. + e.preventDefault(); + // Don't propagate mousewheel event (zooming). + e.stopPropagation(); +}; + +/** + * Lay out the blocks in the flyout. + * @param {!Array.} contents The blocks and buttons to lay out. + * @param {!Array.} gaps The visible gaps between blocks. + * @private + */ +Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) { + this.workspace_.scale = this.targetWorkspace_.scale; + var margin = this.MARGIN; + var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; + var cursorY = margin; + + for (var i = 0, item; item = contents[i]; i++) { + if (item.type == 'block') { + var block = item.block; + var allBlocks = block.getDescendants(); + for (var j = 0, child; child = allBlocks[j]; j++) { + // Mark blocks as being inside a flyout. This is used to detect and + // prevent the closure of the flyout if the user right-clicks on such a + // block. + child.isInFlyout = true; + } + block.render(); + var root = block.getSvgRoot(); + var blockHW = block.getHeightWidth(); + block.moveBy(cursorX, cursorY); + + var rect = this.createRect_(block, + this.RTL ? cursorX - blockHW.width : cursorX, cursorY, blockHW, i); + + this.addBlockListeners_(root, block, rect); + + cursorY += blockHW.height + gaps[i]; + } else if (item.type == 'button') { + this.initFlyoutButton_(item.button, cursorX, cursorY); + cursorY += item.button.height + gaps[i]; + } + } +}; + +/** + * Determine if a drag delta is toward the workspace, based on the position + * and orientation of the flyout. This is used in determineDragIntention_ to + * determine if a new block should be created or if the flyout should scroll. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at mouse down, in pixel units. + * @return {boolean} true if the drag is toward the workspace. + * @package + */ +Blockly.VerticalFlyout.prototype.isDragTowardWorkspace = function( + currentDragDeltaXY) { + var dx = currentDragDeltaXY.x; + var dy = currentDragDeltaXY.y; + // Direction goes from -180 to 180, with 0 toward the right and 90 on top. + var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; + + var range = this.dragAngleRange_; + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + // Vertical at left. + if (dragDirection < range && dragDirection > -range) { + return true; + } + } else { + // Vertical at right. + if (dragDirection < -180 + range || dragDirection > 180 - range) { + return true; + } + } + return false; +}; + +/** + * Return the deletion rectangle for this flyout in viewport coordinates. + * @return {goog.math.Rect} Rectangle in which to delete. + */ +Blockly.VerticalFlyout.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + + var flyoutRect = this.svgGroup_.getBoundingClientRect(); + // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout + // area are still deleted. Must be larger than the largest screen size, + // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). + var BIG_NUM = 1000000000; + var x = flyoutRect.left; + var width = flyoutRect.width; + + if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { + return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width, + BIG_NUM * 2); + } else { // Right + return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2); + } +}; + +/** + * Compute width of flyout. Position mat under each block. + * For RTL: Lay out the blocks and buttons to be right-aligned. + * @private + */ +Blockly.VerticalFlyout.prototype.reflowInternal_ = function() { + this.workspace_.scale = this.targetWorkspace_.scale; + var flyoutWidth = 0; + var blocks = this.workspace_.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + var width = block.getHeightWidth().width; + if (block.outputConnection) { + width -= Blockly.BlockSvg.TAB_WIDTH; + } + flyoutWidth = Math.max(flyoutWidth, width); + } + for (var i = 0, button; button = this.buttons_[i]; i++) { + flyoutWidth = Math.max(flyoutWidth, button.width); + } + flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH; + flyoutWidth *= this.workspace_.scale; + flyoutWidth += Blockly.Scrollbar.scrollbarThickness; + + if (this.width_ != flyoutWidth) { + for (var i = 0, block; block = blocks[i]; i++) { + if (this.RTL) { + // With the flyoutWidth known, right-align the blocks. + var oldX = block.getRelativeToSurfaceXY().x; + var newX = flyoutWidth / this.workspace_.scale - this.MARGIN - + Blockly.BlockSvg.TAB_WIDTH; + block.moveBy(newX - oldX, 0); + } + if (block.flyoutRect_) { + this.moveRectToBlock_(block.flyoutRect_, block); + } + } + if (this.RTL) { + // With the flyoutWidth known, right-align the buttons. + for (var i = 0, button; button = this.buttons_[i]; i++) { + var y = button.getPosition().y; + var x = flyoutWidth / this.workspace_.scale - button.width - this.MARGIN - + Blockly.BlockSvg.TAB_WIDTH; + button.moveTo(x, y); + } + } + // Record the width for .getMetrics_ and .position. + this.width_ = flyoutWidth; + // Call this since it is possible the trash and zoom buttons need + // to move. e.g. on a bottom positioned flyout when zoom is clicked. + this.targetWorkspace_.resize(); + } +}; diff --git a/core/generator.js b/core/generator.js index 3cab1cc..eadde65 100644 --- a/core/generator.js +++ b/core/generator.js @@ -70,6 +70,19 @@ Blockly.Generator.prototype.STATEMENT_PREFIX = null; */ Blockly.Generator.prototype.INDENT = ' '; +/** + * Maximum length for a comment before wrapping. Does not account for + * indenting level. + * @type {number} + */ +Blockly.Generator.prototype.COMMENT_WRAP = 60; + +/** + * List of outer-inner pairings that do NOT require parentheses. + * @type {!Array.>} + */ +Blockly.Generator.prototype.ORDER_OVERRIDES = []; + /** * Generate code for all blocks in the workspace to the specified language. * @param {Blockly.Workspace} workspace Workspace to generate code from. @@ -92,7 +105,7 @@ Blockly.Generator.prototype.workspaceToCode = function(workspace) { line = line[0]; } if (line) { - if (block.outputConnection && this.scrubNakedValue) { + if (block.outputConnection) { // This block is a naked value. Ask the language's code generator if // it wants to append a semicolon, or something. line = this.scrubNakedValue(line); @@ -119,7 +132,7 @@ Blockly.Generator.prototype.workspaceToCode = function(workspace) { * @return {string} The prefixed lines of code. */ Blockly.Generator.prototype.prefixLines = function(text, prefix) { - return prefix + text.replace(/\n(.)/g, '\n' + prefix + '$1'); + return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix); }; /** @@ -130,8 +143,8 @@ Blockly.Generator.prototype.prefixLines = function(text, prefix) { Blockly.Generator.prototype.allNestedComments = function(block) { var comments = []; var blocks = block.getDescendants(); - for (var x = 0; x < blocks.length; x++) { - var comment = blocks[x].getCommentText(); + for (var i = 0; i < blocks.length; i++) { + var comment = blocks[i].getCommentText(); if (comment) { comments.push(comment); } @@ -174,8 +187,9 @@ Blockly.Generator.prototype.blockToCode = function(block) { 'Expecting string from statement block "%s".', block.type); return [this.scrub_(block, code[0]), code[1]]; } else if (goog.isString(code)) { + var id = block.id.replace(/\$/g, '$$$$'); // Issue 251. if (this.STATEMENT_PREFIX) { - code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') + + code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + id + '\'') + code; } return this.scrub_(block, code); @@ -191,13 +205,13 @@ Blockly.Generator.prototype.blockToCode = function(block) { * Generate code representing the specified value input. * @param {!Blockly.Block} block The block containing the input. * @param {string} name The name of the input. - * @param {number} order The maximum binding strength (minimum order value) + * @param {number} outerOrder The maximum binding strength (minimum order value) * of any operators adjacent to "block". * @return {string} Generated code or '' if no blocks are connected or the * specified input does not exist. */ -Blockly.Generator.prototype.valueToCode = function(block, name, order) { - if (isNaN(order)) { +Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) { + if (isNaN(outerOrder)) { goog.asserts.fail('Expecting valid order from block "%s".', block.type); } var targetBlock = block.getInputTargetBlock(name); @@ -219,21 +233,41 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) { goog.asserts.fail('Expecting valid order from value block "%s".', targetBlock.type); } - if (code && order <= innerOrder) { - if (order == innerOrder && (order == 0 || order == 99)) { + if (!code) { + return ''; + } + + // Add parentheses if needed. + var parensNeeded = false; + var outerOrderClass = Math.floor(outerOrder); + var innerOrderClass = Math.floor(innerOrder); + if (outerOrderClass <= innerOrderClass) { + if (outerOrderClass == innerOrderClass && + (outerOrderClass == 0 || outerOrderClass == 99)) { // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs. // 0 is the atomic order, 99 is the none order. No parentheses needed. // In all known languages multiple such code blocks are not order // sensitive. In fact in Python ('a' 'b') 'c' would fail. } else { - // The operators outside this code are stonger than the operators + // The operators outside this code are stronger than the operators // inside this code. To prevent the code from being pulled apart, // wrap the code in parentheses. - // Technically, this should be handled on a language-by-language basis. - // However all known (sane) languages use parentheses for grouping. - code = '(' + code + ')'; + parensNeeded = true; + // Check for special exceptions. + for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) { + if (this.ORDER_OVERRIDES[i][0] == outerOrder && + this.ORDER_OVERRIDES[i][1] == innerOrder) { + parensNeeded = false; + break; + } + } } } + if (parensNeeded) { + // Technically, this should be handled on a language-by-language basis. + // However all known (sane) languages use parentheses for grouping. + code = '(' + code + ')'; + } return code; }; @@ -264,6 +298,7 @@ Blockly.Generator.prototype.statementToCode = function(block, name) { * @return {string} Loop contents, with infinite loop trap added. */ Blockly.Generator.prototype.addLoopTrap = function(branch, id) { + id = id.replace(/\$/g, '$$$$'); // Issue 251. if (this.INFINITE_LOOP_TRAP) { branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch; } @@ -319,18 +354,81 @@ Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}'; */ Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) { if (!this.definitions_[desiredName]) { - var functionName = - this.variableDB_.getDistinctName(desiredName, this.NAME_TYPE); + var functionName = this.variableDB_.getDistinctName(desiredName, + Blockly.Procedures.NAME_TYPE); this.functionNames_[desiredName] = functionName; var codeText = code.join('\n').replace( this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName); // Change all ' ' indents into the desired indent. + // To avoid an infinite loop of replacements, change all indents to '\0' + // character first, then replace them all with the indent. + // We are assuming that no provided functions contain a literal null char. var oldCodeText; while (oldCodeText != codeText) { oldCodeText = codeText; - codeText = codeText.replace(/^(( )*) /gm, '$1' + this.INDENT); + codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0'); } + codeText = codeText.replace(/\0/g, this.INDENT); this.definitions_[desiredName] = codeText; } return this.functionNames_[desiredName]; }; + +/** + * Hook for code to run before code generation starts. + * Subclasses may override this, e.g. to initialise the database of variable + * names. + * @param {!Blockly.Workspace} workspace Workspace to generate code from. + */ +Blockly.Generator.prototype.init = function( + /* eslint-disable no-unused-vars */ workspace + /* eslint-enable no-unused-vars */) { + // Optionally override +}; + +/** + * Common tasks for generating code from blocks. This is called from + * blockToCode and is called on every block, not just top level blocks. + * Subclasses may override this, e.g. to generate code for statements following + * the block, or to handle comments for the specified block and any connected + * value blocks. + * @param {!Blockly.Block} block The current block. + * @param {string} code The JavaScript code created for this block. + * @return {string} JavaScript code with comments and subsequent blocks added. + * @private + */ +Blockly.Generator.prototype.scrub_ = function( + /* eslint-disable no-unused-vars */ block, code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; + +/** + * Hook for code to run at end of code generation. + * Subclasses may override this, e.g. to prepend the generated code with the + * variable definitions. + * @param {string} code Generated code. + * @return {string} Completed code. + */ +Blockly.Generator.prototype.finish = function( + /* eslint-disable no-unused-vars */ code + /* eslint-enable no-unused-vars */) { + // Optionally override + return code; +}; + +/** + * Naked values are top-level blocks with outputs that aren't plugged into + * anything. + * Subclasses may override this, e.g. if their language does not allow + * naked values. + * @param {string} line Line of generated code. + * @return {string} Legal line of code. + */ +Blockly.Generator.prototype.scrubNakedValue = function( + /* eslint-disable no-unused-vars */ line + /* eslint-enable no-unused-vars */) { + // Optionally override + return line; +}; diff --git a/core/gesture.js b/core/gesture.js new file mode 100644 index 0000000..6c222dd --- /dev/null +++ b/core/gesture.js @@ -0,0 +1,918 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 The class representing an in-progress gesture, usually a drag + * or a tap. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Gesture'); + +goog.require('Blockly.BlockDragger'); +goog.require('Blockly.BubbleDragger'); +goog.require('Blockly.constants'); +goog.require('Blockly.FlyoutDragger'); +goog.require('Blockly.Tooltip'); +goog.require('Blockly.Touch'); +goog.require('Blockly.WorkspaceDragger'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/* + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ +// TODO: Consider touchcancel/pointercancel. + +/** + * Class for one gesture. + * @param {!Event} e The event that kicked off this gesture. + * @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. + * @constructor + */ +Blockly.Gesture = function(e, creatorWorkspace) { + + /** + * The position of the mouse when the gesture started. Units are css pixels, + * with (0, 0) at the top left of the browser window (mouseEvent clientX/Y). + * @type {goog.math.Coordinate} + */ + this.mouseDownXY_ = null; + + /** + * How far the mouse has moved during this drag, in pixel units. + * (0, 0) is at this.mouseDownXY_. + * @type {goog.math.Coordinate} + * private + */ + this.currentDragDeltaXY_ = 0; + + /** + * The bubble that the gesture started on, or null if it did not start on a + * bubble. + * @type {Blockly.Bubble} + * @private + */ + this.startBubble_ = null; + + /** + * The field that the gesture started on, or null if it did not start on a + * field. + * @type {Blockly.Field} + * @private + */ + this.startField_ = null; + + /** + * The block that the gesture started on, or null if it did not start on a + * block. + * @type {Blockly.BlockSvg} + * @private + */ + this.startBlock_ = null; + + /** + * The block that this gesture targets. If the gesture started on a + * shadow block, this is the first non-shadow parent of the block. If the + * gesture started in the flyout, this is the root block of the block group + * that was clicked or dragged. + * @type {Blockly.BlockSvg} + * @private + */ + this.targetBlock_ = null; + + /** + * The workspace that the gesture started on. There may be multiple + * workspaces on a page; this is more accurate than using + * Blockly.getMainWorkspace(). + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.startWorkspace_ = null; + + /** + * The workspace that created this gesture. This workspace keeps a reference + * to the gesture, which will need to be cleared at deletion. + * This may be different from the start workspace. For instance, a flyout is + * a workspace, but its parent workspace manages gestures for it. + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.creatorWorkspace_ = creatorWorkspace; + + /** + * Whether the pointer has at any point moved out of the drag radius. + * A gesture that exceeds the drag radius is a drag even if it ends exactly at + * its start point. + * @type {boolean} + * @private + */ + this.hasExceededDragRadius_ = false; + + /** + * Whether the workspace is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingWorkspace_ = false; + + /** + * Whether the block is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBlock_ = false; + + /** + * Whether the bubble is currently being dragged. + * @type {boolean} + * @private + */ + this.isDraggingBubble_ = false; + + /** + * The event that most recently updated this gesture. + * @type {!Event} + * @private + */ + this.mostRecentEvent_ = e; + + /** + * A handle to use to unbind a mouse move listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onMoveWrapper_ = null; + + /** + * A handle to use to unbind a mouse up listener at the end of a drag. + * Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onUpWrapper_ = null; + + /** + * The object tracking a bubble drag, or null if none is in progress. + * @type {Blockly.BubbleDragger} + * @private + */ + this.bubbleDragger_ = null; + + /** + * The object tracking a block drag, or null if none is in progress. + * @type {Blockly.BlockDragger} + * @private + */ + this.blockDragger_ = null; + + /** + * The object tracking a workspace or flyout workspace drag, or null if none + * is in progress. + * @type {Blockly.WorkspaceDragger} + * @private + */ + this.workspaceDragger_ = null; + + /** + * The flyout a gesture started in, if any. + * @type {Blockly.Flyout} + * @private + */ + this.flyout_ = null; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.calledUpdateIsDragging_ = false; + + /** + * Boolean for sanity-checking that some code is only called once. + * @type {boolean} + * @private + */ + this.hasStarted_ = false; + + /** + * Boolean used internally to break a cycle in disposal. + * @type {boolean} + * @private + */ + this.isEnding_ = false; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.Gesture.prototype.dispose = function() { + Blockly.Touch.clearTouchIdentifier(); + Blockly.Tooltip.unblock(); + // Clear the owner's reference to this gesture. + this.creatorWorkspace_.clearGesture(); + + if (this.onMoveWrapper_) { + Blockly.unbindEvent_(this.onMoveWrapper_); + } + if (this.onUpWrapper_) { + Blockly.unbindEvent_(this.onUpWrapper_); + } + + + this.startField_ = null; + this.startBlock_ = null; + this.targetBlock_ = null; + this.startWorkspace_ = null; + this.flyout_ = null; + + if (this.blockDragger_) { + this.blockDragger_.dispose(); + this.blockDragger_ = null; + } + if (this.workspaceDragger_) { + this.workspaceDragger_.dispose(); + this.workspaceDragger_ = null; + } + if (this.bubbleDragger_) { + this.bubbleDragger_.dispose(); + this.bubbleDragger_ = null; + } +}; + +/** + * Update internal state based on an event. + * @param {!Event} e The most recent mouse or touch event. + * @private + */ +Blockly.Gesture.prototype.updateFromEvent_ = function(e) { + var currentXY = new goog.math.Coordinate(e.clientX, e.clientY); + var changed = this.updateDragDelta_(currentXY); + // Exceeded the drag radius for the first time. + if (changed) { + this.updateIsDragging_(); + Blockly.longStop_(); + } + this.mostRecentEvent_ = e; +}; + +/** + * DO MATH to set currentDragDeltaXY_ based on the most recent mouse position. + * @param {!goog.math.Coordinate} currentXY The most recent mouse/pointer + * position, in pixel units, with (0, 0) at the window's top left corner. + * @return {boolean} True if the drag just exceeded the drag radius for the + * first time. + * @private + */ +Blockly.Gesture.prototype.updateDragDelta_ = function(currentXY) { + this.currentDragDeltaXY_ = goog.math.Coordinate.difference(currentXY, + this.mouseDownXY_); + + if (!this.hasExceededDragRadius_) { + var currentDragDelta = goog.math.Coordinate.magnitude( + this.currentDragDeltaXY_); + + // The flyout has a different drag radius from the rest of Blockly. + var limitRadius = this.flyout_ ? Blockly.FLYOUT_DRAG_RADIUS : + Blockly.DRAG_RADIUS; + + this.hasExceededDragRadius_ = currentDragDelta > limitRadius; + return this.hasExceededDragRadius_; + } + return false; +}; + +/** + * Update this gesture to record whether a block is being dragged from the + * flyout. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a block should be dragged from the flyout this function creates the new + * block on the main workspace and updates targetBlock_ and startWorkspace_. + * @return {boolean} True if a block is being dragged from the flyout. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() { + // Disabled blocks may not be dragged from the flyout. + if (this.targetBlock_.disabled) { + return false; + } + if (!this.flyout_.isScrollable() || + this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) { + this.startWorkspace_ = this.flyout_.targetWorkspace_; + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + // Start the event group now, so that the same event group is used for block + // creation and block dragging. + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + // The start block is no longer relevant, because this is a drag. + this.startBlock_ = null; + this.targetBlock_ = this.flyout_.createBlock(this.targetBlock_); + this.targetBlock_.select(); + return true; + } + return false; +}; + +/** + * Update this gesture to record whether a bubble is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a bubble should be dragged this function creates the necessary + * BubbleDragger and starts the drag. + * @return {boolean} true if a bubble is being dragged. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingBubble_ = function() { + if (!this.startBubble_) { + return false; + } + + this.isDraggingBubble_ = true; + this.startDraggingBubble_(); + return true; +}; + +/** + * Update this gesture to record whether a block is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a block should be dragged, either from the flyout or in the workspace, + * this function creates the necessary BlockDragger and starts the drag. + * @return {boolean} true if a block is being dragged. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() { + if (!this.targetBlock_) { + return false; + } + + if (this.flyout_) { + this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_(); + } else if (this.targetBlock_.isMovable()) { + this.isDraggingBlock_ = true; + } + + if (this.isDraggingBlock_) { + this.startDraggingBlock_(); + return true; + } + return false; +}; + +/** + * Update this gesture to record whether a workspace is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * If a workspace is being dragged this function creates the necessary + * WorkspaceDragger or FlyoutDragger and starts the drag. + * @private + */ +Blockly.Gesture.prototype.updateIsDraggingWorkspace_ = function() { + var wsMovable = this.flyout_ ? this.flyout_.isScrollable() : + this.startWorkspace_ && this.startWorkspace_.isDraggable(); + + if (!wsMovable) { + return; + } + + if (this.flyout_) { + this.workspaceDragger_ = new Blockly.FlyoutDragger(this.flyout_); + } else { + this.workspaceDragger_ = new Blockly.WorkspaceDragger(this.startWorkspace_); + } + + this.isDraggingWorkspace_ = true; + this.workspaceDragger_.startDrag(); +}; + +/** + * Update this gesture to record whether anything is being dragged. + * This function should be called on a mouse/touch move event the first time the + * drag radius is exceeded. It should be called no more than once per gesture. + * @private + */ +Blockly.Gesture.prototype.updateIsDragging_ = function() { + // Sanity check. + goog.asserts.assert(!this.calledUpdateIsDragging_, + 'updateIsDragging_ should only be called once per gesture.'); + this.calledUpdateIsDragging_ = true; + + // First check if it was a bubble drag. Bubbles always sit on top of blocks. + if (this.updateIsDraggingBubble_()) { + return; + } + // Then check if it was a block drag. + if (this.updateIsDraggingBlock_()) { + return; + } + // Then check if it's a workspace drag. + this.updateIsDraggingWorkspace_(); +}; + +/** + * Create a block dragger and start dragging the selected block. + * @private + */ +Blockly.Gesture.prototype.startDraggingBlock_ = function() { + this.blockDragger_ = new Blockly.BlockDragger(this.targetBlock_, + this.startWorkspace_); + this.blockDragger_.startBlockDrag(this.currentDragDeltaXY_); + this.blockDragger_.dragBlock(this.mostRecentEvent_, + this.currentDragDeltaXY_); +}; + +/** + * Create a bubble dragger and start dragging the selected bubble. + * TODO (fenichel): Possibly combine this and startDraggingBlock_. + * @private + */ +Blockly.Gesture.prototype.startDraggingBubble_ = function() { + this.bubbleDragger_ = new Blockly.BubbleDragger(this.startBubble_, + this.startWorkspace_); + this.bubbleDragger_.startBubbleDrag(); + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); +}; + +/** + * Start a gesture: update the workspace to indicate that a gesture is in + * progress and bind mousemove and mouseup handlers. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.Gesture.prototype.doStart = function(e) { + if (Blockly.utils.isTargetInput(e)) { + this.cancel(); + return; + } + this.hasStarted_ = true; + + Blockly.BlockSvg.disconnectUiStop_(); + this.startWorkspace_.updateScreenCalculationsIfScrolled(); + if (this.startWorkspace_.isMutator) { + // Mutator's coordinate system could be out of date because the bubble was + // dragged, the block was moved, the parent workspace zoomed, etc. + this.startWorkspace_.resize(); + } + this.startWorkspace_.markFocused(); + this.mostRecentEvent_ = e; + + // Hide chaff also hides the flyout, so don't do it if the click is in a flyout. + Blockly.hideChaff(!!this.flyout_); + Blockly.Tooltip.block(); + + if (this.targetBlock_) { + this.targetBlock_.select(); + } + + if (Blockly.utils.isRightButton(e)) { + this.handleRightClick(e); + return; + } + + if (goog.string.caseInsensitiveEquals(e.type, 'touchstart') || + goog.string.caseInsensitiveEquals(e.type, 'pointerdown')) { + Blockly.longStart_(e, this); + } + + this.mouseDownXY_ = new goog.math.Coordinate(e.clientX, e.clientY); + + this.bindMouseEvents(e); +}; + +/** + * Bind gesture events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.Gesture.prototype.bindMouseEvents = function(e) { + this.onMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', null, this.handleMove.bind(this)); + this.onUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', null, this.handleUp.bind(this)); + + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Handle a mouse move or touch move event. + * @param {!Event} e A mouse move or touch move event. + * @package + */ +Blockly.Gesture.prototype.handleMove = function(e) { + this.updateFromEvent_(e); + if (this.isDraggingWorkspace_) { + this.workspaceDragger_.drag(this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.dragBlock(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingBubble_) { + this.bubbleDragger_.dragBubble(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } + e.preventDefault(); + e.stopPropagation(); +}; + +/** + * Handle a mouse up or touch end event. + * @param {!Event} e A mouse up or touch end event. + * @package + */ +Blockly.Gesture.prototype.handleUp = function(e) { + this.updateFromEvent_(e); + Blockly.longStop_(); + + if (this.isEnding_) { + console.log('Trying to end a gesture recursively.'); + return; + } + this.isEnding_ = true; + // The ordering of these checks is important: drags have higher priority than + // clicks. Fields have higher priority than blocks; blocks have higher + // priority than workspaces. + // The ordering within drags does not matter, because the three types of + // dragging are exclusive. + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_); + } else if (this.isDraggingWorkspace_) { + this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } else if (this.isBubbleClick_()) { + // Bubbles are in front of all fields and blocks. + this.doBubbleClick_(); + } else if (this.isFieldClick_()) { + this.doFieldClick_(); + } else if (this.isBlockClick_()) { + this.doBlockClick_(); + } else if (this.isWorkspaceClick_()) { + this.doWorkspaceClick_(); + } + + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); +}; + +/** + * Cancel an in-progress gesture. If a workspace or block drag is in progress, + * end the drag at the most recent location. + * @package + */ +Blockly.Gesture.prototype.cancel = function() { + // Disposing of a block cancels in-progress drags, but dragging to a delete + // area disposes of a block and leads to recursive disposal. Break that cycle. + if (this.isEnding_) { + return; + } + Blockly.longStop_(); + if (this.isDraggingBubble_) { + this.bubbleDragger_.endBubbleDrag(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingBlock_) { + this.blockDragger_.endBlockDrag(this.mostRecentEvent_, + this.currentDragDeltaXY_); + } else if (this.isDraggingWorkspace_) { + this.workspaceDragger_.endDrag(this.currentDragDeltaXY_); + } + this.dispose(); +}; + +/** + * Handle a real or faked right-click event by showing a context menu. + * @param {!Event} e A mouse move or touch move event. + * @package + */ +Blockly.Gesture.prototype.handleRightClick = function(e) { + if (this.targetBlock_) { + this.bringBlockToFront_(); + Blockly.hideChaff(this.flyout_); + this.targetBlock_.showContextMenu_(e); + } else if (this.startWorkspace_ && !this.flyout_) { + Blockly.hideChaff(); + this.startWorkspace_.showContextMenu_(e); + } + + // TODO: Handle right-click on a bubble. + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); +}; + +/** + * Handle a mousedown/touchstart event on a workspace. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Workspace} ws The workspace the event hit. + * @package + */ +Blockly.Gesture.prototype.handleWsStart = function(e, ws) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleWsStart, but the gesture had already been ' + + 'started.'); + this.setStartWorkspace_(ws); + this.mostRecentEvent_ = e; + this.doStart(e); +}; + +/** + * Handle a mousedown/touchstart event on a flyout. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Flyout} flyout The flyout the event hit. + * @package + */ +Blockly.Gesture.prototype.handleFlyoutStart = function(e, flyout) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleFlyoutStart, but the gesture had already ' + + 'been started.'); + this.setStartFlyout_(flyout); + this.handleWsStart(e, flyout.getWorkspace()); +}; + +/** + * Handle a mousedown/touchstart event on a block. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.BlockSvg} block The block the event hit. + * @package + */ +Blockly.Gesture.prototype.handleBlockStart = function(e, block) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleBlockStart, but the gesture had already ' + + 'been started.'); + this.setStartBlock(block); + this.mostRecentEvent_ = e; +}; + +/** + * Handle a mousedown/touchstart event on a bubble. + * @param {!Event} e A mouse down or touch start event. + * @param {!Blockly.Bubble} bubble The bubble the event hit. + * @package + */ +Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.handleBubbleStart, but the gesture had already ' + + 'been started.'); + this.setStartBubble(bubble); + this.mostRecentEvent_ = e; +}; + +/* Begin functions defining what actions to take to execute clicks on each type + * of target. Any developer wanting to add behaviour on clicks should modify + * only this code. */ + +/** + * Execute a bubble click. + * @private + */ +Blockly.Gesture.prototype.doBubbleClick_ = function() { + // TODO: This isn't really enough, is it. + this.startBubble_.promote_(); +}; + +/** + * Execute a field click. + * @private + */ +Blockly.Gesture.prototype.doFieldClick_ = function() { + this.startField_.showEditor_(); + this.bringBlockToFront_(); +}; + +/** + * Execute a block click. + * @private + */ +Blockly.Gesture.prototype.doBlockClick_ = function() { + // Block click in an autoclosing flyout. + if (this.flyout_ && this.flyout_.autoClose) { + if (!this.targetBlock_.disabled) { + if (!Blockly.Events.getGroup()) { + Blockly.Events.setGroup(true); + } + var newBlock = this.flyout_.createBlock(this.targetBlock_); + newBlock.scheduleSnapAndBump(); + } + } else { + // Clicks events are on the start block, even if it was a shadow. + Blockly.Events.fire( + new Blockly.Events.Ui(this.startBlock_, 'click', undefined, undefined)); + } + this.bringBlockToFront_(); + Blockly.Events.setGroup(false); +}; + +/** + * Execute a workspace click. + * @private + */ +Blockly.Gesture.prototype.doWorkspaceClick_ = function() { + if (Blockly.selected) { + Blockly.selected.unselect(); + } +}; + +/* End functions defining what actions to take to execute clicks on each type + * of target. */ + +// TODO (fenichel): Move bubbles to the front. +/** + * Move the dragged/clicked block to the front of the workspace so that it is + * not occluded by other blocks. + * @private + */ +Blockly.Gesture.prototype.bringBlockToFront_ = function() { + // Blocks in the flyout don't overlap, so skip the work. + if (this.targetBlock_ && !this.flyout_) { + this.targetBlock_.bringToFront(); + } +}; + +/* Begin functions for populating a gesture at mouse down. */ + +/** + * Record the field that a gesture started on. + * @param {Blockly.Field} field The field the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartField = function(field) { + goog.asserts.assert(!this.hasStarted_, + 'Tried to call gesture.setStartField, but the gesture had already been ' + + 'started.'); + if (!this.startField_) { + this.startField_ = field; + } +}; + +/** + * Record the bubble that a gesture started on + * @param {Blockly.Bubble} bubble The bubble the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartBubble = function(bubble) { + if (!this.startBubble_) { + this.startBubble_ = bubble; + } +}; + +/** + * Record the block that a gesture started on, and set the target block + * appropriately. + * @param {Blockly.BlockSvg} block The block the gesture started on. + * @package + */ +Blockly.Gesture.prototype.setStartBlock = function(block) { + if (!this.startBlock_) { + this.startBlock_ = block; + if (block.isInFlyout && block != block.getRootBlock()) { + this.setTargetBlock_(block.getRootBlock()); + } else { + this.setTargetBlock_(block); + } + } +}; + +/** + * Record the block that a gesture targets, meaning the block that will be + * dragged if this turns into a drag. If this block is a shadow, that will be + * its first non-shadow parent. + * @param {Blockly.BlockSvg} block The block the gesture targets. + * @private + */ +Blockly.Gesture.prototype.setTargetBlock_ = function(block) { + if (block.isShadow()) { + this.setTargetBlock_(block.getParent()); + } else { + this.targetBlock_ = block; + } +}; + +/** + * Record the workspace that a gesture started on. + * @param {Blockly.WorkspaceSvg} ws The workspace the gesture started on. + * @private + */ +Blockly.Gesture.prototype.setStartWorkspace_ = function(ws) { + if (!this.startWorkspace_) { + this.startWorkspace_ = ws; + } +}; + +/** + * Record the flyout that a gesture started on. + * @param {Blockly.Flyout} flyout The flyout the gesture started on. + * @private + */ +Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) { + if (!this.flyout_) { + this.flyout_ = flyout; + } +}; + +/* End functions for populating a gesture at mouse down. */ + +/* Begin helper functions defining types of clicks. Any developer wanting + * to change the definition of a click should modify only this code. */ + +/** + * Whether this gesture is a click on a bubble. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a bubble. + * @private + */ +Blockly.Gesture.prototype.isBubbleClick_ = function() { + // A bubble click starts on a bubble and never escapes the drag radius. + var hasStartBubble = !!this.startBubble_; + return hasStartBubble && !this.hasExceededDragRadius_; +}; + +/** + * Whether this gesture is a click on a block. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a block. + * @private + */ +Blockly.Gesture.prototype.isBlockClick_ = function() { + // A block click starts on a block, never escapes the drag radius, and is not + // a field click. + var hasStartBlock = !!this.startBlock_; + return hasStartBlock && !this.hasExceededDragRadius_ && !this.isFieldClick_(); +}; + +/** + * Whether this gesture is a click on a field. This should only be called when + * ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a field. + * @private + */ +Blockly.Gesture.prototype.isFieldClick_ = function() { + var fieldEditable = this.startField_ ? + this.startField_.isCurrentlyEditable() : false; + return fieldEditable && !this.hasExceededDragRadius_ && (!this.flyout_ || + !this.flyout_.autoClose); +}; + +/** + * Whether this gesture is a click on a workspace. This should only be called + * when ending a gesture (mouse up, touch end). + * @return {boolean} whether this gesture was a click on a workspace. + * @private + */ +Blockly.Gesture.prototype.isWorkspaceClick_ = function() { + var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_; + return onlyTouchedWorkspace && !this.hasExceededDragRadius_; +}; + +/* End helper functions defining types of clicks. */ + +/** + * Whether this gesture is a drag of either a workspace or block. + * This function is called externally to block actions that cannot be taken + * mid-drag (e.g. using the keyboard to delete the selected blocks). + * @return {boolean} true if this gesture is a drag of a workspace or block. + * @package + */ +Blockly.Gesture.prototype.isDragging = function() { + return this.isDraggingWorkspace_ || this.isDraggingBlock_ || + this.isDraggingBubble_; +}; + +/** + * Whether this gesture has already been started. In theory every mouse down + * has a corresponding mouse up, but in reality it is possible to lose a + * mouse up, leaving an in-process gesture hanging. + * @return {boolean} whether this gesture was a click on a workspace. + * @package + */ +Blockly.Gesture.prototype.hasStarted = function() { + return this.hasStarted_; +}; diff --git a/core/grid.js b/core/grid.js new file mode 100644 index 0000000..c8b2e25 --- /dev/null +++ b/core/grid.js @@ -0,0 +1,224 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object for configuring and updating a workspace grid in + * Blockly. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.Grid'); + +goog.require('Blockly.utils'); + +goog.require('goog.userAgent'); + + +/** + * Class for a workspace's grid. + * @param {!SVGElement} pattern The grid's SVG pattern, created during injection. + * @param {!Object} options A dictionary of normalized options for the grid. + * See grid documentation: + * https://developers.google.com/blockly/guides/configure/web/grid + * @constructor + */ +Blockly.Grid = function(pattern, options) { + /** + * The grid's SVG pattern, created during injection. + * @type {!SVGElement} + * @private + */ + this.gridPattern_ = pattern; + + /** + * The spacing of the grid lines (in px). + * @type {number} + * @private + */ + this.spacing_ = options['spacing']; + + /** + * How long the grid lines should be (in px). + * @type {number} + * @private + */ + this.length_ = options['length']; + + /** + * The horizontal grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line1_ = pattern.firstChild; + + /** + * The vertical grid line, if it exists. + * @type {SVGElement} + * @private + */ + this.line2_ = this.line1_ && this.line1_.nextSibling; + + /** + * Whether blocks should snap to the grid. + * @type {boolean} + * @private + */ + this.snapToGrid_ = options['snap']; +}; + +/** + * The scale of the grid, used to set stroke width on grid lines. + * This should always be the same as the workspace scale. + * @type {number} + * @private + */ +Blockly.Grid.prototype.scale_ = 1; + +/** + * Dispose of this grid and unlink from the DOM. + * @package + */ +Blockly.Grid.prototype.dispose = function() { + this.gridPattern_ = null; +}; + +/** + * Whether blocks should snap to the grid, based on the initial configuration. + * @return {boolean} True if blocks should snap, false otherwise. + * @package + */ +Blockly.Grid.prototype.shouldSnap = function() { + return this.snapToGrid_; +}; + +/** + * Get the spacing of the grid points (in px). + * @return {number} The spacing of the grid points. + * @package + */ +Blockly.Grid.prototype.getSpacing = function() { + return this.spacing_; +}; + +/** + * Get the id of the pattern element, which should be randomized to avoid + * conflicts with other Blockly instances on the page. + * @return {string} The pattern ID. + * @package + */ +Blockly.Grid.prototype.getPatternId = function() { + return this.gridPattern_.id; +}; + +/** + * Update the grid with a new scale. + * @param {number} scale The new workspace scale. + * @package + */ +Blockly.Grid.prototype.update = function(scale) { + this.scale_ = scale; + // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. + var safeSpacing = (this.spacing_ * scale) || 100; + + this.gridPattern_.setAttribute('width', safeSpacing); + this.gridPattern_.setAttribute('height', safeSpacing); + + var half = Math.floor(this.spacing_ / 2) + 0.5; + var start = half - this.length_ / 2; + var end = half + this.length_ / 2; + + half *= scale; + start *= scale; + end *= scale; + + this.setLineAttributes_(this.line1_, scale, start, end, half, half); + this.setLineAttributes_(this.line2_, scale, half, half, start, end); +}; + +/** + * Set the attributes on one of the lines in the grid. Use this to update the + * length and stroke width of the grid lines. + * @param {!SVGElement} line Which line to update. + * @param {number} width The new stroke size (in px). + * @param {number} x1 The new x start position of the line (in px). + * @param {number} x2 The new x end position of the line (in px). + * @param {number} y1 The new y start position of the line (in px). + * @param {number} y2 The new y end position of the line (in px). + * @private + */ +Blockly.Grid.prototype.setLineAttributes_ = function(line, width, x1, x2, y1, y2) { + if (line) { + line.setAttribute('stroke-width', width); + line.setAttribute('x1', x1); + line.setAttribute('y1', y1); + line.setAttribute('x2', x2); + line.setAttribute('y2', y2); + } +}; + +/** + * Move the grid to a new x and y position, and make sure that change is visible. + * @param {number} x The new x position of the grid (in px). + * @param {number} y The new y position ofthe grid (in px). + * @package + */ +Blockly.Grid.prototype.moveTo = function(x, y) { + this.gridPattern_.setAttribute('x', x); + this.gridPattern_.setAttribute('y', y); + + if (goog.userAgent.IE || goog.userAgent.EDGE) { + // IE/Edge doesn't notice that the x/y offsets have changed. + // Force an update. + this.update(this.scale_); + } +}; + +/** + * Create the DOM for the grid described by options. + * @param {string} rnd A random ID to append to the pattern's ID. + * @param {!Object} gridOptions The object containing grid configuration. + * @param {!SVGElement} defs The root SVG element for this workspace's defs. + * @return {!SVGElement} The SVG element for the grid pattern. + * @package + */ +Blockly.Grid.createDom = function(rnd, gridOptions, defs) { + /* + + + + + */ + var gridPattern = Blockly.utils.createSvgElement('pattern', + { + 'id': 'blocklyGridPattern' + rnd, + 'patternUnits': 'userSpaceOnUse' + }, defs); + if (gridOptions['length'] > 0 && gridOptions['spacing'] > 0) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + if (gridOptions['length'] > 1) { + Blockly.utils.createSvgElement('line', + {'stroke': gridOptions['colour']}, gridPattern); + } + // x1, y1, x1, x2 properties will be set later in update. + } + return gridPattern; +}; diff --git a/core/icon.js b/core/icon.js index 9d2cbea..88b7fb6 100644 --- a/core/icon.js +++ b/core/icon.js @@ -76,12 +76,17 @@ Blockly.Icon.prototype.createIcon = function() { ... */ - this.iconGroup_ = Blockly.createSvgElement('g', + this.iconGroup_ = Blockly.utils.createSvgElement('g', {'class': 'blocklyIconGroup'}, null); + if (this.block_.isInFlyout) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), 'blocklyIconGroupReadonly'); + } this.drawIcon_(this.iconGroup_); this.block_.getSvgRoot().appendChild(this.iconGroup_); - Blockly.bindEvent_(this.iconGroup_, 'mouseup', this, this.iconClick_); + Blockly.bindEventWithChecks_( + this.iconGroup_, 'mouseup', this, this.iconClick_); this.updateEditable(); }; @@ -101,13 +106,6 @@ Blockly.Icon.prototype.dispose = function() { * Add or remove the UI indicating if this icon may be clicked or not. */ Blockly.Icon.prototype.updateEditable = function() { - if (this.block_.isInFlyout || !this.block_.isEditable()) { - Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); - } else { - Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); - } }; /** @@ -124,11 +122,11 @@ Blockly.Icon.prototype.isVisible = function() { * @private */ Blockly.Icon.prototype.iconClick_ = function(e) { - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { + if (this.block_.workspace.isDragging()) { // Drag operation is concluding. Don't open the editor. return; } - if (!this.block_.isInFlyout && !Blockly.isRightButton(e)) { + if (!this.block_.isInFlyout && !Blockly.utils.isRightButton(e)) { this.setVisible(!this.isVisible()); } }; @@ -172,7 +170,7 @@ Blockly.Icon.prototype.renderIcon = function(cursorX) { /** * Notification that the icon has moved. Update the arrow accordingly. - * @param {!goog.math.Coordinate} xy Absolute location. + * @param {!goog.math.Coordinate} xy Absolute location in workspace coordinates. */ Blockly.Icon.prototype.setIconLocation = function(xy) { this.iconXY_ = xy; @@ -188,7 +186,7 @@ Blockly.Icon.prototype.setIconLocation = function(xy) { Blockly.Icon.prototype.computeIconLocation = function() { // Find coordinates for the centre of the icon and update the arrow. var blockXY = this.block_.getRelativeToSurfaceXY(); - var iconXY = Blockly.getRelativeXY_(this.iconGroup_); + var iconXY = Blockly.utils.getRelativeXY(this.iconGroup_); var newXY = new goog.math.Coordinate( blockXY.x + iconXY.x + this.SIZE / 2, blockXY.y + iconXY.y + this.SIZE / 2); @@ -199,7 +197,8 @@ Blockly.Icon.prototype.computeIconLocation = function() { /** * Returns the center of the block's icon relative to the surface. - * @return {!goog.math.Coordinate} Object with x and y properties. + * @return {!goog.math.Coordinate} Object with x and y properties in workspace + * coordinates. */ Blockly.Icon.prototype.getIconLocation = function() { return this.iconXY_; diff --git a/core/inject.js b/core/inject.js index 17bfccc..11f3efa 100644 --- a/core/inject.js +++ b/core/inject.js @@ -26,9 +26,12 @@ goog.provide('Blockly.inject'); +goog.require('Blockly.BlockDragSurfaceSvg'); goog.require('Blockly.Css'); +goog.require('Blockly.Grid'); goog.require('Blockly.Options'); goog.require('Blockly.WorkspaceSvg'); +goog.require('Blockly.WorkspaceDragSurfaceSvg'); goog.require('goog.dom'); goog.require('goog.ui.Component'); goog.require('goog.userAgent'); @@ -51,11 +54,21 @@ Blockly.inject = function(container, opt_options) { throw 'Error: container is not in current document.'; } var options = new Blockly.Options(opt_options || {}); - var svg = Blockly.createDom_(container, options); - var workspace = Blockly.createMainWorkspace_(svg, options); + var subContainer = goog.dom.createDom(goog.dom.TagName.DIV, 'injectionDiv'); + container.appendChild(subContainer); + var svg = Blockly.createDom_(subContainer, options); + + // Create surfaces for dragging things. These are optimizations + // so that the broowser does not repaint during the drag. + var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer); + var workspaceDragSurface = new Blockly.WorkspaceDragSurfaceSvg(subContainer); + + var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface, + workspaceDragSurface); Blockly.init_(workspace); - workspace.markFocused(); - Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused); + Blockly.mainWorkspace = workspace; + + Blockly.svgResize(workspace); return workspace; }; @@ -88,7 +101,7 @@ Blockly.createDom_ = function(container, options) { ... */ - var svg = Blockly.createSvgElement('svg', { + var svg = Blockly.utils.createSvgElement('svg', { 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:html': 'http://www.w3.org/1999/xhtml', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', @@ -100,38 +113,58 @@ Blockly.createDom_ = function(container, options) { ... filters go here ... */ - var defs = Blockly.createSvgElement('defs', {}, svg); + var defs = Blockly.utils.createSvgElement('defs', {}, svg); + // Each filter/pattern needs a unique ID for the case of multiple Blockly + // instances on a page. Browser behaviour becomes undefined otherwise. + // https://neil.fraser.name/news/2015/11/01/ var rnd = String(Math.random()).substring(2); /* - + - + + result="specOut" /> + k1="0" k2="1" k3="1" k4="0" /> */ - var embossFilter = Blockly.createSvgElement('filter', + var embossFilter = Blockly.utils.createSvgElement('filter', {'id': 'blocklyEmbossFilter' + rnd}, defs); - Blockly.createSvgElement('feGaussianBlur', + Blockly.utils.createSvgElement('feGaussianBlur', {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); - var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting', - {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5, - 'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'}, + var feSpecularLighting = Blockly.utils.createSvgElement('feSpecularLighting', + { + 'in': 'blur', + 'surfaceScale': 1, + 'specularConstant': 0.5, + 'specularExponent': 10, + 'lighting-color': 'white', + 'result': 'specOut' + }, embossFilter); - Blockly.createSvgElement('fePointLight', + Blockly.utils.createSvgElement('fePointLight', {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); - Blockly.createSvgElement('feComposite', - {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in', - 'result': 'specOut'}, embossFilter); - Blockly.createSvgElement('feComposite', - {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic', - 'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter); + Blockly.utils.createSvgElement('feComposite', + { + 'in': 'specOut', + 'in2': 'SourceAlpha', + 'operator': 'in', + 'result': 'specOut' + }, embossFilter); + Blockly.utils.createSvgElement('feComposite', + { + 'in': 'SourceGraphic', + 'in2': 'specOut', + 'operator': 'arithmetic', + 'k1': 0, + 'k2': 1, + 'k3': 1, + 'k4': 0 + }, embossFilter); options.embossFilterId = embossFilter.id; /* */ - var disabledPattern = Blockly.createSvgElement('pattern', - {'id': 'blocklyDisabledPattern' + rnd, - 'patternUnits': 'userSpaceOnUse', - 'width': 10, 'height': 10}, defs); - Blockly.createSvgElement('rect', + var disabledPattern = Blockly.utils.createSvgElement('pattern', + { + 'id': 'blocklyDisabledPattern' + rnd, + 'patternUnits': 'userSpaceOnUse', + 'width': 10, + 'height': 10 + }, defs); + Blockly.utils.createSvgElement('rect', {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); - Blockly.createSvgElement('path', + Blockly.utils.createSvgElement('path', {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); options.disabledPatternId = disabledPattern.id; - /* - - - - - */ - var gridPattern = Blockly.createSvgElement('pattern', - {'id': 'blocklyGridPattern' + rnd, - 'patternUnits': 'userSpaceOnUse'}, defs); - if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { - Blockly.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - if (options.gridOptions['length'] > 1) { - Blockly.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - } - // x1, y1, x1, x2 properties will be set later in updateGridPattern_. - } - options.gridPattern = gridPattern; + + options.gridPattern = Blockly.Grid.createDom(rnd, options.gridOptions, defs); return svg; }; @@ -177,23 +194,32 @@ Blockly.createDom_ = function(container, options) { * Create a main workspace and add it to the SVG. * @param {!Element} svg SVG element with pattern defined. * @param {!Blockly.Options} options Dictionary of options. + * @param {!Blockly.BlockDragSurfaceSvg} blockDragSurface Drag surface SVG + * for the blocks. + * @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface + * SVG for the workspace. * @return {!Blockly.Workspace} Newly created main workspace. * @private */ -Blockly.createMainWorkspace_ = function(svg, options) { +Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspaceDragSurface) { options.parentWorkspace = null; - options.getMetrics = Blockly.getMainWorkspaceMetrics_; - options.setMetrics = Blockly.setMainWorkspaceMetrics_; - var mainWorkspace = new Blockly.WorkspaceSvg(options); + var mainWorkspace = new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface); mainWorkspace.scale = options.zoomOptions.startScale; svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); + + if (!options.hasCategories && options.languageTree) { + // Add flyout as an that is a sibling of the workspace svg. + var flyout = mainWorkspace.addFlyout_('svg'); + Blockly.utils.insertAfter_(flyout, svg); + } + // A null translation will also apply the correct initial scale. mainWorkspace.translate(0, 0); - mainWorkspace.markFocused(); + Blockly.mainWorkspace = mainWorkspace; if (!options.readOnly && !options.hasScrollbars) { var workspaceChanged = function() { - if (Blockly.dragMode_ == Blockly.DRAG_NONE) { + if (!mainWorkspace.isDragging()) { var metrics = mainWorkspace.getMetrics(); var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; var edgeTop = metrics.viewTop + metrics.absoluteTop; @@ -255,19 +281,21 @@ Blockly.init_ = function(mainWorkspace) { var options = mainWorkspace.options; var svg = mainWorkspace.getParentSvg(); - // Supress the browser's context menu. - Blockly.bindEvent_(svg, 'contextmenu', null, + // Suppress the browser's context menu. + Blockly.bindEventWithChecks_(svg.parentNode, 'contextmenu', null, function(e) { - if (!Blockly.isTargetInput_(e)) { + if (!Blockly.utils.isTargetInput(e)) { e.preventDefault(); } }); - Blockly.bindEvent_(window, 'resize', null, + var workspaceResizeHandler = Blockly.bindEventWithChecks_(window, 'resize', + null, function() { Blockly.hideChaff(true); - Blockly.asyncSvgResize(mainWorkspace); + Blockly.svgResize(mainWorkspace); }); + mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler); Blockly.inject.bindDocumentEvents_(); @@ -312,18 +340,18 @@ Blockly.init_ = function(mainWorkspace) { */ Blockly.inject.bindDocumentEvents_ = function() { if (!Blockly.documentEventsBound_) { - Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_); + Blockly.bindEventWithChecks_(document, 'keydown', null, Blockly.onKeyDown_); + // longStop needs to run to stop the context menu from showing up. It + // should run regardless of what other touch event handlers have run. Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); - // Don't use bindEvent_ for document's mouseup since that would create a - // corresponding touch handler that would squeltch the ability to interact - // with non-Blockly elements. - document.addEventListener('mouseup', Blockly.onMouseUp_, false); // Some iPad versions don't fire resize after portrait to landscape change. if (goog.userAgent.IPAD) { - Blockly.bindEvent_(window, 'orientationchange', document, function() { - Blockly.asyncSvgResize(); - }); + Blockly.bindEventWithChecks_(window, 'orientationchange', document, + function() { + // TODO(#397): Fix for multiple blockly workspaces. + Blockly.svgResize(Blockly.getMainWorkspace()); + }); } } Blockly.documentEventsBound_ = true; @@ -336,18 +364,25 @@ Blockly.inject.bindDocumentEvents_ = function() { * @private */ Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { - workspace.loadAudio_( - [pathToMedia + 'click.mp3', - pathToMedia + 'click.wav', - pathToMedia + 'click.ogg'], 'click'); - workspace.loadAudio_( - [pathToMedia + 'disconnect.wav', - pathToMedia + 'disconnect.mp3', - pathToMedia + 'disconnect.ogg'], 'disconnect'); - workspace.loadAudio_( - [pathToMedia + 'delete.mp3', - pathToMedia + 'delete.ogg', - pathToMedia + 'delete.wav'], 'delete'); + var audioMgr = workspace.getAudioManager(); + audioMgr.load( + [ + pathToMedia + 'click.mp3', + pathToMedia + 'click.wav', + pathToMedia + 'click.ogg' + ], 'click'); + audioMgr.load( + [ + pathToMedia + 'disconnect.wav', + pathToMedia + 'disconnect.mp3', + pathToMedia + 'disconnect.ogg' + ], 'disconnect'); + audioMgr.load( + [ + pathToMedia + 'delete.mp3', + pathToMedia + 'delete.ogg', + pathToMedia + 'delete.wav' + ], 'delete'); // Bind temporary hooks that preload the sounds. var soundBinds = []; @@ -355,18 +390,27 @@ Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { while (soundBinds.length) { Blockly.unbindEvent_(soundBinds.pop()); } - workspace.preloadAudio_(); + audioMgr.preload(); }; + + // These are bound on mouse/touch events with Blockly.bindEventWithChecks_, so + // they restrict the touch identifier that will be recognized. But this is + // really something that happens on a click, not a drag, so that's not + // necessary. + // Android ignores any sound not loaded as a result of a user action. soundBinds.push( - Blockly.bindEvent_(document, 'mousemove', null, unbindSounds)); + Blockly.bindEventWithChecks_(document, 'mousemove', null, unbindSounds, + true)); soundBinds.push( - Blockly.bindEvent_(document, 'touchstart', null, unbindSounds)); + Blockly.bindEventWithChecks_(document, 'touchstart', null, unbindSounds, + true)); }; /** * Modify the block tree on the existing toolbox. * @param {Node|string} tree DOM tree of blocks, or text representation of same. + * @deprecated April 2015 */ Blockly.updateToolbox = function(tree) { console.warn('Deprecated call to Blockly.updateToolbox, ' + diff --git a/core/input.js b/core/input.js index d5c3e6f..5c7495d 100644 --- a/core/input.js +++ b/core/input.js @@ -26,8 +26,6 @@ goog.provide('Blockly.Input'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); goog.require('Blockly.Connection'); goog.require('Blockly.FieldLabel'); goog.require('goog.asserts'); @@ -43,6 +41,9 @@ goog.require('goog.asserts'); * @constructor */ Blockly.Input = function(type, name, block, connection) { + if (type != Blockly.DUMMY_INPUT && !name) { + throw 'Value inputs and statement inputs must have non-empty name.'; + } /** @type {number} */ this.type = type; /** @type {string} */ @@ -72,16 +73,35 @@ Blockly.Input.prototype.align = Blockly.ALIGN_LEFT; Blockly.Input.prototype.visible_ = true; /** - * Add an item to the end of the input's field row. + * Add a field (or label from string), and all prefix and suffix fields, to the + * end of the input's field row. * @param {string|!Blockly.Field} field Something to add as a field. * @param {string=} opt_name Language-neutral identifier which may used to find * this field again. Should be unique to the host block. * @return {!Blockly.Input} The input being append to (to allow chaining). */ Blockly.Input.prototype.appendField = function(field, opt_name) { + this.insertFieldAt(this.fieldRow.length, field, opt_name); + return this; +}; + +/** + * Inserts a field (or label from string), and all prefix and suffix fields, at + * the location of the input's field row. + * @param {number} index The index at which to insert field. + * @param {string|!Blockly.Field} field Something to add as a field. + * @param {string=} opt_name Language-neutral identifier which may used to find + * this field again. Should be unique to the host block. + * @return {number} The index following the last inserted field. + */ +Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) { + if (index < 0 || index > this.fieldRow.length) { + throw new Error('index ' + index + ' out of bounds.'); + } + // Empty string, Null or undefined generates no field, unless field is named. if (!field && !opt_name) { - return this; + return index; } // Generate a FieldLabel when given a plain text field. if (goog.isString(field)) { @@ -95,13 +115,14 @@ Blockly.Input.prototype.appendField = function(field, opt_name) { if (field.prefixField) { // Add any prefix. - this.appendField(field.prefixField); + index = this.insertFieldAt(index, field.prefixField); } // Add the field to the field row. - this.fieldRow.push(field); + this.fieldRow.splice(index, 0, field); + ++index; if (field.suffixField) { // Add any suffix. - this.appendField(field.suffixField); + index = this.insertFieldAt(index, field.suffixField); } if (this.sourceBlock_.rendered) { @@ -109,20 +130,7 @@ Blockly.Input.prototype.appendField = function(field, opt_name) { // Adding a field will cause the block to change shape. this.sourceBlock_.bumpNeighbours_(); } - return this; -}; - -/** - * Add an item to the end of the input's field row. - * @param {*} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. - * @return {!Blockly.Input} The input being append to (to allow chaining). - * @deprecated December 2013 - */ -Blockly.Input.prototype.appendTitle = function(field, opt_name) { - console.warn('Deprecated call to appendTitle, use appendField instead.'); - return this.appendField(field, opt_name); + return index; }; /** @@ -224,8 +232,8 @@ Blockly.Input.prototype.init = function() { if (!this.sourceBlock_.workspace.rendered) { return; // Headless blocks don't need fields initialized. } - for (var x = 0; x < this.fieldRow.length; x++) { - this.fieldRow[x].init(); + for (var i = 0; i < this.fieldRow.length; i++) { + this.fieldRow[i].init(); } }; diff --git a/core/instances.js b/core/instances.js deleted file mode 100644 index 0bc6bfc..0000000 --- a/core/instances.js +++ /dev/null @@ -1,238 +0,0 @@ -/** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** - * @fileoverview Utility functions for handling Instances. - */ -'use strict'; - -goog.provide('Blockly.Instances'); - -goog.require('Blockly.Workspace'); -goog.require('goog.string'); - - -/** - * Category to separate instance names from instances, procedures and - * generated functions. - */ -Blockly.Instances.NAME_TYPE = 'INSTANCE'; - -/** - * Find all user-created instances. - * @param {?string} - * @param {!Blockly.Workspace} workspace Workspace to transverse for instances. - * @return {!Array.} Array of instance names. - */ -Blockly.Instances.allInstancesOf = function(instanceType, workspace) { - var blocks; - if (workspace.getAllBlocks) { - blocks = workspace.getAllBlocks(); - } else { - throw 'Not a valid Workspace: ' + workspace; - } - - var instanceHash = Object.create(null); - // Iterate through every block and add each instance to the hash. - for (var i = 0; i < blocks.length; i++) { - var blockInstances = blocks[i].getInstances(instanceType); - for (var j = 0; j < blockInstances.length; j++) { - var instanceName = blockInstances[j]; - // Instance name may be null if the block is only half-built. - if (instanceName) { - instanceHash[instanceName.toLowerCase()] = instanceName; - } - } - } - // Flatten the hash into a list. - var instanceList = []; - for (var name in instanceHash) { - instanceList.push(instanceHash[name]); - } - return instanceList; -}; - -/** Returns all instances of all types. */ -Blockly.Instances.allInstances = function(workspace) { - return Blockly.Instances.allInstancesOf(undefined, workspace); -}; - -/** Returns the first Instance name of a given type. */ -Blockly.Instances.getAnyInstanceOf = function(instanceType, workspace) { - var blocks; - if (workspace.getAllBlocks) { - blocks = workspace.getAllBlocks(); - } else { - throw 'Not a valid Workspace: ' + workspace; - } - - for (var i = 0; i < blocks.length; i++) { - var blockInstances = blocks[i].getInstances(instanceType); - if (blockInstances.length) { - return blockInstances[0]; - } - } -}; - -/** Indicates if the given instance is present in the workspace. */ -Blockly.Instances.isInstancePresent = function( - instanceName, instanceType, block) { - var blocks; - if (block.workspace && block.workspace.getAllBlocks) { - blocks = block.workspace.getAllBlocks(); - } else { - throw 'Not a valid block: ' + block; - } - - for (var i = 0; i < blocks.length; i++) { - var blockInstances = blocks[i].getInstances(instanceType); - for (var j = 0; j < blockInstances.length; j++) { - if ((blockInstances[j] === instanceName) && (blocks[i] !== block)) { - return true; - } - } - } - return false; -}; - -/** - * Find all instances of the specified name and type and rename them. - * @param {string} oldName Instance to rename. - * @param {string} newName New Instance name. - * @param {!Blockly.Workspace} workspace Workspace rename instances in. - */ -Blockly.Instances.renameInstance = function( - oldName, newName, instanceType, workspace) { - Blockly.Events.setGroup(true); - var blocks = workspace.getAllBlocks(); - // Iterate through every block. - for (var i = 0; i < blocks.length; i++) { - blocks[i].renameInstance(oldName, newName, instanceType); - } - Blockly.Events.setGroup(false); -}; - -/** - * Return a new instance name that is not yet being used as an instance or as a - * variable name. This will try to generate single letter 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'. - * @param {!Blockly.Workspace} workspace The workspace to be unique in. - * @return {string} New instance name. - */ -Blockly.Instances.generateUniqueName = function(workspace) { - var combinedList = Blockly.Variables.allVariables(workspace).concat( - Blockly.Instances.allInstances(workspace)); - var newName = ''; - if (combinedList.length) { - var nameSuffix = 1; - var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'. - var letterIndex = 0; - var potName = letters.charAt(letterIndex); - while (!newName) { - var inUse = false; - for (var i = 0; i < combinedList.length; i++) { - if (combinedList[i].toLowerCase() == 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; -}; - -/** - * Return a version of the instance name that has not yet been used. - * It does so by adding a number at the end of the name. - * @param {string} instanceName Instance name to make unique. - * @param {!Blockly.Workspace|Blockly.Block} workspace The workspace to be unique in. - * @return {string} Unique instance name based on name input. - */ -Blockly.Instances.convertToUniqueName = function(instanceName, workspace) { - var combinedList = Blockly.Variables.allVariables(workspace).concat( - Blockly.Instances.allInstances(workspace)); - return Blockly.Instances.appendToName_(instanceName, combinedList); -}; - -/** */ -Blockly.Instances.convertToUniqueNameBlock = function(instanceName, block) { - var blocks; - if (block.workspace) { - blocks = block.workspace.getAllBlocks(); - } else { - throw 'Not a valid Workspace: ' + workspace; - } - - var instanceHash = Object.create(null); - // Iterate through every block and add each instance to the hash. - for (var i = 0; i < blocks.length; i++) { - // Do not add to the list this block instance - if (blocks[i] !== block) { - var blockInstances = blocks[i].getInstances(); - for (var j = 0; j < blockInstances.length; j++) { - var blockInstanceName = blockInstances[j]; - // Instance name may be null if the block is only half-built. - if (blockInstanceName) { - instanceHash[blockInstanceName.toLowerCase()] = blockInstanceName; - } - } - } - } - // Flatten the hash into a list. - var instanceList = []; - for (var name in instanceHash) { - instanceList.push(instanceHash[name]); - } - - var combinedList = Blockly.Variables.allVariables(block.workspace).concat( - instanceList); - return Blockly.Instances.appendToName_(instanceName, combinedList); -}; - -/** */ -Blockly.Instances.appendToName_ = function(instanceName, nameList) { - if (!instanceName) { - return Blockly.Instances.generateUniqueName(workspace); - } else { - var newName = instanceName; - var nameSuffix = 2; - - if (instanceName.match(/_\d+$/)) { - // instanceName ends with and underscore and a number, so increase count - var instanceNameSuffix = instanceName.match(/\d+$/)[0]; - instanceName = instanceName.slice( - 0, (instanceNameSuffix.length * -1) - 1); - nameSuffix = parseInt(instanceNameSuffix, 10) + 1; - newName = instanceName + '_' + nameSuffix; - } - - while (nameList.indexOf(newName) !== -1) { - newName = instanceName + '_' + nameSuffix++; - } - return newName; - } -}; diff --git a/core/msg.js b/core/msg.js index 4ebcad1..f6e5205 100644 --- a/core/msg.js +++ b/core/msg.js @@ -42,7 +42,7 @@ goog.getMsgOrig = goog.getMsg; * Overrides the default Closure function to check for a Blockly.Msg first. * Used infrequently, only known case is TODAY button in date picker. * @param {string} str Translatable string, places holders in the form {$foo}. - * @param {Object=} opt_values Maps place holder name to value. + * @param {Object.=} opt_values Maps place holder name to value. * @return {string} message with placeholders filled. * @suppress {duplicate} */ diff --git a/core/mutator.js b/core/mutator.js index 358b90f..a57727a 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -65,20 +65,38 @@ Blockly.Mutator.prototype.workspaceHeight_ = 0; */ Blockly.Mutator.prototype.drawIcon_ = function(group) { // Square with rounded corners. - Blockly.createSvgElement('rect', - {'class': 'blocklyIconShape', - 'rx': '4', 'ry': '4', - 'height': '16', 'width': '16'}, - group); + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconShape', + 'rx': '4', + 'ry': '4', + 'height': '16', + 'width': '16' + }, + group); // Gear teeth. - Blockly.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'}, - group); + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,' + + '0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,' + + '-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,' + + '-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,' + + '-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 ' + + '-0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,' + + '0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z' + }, + group); // Axle hole. - Blockly.createSvgElement('circle', - {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, - group); + Blockly.utils.createSvgElement( + 'circle', + { + 'class': 'blocklyIconShape', + 'r': '2.7', + 'cx': '8', + 'cy': '8' + }, + group); }; /** @@ -105,7 +123,7 @@ Blockly.Mutator.prototype.createEditor_ = function() { [Workspace] */ - this.svgDialog_ = Blockly.createSvgElement('svg', + this.svgDialog_ = Blockly.utils.createSvgElement('svg', {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, null); // Convert the list of names into a list of XML objects for the flyout. @@ -129,8 +147,21 @@ Blockly.Mutator.prototype.createEditor_ = function() { setMetrics: null }; this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); - this.svgDialog_.appendChild( - this.workspace_.createDom('blocklyMutatorBackground')); + this.workspace_.isMutator = true; + + // Mutator flyouts go inside the mutator workspace's rather than in + // a top level svg. Instead of handling scale themselves, mutators + // inherit scale from the parent workspace. + // To fix this, scale needs to be applied at a different level in the dom. + var flyoutSvg = this.workspace_.addFlyout_('g'); + var background = this.workspace_.createDom('blocklyMutatorBackground'); + + // Insert the flyout after the but before the block canvas so that + // the flyout is underneath in z-order. This makes blocks layering during + // dragging work properly. + background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_); + this.svgDialog_.appendChild(background); + return this.svgDialog_; }; @@ -138,17 +169,25 @@ Blockly.Mutator.prototype.createEditor_ = function() { * Add or remove the UI indicating if this icon may be clicked or not. */ Blockly.Mutator.prototype.updateEditable = function() { - if (this.block_.isEditable()) { - // Default behaviour for an icon. - Blockly.Icon.prototype.updateEditable.call(this); - } else { - // Close any mutator bubble. Icon is not clickable. - this.setVisible(false); - if (this.iconGroup_) { - Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); + if (!this.block_.isInFlyout) { + if (this.block_.isEditable()) { + if (this.iconGroup_) { + Blockly.utils.removeClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } + } else { + // Close any mutator bubble. Icon is not clickable. + this.setVisible(false); + if (this.iconGroup_) { + Blockly.utils.addClass( + /** @type {!Element} */ (this.iconGroup_), + 'blocklyIconGroupReadonly'); + } } } + // Default behaviour for an icon. + Blockly.Icon.prototype.updateEditable.call(this); }; /** @@ -178,8 +217,8 @@ Blockly.Mutator.prototype.resizeBubble_ = function() { this.workspaceWidth_ = width; this.workspaceHeight_ = height; // Resize the bubble. - this.bubble_.setBubbleSize(width + doubleBorderWidth, - height + doubleBorderWidth); + this.bubble_.setBubbleSize( + width + doubleBorderWidth, height + doubleBorderWidth); this.svgDialog_.setAttribute('width', this.workspaceWidth_); this.svgDialog_.setAttribute('height', this.workspaceHeight_); } @@ -270,7 +309,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) { * @private */ Blockly.Mutator.prototype.workspaceChanged_ = function() { - if (Blockly.dragMode_ == Blockly.DRAG_NONE) { + if (!this.workspace_.isDragging()) { var blocks = this.workspace_.getTopBlocks(false); var MARGIN = 20; for (var b = 0, block; block = blocks[b]; b++) { @@ -296,25 +335,29 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() { block.compose(this.rootBlock_); // Restore rendering and show the changes. block.rendered = savedRendered; - // Mutation may have added some elements that need initalizing. + // Mutation may have added some elements that need initializing. block.initSvg(); var newMutationDom = block.mutationToDom(); var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); if (oldMutation != newMutation) { - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( block, 'mutation', null, oldMutation, newMutation)); // Ensure that any bump is part of this mutation's event group. var group = Blockly.Events.getGroup(); setTimeout(function() { - Blockly.Events.setGroup(group); - block.bumpNeighbours_(); - Blockly.Events.setGroup(false); + Blockly.Events.setGroup(group); + block.bumpNeighbours_(); + Blockly.Events.setGroup(false); }, Blockly.BUMP_DELAY); } if (block.rendered) { block.render(); } - this.resizeBubble_(); + // Don't update the bubble until the drag has ended, to avoid moving blocks + // under the cursor. + if (!this.workspace_.isDragging()) { + this.resizeBubble_(); + } Blockly.Events.setGroup(false); } }; @@ -372,6 +415,30 @@ Blockly.Mutator.reconnect = function(connectionChild, block, inputName) { return false; }; +/** + * Get the parent workspace of a workspace that is inside a mutator, taking into + * account whether it is a flyout. + * @param {?Blockly.Workspace} workspace The workspace that is inside a mutator. + * @return {?Blockly.Workspace} The mutator's parent workspace or null. + * @package + */ +Blockly.Mutator.findParentWs = function(workspace) { + var outerWs = null; + if (workspace && workspace.options) { + var parent = workspace.options.parentWorkspace; + // If we were in a flyout in a mutator, need to go up two levels to find + // the actual parent. + if (workspace.isFlyout) { + if (parent && parent.options) { + outerWs = parent.options.parentWorkspace; + } + } else if (parent) { + outerWs = parent; + } + } + return outerWs; +}; + // Export symbols that would otherwise be renamed by Closure compiler. if (!goog.global['Blockly']) { goog.global['Blockly'] = {}; diff --git a/core/names.js b/core/names.js index 1a6afcc..7b79772 100644 --- a/core/names.js +++ b/core/names.js @@ -1,143 +1,197 @@ -/** - * @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 and procedure names. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Names'); - - -/** - * Class for a database of entity names (variables, functions, etc). - * @param {string} reservedWords A comma-separated string of words that are - * illegal for use as names in a language (e.g. 'new,if,this,...'). - * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace - * before all variable names. - * @constructor - */ -Blockly.Names = function(reservedWords, opt_variablePrefix) { - this.variablePrefix_ = opt_variablePrefix || ''; - this.reservedDict_ = Object.create(null); - if (reservedWords) { - var splitWords = reservedWords.split(','); - for (var i = 0; i < splitWords.length; i++) { - this.reservedDict_[splitWords[i]] = true; - } - } - this.reset(); -}; - -/** - * When JavaScript (or most other languages) is generated, variable 'foo' and - * procedure 'foo' would collide. However, Blockly has no such problems since - * variable get 'foo' and procedure call 'foo' are unambiguous. - * Therefore, Blockly keeps a separate type name to disambiguate. - * getName('foo', 'variable') -> 'foo' - * getName('foo', 'procedure') -> 'foo2' - */ - -/** - * Empty the database and start from scratch. The reserved words are kept. - */ -Blockly.Names.prototype.reset = function() { - this.db_ = Object.create(null); - this.dbReverse_ = Object.create(null); -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * @param {string} name The Blockly entity name (no constraints). - * @param {string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). - * @return {string} An entity name legal for the exported language. - */ -Blockly.Names.prototype.getName = function(name, type) { - var normalized = name.toLowerCase() + '_' + type; - var prefix = (type == Blockly.Variables.NAME_TYPE) ? - this.variablePrefix_ : ''; - if (normalized in this.db_) { - return prefix + this.db_[normalized]; - } - var safeName = this.getDistinctName(name, type); - this.db_[normalized] = safeName.substr(prefix.length); - return safeName; -}; - -/** - * Convert a Blockly entity name to a legal exportable entity name. - * Ensure that this is a new name not overlapping any previously defined name. - * Also check against list of reserved words for the current language and - * ensure name doesn't collide. - * @param {string} name The Blockly entity name (no constraints). - * @param {string} type The type of entity in Blockly - * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). - * @return {string} An entity name legal for the exported language. - */ -Blockly.Names.prototype.getDistinctName = function(name, type) { - var safeName = this.safeName_(name); - var i = ''; - while (this.dbReverse_[safeName + i] || - (safeName + i) in this.reservedDict_) { - // Collision with existing name. Create a unique name. - i = i ? i + 1 : 2; - } - safeName += i; - this.dbReverse_[safeName] = true; - var prefix = (type == Blockly.Variables.NAME_TYPE) ? - this.variablePrefix_ : ''; - return prefix + safeName; -}; - -/** - * Given a proposed entity name, generate a name that conforms to the - * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for - * variables. - * @param {string} name Potentially illegal entity name. - * @return {string} Safe entity name. - * @private - */ -Blockly.Names.prototype.safeName_ = function(name) { - if (!name) { - name = 'unnamed'; - } else { - // Unfortunately names in non-latin characters will look like - // _E9_9F_B3_E4_B9_90 which is pretty meaningless. - name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); - // Most languages don't allow names with leading numbers. - if ('0123456789'.indexOf(name[0]) != -1) { - name = 'my_' + name; - } - } - return name; -}; - -/** - * Do the given two entity names refer to the same entity? - * Blockly names are case-insensitive. - * @param {string} name1 First name. - * @param {string} name2 Second name. - * @return {boolean} True if names are the same. - */ -Blockly.Names.equals = function(name1, name2) { - return name1.toLowerCase() == name2.toLowerCase(); -}; +/** + * @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 and procedure names. + * @author fraser@google.com (Neil Fraser) + */ +'use strict'; + +goog.provide('Blockly.Names'); + + +/** + * Class for a database of entity names (variables, functions, etc). + * @param {string} reservedWords A comma-separated string of words that are + * illegal for use as names in a language (e.g. 'new,if,this,...'). + * @param {string=} opt_variablePrefix Some languages need a '$' or a namespace + * before all variable names. + * @constructor + */ +Blockly.Names = function(reservedWords, opt_variablePrefix) { + this.variablePrefix_ = opt_variablePrefix || ''; + this.reservedDict_ = Object.create(null); + if (reservedWords) { + var splitWords = reservedWords.split(','); + for (var i = 0; i < splitWords.length; i++) { + this.reservedDict_[splitWords[i]] = true; + } + } + this.reset(); +}; + +/** + * Constant to separate developer variable names from user-defined variable + * names when running generators. + * A developer variable will be declared as a global in the generated code, but + * will never be shown to the user in the workspace or stored in the variable + * map. + */ +Blockly.Names.DEVELOPER_VARIABLE_TYPE = 'DEVELOPER_VARIABLE'; + +/** + * When JavaScript (or most other languages) is generated, variable 'foo' and + * procedure 'foo' would collide. However, Blockly has no such problems since + * variable get 'foo' and procedure call 'foo' are unambiguous. + * Therefore, Blockly keeps a separate type name to disambiguate. + * getName('foo', 'variable') -> 'foo' + * getName('foo', 'procedure') -> 'foo2' + */ + +/** + * Empty the database and start from scratch. The reserved words are kept. + */ +Blockly.Names.prototype.reset = function() { + this.db_ = Object.create(null); + this.dbReverse_ = Object.create(null); + this.variableMap_ = null; +}; + +/** + * Set the variable map that maps from variable name to variable object. + * @param {!Blockly.VariableMap} map The map to track. + * @package + */ +Blockly.Names.prototype.setVariableMap = function(map) { + this.variableMap_ = map; +}; + +/** + * Get the name for a user-defined variable, based on its ID. + * This should only be used for variables of type Blockly.Variables.NAME_TYPE. + * @param {string} id The ID to look up in the variable map. + * @return {?string} The name of the referenced variable, or null if there was + * no variable map or the variable was not found in the map. + * @private + */ +Blockly.Names.prototype.getNameForUserVariable_ = function(id) { + if (!this.variableMap_) { + console.log('Deprecated call to Blockly.Names.prototype.getName without ' + + 'defining a variable map. To fix, add the folowing code in your ' + + 'generator\'s init() function:\n' + + 'Blockly.YourGeneratorName.variableDB_.setVariableMap(' + + 'workspace.getVariableMap());'); + return null; + } + var variable = this.variableMap_.getVariableById(id); + if (variable) { + return variable.name; + } else { + return null; + } +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name that is legal in the exported language. + */ +Blockly.Names.prototype.getName = function(name, type) { + if (type == Blockly.Variables.NAME_TYPE) { + var varName = this.getNameForUserVariable_(name); + if (varName) { + name = varName; + } + } + var normalized = name.toLowerCase() + '_' + type; + + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + + var prefix = isVarType ? this.variablePrefix_ : ''; + if (normalized in this.db_) { + return prefix + this.db_[normalized]; + } + var safeName = this.getDistinctName(name, type); + this.db_[normalized] = safeName.substr(prefix.length); + return safeName; +}; + +/** + * Convert a Blockly entity name to a legal exportable entity name. + * Ensure that this is a new name not overlapping any previously defined name. + * Also check against list of reserved words for the current language and + * ensure name doesn't collide. + * @param {string} name The Blockly entity name (no constraints). + * @param {string} type The type of entity in Blockly + * ('VARIABLE', 'PROCEDURE', 'BUILTIN', etc...). + * @return {string} An entity name that is legal in the exported language. + */ +Blockly.Names.prototype.getDistinctName = function(name, type) { + var safeName = this.safeName_(name); + var i = ''; + while (this.dbReverse_[safeName + i] || + (safeName + i) in this.reservedDict_) { + // Collision with existing name. Create a unique name. + i = i ? i + 1 : 2; + } + safeName += i; + this.dbReverse_[safeName] = true; + var isVarType = type == Blockly.Variables.NAME_TYPE || + type == Blockly.Names.DEVELOPER_VARIABLE_TYPE; + var prefix = isVarType ? this.variablePrefix_ : ''; + return prefix + safeName; +}; + +/** + * Given a proposed entity name, generate a name that conforms to the + * [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for + * variables. + * @param {string} name Potentially illegal entity name. + * @return {string} Safe entity name. + * @private + */ +Blockly.Names.prototype.safeName_ = function(name) { + if (!name) { + name = 'unnamed'; + } else { + // Unfortunately names in non-latin characters will look like + // _E9_9F_B3_E4_B9_90 which is pretty meaningless. + name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_'); + // Most languages don't allow names with leading numbers. + if ('0123456789'.indexOf(name[0]) != -1) { + name = 'my_' + name; + } + } + return name; +}; + +/** + * Do the given two entity names refer to the same entity? + * Blockly names are case-insensitive. + * @param {string} name1 First name. + * @param {string} name2 Second name. + * @return {boolean} True if names are the same. + */ +Blockly.Names.equals = function(name1, name2) { + return name1.toLowerCase() == name2.toLowerCase(); +}; diff --git a/core/options.js b/core/options.js index d8da08b..f9ab040 100644 --- a/core/options.js +++ b/core/options.js @@ -31,7 +31,7 @@ goog.provide('Blockly.Options'); * Parse the user-specified options, using reasonable defaults where behaviour * is unspecified. * @param {!Object} options Dictionary of options. Specification: - * https://developers.google.com/blockly/installation/overview#configuration + * https://developers.google.com/blockly/guides/get-started/web#configuration * @constructor */ Blockly.Options = function(options) { @@ -104,8 +104,14 @@ Blockly.Options = function(options) { // 'path' is a deprecated option which has been replaced by 'media'. pathToMedia = options['path'] + 'media/'; } + if (options['oneBasedIndex'] === undefined) { + var oneBasedIndex = true; + } else { + var oneBasedIndex = !!options['oneBasedIndex']; + } this.RTL = rtl; + this.oneBasedIndex = oneBasedIndex; this.collapse = hasCollapse; this.comments = hasComments; this.disable = hasDisable; @@ -125,28 +131,26 @@ Blockly.Options = function(options) { }; /** - * @type {Blockly.Workspace} the parent of the current workspace, or null if - * there is no parent workspace. + * The parent of the current workspace, or null if there is no parent workspace. + * @type {Blockly.Workspace} **/ Blockly.Options.prototype.parentWorkspace = null; /** * If set, sets the translation of the workspace to match the scrollbars. - * No-op if unset. */ -Blockly.Options.prototype.setMetrics = function(translation) { return; }; +Blockly.Options.prototype.setMetrics = null; /** - * Return an object with the metrics required to size the workspace, or null - * if unset. - * @return {Object} Contains size an position metrics, or null. + * Return an object with the metrics required to size the workspace. + * @return {Object} Contains size and position metrics, or null. */ -Blockly.Options.prototype.getMetrics = function() { return null; }; +Blockly.Options.prototype.getMetrics = null; /** * Parse the user-specified zoom options, using reasonable defaults where * behaviour is unspecified. See zoom documentation: - * https://developers.google.com/blockly/installation/zoom + * https://developers.google.com/blockly/guides/configure/web/zoom * @param {!Object} options Dictionary of options. * @return {!Object} A dictionary of normalized options. * @private @@ -190,7 +194,7 @@ Blockly.Options.parseZoomOptions_ = function(options) { /** * Parse the user-specified grid options, using reasonable defaults where * behaviour is unspecified. See grid documentation: - * https://developers.google.com/blockly/installation/grid + * https://developers.google.com/blockly/guides/configure/web/grid * @param {!Object} options Dictionary of options. * @return {!Object} A dictionary of normalized options. * @private diff --git a/core/procedures.js b/core/procedures.js index 59e7fd5..98ebf41 100644 --- a/core/procedures.js +++ b/core/procedures.js @@ -24,19 +24,25 @@ */ 'use strict'; +/** + * @name Blockly.Procedures + * @namespace + **/ goog.provide('Blockly.Procedures'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); goog.require('Blockly.Field'); goog.require('Blockly.Names'); goog.require('Blockly.Workspace'); /** - * Category to separate procedure names from variables and generated functions. + * Constant to separate procedure names from variables and generated functions + * when running generators. + * @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME */ -Blockly.Procedures.NAME_TYPE = 'PROCEDURE'; +Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME; /** * Find all user-created procedure definitions in a workspace. @@ -90,7 +96,7 @@ Blockly.Procedures.findLegalName = function(name, block) { // Flyouts can have multiple procedures called 'do something'. return name; } - while (!Blockly.Procedures.isLegalName(name, block.workspace, block)) { + while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) { // Collision with another procedure. var r = name.match(/^(.*?)(\d+)$/); if (!r) { @@ -110,8 +116,21 @@ Blockly.Procedures.findLegalName = function(name, block) { * @param {Blockly.Block=} opt_exclude Optional block to exclude from * comparisons (one doesn't want to collide with oneself). * @return {boolean} True if the name is legal. + * @private */ -Blockly.Procedures.isLegalName = function(name, workspace, opt_exclude) { +Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) { + return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude); +}; + +/** + * Return if the given name is already a procedure name. + * @param {string} name The questionable name. + * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. + * @param {Blockly.Block=} opt_exclude Optional block to exclude from + * comparisons (one doesn't want to collide with oneself). + * @return {boolean} True if the name is used, otherwise return false. + */ +Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) { var blocks = workspace.getAllBlocks(); // Iterate through every block and check the name. for (var i = 0; i < blocks.length; i++) { @@ -121,33 +140,36 @@ Blockly.Procedures.isLegalName = function(name, workspace, opt_exclude) { if (blocks[i].getProcedureDef) { var procName = blocks[i].getProcedureDef(); if (Blockly.Names.equals(procName[0], name)) { - return false; + return true; } } } - return true; + return false; }; /** * Rename a procedure. Called by the editable field. - * @param {string} text The proposed new name. + * @param {string} name The proposed new name. * @return {string} The accepted name. - * @this {!Blockly.Field} + * @this {Blockly.Field} */ -Blockly.Procedures.rename = function(text) { +Blockly.Procedures.rename = function(name) { // Strip leading and trailing whitespace. Beyond this, all names are legal. - text = text.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); + name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); // Ensure two identically-named procedures don't exist. - text = Blockly.Procedures.findLegalName(text, this.sourceBlock_); - // Rename any callers. - var blocks = this.sourceBlock_.workspace.getAllBlocks(); - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].renameProcedure) { - blocks[i].renameProcedure(this.text_, text); + var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_); + var oldName = this.text_; + if (oldName != name && oldName != legalName) { + // Rename any callers. + var blocks = this.sourceBlock_.workspace.getAllBlocks(); + for (var i = 0; i < blocks.length; i++) { + if (blocks[i].renameProcedure) { + blocks[i].renameProcedure(oldName, legalName); + } } } - return text; + return legalName; }; /** @@ -157,27 +179,30 @@ Blockly.Procedures.rename = function(text) { */ Blockly.Procedures.flyoutCategory = function(workspace) { var xmlList = []; - if (Blockly.Blocks['InterruptIn']) { - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'InterruptIn'); - block.setAttribute('gap', 16); - // If this parent block present already in the workspace show as disabled - var workspaceTopBlocks = workspace.getTopBlocks(); - xmlList.push(block); - } if (Blockly.Blocks['procedures_defnoreturn']) { - // + // + // do something + // var block = goog.dom.createDom('block'); block.setAttribute('type', 'procedures_defnoreturn'); block.setAttribute('gap', 16); + var nameField = goog.dom.createDom('field', null, + Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE); + nameField.setAttribute('name', 'NAME'); + block.appendChild(nameField); xmlList.push(block); } if (Blockly.Blocks['procedures_defreturn']) { - // + // + // do something + // var block = goog.dom.createDom('block'); block.setAttribute('type', 'procedures_defreturn'); block.setAttribute('gap', 16); + var nameField = goog.dom.createDom('field', null, + Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE); + nameField.setAttribute('name', 'NAME'); + block.appendChild(nameField); xmlList.push(block); } if (Blockly.Blocks['procedures_ifreturn']) { @@ -207,9 +232,9 @@ Blockly.Procedures.flyoutCategory = function(workspace) { var mutation = goog.dom.createDom('mutation'); mutation.setAttribute('name', name); block.appendChild(mutation); - for (var t = 0; t < args.length; t++) { + for (var j = 0; j < args.length; j++) { var arg = goog.dom.createDom('arg'); - arg.setAttribute('name', args[t]); + arg.setAttribute('name', args[j]); mutation.appendChild(arg); } xmlList.push(block); @@ -244,19 +269,6 @@ Blockly.Procedures.getCallers = function(name, workspace) { return callers; }; -/** - * When a procedure definition is disposed of, find and dispose of all its - * callers. - * @param {string} name Name of deleted procedure definition. - * @param {!Blockly.Workspace} workspace The workspace to delete callers from. - */ -Blockly.Procedures.disposeCallers = function(name, workspace) { - var callers = Blockly.Procedures.getCallers(name, workspace); - for (var i = 0; i < callers.length; i++) { - callers[i].dispose(true, false); - } -}; - /** * When a procedure definition changes its parameters, find and edit all its * callers. @@ -278,7 +290,7 @@ Blockly.Procedures.mutateCallers = function(defBlock) { // undo action since it is deterministically tied to the procedure's // definition mutation. Blockly.Events.recordUndo = false; - Blockly.Events.fire(new Blockly.Events.Change( + Blockly.Events.fire(new Blockly.Events.BlockChange( caller, 'mutation', null, oldMutation, newMutation)); Blockly.Events.recordUndo = oldRecordUndo; } @@ -292,7 +304,8 @@ Blockly.Procedures.mutateCallers = function(defBlock) { * @return {Blockly.Block} The procedure definition block, or null not found. */ Blockly.Procedures.getDefinition = function(name, workspace) { - var blocks = workspace.getAllBlocks(); + // Assume that a procedure definition is a top block. + var blocks = workspace.getTopBlocks(false); for (var i = 0; i < blocks.length; i++) { if (blocks[i].getProcedureDef) { var tuple = blocks[i].getProcedureDef(); diff --git a/core/rendered_connection.js b/core/rendered_connection.js index 9c3d90c..cfd0473 100644 --- a/core/rendered_connection.js +++ b/core/rendered_connection.js @@ -33,18 +33,27 @@ goog.require('Blockly.Connection'); * Class for a connection between blocks that may be rendered on screen. * @param {!Blockly.Block} source The block establishing this connection. * @param {number} type The type of the connection. + * @extends {Blockly.Connection} * @constructor */ Blockly.RenderedConnection = function(source, type) { Blockly.RenderedConnection.superClass_.constructor.call(this, source, type); + + /** + * Workspace units, (0, 0) is top left of block. + * @type {!goog.math.Coordinate} + * @private + */ + this.offsetInBlock_ = new goog.math.Coordinate(0, 0); }; goog.inherits(Blockly.RenderedConnection, Blockly.Connection); /** - * Returns the distance between this connection and another connection. + * Returns the distance between this connection and another connection in + * workspace units. * @param {!Blockly.Connection} otherConnection The other connection to measure * the distance to. - * @return {number} The distance between connections. + * @return {number} The distance between connections, in workspace units. */ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) { var xDiff = this.x_ - otherConnection.x_; @@ -60,7 +69,7 @@ Blockly.RenderedConnection.prototype.distanceFrom = function(otherConnection) { * @private */ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) { - if (Blockly.dragMode_ != Blockly.DRAG_NONE) { + if (this.sourceBlock_.workspace.isDragging()) { // Don't move blocks around while the user is doing the same. return; } @@ -83,7 +92,8 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) reverse = true; } // Raise it to the top for extra visibility. - rootBlock.getSvgRoot().parentNode.appendChild(rootBlock.getSvgRoot()); + var selected = Blockly.selected == rootBlock; + selected || rootBlock.addSelect(); var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_; var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_; if (reverse) { @@ -94,12 +104,13 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection) dx = -dx; } rootBlock.moveBy(dx, dy); + selected || rootBlock.removeSelect(); }; /** * Change the connection's coordinates. - * @param {number} x New absolute x coordinate. - * @param {number} y New absolute y coordinate. + * @param {number} x New absolute x coordinate, in workspace coordinates. + * @param {number} y New absolute y coordinate, in workspace coordinates. */ Blockly.RenderedConnection.prototype.moveTo = function(x, y) { // Remove it from its old location in the database (if already present) @@ -116,13 +127,34 @@ Blockly.RenderedConnection.prototype.moveTo = function(x, y) { /** * Change the connection's coordinates. - * @param {number} dx Change to x coordinate. - * @param {number} dy Change to y coordinate. + * @param {number} dx Change to x coordinate, in workspace units. + * @param {number} dy Change to y coordinate, in workspace units. */ Blockly.RenderedConnection.prototype.moveBy = function(dx, dy) { this.moveTo(this.x_ + dx, this.y_ + dy); }; +/** + * Move this connection to the location given by its offset within the block and + * the location of the block's top left corner. + * @param {!goog.math.Coordinate} blockTL The location of the top left corner + * of the block, in workspace coordinates. + */ +Blockly.RenderedConnection.prototype.moveToOffset = function(blockTL) { + this.moveTo(blockTL.x + this.offsetInBlock_.x, + blockTL.y + this.offsetInBlock_.y); +}; + +/** + * Set the offset of this connection relative to the top left of its block. + * @param {number} x The new relative x, in workspace units. + * @param {number} y The new relative y, in workspace units. + */ +Blockly.RenderedConnection.prototype.setOffsetInBlock = function(x, y) { + this.offsetInBlock_.x = x; + this.offsetInBlock_.y = y; +}; + /** * Move the blocks on either side of this connection right next to each other. * @private @@ -136,7 +168,8 @@ Blockly.RenderedConnection.prototype.tighten_ = function() { if (!svgRoot) { throw 'block is not rendered.'; } - var xy = Blockly.getRelativeXY_(svgRoot); + // Workspace coordinates. + var xy = Blockly.utils.getRelativeXY(svgRoot); block.getSvgRoot().setAttribute('transform', 'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')'); block.moveConnections_(-dx, -dy); @@ -145,6 +178,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() { /** * Find the closest compatible connection to this connection. + * All parameters are in workspace units. * @param {number} maxLimit The maximum radius to another connection. * @param {number} dx Horizontal offset between this connection's location * in the database and the current location (as a result of dragging). @@ -164,21 +198,21 @@ Blockly.RenderedConnection.prototype.closest = function(maxLimit, dx, dy) { Blockly.RenderedConnection.prototype.highlight = function() { var steps; if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) { - var tabWidth = this.sourceBlock_.RTL ? -Blockly.BlockSvg.TAB_WIDTH : - Blockly.BlockSvg.TAB_WIDTH; steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5'; - } else { steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5'; } var xy = this.sourceBlock_.getRelativeToSurfaceXY(); var x = this.x_ - xy.x; var y = this.y_ - xy.y; - Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path', - {'class': 'blocklyHighlightedConnectionPath', - 'd': steps, - transform: 'translate(' + x + ',' + y + ')' + - (this.sourceBlock_.RTL ? ' scale(-1 1)' : '')}, + Blockly.Connection.highlightedPath_ = Blockly.utils.createSvgElement( + 'path', + { + 'class': 'blocklyHighlightedConnectionPath', + 'd': steps, + transform: 'translate(' + x + ',' + y + ')' + + (this.sourceBlock_.RTL ? ' scale(-1 1)' : '') + }, this.sourceBlock_.getSvgRoot()); }; @@ -212,8 +246,8 @@ Blockly.RenderedConnection.prototype.unhideAll = function() { // Show all connections of this block. connections = block.getConnections_(true); } - for (var c = 0; c < connections.length; c++) { - renderList.push.apply(renderList, connections[c].unhideAll()); + for (var i = 0; i < connections.length; i++) { + renderList.push.apply(renderList, connections[i].unhideAll()); } if (!renderList.length) { // Leaf block. @@ -253,17 +287,17 @@ Blockly.RenderedConnection.prototype.hideAll = function() { this.setHidden(true); if (this.targetConnection) { var blocks = this.targetBlock().getDescendants(); - for (var b = 0; b < blocks.length; b++) { - var block = blocks[b]; + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; // Hide all connections of all children. var connections = block.getConnections_(true); - for (var c = 0; c < connections.length; c++) { - connections[c].setHidden(true); + for (var j = 0; j < connections.length; j++) { + connections[j].setHidden(true); } // Close all bubbles of all children. var icons = block.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].setVisible(false); + for (var j = 0; j < icons.length; j++) { + icons[j].setVisible(false); } } } @@ -272,7 +306,8 @@ Blockly.RenderedConnection.prototype.hideAll = function() { /** * Check if the two connections can be dragged to connect to each other. * @param {!Blockly.Connection} candidate A nearby connection to check. - * @param {number} maxRadius The maximum radius allowed for connections. + * @param {number} maxRadius The maximum radius allowed for connections, in + * workspace units. * @return {boolean} True if the connection is allowed, false otherwise. */ Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate, @@ -315,8 +350,8 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() { // Respawn the shadow block if there is one. var shadow = this.getShadowDom(); if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { - var blockShadow = - Blockly.RenderedConnection.superClass_.respawnShadow_.call(this); + Blockly.RenderedConnection.superClass_.respawnShadow_.call(this); + var blockShadow = this.targetBlock(); if (!blockShadow) { throw 'Couldn\'t respawn the shadow block that should exist here.'; } @@ -331,7 +366,8 @@ Blockly.RenderedConnection.prototype.respawnShadow_ = function() { /** * Find all nearby compatible connections to this connection. * Type checking does not apply, since this function is used for bumping. - * @param {number} maxLimit The maximum radius to another connection. + * @param {number} maxLimit The maximum radius to another connection, in + * workspace units. * @return {!Array.} List of connections. * @private */ @@ -371,3 +407,17 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) { } } }; + +/** + * Function to be called when this connection's compatible types have changed. + * @private + */ +Blockly.RenderedConnection.prototype.onCheckChanged_ = function() { + // The new value type may not be compatible with the existing connection. + if (this.isConnected() && !this.checkType_(this.targetConnection)) { + var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; + child.unplug(); + // Bump away. + this.sourceBlock_.bumpNeighbours_(); + } +}; diff --git a/core/scrollbar.js b/core/scrollbar.js index b97fe35..ec0458e 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -31,6 +31,11 @@ goog.require('goog.dom'); goog.require('goog.events'); +/** + * A note on units: most of the numbers that are in CSS pixels are scaled if the + * scrollbar is in a mutator. + */ + /** * Class for a pair of scrollbars. Horizontal and vertical. * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to. @@ -38,13 +43,19 @@ goog.require('goog.events'); */ Blockly.ScrollbarPair = function(workspace) { this.workspace_ = workspace; - this.hScroll = new Blockly.Scrollbar(workspace, true, true); - this.vScroll = new Blockly.Scrollbar(workspace, false, true); - this.corner_ = Blockly.createSvgElement('rect', - {'height': Blockly.Scrollbar.scrollbarThickness, - 'width': Blockly.Scrollbar.scrollbarThickness, - 'class': 'blocklyScrollbarBackground'}, null); - Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas()); + this.hScroll = new Blockly.Scrollbar( + workspace, true, true, 'blocklyMainWorkspaceScrollbar'); + this.vScroll = new Blockly.Scrollbar( + workspace, false, true, 'blocklyMainWorkspaceScrollbar'); + this.corner_ = Blockly.utils.createSvgElement( + 'rect', + { + 'height': Blockly.Scrollbar.scrollbarThickness, + 'width': Blockly.Scrollbar.scrollbarThickness, + 'class': 'blocklyScrollbarBackground' + }, + null); + Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas()); }; /** @@ -118,12 +129,12 @@ Blockly.ScrollbarPair.prototype.resize = function() { if (!this.oldHostMetrics_ || this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { - this.corner_.setAttribute('x', this.vScroll.xCoordinate); + this.corner_.setAttribute('x', this.vScroll.position_.x); } if (!this.oldHostMetrics_ || this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) { - this.corner_.setAttribute('y', this.hScroll.yCoordinate); + this.corner_.setAttribute('y', this.hScroll.position_.y); } // Cache the current metrics to potentially short-cut the next resize event. @@ -131,7 +142,8 @@ Blockly.ScrollbarPair.prototype.resize = function() { }; /** - * Set the sliders of both scrollbars to be at a certain position. + * Set the handles of both scrollbars to be at a certain position in CSS pixels + * relative to their parents. * @param {number} x Horizontal scroll value. * @param {number} y Vertical scroll value. */ @@ -144,31 +156,29 @@ Blockly.ScrollbarPair.prototype.set = function(x, y) { // Combining them speeds up rendering. var xyRatio = {}; - var hKnobValue = x * this.hScroll.ratio_; - var vKnobValue = y * this.vScroll.ratio_; + var hHandlePosition = x * this.hScroll.ratio_; + var vHandlePosition = y * this.vScroll.ratio_; - var hBarLength = - parseFloat(this.hScroll.svgBackground_.getAttribute('width')); - var vBarLength = - parseFloat(this.vScroll.svgBackground_.getAttribute('height')); - - xyRatio.x = this.getRatio_(hKnobValue, hBarLength); - xyRatio.y = this.getRatio_(vKnobValue, vBarLength); + var hBarLength = this.hScroll.scrollViewSize_; + var vBarLength = this.vScroll.scrollViewSize_; + xyRatio.x = this.getRatio_(hHandlePosition, hBarLength); + xyRatio.y = this.getRatio_(vHandlePosition, vBarLength); this.workspace_.setMetrics(xyRatio); - this.hScroll.svgKnob_.setAttribute('x', hKnobValue); - this.vScroll.svgKnob_.setAttribute('y', vKnobValue); + + this.hScroll.setHandlePosition(hHandlePosition); + this.vScroll.setHandlePosition(vHandlePosition); }; /** - * Helper to calculate the ratio of knob value to bar length. - * @param {number} knobValue The value of the knob. - * @param {number} barLength The length of the scroll bar. + * Helper to calculate the ratio of handle position to scrollbar view size. + * @param {number} handlePosition The value of the handle. + * @param {number} viewSize The total size of the scrollbar's view. * @return {number} Ratio. * @private */ -Blockly.ScrollbarPair.prototype.getRatio_ = function(knobValue, barLength) { - var ratio = knobValue / barLength; +Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) { + var ratio = handlePosition / viewSize; if (isNaN(ratio)) { return 0; } @@ -184,63 +194,223 @@ Blockly.ScrollbarPair.prototype.getRatio_ = function(knobValue, barLength) { * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to. * @param {boolean} horizontal True if horizontal, false if vertical. * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. + * @param {string=} opt_class A class to be applied to this scrollbar. * @constructor */ -Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { +Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) { this.workspace_ = workspace; this.pair_ = opt_pair || false; this.horizontal_ = horizontal; + this.oldHostMetrics_ = null; + + this.createDom_(opt_class); - this.createDom_(); + /** + * The upper left corner of the scrollbar's SVG group in CSS pixels relative + * to the scrollbar's origin. This is usually relative to the injection div + * origin. + * @type {goog.math.Coordinate} + * @private + */ + this.position_ = new goog.math.Coordinate(0, 0); + // Store the thickness in a temp variable for readability. + var scrollbarThickness = Blockly.Scrollbar.scrollbarThickness; if (horizontal) { - this.svgBackground_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness); - this.svgKnob_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness - 5); - this.svgKnob_.setAttribute('y', 2.5); + this.svgBackground_.setAttribute('height', scrollbarThickness); + this.outerSvg_.setAttribute('height', scrollbarThickness); + this.svgHandle_.setAttribute('height', scrollbarThickness - 5); + this.svgHandle_.setAttribute('y', 2.5); + + this.lengthAttribute_ = 'width'; + this.positionAttribute_ = 'x'; } else { - this.svgBackground_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness); - this.svgKnob_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness - 5); - this.svgKnob_.setAttribute('x', 2.5); + this.svgBackground_.setAttribute('width', scrollbarThickness); + this.outerSvg_.setAttribute('width', scrollbarThickness); + this.svgHandle_.setAttribute('width', scrollbarThickness - 5); + this.svgHandle_.setAttribute('x', 2.5); + + this.lengthAttribute_ = 'height'; + this.positionAttribute_ = 'y'; } var scrollbar = this; - this.onMouseDownBarWrapper_ = Blockly.bindEvent_(this.svgBackground_, - 'mousedown', scrollbar, scrollbar.onMouseDownBar_); - this.onMouseDownKnobWrapper_ = Blockly.bindEvent_(this.svgKnob_, - 'mousedown', scrollbar, scrollbar.onMouseDownKnob_); + this.onMouseDownBarWrapper_ = Blockly.bindEventWithChecks_( + this.svgBackground_, 'mousedown', scrollbar, scrollbar.onMouseDownBar_); + this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_, + 'mousedown', scrollbar, scrollbar.onMouseDownHandle_); }; /** - * Width of vertical scrollbar or height of horizontal scrollbar. - * Increase the size of scrollbars on touch devices. - * Don't define if there is no document object (e.g. node.js). + * The location of the origin of the workspace that the scrollbar is in, + * measured in CSS pixels relative to the injection div origin. This is usually + * (0, 0). When the scrollbar is in a flyout it may have a different origin. + * @type {goog.math.Coordinate} + * @private + */ +Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0); + +/** + * The position of the mouse along this scrollbar's major axis at the start of + * the most recent drag. + * Units are CSS pixels, with (0, 0) at the top left of the browser window. + * For a horizontal scrollbar this is the x coordinate of the mouse down event; + * for a vertical scrollbar it's the y coordinate of the mouse down event. + * @type {goog.math.Coordinate} + */ +Blockly.Scrollbar.prototype.startDragMouse_ = 0; + +/** + * The size of the area within which the scrollbar handle can move, in CSS + * pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.scrollViewSize_ = 0; + +/** + * The length of the scrollbar handle in CSS pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handleLength_ = 0; + +/** + * The offset of the start of the handle from the scrollbar position, in CSS + * pixels. + * @type {number} + * @private + */ +Blockly.Scrollbar.prototype.handlePosition_ = 0; + +/** + * Whether the scrollbar handle is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.isVisible_ = true; + +/** + * Whether the workspace containing this scrollbar is visible. + * @type {boolean} + * @private + */ +Blockly.Scrollbar.prototype.containerVisible_ = true; + +/** + * Width of vertical scrollbar or height of horizontal scrollbar in CSS pixels. + * Scrollbars should be larger on touch devices. */ Blockly.Scrollbar.scrollbarThickness = 15; if (goog.events.BrowserFeature.TOUCH_ENABLED) { Blockly.Scrollbar.scrollbarThickness = 25; } +/** + * @param {!Object} first An object containing computed measurements of a + * workspace. + * @param {!Object} second Another object containing computed measurements of a + * workspace. + * @return {boolean} Whether the two sets of metrics are equivalent. + * @private + */ +Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) { + if (!(first && second)) { + return false; + } + + if (first.viewWidth != second.viewWidth || + first.viewHeight != second.viewHeight || + first.viewLeft != second.viewLeft || + first.viewTop != second.viewTop || + first.absoluteTop != second.absoluteTop || + first.absoluteLeft != second.absoluteLeft || + first.contentWidth != second.contentWidth || + first.contentHeight != second.contentHeight || + first.contentLeft != second.contentLeft || + first.contentTop != second.contentTop) { + return false; + } + + return true; +}; + /** * Dispose of this scrollbar. * Unlink from all DOM elements to prevent memory leaks. */ Blockly.Scrollbar.prototype.dispose = function() { - this.onMouseUpKnob_(); + this.cleanUp_(); Blockly.unbindEvent_(this.onMouseDownBarWrapper_); this.onMouseDownBarWrapper_ = null; - Blockly.unbindEvent_(this.onMouseDownKnobWrapper_); - this.onMouseDownKnobWrapper_ = null; + Blockly.unbindEvent_(this.onMouseDownHandleWrapper_); + this.onMouseDownHandleWrapper_ = null; - goog.dom.removeNode(this.svgGroup_); + goog.dom.removeNode(this.outerSvg_); + this.outerSvg_ = null; this.svgGroup_ = null; this.svgBackground_ = null; - this.svgKnob_ = null; + this.svgHandle_ = null; this.workspace_ = null; }; +/** + * Set the length of the scrollbar's handle and change the SVG attribute + * accordingly. + * @param {number} newLength The new scrollbar handle length in CSS pixels. + */ +Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) { + this.handleLength_ = newLength; + this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_); +}; + +/** + * Set the offset of the scrollbar's handle from the scrollbar's position, and + * change the SVG attribute accordingly. + * @param {number} newPosition The new scrollbar handle offset in CSS pixels. + */ +Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) { + this.handlePosition_ = newPosition; + this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_); +}; + +/** + * Set the size of the scrollbar's background and change the SVG attribute + * accordingly. + * @param {number} newSize The new scrollbar background length in CSS pixels. + * @private + */ +Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) { + this.scrollViewSize_ = newSize; + this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); + this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); +}; + +/** + * Set whether this scrollbar's container is visible. + * @param {boolean} visible Whether the container is visible. + */ +Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) { + this.hScroll.setContainerVisible(visible); + this.vScroll.setContainerVisible(visible); +}; + +/** + * Set the position of the scrollbar's SVG group in CSS pixels relative to the + * scrollbar's origin. This sets the scrollbar's location within the workspace. + * @param {number} x The new x coordinate. + * @param {number} y The new y coordinate. + * @private + */ +Blockly.Scrollbar.prototype.setPosition_ = function(x, y) { + this.position_.x = x; + this.position_.y = y; + + var tempX = this.position_.x + this.origin_.x; + var tempY = this.position_.y + this.origin_.y; + var transform = 'translate(' + tempX + 'px,' + tempY + 'px)'; + Blockly.utils.setCssTransform(this.outerSvg_, transform); +}; + /** * Recalculate the scrollbar's location and its length. * @param {Object=} opt_metrics A data structure of from the describing all the @@ -257,6 +427,13 @@ Blockly.Scrollbar.prototype.resize = function(opt_metrics) { return; } } + + if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics, + this.oldHostMetrics_)) { + return; + } + this.oldHostMetrics_ = hostMetrics; + /* hostMetrics is an object with the following properties. * .viewHeight: Height of the visible rectangle, * .viewWidth: Width of the visible rectangle, @@ -285,36 +462,66 @@ Blockly.Scrollbar.prototype.resize = function(opt_metrics) { * @private */ Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) { - var outerLength = hostMetrics.viewWidth - 1; + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) { + var viewSize = hostMetrics.viewWidth - 1; if (this.pair_) { // Shorten the scrollbar to make room for the corner square. - outerLength -= Blockly.Scrollbar.scrollbarThickness; - } else { + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (this.pair_ && this.workspace_.RTL) { + xCoordinate += Blockly.Scrollbar.scrollbarThickness; + } + + // Horizontal toolbar should always be just above the bottom of the workspace. + var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - + Blockly.Scrollbar.scrollbarThickness - 0.5; + this.setPosition_(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentHorizontal(hostMetrics); +}; + +/** + * Recalculate a horizontal scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) { + if (!this.pair_) { // Only show the scrollbar if needed. // Ideally this would also apply to scrollbar pairs, but that's a bigger // headache (due to interactions with the corner square). - this.setVisible(outerLength < hostMetrics.contentWidth); + this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth); } - this.ratio_ = outerLength / hostMetrics.contentWidth; - if (this.ratio_ === -Infinity || this.ratio_ === Infinity || + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || isNaN(this.ratio_)) { this.ratio_ = 0; } - var innerLength = hostMetrics.viewWidth * this.ratio_; - var innerOffset = (hostMetrics.viewLeft - hostMetrics.contentLeft) * + + var handleLength = hostMetrics.viewWidth * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) * this.ratio_; - this.svgKnob_.setAttribute('width', Math.max(0, innerLength)); - this.xCoordinate = hostMetrics.absoluteLeft + 0.5; - if (this.pair_ && this.workspace_.RTL) { - this.xCoordinate += Blockly.Scrollbar.scrollbarThickness; - } - // Horizontal toolbar should always be just above the bottom of the workspace. - this.yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - - Blockly.Scrollbar.scrollbarThickness - 0.5; - this.svgGroup_.setAttribute('transform', - 'translate(' + this.xCoordinate + ',' + this.yCoordinate + ')'); - this.svgBackground_.setAttribute('width', Math.max(0, outerLength)); - this.svgKnob_.setAttribute('x', this.constrainKnob_(innerOffset)); + this.setHandlePosition(this.constrainHandle_(handlePosition)); }; /** @@ -324,58 +531,99 @@ Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) { * @private */ Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) { - var outerLength = hostMetrics.viewHeight - 1; + // TODO: Inspect metrics to determine if we can get away with just a content + // resize. + this.resizeViewVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location on the screen and path length. + * This should be called when the layout or size of the window has changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) { + var viewSize = hostMetrics.viewHeight - 1; if (this.pair_) { // Shorten the scrollbar to make room for the corner square. - outerLength -= Blockly.Scrollbar.scrollbarThickness; - } else { + viewSize -= Blockly.Scrollbar.scrollbarThickness; + } + this.setScrollViewSize_(Math.max(0, viewSize)); + + var xCoordinate = hostMetrics.absoluteLeft + 0.5; + if (!this.workspace_.RTL) { + xCoordinate += hostMetrics.viewWidth - + Blockly.Scrollbar.scrollbarThickness - 1; + } + var yCoordinate = hostMetrics.absoluteTop + 0.5; + this.setPosition_(xCoordinate, yCoordinate); + + // If the view has been resized, a content resize will also be necessary. The + // reverse is not true. + this.resizeContentVertical(hostMetrics); +}; + +/** + * Recalculate a vertical scrollbar's location within its path and length. + * This should be called when the contents of the workspace have changed. + * @param {!Object} hostMetrics A data structure describing all the + * required dimensions, possibly fetched from the host object. + */ +Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) { + if (!this.pair_) { // Only show the scrollbar if needed. - this.setVisible(outerLength < hostMetrics.contentHeight); + this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight); } - this.ratio_ = outerLength / hostMetrics.contentHeight; - if (this.ratio_ === -Infinity || this.ratio_ === Infinity || + + this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight; + if (this.ratio_ == -Infinity || this.ratio_ == Infinity || isNaN(this.ratio_)) { this.ratio_ = 0; } - var innerLength = hostMetrics.viewHeight * this.ratio_; - var innerOffset = (hostMetrics.viewTop - hostMetrics.contentTop) * + + var handleLength = hostMetrics.viewHeight * this.ratio_; + this.setHandleLength_(Math.max(0, handleLength)); + + var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) * this.ratio_; - this.svgKnob_.setAttribute('height', Math.max(0, innerLength)); - this.xCoordinate = hostMetrics.absoluteLeft + 0.5; - if (!this.workspace_.RTL) { - this.xCoordinate += hostMetrics.viewWidth - - Blockly.Scrollbar.scrollbarThickness - 1; - } - this.yCoordinate = hostMetrics.absoluteTop + 0.5; - this.svgGroup_.setAttribute('transform', - 'translate(' + this.xCoordinate + ',' + this.yCoordinate + ')'); - this.svgBackground_.setAttribute('height', Math.max(0, outerLength)); - this.svgKnob_.setAttribute('y', this.constrainKnob_(innerOffset)); + this.setHandlePosition(this.constrainHandle_(handlePosition)); }; /** * Create all the DOM elements required for a scrollbar. * The resulting widget is not sized. + * @param {string=} opt_class A class to be applied to this scrollbar. * @private */ -Blockly.Scrollbar.prototype.createDom_ = function() { +Blockly.Scrollbar.prototype.createDom_ = function(opt_class) { /* Create the following DOM: - - - - + + + + + + */ var className = 'blocklyScrollbar' + (this.horizontal_ ? 'Horizontal' : 'Vertical'); - this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null); - this.svgBackground_ = Blockly.createSvgElement('rect', - {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); + if (opt_class) { + className += ' ' + opt_class; + } + this.outerSvg_ = Blockly.utils.createSvgElement( + 'svg', {'class': className}, null); + this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, this.outerSvg_); + this.svgBackground_ = Blockly.utils.createSvgElement( + 'rect', {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); - this.svgKnob_ = Blockly.createSvgElement('rect', - {'class': 'blocklyScrollbarKnob', 'rx': radius, 'ry': radius}, + this.svgHandle_ = Blockly.utils.createSvgElement( + 'rect', + { + 'class': 'blocklyScrollbarHandle', + 'rx': radius, + 'ry': radius + }, this.svgGroup_); - Blockly.Scrollbar.insertAfter_(this.svgGroup_, - this.workspace_.getBubbleCanvas()); + Blockly.utils.insertAfter_(this.outerSvg_, this.workspace_.getParentSvg()); }; /** @@ -384,7 +632,21 @@ Blockly.Scrollbar.prototype.createDom_ = function() { * @return {boolean} True if visible. */ Blockly.Scrollbar.prototype.isVisible = function() { - return this.svgGroup_.getAttribute('display') != 'none'; + return this.isVisible_; +}; + +/** + * Set whether the scrollbar's container is visible and update + * display accordingly if visibility has changed. + * @param {boolean} visible Whether the container is visible + */ +Blockly.Scrollbar.prototype.setContainerVisible = function(visible) { + var visibilityChanged = (visible != this.containerVisible_); + + this.containerVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } }; /** @@ -393,20 +655,37 @@ Blockly.Scrollbar.prototype.isVisible = function() { * @param {boolean} visible True if visible. */ Blockly.Scrollbar.prototype.setVisible = function(visible) { - if (visible == this.isVisible()) { - return; - } + var visibilityChanged = (visible != this.isVisible()); + // Ideally this would also apply to scrollbar pairs, but that's a bigger // headache (due to interactions with the corner square). if (this.pair_) { throw 'Unable to toggle visibility of paired scrollbars.'; } - if (visible) { - this.svgGroup_.setAttribute('display', 'block'); + this.isVisible_ = visible; + if (visibilityChanged) { + this.updateDisplay_(); + } +}; + +/** + * Update visibility of scrollbar based on whether it thinks it should + * be visible and whether its containing workspace is visible. + * We cannot rely on the containing workspace being hidden to hide us + * because it is not necessarily our parent in the DOM. + */ +Blockly.Scrollbar.prototype.updateDisplay_ = function() { + var show = true; + // Check whether our parent/container is visible. + if (!this.containerVisible_) { + show = false; + } else { + show = this.isVisible(); + } + if (show) { + this.outerSvg_.setAttribute('display', 'block'); } else { - // Hide the scrollbar. - this.workspace_.setMetrics({x: 0, y: 0}); - this.svgGroup_.setAttribute('display', 'none'); + this.outerSvg_.setAttribute('display', 'none'); } }; @@ -417,33 +696,34 @@ Blockly.Scrollbar.prototype.setVisible = function(visible) { * @private */ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { - this.onMouseUpKnob_(); - if (Blockly.isRightButton(e)) { + this.workspace_.markFocused(); + Blockly.Touch.clearTouchIdentifier(); // This is really a click. + this.cleanUp_(); + if (Blockly.utils.isRightButton(e)) { // Right-click. // Scrollbars have no context menu. e.stopPropagation(); return; } - var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg()); + var mouseXY = Blockly.utils.mouseToSvg(e, this.workspace_.getParentSvg(), + this.workspace_.getInverseScreenCTM()); var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; - var knobXY = Blockly.getSvgXY_(this.svgKnob_, this.workspace_); - var knobStart = this.horizontal_ ? knobXY.x : knobXY.y; - var knobLength = parseFloat( - this.svgKnob_.getAttribute(this.horizontal_ ? 'width' : 'height')); - var knobValue = parseFloat( - this.svgKnob_.getAttribute(this.horizontal_ ? 'x' : 'y')); + var handleXY = Blockly.utils.getInjectionDivXY_(this.svgHandle_); + var handleStart = this.horizontal_ ? handleXY.x : handleXY.y; + var handlePosition = this.handlePosition_; - var pageLength = knobLength * 0.95; - if (mouseLocation <= knobStart) { + var pageLength = this.handleLength_ * 0.95; + if (mouseLocation <= handleStart) { // Decrease the scrollbar's value by a page. - knobValue -= pageLength; - } else if (mouseLocation >= knobStart + knobLength) { + handlePosition -= pageLength; + } else if (mouseLocation >= handleStart + this.handleLength_) { // Increase the scrollbar's value by a page. - knobValue += pageLength; + handlePosition += pageLength; } - this.svgKnob_.setAttribute(this.horizontal_ ? 'x' : 'y', - this.constrainKnob_(knobValue)); + + this.setHandlePosition(this.constrainHandle_(handlePosition)); + this.onScroll_(); e.stopPropagation(); e.preventDefault(); @@ -451,51 +731,68 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { /** * Start a dragging operation. - * Called when scrollbar knob is clicked. + * Called when scrollbar handle is clicked. * @param {!Event} e Mouse down event. * @private */ -Blockly.Scrollbar.prototype.onMouseDownKnob_ = function(e) { - this.onMouseUpKnob_(); - if (Blockly.isRightButton(e)) { +Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) { + this.workspace_.markFocused(); + this.cleanUp_(); + if (Blockly.utils.isRightButton(e)) { // Right-click. // Scrollbars have no context menu. e.stopPropagation(); return; } // Look up the current translation and record it. - this.startDragKnob = parseFloat( - this.svgKnob_.getAttribute(this.horizontal_ ? 'x' : 'y')); + this.startDragHandle = this.handlePosition_; + + // Tell the workspace to setup its drag surface since it is about to move. + // onMouseMoveHandle will call onScroll which actually tells the workspace + // to move. + this.workspace_.setupDragSurface(); + // Record the current mouse position. - this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY; - Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, this.onMouseUpKnob_); - Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.onMouseMoveKnob_); + this.startDragMouse_ = this.horizontal_ ? e.clientX : e.clientY; + Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document, + 'mouseup', this, this.onMouseUpHandle_); + Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document, + 'mousemove', this, this.onMouseMoveHandle_); e.stopPropagation(); e.preventDefault(); }; /** - * Drag the scrollbar's knob. + * Drag the scrollbar's handle. * @param {!Event} e Mouse up event. * @private */ -Blockly.Scrollbar.prototype.onMouseMoveKnob_ = function(e) { +Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) { var currentMouse = this.horizontal_ ? e.clientX : e.clientY; - var mouseDelta = currentMouse - this.startDragMouse; - var knobValue = this.startDragKnob + mouseDelta; + var mouseDelta = currentMouse - this.startDragMouse_; + var handlePosition = this.startDragHandle + mouseDelta; // Position the bar. - this.svgKnob_.setAttribute(this.horizontal_ ? 'x' : 'y', - this.constrainKnob_(knobValue)); + this.setHandlePosition(this.constrainHandle_(handlePosition)); this.onScroll_(); }; /** - * Stop binding to the global mouseup and mousemove events. + * Release the scrollbar handle and reset state accordingly. * @private */ -Blockly.Scrollbar.prototype.onMouseUpKnob_ = function() { +Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() { + // Tell the workspace to clean up now that the workspace is done moving. + this.workspace_.resetDragSurface(); + Blockly.Touch.clearTouchIdentifier(); + this.cleanUp_(); +}; + +/** + * Hide chaff and stop binding to mouseup and mousemove events. Call this to + * wrap up lose ends associated with the scrollbar. + * @private + */ +Blockly.Scrollbar.prototype.cleanUp_ = function() { Blockly.hideChaff(true); if (Blockly.Scrollbar.onMouseUpWrapper_) { Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_); @@ -508,20 +805,17 @@ Blockly.Scrollbar.prototype.onMouseUpKnob_ = function() { }; /** - * Constrain the knob's position within the minimum (0) and maximum + * Constrain the handle's position within the minimum (0) and maximum * (length of scrollbar) values allowed for the scrollbar. - * @param {number} value Value that is potentially out of bounds. - * @return {number} Constrained value. + * @param {number} value Value that is potentially out of bounds, in CSS pixels. + * @return {number} Constrained value, in CSS pixels. * @private */ -Blockly.Scrollbar.prototype.constrainKnob_ = function(value) { - if (value <= 0 || isNaN(value)) { +Blockly.Scrollbar.prototype.constrainHandle_ = function(value) { + if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) { value = 0; } else { - var axis = this.horizontal_ ? 'width' : 'height'; - var barLength = parseFloat(this.svgBackground_.getAttribute(axis)); - var knobLength = parseFloat(this.svgKnob_.getAttribute(axis)); - value = Math.min(value, barLength - knobLength); + value = Math.min(value, this.scrollViewSize_ - this.handleLength_); } return value; }; @@ -531,12 +825,8 @@ Blockly.Scrollbar.prototype.constrainKnob_ = function(value) { * @private */ Blockly.Scrollbar.prototype.onScroll_ = function() { - var knobValue = parseFloat( - this.svgKnob_.getAttribute(this.horizontal_ ? 'x' : 'y')); - var barLength = parseFloat( - this.svgBackground_.getAttribute(this.horizontal_ ? 'width' : 'height')); - var ratio = knobValue / barLength; - if (isNaN(ratio) || !barLength) { + var ratio = this.handlePosition_ / this.scrollViewSize_; + if (isNaN(ratio)) { ratio = 0; } var xyRatio = {}; @@ -549,32 +839,24 @@ Blockly.Scrollbar.prototype.onScroll_ = function() { }; /** - * Set the scrollbar slider's position. - * @param {number} value The distance from the top/left end of the bar. + * Set the scrollbar handle's position. + * @param {number} value The distance from the top/left end of the bar, in CSS + * pixels. It may be larger than the maximum allowable position of the + * scrollbar handle. */ Blockly.Scrollbar.prototype.set = function(value) { - var constrainedValue = this.constrainKnob_(value * this.ratio_); - // Move the scrollbar slider. - this.svgKnob_.setAttribute(this.horizontal_ ? 'x' : 'y', constrainedValue); + this.setHandlePosition(this.constrainHandle_(value * this.ratio_)); this.onScroll_(); }; /** - * Insert a node after a reference node. - * Contrast with node.insertBefore function. - * @param {!Element} newNode New element to insert. - * @param {!Element} refNode Existing element to precede new node. - * @private + * Record the origin of the workspace that the scrollbar is in, in pixels + * relative to the injection div origin. This is for times when the scrollbar is + * used in an object whose origin isn't the same as the main workspace + * (e.g. in a flyout.) + * @param {number} x The x coordinate of the scrollbar's origin, in CSS pixels. + * @param {number} y The y coordinate of the scrollbar's origin, in CSS pixels. */ -Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) { - var siblingNode = refNode.nextSibling; - var parentNode = refNode.parentNode; - if (!parentNode) { - throw 'Reference node has no parent.'; - } - if (siblingNode) { - parentNode.insertBefore(newNode, siblingNode); - } else { - parentNode.appendChild(newNode); - } +Blockly.Scrollbar.prototype.setOrigin = function(x, y) { + this.origin_ = new goog.math.Coordinate(x, y); }; diff --git a/core/toolbox.js b/core/toolbox.js index d89d06f..4297202 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -27,7 +27,11 @@ goog.provide('Blockly.Toolbox'); goog.require('Blockly.Flyout'); +goog.require('Blockly.HorizontalFlyout'); +goog.require('Blockly.Touch'); +goog.require('Blockly.VerticalFlyout'); goog.require('goog.dom'); +goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.BrowserFeature'); goog.require('goog.html.SafeHtml'); @@ -144,27 +148,34 @@ Blockly.Toolbox.prototype.lastCategory_ = null; */ Blockly.Toolbox.prototype.init = function() { var workspace = this.workspace_; + var svg = this.workspace_.getParentSvg(); - // Create an HTML container for the Toolbox menu. - this.HtmlDiv = goog.dom.createDom('div', 'blocklyToolboxDiv'); + /** + * HTML container for the Toolbox menu. + * @type {Element} + */ + this.HtmlDiv = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyToolboxDiv'); this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR'); - document.body.appendChild(this.HtmlDiv); + svg.parentNode.insertBefore(this.HtmlDiv, svg); // Clicking on toolbox closes popups. - Blockly.bindEvent_(this.HtmlDiv, 'mousedown', this, + Blockly.bindEventWithChecks_(this.HtmlDiv, 'mousedown', this, function(e) { - if (Blockly.isRightButton(e) || e.target == this.HtmlDiv) { + if (Blockly.utils.isRightButton(e) || e.target == this.HtmlDiv) { // Close flyout. Blockly.hideChaff(false); } else { // Just close popups. Blockly.hideChaff(true); } - }); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. + }, /*opt_noCaptureIdentifier*/ false, /*opt_noPreventDefault*/ true); var workspaceOptions = { disabledPatternId: workspace.options.disabledPatternId, parentWorkspace: workspace, RTL: workspace.RTL, + oneBasedIndex: workspace.options.oneBasedIndex, horizontalLayout: workspace.horizontalLayout, toolboxPosition: workspace.options.toolboxPosition }; @@ -172,8 +183,14 @@ Blockly.Toolbox.prototype.init = function() { * @type {!Blockly.Flyout} * @private */ - this.flyout_ = new Blockly.Flyout(workspaceOptions); - goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_); + this.flyout_ = null; + if (workspace.horizontalLayout) { + this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions); + } else { + this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions); + } + goog.dom.insertSiblingAfter( + this.flyout_.createDom('svg'), this.workspace_.getParentSvg()); this.flyout_.init(workspace); this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif'; @@ -185,8 +202,11 @@ Blockly.Toolbox.prototype.init = function() { tree.setShowLines(false); tree.setShowExpandIcons(false); tree.setSelectedItem(null); - this.populate_(workspace.options.languageTree); + var openNode = this.populate_(workspace.options.languageTree); tree.render(this.HtmlDiv); + if (openNode) { + tree.setSelectedItem(openNode); + } this.addColour_(); this.position(); }; @@ -228,132 +248,148 @@ Blockly.Toolbox.prototype.position = function() { return; } var svg = this.workspace_.getParentSvg(); - var svgPosition = goog.style.getPageOffset(svg); var svgSize = Blockly.svgSize(svg); if (this.horizontalLayout_) { - treeDiv.style.left = svgPosition.x + 'px'; + treeDiv.style.left = '0'; treeDiv.style.height = 'auto'; treeDiv.style.width = svgSize.width + 'px'; this.height = treeDiv.offsetHeight; if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top - treeDiv.style.top = svgPosition.y + 'px'; + treeDiv.style.top = '0'; } else { // Bottom - var topOfToolbox = svgPosition.y + svgSize.height - treeDiv.offsetHeight; - treeDiv.style.top = topOfToolbox + 'px'; + treeDiv.style.bottom = '0'; } } else { if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right - treeDiv.style.left = - (svgPosition.x + svgSize.width - treeDiv.offsetWidth) + 'px'; + treeDiv.style.right = '0'; } else { // Left - treeDiv.style.left = svgPosition.x + 'px'; + treeDiv.style.left = '0'; } treeDiv.style.height = svgSize.height + 'px'; - treeDiv.style.top = svgPosition.y + 'px'; this.width = treeDiv.offsetWidth; - if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { - // For some reason the LTR toolbox now reports as 1px too wide. - this.width -= 1; - } } this.flyout_.position(); }; /** * Fill the toolbox with categories and blocks. - * @param {Node} newTree DOM tree of blocks, or null. + * @param {!Node} newTree DOM tree of blocks. + * @return {Node} Tree node to open at startup (or null). * @private */ Blockly.Toolbox.prototype.populate_ = function(newTree) { - var rootOut = this.tree_; - var that = this; - rootOut.removeChildren(); // Delete any existing content. - rootOut.blocks = []; - var hasColours = false; - function syncTrees(treeIn, treeOut, pathToMedia) { - var lastElement = null; - for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) { - if (!childIn.tagName) { - // Skip over text. - continue; - } - switch (childIn.tagName.toUpperCase()) { - case 'CATEGORY': - var childOut = rootOut.createNode(childIn.getAttribute('name')); - childOut.blocks = []; - treeOut.add(childOut); - var custom = childIn.getAttribute('custom'); - if (custom) { - // Variables and procedures are special dynamic categories. - childOut.blocks = custom; - } else { - syncTrees(childIn, childOut, pathToMedia); + this.tree_.removeChildren(); // Delete any existing content. + this.tree_.blocks = []; + this.hasColours_ = false; + var openNode = + this.syncTrees_(newTree, this.tree_, this.workspace_.options.pathToMedia); + + if (this.tree_.blocks.length) { + throw 'Toolbox cannot have both blocks and categories in the root level.'; + } + + // Fire a resize event since the toolbox may have changed width and height. + this.workspace_.resizeContents(); + return openNode; +}; + +/** + * Sync trees of the toolbox. + * @param {!Node} treeIn DOM tree of blocks. + * @param {!Blockly.Toolbox.TreeControl} treeOut The TreeContorol object built + * from treeIn. + * @param {string} pathToMedia The path to the Blockly media directory. + * @return {Node} Tree node to open at startup (or null). + * @private + */ +Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) { + var openNode = null; + var lastElement = null; + for (var i = 0, childIn; childIn = treeIn.childNodes[i]; i++) { + if (!childIn.tagName) { + // Skip over text. + continue; + } + switch (childIn.tagName.toUpperCase()) { + case 'CATEGORY': + // Decode the category name for any potential message references + // (eg. `%{BKY_CATEGORY_NAME_LOGIC}`). + var categoryName = Blockly.utils.replaceMessageReferences( + childIn.getAttribute('name')); + var childOut = this.tree_.createNode(categoryName); + childOut.blocks = []; + treeOut.add(childOut); + var custom = childIn.getAttribute('custom'); + if (custom) { + // Variables and procedures are special dynamic categories. + childOut.blocks = custom; + } else { + var newOpenNode = this.syncTrees_(childIn, childOut, pathToMedia); + if (newOpenNode) { + openNode = newOpenNode; } - var colour = childIn.getAttribute('colour'); - if (goog.isString(colour)) { - if (colour.match(/^#[0-9a-fA-F]{6}$/)) { - childOut.hexColour = colour; - } else { - childOut.hexColour = Blockly.hueToRgb(colour); - } - hasColours = true; + } + // Decode the colour for any potential message references + // (eg. `%{BKY_MATH_HUE}`). + var colour = Blockly.utils.replaceMessageReferences( + childIn.getAttribute('colour')); + if (goog.isString(colour)) { + if (/^#[0-9a-fA-F]{6}$/.test(colour)) { + childOut.hexColour = colour; } else { - childOut.hexColour = ''; + childOut.hexColour = Blockly.hueToRgb(Number(colour)); } - if (childIn.getAttribute('expanded') == 'true') { - if (childOut.blocks.length) { - rootOut.setSelectedItem(childOut); - } - childOut.setExpanded(true); - } else { - childOut.setExpanded(false); + this.hasColours_ = true; + } else { + childOut.hexColour = ''; + } + if (childIn.getAttribute('expanded') == 'true') { + if (childOut.blocks.length) { + // This is a category that directly contains blocks. + // After the tree is rendered, open this category and show flyout. + openNode = childOut; } - lastElement = childIn; - break; - case 'SEP': - if (lastElement) { - if (lastElement.tagName.toUpperCase() == 'CATEGORY') { - // Separator between two categories. - // - treeOut.add(new Blockly.Toolbox.TreeSeparator( - that.treeSeparatorConfig_)); - } else { - // Change the gap between two blocks. - // - // The default gap is 24, can be set larger or smaller. - // Note that a deprecated method is to add a gap to a block. - // - var newGap = parseFloat(childIn.getAttribute('gap')); - if (!isNaN(newGap)) { - var oldGap = parseFloat(lastElement.getAttribute('gap')); - var gap = isNaN(oldGap) ? newGap : oldGap + newGap; - lastElement.setAttribute('gap', gap); - } + childOut.setExpanded(true); + } else { + childOut.setExpanded(false); + } + lastElement = childIn; + break; + case 'SEP': + if (lastElement) { + if (lastElement.tagName.toUpperCase() == 'CATEGORY') { + // Separator between two categories. + // + treeOut.add(new Blockly.Toolbox.TreeSeparator( + this.treeSeparatorConfig_)); + } else { + // Change the gap between two blocks. + // + // The default gap is 24, can be set larger or smaller. + // Note that a deprecated method is to add a gap to a block. + // + var newGap = parseFloat(childIn.getAttribute('gap')); + if (!isNaN(newGap) && lastElement) { + lastElement.setAttribute('gap', newGap); } } - break; - case 'BLOCK': - case 'SHADOW': - treeOut.blocks.push(childIn); - lastElement = childIn; - break; - } + } + break; + case 'BLOCK': + case 'SHADOW': + case 'LABEL': + case 'BUTTON': + treeOut.blocks.push(childIn); + lastElement = childIn; + break; } } - syncTrees(newTree, this.tree_, this.workspace_.options.pathToMedia); - this.hasColours_ = hasColours; - - if (rootOut.blocks.length) { - throw 'Toolbox cannot have both blocks and categories in the root level.'; - } - - // Fire a resize event since the toolbox may have changed width and height. - Blockly.asyncSvgResize(this.workspace_); + return openNode; }; /** * Recursively add colours to this toolbox. - * @param {Blockly.Toolbox.TreeNode} opt_tree Starting point of tree. + * @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree. * Defaults to the root node. * @private */ @@ -385,11 +421,33 @@ Blockly.Toolbox.prototype.clearSelection = function() { this.tree_.setSelectedItem(null); }; +/** + * Adds a style on the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to add. + * @package + */ +Blockly.Toolbox.prototype.addStyle = function(style) { + Blockly.utils.addClass(/** @type {!Element} */ (this.HtmlDiv), style); +}; + +/** + * Removes a style from the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to remove. + * @package + */ +Blockly.Toolbox.prototype.removeStyle = function(style) { + Blockly.utils.removeClass(/** @type {!Element} */ (this.HtmlDiv), style); +}; + /** * Return the deletion rectangle for this toolbox. * @return {goog.math.Rect} Rectangle in which to delete. */ Blockly.Toolbox.prototype.getClientRect = function() { + if (!this.HtmlDiv) { + return null; + } + // BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox // area are still deleted. Must be smaller than Infinity, but larger than // the largest screen size. @@ -416,10 +474,22 @@ Blockly.Toolbox.prototype.getClientRect = function() { } }; +/** + * Update the flyout's contents without closing it. Should be used in response + * to a change in one of the dynamic categories, such as variables or + * procedures. + */ +Blockly.Toolbox.prototype.refreshSelection = function() { + var selectedItem = this.tree_.getSelectedItem(); + if (selectedItem && selectedItem.blocks) { + this.flyout_.show(selectedItem.blocks); + } +}; + // Extending Closure's Tree UI. /** - * Extention of a TreeControl object that uses a custom tree node. + * Extension of a TreeControl object that uses a custom tree node. * @param {Blockly.Toolbox} toolbox The parent toolbox for this tree. * @param {Object} config The configuration for the tree. See * goog.ui.tree.TreeControl.DefaultConfig. @@ -442,23 +512,23 @@ Blockly.Toolbox.TreeControl.prototype.enterDocument = function() { // Add touch handler. if (goog.events.BrowserFeature.TOUCH_ENABLED) { var el = this.getElement(); - Blockly.bindEvent_(el, goog.events.EventType.TOUCHSTART, this, + Blockly.bindEventWithChecks_(el, goog.events.EventType.TOUCHEND, this, this.handleTouchEvent_); } }; + /** * Handles touch events. * @param {!goog.events.BrowserEvent} e The browser event. * @private */ Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) { - e.preventDefault(); var node = this.getNodeFromEvent_(e); - if (node && e.type === goog.events.EventType.TOUCHSTART) { + if (node && e.type === goog.events.EventType.TOUCHEND) { // Fire asynchronously since onMouseDown takes long enough that the browser // would fire the default mouse event before this method returns. setTimeout(function() { - node.onMouseDown(e); // Same behaviour for click and touch. + node.onClick_(e); // Same behaviour for click and touch. }, 1); } }; @@ -470,8 +540,9 @@ Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) { * @override */ Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) { - return new Blockly.Toolbox.TreeNode(this.toolbox_, opt_html ? - goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY, + var html = opt_html ? + goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY; + return new Blockly.Toolbox.TreeNode(this.toolbox_, html, this.getConfig(), this.getDomHelper()); }; @@ -533,7 +604,9 @@ Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { goog.ui.tree.TreeNode.call(this, html, opt_config, opt_domHelper); if (toolbox) { var resize = function() { - Blockly.asyncSvgResize(toolbox.workspace_); + // Even though the div hasn't changed size, the visible workspace + // surface of the workspace has, so we may need to reposition everything. + Blockly.svgResize(toolbox.workspace_); }; // Fire a resize event since the toolbox may have changed width. goog.events.listen(toolbox.tree_, @@ -545,7 +618,7 @@ Blockly.Toolbox.TreeNode = function(toolbox, html, opt_config, opt_domHelper) { goog.inherits(Blockly.Toolbox.TreeNode, goog.ui.tree.TreeNode); /** - * Supress population of the +/- icon. + * Suppress population of the +/- icon. * @return {!goog.html.SafeHtml} The source for the icon. * @override */ @@ -558,7 +631,8 @@ Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml = function() { * @param {!goog.events.BrowserEvent} e The browser event. * @override */ -Blockly.Toolbox.TreeNode.prototype.onMouseDown = function(e) { +Blockly.Toolbox.TreeNode.prototype.onClick_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { // Expand icon. if (this.hasChildren() && this.isUserCollapsible_) { this.toggle(); @@ -572,21 +646,60 @@ Blockly.Toolbox.TreeNode.prototype.onMouseDown = function(e) { }; /** - * Supress the inherited double-click behaviour. + * Suppress the inherited mouse down behaviour. * @param {!goog.events.BrowserEvent} e The browser event. * @override * @private */ -Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(e) { +Blockly.Toolbox.TreeNode.prototype.onMouseDown = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { + // NOPE. +}; + +/** + * Suppress the inherited double-click behaviour. + * @param {!goog.events.BrowserEvent} e The browser event. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-disable no-unused-vars */) { // NOP. }; +/** + * Remap event.keyCode in horizontalLayout so that arrow + * keys work properly and call original onKeyDown handler. + * @param {!goog.events.BrowserEvent} e The browser event. + * @return {boolean} The handled value. + * @override + * @private + */ +Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) { + if (this.tree.toolbox_.horizontalLayout_) { + var map = {}; + var next = goog.events.KeyCodes.DOWN; + var prev = goog.events.KeyCodes.UP; + map[goog.events.KeyCodes.RIGHT] = this.rightToLeft_ ? prev : next; + map[goog.events.KeyCodes.LEFT] = this.rightToLeft_ ? next : prev; + map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT; + map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT; + + var newKeyCode = map[e.keyCode]; + e.keyCode = newKeyCode || e.keyCode; + } + return Blockly.Toolbox.TreeNode.superClass_.onKeyDown.call(this, e); +}; + /** * A blank separator node in the tree. + * @param {Object=} config The configuration for the tree. See + * goog.ui.tree.TreeControl.DefaultConfig. If not specified, a default config + * will be used. * @constructor * @extends {Blockly.Toolbox.TreeNode} */ Blockly.Toolbox.TreeSeparator = function(config) { - Blockly.Toolbox.TreeNode.call(this, null, '', config); + Blockly.Toolbox.TreeNode.call(this, null, goog.html.SafeHtml.EMPTY, config); }; goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode); diff --git a/core/tooltip.js b/core/tooltip.js index 6e520e6..d210d00 100644 --- a/core/tooltip.js +++ b/core/tooltip.js @@ -29,9 +29,14 @@ */ 'use strict'; +/** + * @name Blockly.Tooltip + * @namespace + **/ goog.provide('Blockly.Tooltip'); goog.require('goog.dom'); +goog.require('goog.dom.TagName'); /** @@ -39,6 +44,13 @@ goog.require('goog.dom'); */ Blockly.Tooltip.visible = false; +/** + * Is someone else blocking the tooltip from being shown? + * @type {boolean} + * @private + */ +Blockly.Tooltip.blocked_ = false; + /** * Maximum width (in characters) of a tooltip. */ @@ -120,7 +132,8 @@ Blockly.Tooltip.createDom = function() { return; // Already created. } // Create an HTML container for popup overlays (e.g. editor widgets). - Blockly.Tooltip.DIV = goog.dom.createDom('div', 'blocklyTooltipDiv'); + Blockly.Tooltip.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyTooltipDiv'); document.body.appendChild(Blockly.Tooltip.DIV); }; @@ -129,9 +142,15 @@ Blockly.Tooltip.createDom = function() { * @param {!Element} element SVG element onto which tooltip is to be bound. */ Blockly.Tooltip.bindMouseEvents = function(element) { - Blockly.bindEvent_(element, 'mouseover', null, Blockly.Tooltip.onMouseOver_); - Blockly.bindEvent_(element, 'mouseout', null, Blockly.Tooltip.onMouseOut_); - Blockly.bindEvent_(element, 'mousemove', null, Blockly.Tooltip.onMouseMove_); + Blockly.bindEvent_(element, 'mouseover', null, + Blockly.Tooltip.onMouseOver_); + Blockly.bindEvent_(element, 'mouseout', null, + Blockly.Tooltip.onMouseOut_); + + // Don't use bindEvent_ for mousemove since that would create a + // corresponding touch handler, even though this only makes sense in the + // context of a mouseover/mouseout. + element.addEventListener('mousemove', Blockly.Tooltip.onMouseMove_, false); }; /** @@ -141,6 +160,10 @@ Blockly.Tooltip.bindMouseEvents = function(element) { * @private */ Blockly.Tooltip.onMouseOver_ = function(e) { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } // If the tooltip is an object, treat it as a pointer to the next object in // the chain to look at. Terminate when a string or function is found. var element = e.target; @@ -152,7 +175,7 @@ Blockly.Tooltip.onMouseOver_ = function(e) { Blockly.Tooltip.poisonedElement_ = null; Blockly.Tooltip.element_ = element; } - // Forget about any immediately preceeding mouseOut event. + // Forget about any immediately preceding mouseOut event. clearTimeout(Blockly.Tooltip.mouseOutPid_); }; @@ -161,16 +184,21 @@ Blockly.Tooltip.onMouseOver_ = function(e) { * @param {!Event} e Mouse event. * @private */ -Blockly.Tooltip.onMouseOut_ = function(e) { +Blockly.Tooltip.onMouseOut_ = function(/* eslint-disable no-unused-vars */e + /* eslint-enable no-unused-vars */) { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } // Moving from one element to another (overlapping or with no gap) generates // a mouseOut followed instantly by a mouseOver. Fork off the mouseOut // event and kill it if a mouseOver is received immediately. // This way the task only fully executes if mousing into the void. Blockly.Tooltip.mouseOutPid_ = setTimeout(function() { - Blockly.Tooltip.element_ = null; - Blockly.Tooltip.poisonedElement_ = null; - Blockly.Tooltip.hide(); - }, 1); + Blockly.Tooltip.element_ = null; + Blockly.Tooltip.poisonedElement_ = null; + Blockly.Tooltip.hide(); + }, 1); clearTimeout(Blockly.Tooltip.showPid_); }; @@ -184,12 +212,13 @@ Blockly.Tooltip.onMouseMove_ = function(e) { if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) { // No tooltip here to show. return; - } else if (Blockly.dragMode_ != Blockly.DRAG_NONE) { - // Don't display a tooltip during a drag. - return; } else if (Blockly.WidgetDiv.isVisible()) { // Don't display a tooltip if a widget is open (tooltip would be under it). return; + } else if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. We are probably handling a + // user gesture, such as a click or drag. + return; } if (Blockly.Tooltip.visible) { // Compute the distance between the mouse position when the tooltip was @@ -220,7 +249,28 @@ Blockly.Tooltip.hide = function() { Blockly.Tooltip.DIV.style.display = 'none'; } } - clearTimeout(Blockly.Tooltip.showPid_); + if (Blockly.Tooltip.showPid_) { + clearTimeout(Blockly.Tooltip.showPid_); + } +}; + +/** + * Hide any in-progress tooltips and block showing new tooltips until the next + * call to unblock(). + * @package + */ +Blockly.Tooltip.block = function() { + Blockly.Tooltip.hide(); + Blockly.Tooltip.blocked_ = true; +}; + +/** + * Unblock tooltips: allow them to be scheduled and shown according to their own + * logic. + * @package + */ +Blockly.Tooltip.unblock = function() { + Blockly.Tooltip.blocked_ = false; }; /** @@ -228,6 +278,10 @@ Blockly.Tooltip.hide = function() { * @private */ Blockly.Tooltip.show_ = function() { + if (Blockly.Tooltip.blocked_) { + // Someone doesn't want us to show tooltips. + return; + } Blockly.Tooltip.poisonedElement_ = Blockly.Tooltip.element_; if (!Blockly.Tooltip.DIV) { return; @@ -239,7 +293,7 @@ Blockly.Tooltip.show_ = function() { while (goog.isFunction(tip)) { tip = tip(); } - tip = Blockly.Tooltip.wrap_(tip, Blockly.Tooltip.LIMIT); + tip = Blockly.utils.wrap(tip, Blockly.Tooltip.LIMIT); // Create new text, line by line. var lines = tip.split('\n'); for (var i = 0; i < lines.length; i++) { @@ -282,157 +336,3 @@ Blockly.Tooltip.show_ = function() { Blockly.Tooltip.DIV.style.top = anchorY + 'px'; Blockly.Tooltip.DIV.style.left = anchorX + 'px'; }; - -/** - * Wrap text to the specified width. - * @param {string} text Text to wrap. - * @param {number} limit Width to wrap each line. - * @return {string} Wrapped text. - * @private - */ -Blockly.Tooltip.wrap_ = function(text, limit) { - if (text.length <= limit) { - // Short text, no need to wrap. - return text; - } - // Split the text into words. - var words = text.trim().split(/\s+/); - // Set limit to be the length of the largest word. - for (var i = 0; i < words.length; i++) { - if (words[i].length > limit) { - limit = words[i].length; - } - } - - var lastScore; - var score = -Infinity; - var lastText; - var lineCount = 1; - do { - lastScore = score; - lastText = text; - // Create a list of booleans representing if a space (false) or - // a break (true) appears after each word. - var wordBreaks = []; - // Seed the list with evenly spaced linebreaks. - var steps = words.length / lineCount; - var insertedBreaks = 1; - for (var i = 0; i < words.length - 1; i++) { - if (insertedBreaks < (i + 1.5) / steps) { - insertedBreaks++; - wordBreaks[i] = true; - } else { - wordBreaks[i] = false; - } - } - wordBreaks = Blockly.Tooltip.wrapMutate_(words, wordBreaks, limit); - score = Blockly.Tooltip.wrapScore_(words, wordBreaks, limit); - text = Blockly.Tooltip.wrapToText_(words, wordBreaks); - lineCount++; - } while (score > lastScore); - return lastText; -}; - -/** - * Compute a score for how good the wrapping is. - * @param {!Array.} words Array of each word. - * @param {!Array.} wordBreaks Array of line breaks. - * @param {number} limit Width to wrap each line. - * @return {number} Larger the better. - * @private - */ -Blockly.Tooltip.wrapScore_ = function(words, wordBreaks, limit) { - // If this function becomes a performance liability, add caching. - // Compute the length of each line. - var lineLengths = [0]; - var linePunctuation = []; - for (var i = 0; i < words.length; i++) { - lineLengths[lineLengths.length - 1] += words[i].length; - if (wordBreaks[i] === true) { - lineLengths.push(0); - linePunctuation.push(words[i].charAt(words[i].length - 1)); - } else if (wordBreaks[i] === false) { - lineLengths[lineLengths.length - 1]++; - } - } - var maxLength = Math.max.apply(Math, lineLengths); - - var score = 0; - for (var i = 0; i < lineLengths.length; i++) { - // Optimize for width. - // -2 points per char over limit (scaled to the power of 1.5). - score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2; - // Optimize for even lines. - // -1 point per char smaller than max (scaled to the power of 1.5). - score -= Math.pow(maxLength - lineLengths[i], 1.5); - // Optimize for structure. - // Add score to line endings after punctuation. - if ('.?!'.indexOf(linePunctuation[i]) != -1) { - score += limit / 3; - } else if (',;)]}'.indexOf(linePunctuation[i]) != -1) { - score += limit / 4; - } - } - // All else being equal, the last line should not be longer than the - // previous line. For example, this looks wrong: - // aaa bbb - // ccc ddd eee - if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <= - lineLengths[lineLengths.length - 2]) { - score += 0.5; - } - return score; -}; - -/** - * Mutate the array of line break locations until an optimal solution is found. - * No line breaks are added or deleted, they are simply moved around. - * @param {!Array.} words Array of each word. - * @param {!Array.} wordBreaks Array of line breaks. - * @param {number} limit Width to wrap each line. - * @return {!Array.} New array of optimal line breaks. - * @private - */ -Blockly.Tooltip.wrapMutate_ = function(words, wordBreaks, limit) { - var bestScore = Blockly.Tooltip.wrapScore_(words, wordBreaks, limit); - var bestBreaks; - // Try shifting every line break forward or backward. - for (var i = 0; i < wordBreaks.length - 1; i++) { - if (wordBreaks[i] == wordBreaks[i + 1]) { - continue; - } - var mutatedWordBreaks = [].concat(wordBreaks); - mutatedWordBreaks[i] = !mutatedWordBreaks[i]; - mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1]; - var mutatedScore = - Blockly.Tooltip.wrapScore_(words, mutatedWordBreaks, limit); - if (mutatedScore > bestScore) { - bestScore = mutatedScore; - bestBreaks = mutatedWordBreaks; - } - } - if (bestBreaks) { - // Found an improvement. See if it may be improved further. - return Blockly.Tooltip.wrapMutate_(words, bestBreaks, limit); - } - // No improvements found. Done. - return wordBreaks; -}; - -/** - * Reassemble the array of words into text, with the specified line breaks. - * @param {!Array.} words Array of each word. - * @param {!Array.} wordBreaks Array of line breaks. - * @return {string} Plain text. - * @private - */ -Blockly.Tooltip.wrapToText_ = function(words, wordBreaks) { - var text = []; - for (var i = 0; i < words.length; i++) { - text.push(words[i]); - if (wordBreaks[i] !== undefined) { - text.push(wordBreaks[i] ? '\n' : ' '); - } - } - return text.join(''); -}; diff --git a/core/touch.js b/core/touch.js new file mode 100644 index 0000000..816e8d6 --- /dev/null +++ b/core/touch.js @@ -0,0 +1,243 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 Touch handling for Blockly. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +/** + * @name Blockly.Touch + * @namespace + **/ +goog.provide('Blockly.Touch'); + +goog.require('goog.events'); +goog.require('goog.events.BrowserFeature'); +goog.require('goog.string'); + + +/** + * Which touch events are we currently paying attention to? + * @type {?string} + * @private + */ +Blockly.Touch.touchIdentifier_ = null; + +/** + * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, + * in conjunction with mouse events. + * @type {Object} + */ +Blockly.Touch.TOUCH_MAP = {}; +if (goog.events.BrowserFeature.TOUCH_ENABLED) { + Blockly.Touch.TOUCH_MAP = { + 'mousedown': ['touchstart'], + 'mousemove': ['touchmove'], + 'mouseup': ['touchend', 'touchcancel'] + }; +} + +/** + * PID of queued long-press task. + * @private + */ +Blockly.longPid_ = 0; + +/** + * Context menus on touch devices are activated using a long-press. + * Unfortunately the contextmenu touch event is currently (2015) only supported + * by Chrome. This function is fired on any touchstart event, queues a task, + * which after about a second opens the context menu. The tasks is killed + * if the touch event terminates early. + * @param {!Event} e Touch start event. + * @param {Blockly.Gesture} gesture The gesture that triggered this longStart. + * @private + */ +Blockly.longStart_ = function(e, gesture) { + Blockly.longStop_(); + // Punt on multitouch events. + if (e.changedTouches && e.changedTouches.length != 1) { + return; + } + Blockly.longPid_ = setTimeout(function() { + // Additional check to distinguish between touch events and pointer events + if (e.changedTouches) { + // TouchEvent + e.button = 2; // Simulate a right button click. + // e was a touch event. It needs to pretend to be a mouse event. + e.clientX = e.changedTouches[0].clientX; + e.clientY = e.changedTouches[0].clientY; + } + + // Let the gesture route the right-click correctly. + if (gesture) { + gesture.handleRightClick(e); + } + + }, Blockly.LONGPRESS); +}; + +/** + * Nope, that's not a long-press. Either touchend or touchcancel was fired, + * or a drag hath begun. Kill the queued long-press task. + * @private + */ +Blockly.longStop_ = function() { + if (Blockly.longPid_) { + clearTimeout(Blockly.longPid_); + Blockly.longPid_ = 0; + } +}; + +/** + * Clear the touch identifier that tracks which touch stream to pay attention + * to. This ends the current drag/gesture and allows other pointers to be + * captured. + */ +Blockly.Touch.clearTouchIdentifier = function() { + Blockly.Touch.touchIdentifier_ = null; +}; + +/** + * Decide whether Blockly should handle or ignore this event. + * Mouse and touch events require special checks because we only want to deal + * with one touch stream at a time. All other events should always be handled. + * @param {!Event} e The event to check. + * @return {boolean} True if this event should be passed through to the + * registered handler; false if it should be blocked. + */ +Blockly.Touch.shouldHandleEvent = function(e) { + return !Blockly.Touch.isMouseOrTouchEvent(e) || + Blockly.Touch.checkTouchIdentifier(e); +}; + +/** + * Get the touch identifier from the given event. If it was a mouse event, the + * identifier is the string 'mouse'. + * @param {!Event} e Mouse event or touch event. + * @return {string} The touch identifier from the first changed touch, if + * defined. Otherwise 'mouse'. + */ +Blockly.Touch.getTouchIdentifierFromEvent = function(e) { + return e.pointerId != undefined ? e.pointerId : + (e.changedTouches && e.changedTouches[0] && + e.changedTouches[0].identifier != undefined && + e.changedTouches[0].identifier != null) ? + e.changedTouches[0].identifier : 'mouse'; +}; + +/** + * Check whether the touch identifier on the event matches the current saved + * identifier. If there is no identifier, that means it's a mouse event and + * we'll use the identifier "mouse". This means we won't deal well with + * multiple mice being used at the same time. That seems okay. + * If the current identifier was unset, save the identifier from the + * event. This starts a drag/gesture, during which touch events with other + * identifiers will be silently ignored. + * @param {!Event} e Mouse event or touch event. + * @return {boolean} Whether the identifier on the event matches the current + * saved identifier. + */ +Blockly.Touch.checkTouchIdentifier = function(e) { + var identifier = Blockly.Touch.getTouchIdentifierFromEvent(e); + + // if (Blockly.touchIdentifier_ )is insufficient because Android touch + // identifiers may be zero. + if (Blockly.Touch.touchIdentifier_ != undefined && + Blockly.Touch.touchIdentifier_ != null) { + // We're already tracking some touch/mouse event. Is this from the same + // source? + return Blockly.Touch.touchIdentifier_ == identifier; + } + if (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown') { + // No identifier set yet, and this is the start of a drag. Set it and + // return. + Blockly.Touch.touchIdentifier_ = identifier; + return true; + } + // There was no identifier yet, but this wasn't a start event so we're going + // to ignore it. This probably means that another drag finished while this + // pointer was down. + return false; +}; + +/** + * Set an event's clientX and clientY from its first changed touch. Use this to + * make a touch event work in a mouse event handler. + * @param {!Event} e A touch event. + */ +Blockly.Touch.setClientFromTouch = function(e) { + if (goog.string.startsWith(e.type, 'touch')) { + // Map the touch event's properties to the event. + var touchPoint = e.changedTouches[0]; + e.clientX = touchPoint.clientX; + e.clientY = touchPoint.clientY; + } +}; + +/** + * Check whether a given event is a mouse or touch event. + * @param {!Event} e An event. + * @return {boolean} true if it is a mouse or touch event; false otherwise. + */ +Blockly.Touch.isMouseOrTouchEvent = function(e) { + return goog.string.startsWith(e.type, 'touch') || + goog.string.startsWith(e.type, 'mouse') || + goog.string.startsWith(e.type, 'pointer'); +}; + +/** + * Check whether a given event is a touch event or a pointer event. + * @param {!Event} e An event. + * @return {boolean} true if it is a touch event; false otherwise. + */ +Blockly.Touch.isTouchEvent = function(e) { + return goog.string.startsWith(e.type, 'touch') || + goog.string.startsWith(e.type, 'pointer'); +}; + +/** + * Split an event into an array of events, one per changed touch or mouse + * point. + * @param {!Event} e A mouse event or a touch event with one or more changed + * touches. + * @return {!Array.} An array of mouse or touch events. Each touch + * event will have exactly one changed touch. + */ +Blockly.Touch.splitEventByTouches = function(e) { + var events = []; + if (e.changedTouches) { + for (var i = 0; i < e.changedTouches.length; i++) { + var newEvent = { + type: e.type, + changedTouches: [e.changedTouches[i]], + target: e.target, + stopPropagation: function(){ e.stopPropagation(); }, + preventDefault: function(){ e.preventDefault(); } + }; + events[i] = newEvent; + } + } else { + events.push(e); + } + return events; +}; diff --git a/core/touch_gesture.js b/core/touch_gesture.js new file mode 100644 index 0000000..26adc0f --- /dev/null +++ b/core/touch_gesture.js @@ -0,0 +1,309 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 The class extends Blockly.Gesture to support pinch to zoom + * for both pointer and touch events. + * @author samelh@microsoft.com (Sam El-Husseini) + */ +'use strict'; + +goog.provide('Blockly.TouchGesture'); + +goog.require('Blockly.Gesture'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/* + * Note: In this file "start" refers to touchstart, mousedown, and pointerstart + * events. "End" refers to touchend, mouseup, and pointerend events. + */ + +/** + * Class for one gesture. + * @param {!Event} e The event that kicked off this gesture. + * @param {!Blockly.WorkspaceSvg} creatorWorkspace The workspace that created + * this gesture and has a reference to it. + * @extends {Blockly.Gesture} + * @constructor + */ +Blockly.TouchGesture = function(e, creatorWorkspace) { + Blockly.TouchGesture.superClass_.constructor.call(this, e, creatorWorkspace); + + /** + * Boolean for whether or not this gesture is a multi-touch gesture. + * @type {boolean} + * @private + */ + this.isMultiTouch_ = false; + + /** + * A map of cached points used for tracking multi-touch gestures. + * @type {Object} + * @private + */ + this.cachedPoints_ = {}; + + /** + * This is the ratio between the starting distance between the touch points + * and the most recent distance between the touch points. + * Scales between 0 and 1 mean the most recent zoom was a zoom out. + * Scales above 1.0 mean the most recent zoom was a zoom in. + * @type {number} + * @private + */ + this.previousScale_ = 0; + + /** + * The starting distance between two touch points. + * @type {number} + * @private + */ + this.startDistance_ = 0; + + /** + * A handle to use to unbind the second touch start or pointer down listener + * at the end of a drag. Opaque data returned from Blockly.bindEventWithChecks_. + * @type {Array.} + * @private + */ + this.onStartWrapper_ = null; +}; +goog.inherits(Blockly.TouchGesture, Blockly.Gesture); + +/** + * A multiplier used to convert the gesture scale to a zoom in delta. + * @const + */ +Blockly.TouchGesture.ZOOM_IN_MULTIPLIER = 5; + +/** + * A multiplier used to convert the gesture scale to a zoom out delta. + * @const + */ +Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER = 6; + +/** + * Start a gesture: update the workspace to indicate that a gesture is in + * progress and bind mousemove and mouseup handlers. + * @param {!Event} e A mouse down, touch start or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.doStart = function(e) { + Blockly.TouchGesture.superClass_.doStart.call(this, e); + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + } +}; + +/** + * Bind gesture events. + * Overriding the gesture definition of this function, binding the same + * functions for onMoveWrapper_ and onUpWrapper_ but passing opt_noCaptureIdentifier. + * In addition, binding a second mouse down event to detect multi-touch events. + * @param {!Event} e A mouse down or touch start event. + * @package + */ +Blockly.TouchGesture.prototype.bindMouseEvents = function(e) { + this.onStartWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousedown', null, this.handleStart.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onMoveWrapper_ = Blockly.bindEventWithChecks_( + document, 'mousemove', null, this.handleMove.bind(this), + /*opt_noCaptureIdentifier*/ true); + this.onUpWrapper_ = Blockly.bindEventWithChecks_( + document, 'mouseup', null, this.handleUp.bind(this), + /*opt_noCaptureIdentifier*/ true); + + e.preventDefault(); +}; + +/** + * Handle a mouse down, touch start, or pointer down event. + * @param {!Event} e A mouse down, touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleStart = function(e) { + if (!this.isDragging) { + // A drag has already started, so this can no longer be a pinch-zoom. + return; + } + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchStart(e); + + if (this.isMultiTouch()) { + Blockly.longStop_(); + } + } +}; + +/** + * Handle a mouse move, touch move, or pointer move event. + * @param {!Event} e A mouse move, touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleMove = function(e) { + if (this.isDragging()) { + // We are in the middle of a drag, only handle the relevant events + if (Blockly.Touch.shouldHandleEvent(e)) { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } + return; + } + if (this.isMultiTouch()) { + if (Blockly.Touch.isTouchEvent(e)) { + this.handleTouchMove(e); + } + Blockly.longStop_(); + } else { + Blockly.TouchGesture.superClass_.handleMove.call(this, e); + } +}; + +/** + * Handle a mouse up, touch end, or pointer up event. + * @param {!Event} e A mouse up, touch end, or pointer up event. + * @package + */ +Blockly.TouchGesture.prototype.handleUp = function(e) { + if (Blockly.Touch.isTouchEvent(e) && !this.isDragging()) { + this.handleTouchEnd(e); + } + if (!this.isMultiTouch() || this.isDragging()) { + if (!Blockly.Touch.shouldHandleEvent(e)) { + return; + } + Blockly.TouchGesture.superClass_.handleUp.call(this, e); + } else { + e.preventDefault(); + e.stopPropagation(); + + this.dispose(); + } +}; + +/** + * Whether this gesture is part of a multi-touch gesture. + * @return {boolean} whether this gesture is part of a multi-touch gesture. + * @package + */ +Blockly.TouchGesture.prototype.isMultiTouch = function() { + return this.isMultiTouch_; +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.TouchGesture.prototype.dispose = function() { + Blockly.TouchGesture.superClass_.dispose.call(this); + + if (this.onStartWrapper_) { + Blockly.unbindEvent_(this.onStartWrapper_); + } +}; + +/** + * Handle a touch start or pointer down event and keep track of current pointers. + * @param {!Event} e A touch start, or pointer down event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchStart = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // store the pointerId in the current list of pointers + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + this.startDistance_ = goog.math.Coordinate.distance(point0, point1); + this.isMultiTouch_ = true; + } + e.preventDefault(); +}; + +/** + * Handle a touch move or pointer move event and zoom in/out if two pointers are on the screen. + * @param {!Event} e A touch move, or pointer move event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchMove = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + // Update the cache + this.cachedPoints_[pointerId] = this.getTouchPoint(e); + + var pointers = Object.keys(this.cachedPoints_); + // If two pointers are down, check for pinch gestures + if (pointers.length == 2) { + // Calculate the distance between the two pointers + var point0 = this.cachedPoints_[pointers[0]]; + var point1 = this.cachedPoints_[pointers[1]]; + var moveDistance = goog.math.Coordinate.distance(point0, point1); + var startDistance = this.startDistance_; + var scale = this.touchScale_ = moveDistance / startDistance; + + if (this.previousScale_ > 0 && this.previousScale_ < Infinity) { + var gestureScale = scale - this.previousScale_; + var delta = gestureScale > 0 ? + gestureScale * Blockly.TouchGesture.ZOOM_IN_MULTIPLIER : + gestureScale * Blockly.TouchGesture.ZOOM_OUT_MULTIPLIER; + var workspace = this.startWorkspace_; + var position = Blockly.utils.mouseToSvg(e, workspace.getParentSvg(), workspace.getInverseScreenCTM()); + workspace.zoom(position.x, position.y, delta); + } + this.previousScale_ = scale; + } + e.preventDefault(); +}; + +/** + * Handle a touch end or pointer end event and end the gesture. + * @param {!Event} e A touch end, or pointer end event. + * @package + */ +Blockly.TouchGesture.prototype.handleTouchEnd = function(e) { + var pointerId = Blockly.Touch.getTouchIdentifierFromEvent(e); + if (this.cachedPoints_[pointerId]) { + delete this.cachedPoints_[pointerId]; + } + if (Object.keys(this.cachedPoints_).length < 2) { + this.cachedPoints_ = {}; + this.previousScale_ = 0; + } +}; + +/** + * Helper function returning the current touch point coordinate. + * @param {!Event} e A touch or pointer event. + * @return {goog.math.Coordinate} the current touch point coordinate + * @package + */ +Blockly.TouchGesture.prototype.getTouchPoint = function(e) { + if (!this.startWorkspace_) { + return null; + } + return new goog.math.Coordinate( + (e.pageX ? e.pageX : e.changedTouches[0].pageX), + (e.pageY ? e.pageY : e.changedTouches[0].pageY) + ); +}; diff --git a/core/trashcan.js b/core/trashcan.js index 0a29543..031daf7 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -164,38 +164,50 @@ Blockly.Trashcan.prototype.createDom = function() { clip-path="url(#blocklyTrashLidClipPath837493)"> */ - this.svgGroup_ = Blockly.createSvgElement('g', + this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': 'blocklyTrash'}, null); + var clip; var rnd = String(Math.random()).substring(2); - var clip = Blockly.createSvgElement('clipPath', + clip = Blockly.utils.createSvgElement('clipPath', {'id': 'blocklyTrashBodyClipPath' + rnd}, this.svgGroup_); - Blockly.createSvgElement('rect', - {'width': this.WIDTH_, 'height': this.BODY_HEIGHT_, - 'y': this.LID_HEIGHT_}, + Blockly.utils.createSvgElement('rect', + { + 'width': this.WIDTH_, + 'height': this.BODY_HEIGHT_, + 'y': this.LID_HEIGHT_ + }, clip); - var body = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'}, + var body = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')' + }, this.svgGroup_); body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.workspace_.options.pathToMedia + Blockly.SPRITE.url); - var clip = Blockly.createSvgElement('clipPath', + clip = Blockly.utils.createSvgElement('clipPath', {'id': 'blocklyTrashLidClipPath' + rnd}, this.svgGroup_); - Blockly.createSvgElement('rect', + Blockly.utils.createSvgElement('rect', {'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip); - this.svgLid_ = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'}, + this.svgLid_ = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'x': -this.SPRITE_LEFT_, + 'height': Blockly.SPRITE.height, + 'y': -this.SPRITE_TOP_, + 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')' + }, this.svgGroup_); this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', this.workspace_.options.pathToMedia + Blockly.SPRITE.url); - Blockly.bindEvent_(this.svgGroup_, 'mouseup', this, this.click); + Blockly.bindEventWithChecks_(this.svgGroup_, 'mouseup', this, this.click); this.animateLid_(); return this.svgGroup_; }; @@ -265,6 +277,10 @@ Blockly.Trashcan.prototype.position = function() { * @return {goog.math.Rect} Rectangle in which to delete. */ Blockly.Trashcan.prototype.getClientRect = function() { + if (!this.svgGroup_) { + return null; + } + var trashRect = this.svgGroup_.getBoundingClientRect(); var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_; var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_; diff --git a/core/ui_menu_utils.js b/core/ui_menu_utils.js new file mode 100644 index 0000000..8fda602 --- /dev/null +++ b/core/ui_menu_utils.js @@ -0,0 +1,68 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 methods for working with the closure menu (goog.ui.menu). + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +/** + * @name Blockly.utils.uiMenu + * @namespace + **/ +goog.provide('Blockly.utils.uiMenu'); + + +/** + * Get the size of a rendered goog.ui.Menu. + * @param {!goog.ui.Menu} menu The menu to measure. + * @return {!goog.math.Size} Object with width and height properties. + * @package + */ +Blockly.utils.uiMenu.getSize = function(menu) { + var menuDom = menu.getElement(); + var menuSize = goog.style.getSize(menuDom); + // Recalculate height for the total content, not only box height. + menuSize.height = menuDom.scrollHeight; + return menuSize; +}; + +/** + * Adjust the bounding boxes used to position the widget div to deal with RTL + * goog.ui.Menu positioning. In RTL mode the menu renders down and to the left + * of its start point, instead of down and to the right. Adjusting all of the + * bounding boxes accordingly allows us to use the same code for all widgets. + * This function in-place modifies the provided bounding boxes. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {!goog.math.Size} menuSize The size of the menu that is inside the + * widget div, in window coordinates. + * @package + */ +Blockly.utils.uiMenu.adjustBBoxesForRTL = function(viewportBBox, anchorBBox, + menuSize) { + anchorBBox.left += menuSize.width; + anchorBBox.right += menuSize.width; + viewportBBox.left += menuSize.width; + viewportBBox.right += menuSize.width; +}; diff --git a/core/utils.js b/core/utils.js index 89fcfb9..030079e 100644 --- a/core/utils.js +++ b/core/utils.js @@ -26,29 +26,67 @@ */ 'use strict'; +/** + * @name Blockly.utils + * @namespace + **/ goog.provide('Blockly.utils'); +goog.require('Blockly.Touch'); goog.require('goog.dom'); goog.require('goog.events.BrowserFeature'); goog.require('goog.math.Coordinate'); goog.require('goog.userAgent'); +/** + * To allow ADVANCED_OPTIMIZATIONS, combining variable.name and variable['name'] + * is not possible. To access the exported Blockly.Msg.Something it needs to be + * accessed through the exact name that was exported. Note, that all the exports + * are happening as the last thing in the generated js files, so they won't be + * accessible before JavaScript loads! + * @return {!Object.} The message array. + * @private + */ +Blockly.utils.getMessageArray_ = function() { + return goog.global['Blockly']['Msg']; +}; + +/** + * Remove an attribute from a element even if it's in IE 10. + * Similar to Element.removeAttribute() but it works on SVG elements in IE 10. + * Sets the attribute to null in IE 10, which treats removeAttribute as a no-op + * if it's called on an SVG element. + * @param {!Element} element DOM element to remove attribute from. + * @param {string} attributeName Name of attribute to remove. + */ +Blockly.utils.removeAttribute = function(element, attributeName) { + // goog.userAgent.isVersion is deprecated, but the replacement is + // goog.userAgent.isVersionOrHigher. + if (goog.userAgent.IE && goog.userAgent.isVersion('10.0')) { + element.setAttribute(attributeName, null); + } else { + element.removeAttribute(attributeName); + } +}; + /** * Add a CSS class to a element. * Similar to Closure's goog.dom.classes.add, except it handles SVG elements. * @param {!Element} element DOM element to add class to. * @param {string} className Name of class to add. - * @private + * @return {boolean} True if class was added, false if already present. */ -Blockly.addClass_ = function(element, className) { +Blockly.utils.addClass = function(element, className) { var classes = element.getAttribute('class') || ''; - if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) { - if (classes) { - classes += ' '; - } - element.setAttribute('class', classes + className); + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) { + return false; } + if (classes) { + classes += ' '; + } + element.setAttribute('class', classes + className); + return true; }; /** @@ -56,24 +94,26 @@ Blockly.addClass_ = function(element, className) { * Similar to Closure's goog.dom.classes.remove, except it handles SVG elements. * @param {!Element} element DOM element to remove class from. * @param {string} className Name of class to remove. - * @private + * @return {boolean} True if class was removed, false if never present. */ -Blockly.removeClass_ = function(element, className) { +Blockly.utils.removeClass = function(element, className) { var classes = element.getAttribute('class'); - if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) { - var classList = classes.split(/\s+/); - for (var i = 0; i < classList.length; i++) { - if (!classList[i] || classList[i] == className) { - classList.splice(i, 1); - i--; - } - } - if (classList.length) { - element.setAttribute('class', classList.join(' ')); - } else { - element.removeAttribute('class'); + if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) { + return false; + } + var classList = classes.split(/\s+/); + for (var i = 0; i < classList.length; i++) { + if (!classList[i] || classList[i] == className) { + classList.splice(i, 1); + i--; } } + if (classList.length) { + element.setAttribute('class', classList.join(' ')); + } else { + Blockly.utils.removeAttribute(element, 'class'); + } + return true; }; /** @@ -84,90 +124,16 @@ Blockly.removeClass_ = function(element, className) { * @return {boolean} True if class exists, false otherwise. * @private */ -Blockly.hasClass_ = function(element, className) { +Blockly.utils.hasClass = function(element, className) { var classes = element.getAttribute('class'); return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1; }; -/** - * Bind an event to a function call. - * @param {!Node} node Node upon which to listen. - * @param {string} name Event name to listen to (e.g. 'mousedown'). - * @param {Object} thisObject The value of 'this' in the function. - * @param {!Function} func Function to call when event is triggered. - * @return {!Array.} Opaque data that can be passed to unbindEvent_. - * @private - */ -Blockly.bindEvent_ = function(node, name, thisObject, func) { - if (thisObject) { - var wrapFunc = function(e) { - func.call(thisObject, e); - }; - } else { - var wrapFunc = func; - } - node.addEventListener(name, wrapFunc, false); - var bindData = [[node, name, wrapFunc]]; - // Add equivalent touch event. - if (name in Blockly.bindEvent_.TOUCH_MAP) { - wrapFunc = function(e) { - // Punt on multitouch events. - if (e.changedTouches.length == 1) { - // Map the touch event's properties to the event. - var touchPoint = e.changedTouches[0]; - e.clientX = touchPoint.clientX; - e.clientY = touchPoint.clientY; - } - func.call(thisObject, e); - // Stop the browser from scrolling/zooming the page. - e.preventDefault(); - }; - for (var i = 0, eventName; - eventName = Blockly.bindEvent_.TOUCH_MAP[name][i]; i++) { - node.addEventListener(eventName, wrapFunc, false); - bindData.push([node, eventName, wrapFunc]); - } - } - return bindData; -}; - -/** - * The TOUCH_MAP lookup dictionary specifies additional touch events to fire, - * in conjunction with mouse events. - * @type {Object} - */ -Blockly.bindEvent_.TOUCH_MAP = {}; -if (goog.events.BrowserFeature.TOUCH_ENABLED) { - Blockly.bindEvent_.TOUCH_MAP = { - 'mousedown': ['touchstart'], - 'mousemove': ['touchmove'], - 'mouseup': ['touchend', 'touchcancel'] - }; -} - -/** - * Unbind one or more events event from a function call. - * @param {!Array.} bindData Opaque data from bindEvent_. This list is - * emptied during the course of calling this function. - * @return {!Function} The function call. - * @private - */ -Blockly.unbindEvent_ = function(bindData) { - while (bindData.length) { - var bindDatum = bindData.pop(); - var node = bindDatum[0]; - var name = bindDatum[1]; - var func = bindDatum[2]; - node.removeEventListener(name, func, false); - } - return func; -}; - /** * Don't do anything for this event, just halt propagation. * @param {!Event} e An event. */ -Blockly.noEvent = function(e) { +Blockly.utils.noEvent = function(e) { // This event has been handled. No need to bubble up to the document. e.preventDefault(); e.stopPropagation(); @@ -177,9 +143,8 @@ Blockly.noEvent = function(e) { * Is this event targeting a text input widget? * @param {!Event} e An event. * @return {boolean} True if text input. - * @private */ -Blockly.isTargetInput_ = function(e) { +Blockly.utils.isTargetInput = function(e) { return e.target.type == 'textarea' || e.target.type == 'text' || e.target.type == 'number' || e.target.type == 'email' || e.target.type == 'password' || e.target.type == 'search' || @@ -192,9 +157,8 @@ Blockly.isTargetInput_ = function(e) { * its parent. Only for SVG elements and children (e.g. rect, g, path). * @param {!Element} element SVG element to find the coordinates of. * @return {!goog.math.Coordinate} Object with .x and .y properties. - * @private */ -Blockly.getRelativeXY_ = function(element) { +Blockly.utils.getRelativeXY = function(element) { var xy = new goog.math.Coordinate(0, 0); // First, check for x and y attributes. var x = element.getAttribute('x'); @@ -207,16 +171,76 @@ Blockly.getRelativeXY_ = function(element) { } // Second, check for transform="translate(...)" attribute. var transform = element.getAttribute('transform'); - var r = transform && transform.match(Blockly.getRelativeXY_.XY_REGEXP_); + var r = transform && transform.match(Blockly.utils.getRelativeXY.XY_REGEX_); if (r) { xy.x += parseFloat(r[1]); if (r[3]) { xy.y += parseFloat(r[3]); } } + + // Then check for style = transform: translate(...) or translate3d(...) + var style = element.getAttribute('style'); + if (style && style.indexOf('translate') > -1) { + var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_2D_REGEX_); + // Try transform3d if 2d transform wasn't there. + if (!styleComponents) { + styleComponents = style.match(Blockly.utils.getRelativeXY.XY_3D_REGEX_); + } + if (styleComponents) { + xy.x += parseFloat(styleComponents[1]); + if (styleComponents[3]) { + xy.y += parseFloat(styleComponents[3]); + } + } + } return xy; }; +/** + * Return the coordinates of the top-left corner of this element relative to + * the div blockly was injected into. + * @param {!Element} element SVG element to find the coordinates of. If this is + * not a child of the div blockly was injected into, the behaviour is + * undefined. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.utils.getInjectionDivXY_ = function(element) { + var x = 0; + var y = 0; + while (element) { + var xy = Blockly.utils.getRelativeXY(element); + var scale = Blockly.utils.getScale_(element); + x = (x * scale) + xy.x; + y = (y * scale) + xy.y; + var classes = element.getAttribute('class') || ''; + if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) { + break; + } + element = element.parentNode; + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Return the scale of this element. + * @param {!Element} element The element to find the coordinates of. + * @return {!number} number represending the scale applied to the element. + * @private + */ +Blockly.utils.getScale_ = function(element) { + var scale = 1; + var transform = element.getAttribute('transform'); + if (transform) { + var transformComponents = + transform.match(Blockly.utils.getScale_.REGEXP_); + if (transformComponents && transformComponents[0]) { + scale = parseFloat(transformComponents[0]); + } + } + return scale; +}; + /** * Static regex to pull the x,y values out of an SVG translate() directive. * Note that Firefox and IE (9,10) return 'translate(12)' instead of @@ -226,54 +250,46 @@ Blockly.getRelativeXY_ = function(element) { * @type {!RegExp} * @private */ -Blockly.getRelativeXY_.XY_REGEXP_ = +Blockly.utils.getRelativeXY.XY_REGEX_ = /translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/; + /** - * Return the absolute coordinates of the top-left corner of this element, - * scales that after canvas SVG element, if it's a descendant. - * The origin (0,0) is the top-left corner of the Blockly SVG. - * @param {!Element} element Element to find the coordinates of. - * @param {!Blockly.Workspace} workspace Element must be in this workspace. - * @return {!goog.math.Coordinate} Object with .x and .y properties. + * Static regex to pull the scale values out of a transform style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} * @private */ -Blockly.getSvgXY_ = function(element, workspace) { - var x = 0; - var y = 0; - var scale = 1; - if (goog.dom.contains(workspace.getCanvas(), element) || - goog.dom.contains(workspace.getBubbleCanvas(), element)) { - // Before the SVG canvas, scale the coordinates. - scale = workspace.scale; - } - do { - // Loop through this block and every parent. - var xy = Blockly.getRelativeXY_(element); - if (element == workspace.getCanvas() || - element == workspace.getBubbleCanvas()) { - // After the SVG canvas, don't scale the coordinates. - scale = 1; - } - x += xy.x * scale; - y += xy.y * scale; - element = element.parentNode; - } while (element && element != workspace.getParentSvg()); - return new goog.math.Coordinate(x, y); -}; +Blockly.utils.getScale_REGEXP_ = /scale\(\s*([-+\d.e]+)\s*\)/; + +/** + * Static regex to pull the x,y,z values out of a translate3d() style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_3D_REGEX_ = + /transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; + +/** + * Static regex to pull the x,y,z values out of a translate3d() style property. + * Accounts for same exceptions as XY_REGEXP_. + * @type {!RegExp} + * @private + */ +Blockly.utils.getRelativeXY.XY_2D_REGEX_ = + /transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/; /** * Helper method for creating SVG elements. * @param {string} name Element's tag name. * @param {!Object} attrs Dictionary of attribute names and values. * @param {Element} parent Optional parent on which to append the element. - * @param {Blockly.Workspace=} opt_workspace Optional workspace for access to - * context (scale...). * @return {!SVGElement} Newly created SVG element. */ -Blockly.createSvgElement = function(name, attrs, parent, opt_workspace) { - var e = /** @type {!SVGElement} */ ( - document.createElementNS(Blockly.SVG_NS, name)); +Blockly.utils.createSvgElement = function(name, attrs, parent) { + var e = /** @type {!SVGElement} */ + (document.createElementNS(Blockly.SVG_NS, name)); for (var key in attrs) { e.setAttribute(key, attrs[key]); } @@ -294,7 +310,7 @@ Blockly.createSvgElement = function(name, attrs, parent, opt_workspace) { * @param {!Event} e Mouse event. * @return {boolean} True if right-click. */ -Blockly.isRightButton = function(e) { +Blockly.utils.isRightButton = function(e) { if (e.ctrlKey && goog.userAgent.MAC) { // Control-clicking on Mac OS X is treated as a right-click. // WebKit on Mac OS X fails to change button to 2 (but Gecko does). @@ -305,17 +321,20 @@ Blockly.isRightButton = function(e) { /** * Return the converted coordinates of the given mouse event. - * The origin (0,0) is the top-left corner of the Blockly svg. + * The origin (0,0) is the top-left corner of the Blockly SVG. * @param {!Event} e Mouse event. * @param {!Element} svg SVG element. - * @return {!Object} Object with .x and .y properties. + * @param {SVGMatrix} matrix Inverted screen CTM to use. + * @return {!SVGPoint} Object with .x and .y properties. */ -Blockly.mouseToSvg = function(e, svg) { +Blockly.utils.mouseToSvg = function(e, svg, matrix) { var svgPoint = svg.createSVGPoint(); svgPoint.x = e.clientX; svgPoint.y = e.clientY; - var matrix = svg.getScreenCTM(); - matrix = matrix.inverse(); + + if (!matrix) { + matrix = svg.getScreenCTM().inverse(); + } return svgPoint.matrixTransform(matrix); }; @@ -324,15 +343,13 @@ Blockly.mouseToSvg = function(e, svg) { * @param {!Array.} array Array of strings. * @return {number} Length of shortest string. */ -Blockly.shortestStringLength = function(array) { +Blockly.utils.shortestStringLength = function(array) { if (!array.length) { return 0; } - var len = array[0].length; - for (var i = 1; i < array.length; i++) { - len = Math.min(len, array[i].length); - } - return len; + return array.reduce(function(a, b) { + return a.length < b.length ? a : b; + }).length; }; /** @@ -342,14 +359,14 @@ Blockly.shortestStringLength = function(array) { * @param {number=} opt_shortest Length of shortest string. * @return {number} Length of common prefix. */ -Blockly.commonWordPrefix = function(array, opt_shortest) { +Blockly.utils.commonWordPrefix = function(array, opt_shortest) { if (!array.length) { return 0; } else if (array.length == 1) { return array[0].length; } var wordPrefix = 0; - var max = opt_shortest || Blockly.shortestStringLength(array); + var max = opt_shortest || Blockly.utils.shortestStringLength(array); for (var len = 0; len < max; len++) { var letter = array[0][len]; for (var i = 1; i < array.length; i++) { @@ -377,14 +394,14 @@ Blockly.commonWordPrefix = function(array, opt_shortest) { * @param {number=} opt_shortest Length of shortest string. * @return {number} Length of common suffix. */ -Blockly.commonWordSuffix = function(array, opt_shortest) { +Blockly.utils.commonWordSuffix = function(array, opt_shortest) { if (!array.length) { return 0; } else if (array.length == 1) { return array[0].length; } var wordPrefix = 0; - var max = opt_shortest || Blockly.shortestStringLength(array); + var max = opt_shortest || Blockly.utils.shortestStringLength(array); for (var len = 0; len < max; len++) { var letter = array[0].substr(-len - 1, 1); for (var i = 1; i < array.length; i++) { @@ -406,21 +423,83 @@ Blockly.commonWordSuffix = function(array, opt_shortest) { }; /** - * Is the given string a number (includes negative and decimals). - * @param {string} str Input string. - * @return {boolean} True if number, false otherwise. + * Parse a string with any number of interpolation tokens (%1, %2, ...). + * It will also replace string table references (e.g., %{bky_my_msg} and + * %{BKY_MY_MSG} will both be replaced with the value in + * Blockly.Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped + * (e.g., '%%'). + * @param {string} message Text which might contain string table references and + * interpolation tokens. + * @return {!Array.} Array of strings and numbers. */ -Blockly.isNumber = function(str) { - return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/); +Blockly.utils.tokenizeInterpolation = function(message) { + return Blockly.utils.tokenizeInterpolation_(message, true); }; /** - * Parse a string with any number of interpolation tokens (%1, %2, ...). - * '%' characters may be self-escaped (%%). - * @param {string} message Text containing interpolation tokens. + * Replaces string table references in a message, if the message is a string. + * For example, "%{bky_my_msg}" and "%{BKY_MY_MSG}" will both be replaced with + * the value in Blockly.Msg['MY_MSG']. + * @param {string|?} message Message, which may be a string that contains + * string table references. + * @return {!string} String with message references replaced. + */ +Blockly.utils.replaceMessageReferences = function(message) { + if (!goog.isString(message)) { + return message; + } + var interpolatedResult = Blockly.utils.tokenizeInterpolation_(message, false); + // When parseInterpolationTokens == false, interpolatedResult should be at + // most length 1. + return interpolatedResult.length ? interpolatedResult[0] : ''; +}; + +/** + * Validates that any %{MSG_KEY} references in the message refer to keys of + * the Blockly.Msg string table. + * @param {string} message Text which might contain string table references. + * @return {boolean} True if all message references have matching values. + * Otherwise, false. + */ +Blockly.utils.checkMessageReferences = function(message) { + var validSoFar = true; + + var msgTable = Blockly.utils.getMessageArray_(); + + // TODO(#1169): Implement support for other string tables, prefixes other than BKY_. + var regex = /%{(BKY_[A-Z][A-Z0-9_]*)}/gi; + var match = regex.exec(message); + while (match) { + var msgKey = match[1]; + msgKey = msgKey.toUpperCase(); + if (msgKey.substr(0, 4) != 'BKY_') { + console.log('WARNING: Unsupported message table prefix in %{' + match[1] + '}.'); + validSoFar = false; // Continue to report other errors. + } else if (msgTable[msgKey.substr(4)] == undefined) { + console.log('WARNING: No message string for %{' + match[1] + '}.'); + validSoFar = false; // Continue to report other errors. + } + + // Re-run on remainder of string. + message = message.substring(match.index + msgKey.length + 1); + match = regex.exec(message); + } + + return validSoFar; +}; + +/** + * Internal implementation of the message reference and interpolation token + * parsing used by tokenizeInterpolation() and replaceMessageReferences(). + * @param {string} message Text which might contain string table references and + * interpolation tokens. + * @param {boolean} parseInterpolationTokens Option to parse numeric + * interpolation tokens (%1, %2, ...) when true. * @return {!Array.} Array of strings and numbers. + * @private */ -Blockly.tokenizeInterpolation = function(message) { +Blockly.utils.tokenizeInterpolation_ = function(message, + parseInterpolationTokens) { var tokens = []; var chars = message.split(''); chars.push(''); // End marker. @@ -428,6 +507,7 @@ Blockly.tokenizeInterpolation = function(message) { // 0 - Base case. // 1 - % found. // 2 - Digit found. + // 3 - Message ref found. var state = 0; var buffer = []; var number = null; @@ -435,6 +515,11 @@ Blockly.tokenizeInterpolation = function(message) { var c = chars[i]; if (state == 0) { if (c == '%') { + var text = buffer.join(''); + if (text) { + tokens.push(text); + } + buffer.length = 0; state = 1; // Start escape. } else { buffer.push(c); // Regular char. @@ -443,7 +528,7 @@ Blockly.tokenizeInterpolation = function(message) { if (c == '%') { buffer.push(c); // Escaped %: %% state = 0; - } else if ('0' <= c && c <= '9') { + } else if (parseInterpolationTokens && '0' <= c && c <= '9') { state = 2; number = c; var text = buffer.join(''); @@ -451,8 +536,10 @@ Blockly.tokenizeInterpolation = function(message) { tokens.push(text); } buffer.length = 0; + } else if (c == '{') { + state = 3; } else { - buffer.push('%', c); // Not an escape: %a + buffer.push('%', c); // Not recognized. Return as literal. state = 0; } } else if (state == 2) { @@ -463,13 +550,81 @@ Blockly.tokenizeInterpolation = function(message) { i--; // Parse this char again. state = 0; } + } else if (state == 3) { // String table reference + if (c == '') { + // Premature end before closing '}' + buffer.splice(0, 0, '%{'); // Re-insert leading delimiter + i--; // Parse this char again. + state = 0; // and parse as string literal. + } else if (c != '}') { + buffer.push(c); + } else { + var rawKey = buffer.join(''); + if (/[a-zA-Z][a-zA-Z0-9_]*/.test(rawKey)) { // Strict matching + // Found a valid string key. Attempt case insensitive match. + var keyUpper = rawKey.toUpperCase(); + + // BKY_ is the prefix used to namespace the strings used in Blockly + // core files and the predefined blocks in ../blocks/. These strings + // are defined in ../msgs/ files. + var bklyKey = goog.string.startsWith(keyUpper, 'BKY_') ? + keyUpper.substring(4) : null; + if (bklyKey && bklyKey in Blockly.utils.getMessageArray_()) { + var rawValue = Blockly.utils.getMessageArray_()[bklyKey]; + if (goog.isString(rawValue)) { + // Attempt to dereference substrings, too, appending to the end. + Array.prototype.push.apply(tokens, + Blockly.utils.tokenizeInterpolation_( + rawValue, parseInterpolationTokens)); + } else if (parseInterpolationTokens) { + // When parsing interpolation tokens, numbers are special + // placeholders (%1, %2, etc). Make sure all other values are + // strings. + tokens.push(String(rawValue)); + } else { + tokens.push(rawValue); + } + } else { + // No entry found in the string table. Pass reference as string. + tokens.push('%{' + rawKey + '}'); + } + buffer.length = 0; // Clear the array + state = 0; + } else { + tokens.push('%{' + rawKey + '}'); + buffer.length = 0; + state = 0; // and parse as string literal. + } + } } } var text = buffer.join(''); if (text) { tokens.push(text); } - return tokens; + + // Merge adjacent text tokens into a single string. + var mergedTokens = []; + buffer.length = 0; + for (var i = 0; i < tokens.length; ++i) { + if (typeof tokens[i] == 'string') { + buffer.push(tokens[i]); + } else { + text = buffer.join(''); + if (text) { + mergedTokens.push(text); + } + buffer.length = 0; + mergedTokens.push(tokens[i]); + } + } + text = buffer.join(''); + if (text) { + mergedTokens.push(text); + } + buffer.length = 0; + + return mergedTokens; }; /** @@ -477,49 +632,314 @@ Blockly.tokenizeInterpolation = function(message) { * 87 characters ^ 20 length > 128 bits (better than a UUID). * @return {string} A globally unique ID string. */ -Blockly.genUid = function() { +Blockly.utils.genUid = function() { var length = 20; - var soupLength = Blockly.genUid.soup_.length; + var soupLength = Blockly.utils.genUid.soup_.length; var id = []; for (var i = 0; i < length; i++) { - id[i] = Blockly.genUid.soup_.charAt(Math.random() * soupLength); + id[i] = Blockly.utils.genUid.soup_.charAt(Math.random() * soupLength); } return id.join(''); }; /** - * Legal characters for the unique ID. - * Should be all on a US keyboard. No XML special characters or control codes. - * Removed $ due to issue 251. + * Legal characters for the unique ID. Should be all on a US keyboard. + * No characters that conflict with XML or JSON. Requests to remove additional + * 'problematic' characters from this soup will be denied. That's your failure + * to properly escape in your own environment. Issues #251, #625, #682, #1304. * @private */ -Blockly.genUid.soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' + +Blockly.utils.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; /** - * Local prompt function created to allow blockly developers to overwrite it - * with a customised version. This version uses the default window.prompt - * functionality, but it has been designed to be easily replaced by an - * asynchronous HTML based prompt. - * @param {string} message Main text message for the window prompt. - * @param {string=} opt_defaultInput Input string to be displayed by default. - * @param {function=} opt_callback Optional function callback to process the - * user input. - * @return {undefined|null|string} If no callback is provided it returns the - * value directly from window.prompt (null or string), otherwise it - * returns undefined. - */ -Blockly.prompt = function(message, opt_defaultInput, opt_callback) { - if (opt_callback === undefined) { - // If no callback provided to revert back to the normal blockly prompt - return window.prompt(message, opt_defaultInput); - } else { - // window.prompt still a blocking function, but returns value via callback - if (typeof opt_callback == 'function') { - opt_callback(window.prompt(message, opt_defaultInput)); - } else { - console.log('Blocky prompt callback needs to be a callable function.'); + * Wrap text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + */ +Blockly.utils.wrap = function(text, limit) { + var lines = text.split('\n'); + for (var i = 0; i < lines.length; i++) { + lines[i] = Blockly.utils.wrapLine_(lines[i], limit); + } + return lines.join('\n'); +}; + +/** + * Wrap single line of text to the specified width. + * @param {string} text Text to wrap. + * @param {number} limit Width to wrap each line. + * @return {string} Wrapped text. + * @private + */ +Blockly.utils.wrapLine_ = function(text, limit) { + if (text.length <= limit) { + // Short text, no need to wrap. + return text; + } + // Split the text into words. + var words = text.trim().split(/\s+/); + // Set limit to be the length of the largest word. + for (var i = 0; i < words.length; i++) { + if (words[i].length > limit) { + limit = words[i].length; + } + } + + var lastScore; + var score = -Infinity; + var lastText; + var lineCount = 1; + do { + lastScore = score; + lastText = text; + // Create a list of booleans representing if a space (false) or + // a break (true) appears after each word. + var wordBreaks = []; + // Seed the list with evenly spaced linebreaks. + var steps = words.length / lineCount; + var insertedBreaks = 1; + for (var i = 0; i < words.length - 1; i++) { + if (insertedBreaks < (i + 1.5) / steps) { + insertedBreaks++; + wordBreaks[i] = true; + } else { + wordBreaks[i] = false; + } + } + wordBreaks = Blockly.utils.wrapMutate_(words, wordBreaks, limit); + score = Blockly.utils.wrapScore_(words, wordBreaks, limit); + text = Blockly.utils.wrapToText_(words, wordBreaks); + lineCount++; + } while (score > lastScore); + return lastText; +}; + +/** + * Compute a score for how good the wrapping is. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {number} Larger the better. + * @private + */ +Blockly.utils.wrapScore_ = function(words, wordBreaks, limit) { + // If this function becomes a performance liability, add caching. + // Compute the length of each line. + var lineLengths = [0]; + var linePunctuation = []; + for (var i = 0; i < words.length; i++) { + lineLengths[lineLengths.length - 1] += words[i].length; + if (wordBreaks[i] === true) { + lineLengths.push(0); + linePunctuation.push(words[i].charAt(words[i].length - 1)); + } else if (wordBreaks[i] === false) { + lineLengths[lineLengths.length - 1]++; + } + } + var maxLength = Math.max.apply(Math, lineLengths); + + var score = 0; + for (var i = 0; i < lineLengths.length; i++) { + // Optimize for width. + // -2 points per char over limit (scaled to the power of 1.5). + score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2; + // Optimize for even lines. + // -1 point per char smaller than max (scaled to the power of 1.5). + score -= Math.pow(maxLength - lineLengths[i], 1.5); + // Optimize for structure. + // Add score to line endings after punctuation. + if ('.?!'.indexOf(linePunctuation[i]) != -1) { + score += limit / 3; + } else if (',;)]}'.indexOf(linePunctuation[i]) != -1) { + score += limit / 4; + } + } + // All else being equal, the last line should not be longer than the + // previous line. For example, this looks wrong: + // aaa bbb + // ccc ddd eee + if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <= + lineLengths[lineLengths.length - 2]) { + score += 0.5; + } + return score; +}; + +/** + * Mutate the array of line break locations until an optimal solution is found. + * No line breaks are added or deleted, they are simply moved around. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @param {number} limit Width to wrap each line. + * @return {!Array.} New array of optimal line breaks. + * @private + */ +Blockly.utils.wrapMutate_ = function(words, wordBreaks, limit) { + var bestScore = Blockly.utils.wrapScore_(words, wordBreaks, limit); + var bestBreaks; + // Try shifting every line break forward or backward. + for (var i = 0; i < wordBreaks.length - 1; i++) { + if (wordBreaks[i] == wordBreaks[i + 1]) { + continue; + } + var mutatedWordBreaks = [].concat(wordBreaks); + mutatedWordBreaks[i] = !mutatedWordBreaks[i]; + mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1]; + var mutatedScore = + Blockly.utils.wrapScore_(words, mutatedWordBreaks, limit); + if (mutatedScore > bestScore) { + bestScore = mutatedScore; + bestBreaks = mutatedWordBreaks; + } + } + if (bestBreaks) { + // Found an improvement. See if it may be improved further. + return Blockly.utils.wrapMutate_(words, bestBreaks, limit); + } + // No improvements found. Done. + return wordBreaks; +}; + +/** + * Reassemble the array of words into text, with the specified line breaks. + * @param {!Array.} words Array of each word. + * @param {!Array.} wordBreaks Array of line breaks. + * @return {string} Plain text. + * @private + */ +Blockly.utils.wrapToText_ = function(words, wordBreaks) { + var text = []; + for (var i = 0; i < words.length; i++) { + text.push(words[i]); + if (wordBreaks[i] !== undefined) { + text.push(wordBreaks[i] ? '\n' : ' '); + } + } + return text.join(''); +}; + +/** + * Check if 3D transforms are supported by adding an element + * and attempting to set the property. + * @return {boolean} true if 3D transforms are supported. + */ +Blockly.utils.is3dSupported = function() { + if (Blockly.utils.is3dSupported.cached_ !== undefined) { + return Blockly.utils.is3dSupported.cached_; + } + // CC-BY-SA Lorenzo Polidori + // stackoverflow.com/questions/5661671/detecting-transform-translate3d-support + if (!goog.global.getComputedStyle) { + return false; + } + + var el = document.createElement('p'); + var has3d = 'none'; + var transforms = { + 'webkitTransform': '-webkit-transform', + 'OTransform': '-o-transform', + 'msTransform': '-ms-transform', + 'MozTransform': '-moz-transform', + 'transform': 'transform' + }; + + // Add it to the body to get the computed style. + document.body.insertBefore(el, null); + + for (var t in transforms) { + if (el.style[t] !== undefined) { + el.style[t] = 'translate3d(1px,1px,1px)'; + var computedStyle = goog.global.getComputedStyle(el); + if (!computedStyle) { + // getComputedStyle in Firefox returns null when blockly is loaded + // inside an iframe with display: none. Returning false and not + // caching is3dSupported means we try again later. This is most likely + // when users are interacting with blocks which should mean blockly is + // visible again. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + document.body.removeChild(el); + return false; + } + has3d = computedStyle.getPropertyValue(transforms[t]); } } - return undefined; + document.body.removeChild(el); + Blockly.utils.is3dSupported.cached_ = has3d !== 'none'; + return Blockly.utils.is3dSupported.cached_; +}; + +/** + * Insert a node after a reference node. + * Contrast with node.insertBefore function. + * @param {!Element} newNode New element to insert. + * @param {!Element} refNode Existing element to precede new node. + * @private + */ +Blockly.utils.insertAfter_ = function(newNode, refNode) { + var siblingNode = refNode.nextSibling; + var parentNode = refNode.parentNode; + if (!parentNode) { + throw 'Reference node has no parent.'; + } + if (siblingNode) { + parentNode.insertBefore(newNode, siblingNode); + } else { + parentNode.appendChild(newNode); + } +}; + +/** + * Calls a function after the page has loaded, possibly immediately. + * @param {function()} fn Function to run. + * @throws Error Will throw if no global document can be found (e.g., Node.js). + */ +Blockly.utils.runAfterPageLoad = function(fn) { + if (typeof document != 'object') { + throw new Error('Blockly.utils.runAfterPageLoad() requires browser document.'); + } + if (document.readyState === 'complete') { + fn(); // Page has already loaded. Call immediately. + } else { + // Poll readyState. + var readyStateCheckInterval = setInterval(function() { + if (document.readyState === 'complete') { + clearInterval(readyStateCheckInterval); + fn(); + } + }, 10); + } +}; + +/** + * Sets the CSS transform property on an element. This function sets the + * non-vendor-prefixed and vendor-prefixed versions for backwards compatibility + * with older browsers. See http://caniuse.com/#feat=transforms2d + * @param {!Element} node The node which the CSS transform should be applied. + * @param {string} transform The value of the CSS `transform` property. + */ +Blockly.utils.setCssTransform = function(node, transform) { + node.style['transform'] = transform; + node.style['-webkit-transform'] = transform; +}; + +/** + * Get the position of the current viewport in window coordinates. This takes + * scroll into account. + * @return {!Object} an object containing window width, height, and scroll + * position in window coordinates. + * @package + */ +Blockly.utils.getViewportBBox = function() { + // Pixels. + var windowSize = goog.dom.getViewportSize(); + // Pixels, in window coordinates. + var scrollOffset = goog.style.getViewportPageOffset(document); + return { + right: windowSize.width + scrollOffset.x, + bottom: windowSize.height + scrollOffset.y, + top: scrollOffset.y, + left: scrollOffset.x + }; }; diff --git a/core/variable_map.js b/core/variable_map.js new file mode 100644 index 0000000..a704489 --- /dev/null +++ b/core/variable_map.js @@ -0,0 +1,396 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object representing a map of variables and their types. + * @author marisaleung@google.com (Marisa Leung) + */ +'use strict'; + +goog.provide('Blockly.VariableMap'); + + +/** + * Class for a variable map. This contains a dictionary data structure with + * variable types as keys and lists of variables as values. The list of + * variables are the type indicated by the key. + * @param {!Blockly.Workspace} workspace The workspace this map belongs to. + * @constructor + */ +Blockly.VariableMap = function(workspace) { + /** + * A map from variable type to list of variable names. The lists contain all + * of the named variables in the workspace, including variables + * that are not currently in use. + * @type {!Object.>} + * @private + */ + this.variableMap_ = {}; + + /** + * The workspace this map belongs to. + * @type {!Blockly.Workspace} + */ + this.workspace = workspace; +}; + +/** + * Clear the variable map. + */ +Blockly.VariableMap.prototype.clear = function() { + this.variableMap_ = new Object(null); +}; + +/* Begin functions for renaming variables. */ + +/** + * Rename the given variable by updating its name in the variable map. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @package + */ +Blockly.VariableMap.prototype.renameVariable = function(variable, newName) { + var type = variable.type; + var conflictVar = this.getVariable(newName, type); + var blocks = this.workspace.getAllBlocks(); + Blockly.Events.setGroup(true); + try { + // The IDs may match if the rename is a simple case change (name1 -> Name1). + if (!conflictVar || conflictVar.getId() == variable.getId()) { + this.renameVariableAndUses_(variable, newName, blocks); + } else { + this.renameVariableWithConflict_(variable, newName, conflictVar, blocks); + } + } finally { + Blockly.Events.setGroup(false); + } +}; + +/** + * Rename a variable by updating its name in the variable map. Identify the + * variable to rename with the given ID. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + */ +Blockly.VariableMap.prototype.renameVariableById = function(id, newName) { + var variable = this.getVariableById(id); + if (!variable) { + throw new Error('Tried to rename a variable that didn\'t exist. ID: ' + id); + } + + this.renameVariable(variable, newName); +}; + +/** + * Update the name of the given variable and refresh all references to it. + * The new name must not conflict with any existing variable names. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableAndUses_ = function(variable, + newName, blocks) { + Blockly.Events.fire(new Blockly.Events.VarRename(variable, newName)); + variable.name = newName; + for (var i = 0; i < blocks.length; i++) { + blocks[i].updateVarName(variable); + } +}; + +/** + * Update the name of the given variable to the same name as an existing + * variable. The two variables are coalesced into a single variable with the ID + * of the existing variable that was already using newName. + * Refresh all references to the variable. + * @param {!Blockly.VariableModel} variable Variable to rename. + * @param {string} newName New variable name. + * @param {!Blockly.VariableModel} conflictVar The variable that was already + * using newName. + * @param {!Array.} blocks The list of all blocks in the + * workspace. + * @private + */ +Blockly.VariableMap.prototype.renameVariableWithConflict_ = function(variable, + newName, conflictVar, blocks) { + var type = variable.type; + var oldCase = conflictVar.name; + + if (newName != oldCase) { + // Simple rename to change the case and update references. + this.renameVariableAndUses_(conflictVar, newName, blocks); + } + + // These blocks now refer to a different variable. + // These will fire change events. + for (var i = 0; i < blocks.length; i++) { + blocks[i].renameVarById(variable.getId(), conflictVar.getId()); + } + + // Finally delete the original variable, which is now unreferenced. + Blockly.Events.fire(new Blockly.Events.VarDelete(variable)); + // And remove it from the list. + var variableList = this.getVariablesOfType(type); + var variableIndex = variableList.indexOf(variable); + this.variableMap_[type].splice(variableIndex, 1); + +}; + +/* End functions for renaming variabless. */ + +/** + * Create a variable with a given name, optional type, and optional ID. + * @param {string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {Blockly.VariableModel} The newly created variable. + */ +Blockly.VariableMap.prototype.createVariable = function(name, + opt_type, opt_id) { + var variable = this.getVariable(name, opt_type); + if (variable) { + if (opt_id && variable.getId() != opt_id) { + throw Error('Variable "' + name + '" is already in use and its id is "' + + variable.getId() + '" which conflicts with the passed in ' + + 'id, "' + opt_id + '".'); + } + // The variable already exists and has the same ID. + return variable; + } + if (opt_id && this.getVariableById(opt_id)) { + throw Error('Variable id, "' + opt_id + '", is already in use.'); + } + opt_id = opt_id || Blockly.utils.genUid(); + opt_type = opt_type || ''; + + variable = new Blockly.VariableModel(this.workspace, name, opt_type, opt_id); + // If opt_type is not a key, create a new list. + if (!this.variableMap_[opt_type]) { + this.variableMap_[opt_type] = [variable]; + } else { + // Else append the variable to the preexisting list. + this.variableMap_[opt_type].push(variable); + } + return variable; +}; + +/* Begin functions for variable deletion. */ + +/** + * Delete a variable. + * @param {!Blockly.VariableModel} variable Variable to delete. + */ +Blockly.VariableMap.prototype.deleteVariable = function(variable) { + var variableList = this.variableMap_[variable.type]; + for (var i = 0, tempVar; tempVar = variableList[i]; i++) { + if (tempVar.getId() == variable.getId()) { + variableList.splice(i, 1); + Blockly.Events.fire(new Blockly.Events.VarDelete(variable)); + return; + } + } +}; + +/** + * Delete a variables by the passed in ID and all of its uses from this + * workspace. May prompt the user for confirmation. + * @param {string} id ID of variable to delete. + */ +Blockly.VariableMap.prototype.deleteVariableById = function(id) { + var variable = this.getVariableById(id); + if (variable) { + // Check whether this variable is a function parameter before deleting. + var variableName = variable.name; + var uses = this.getVariableUsesById(id); + for (var i = 0, block; block = uses[i]; i++) { + if (block.type == 'procedures_defnoreturn' || + block.type == 'procedures_defreturn') { + var procedureName = block.getFieldValue('NAME'); + var deleteText = Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE. + replace('%1', variableName). + replace('%2', procedureName); + Blockly.alert(deleteText); + return; + } + } + + var map = this; + if (uses.length > 1) { + // Confirm before deleting multiple blocks. + var confirmText = Blockly.Msg.DELETE_VARIABLE_CONFIRMATION. + replace('%1', String(uses.length)). + replace('%2', variableName); + Blockly.confirm(confirmText, + function(ok) { + if (ok) { + map.deleteVariableInternal_(variable, uses); + } + }); + } else { + // No confirmation necessary for a single block. + map.deleteVariableInternal_(variable, uses); + } + } else { + console.warn("Can't delete non-existent variable: " + id); + } +}; + +/** + * Deletes a variable and all of its uses from this workspace without asking the + * user for confirmation. + * @param {!Blockly.VariableModel} variable Variable to delete. + * @param {!Array.} uses An array of uses of the variable. + * @private + */ +Blockly.VariableMap.prototype.deleteVariableInternal_ = function(variable, + uses) { + var existingGroup = Blockly.Events.getGroup(); + if (!existingGroup) { + Blockly.Events.setGroup(true); + } + try { + for (var i = 0; i < uses.length; i++) { + uses[i].dispose(true, false); + } + this.deleteVariable(variable); + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + } +}; + +/* End functions for variable deletion. */ + +/** + * Find the variable by the given name and type and return it. Return null if + * it is not found. + * @param {string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. + * @return {Blockly.VariableModel} The variable with the given name, or null if + * it was not found. + */ +Blockly.VariableMap.prototype.getVariable = function(name, opt_type) { + var type = opt_type || ''; + var list = this.variableMap_[type]; + if (list) { + for (var j = 0, variable; variable = list[j]; j++) { + if (Blockly.Names.equals(variable.name, name)) { + return variable; + } + } + } + return null; +}; + +/** + * Find the variable by the given ID and return it. Return null if it is not + * found. + * @param {string} id The ID to check for. + * @return {Blockly.VariableModel} The variable with the given ID. + */ +Blockly.VariableMap.prototype.getVariableById = function(id) { + var keys = Object.keys(this.variableMap_); + for (var i = 0; i < keys.length; i++ ) { + var key = keys[i]; + for (var j = 0, variable; variable = this.variableMap_[key][j]; j++) { + if (variable.getId() == id) { + return variable; + } + } + } + return null; +}; + +/** + * Get a list containing all of the variables of a specified type. If type is + * null, return list of variables with empty string type. + * @param {?string} type Type of the variables to find. + * @return {!Array.} The sought after variables of the + * passed in type. An empty array if none are found. + */ +Blockly.VariableMap.prototype.getVariablesOfType = function(type) { + type = type || ''; + var variable_list = this.variableMap_[type]; + if (variable_list) { + return variable_list.slice(); + } + return []; +}; + +/** + * Return all variable types. This list always contains the empty string. + * @return {!Array.} List of variable types. + * @package + */ +Blockly.VariableMap.prototype.getVariableTypes = function() { + var types = Object.keys(this.variableMap_); + var hasEmpty = false; + for (var i = 0; i < types.length; i++) { + if (types[i] == '') { + hasEmpty = true; + } + } + if (!hasEmpty) { + types.push(''); + } + return types; +}; + +/** + * Return all variables of all types. + * @return {!Array.} List of variable models. + */ +Blockly.VariableMap.prototype.getAllVariables = function() { + var all_variables = []; + var keys = Object.keys(this.variableMap_); + for (var i = 0; i < keys.length; i++ ) { + all_variables = all_variables.concat(this.variableMap_[keys[i]]); + } + return all_variables; +}; + +/** + * Find all the uses of a named variable. + * @param {string} id ID of the variable to find. + * @return {!Array.} Array of block usages. + */ +Blockly.VariableMap.prototype.getVariableUsesById = function(id) { + var uses = []; + var blocks = this.workspace.getAllBlocks(); + // Iterate through every block and check the name. + for (var i = 0; i < blocks.length; i++) { + var blockVariables = blocks[i].getVarModels(); + if (blockVariables) { + for (var j = 0; j < blockVariables.length; j++) { + if (blockVariables[j].getId() == id) { + uses.push(blocks[i]); + } + } + } + } + return uses; +}; diff --git a/core/variable_model.js b/core/variable_model.js new file mode 100644 index 0000000..4dd4879 --- /dev/null +++ b/core/variable_model.js @@ -0,0 +1,99 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Components for the variable model. + * @author marisaleung@google.com (Marisa Leung) + */ +'use strict'; + +goog.provide('Blockly.VariableModel'); + +goog.require('goog.string'); + + +/** + * Class for a variable model. + * Holds information for the variable including name, ID, and type. + * @param {!Blockly.Workspace} workspace The variable's workspace. + * @param {!string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @see {Blockly.FieldVariable} + * @constructor + */ +Blockly.VariableModel = function(workspace, name, opt_type, opt_id) { + /** + * The workspace the variable is in. + * @type {!Blockly.Workspace} + */ + this.workspace = workspace; + + /** + * The name of the variable, typically defined by the user. It must be + * unique across all names used for procedures and variables. It may be + * changed by the user. + * @type {string} + */ + this.name = name; + + /** + * The type of the variable, such as 'int' or 'sound_effect'. This may be + * used to build a list of variables of a specific type. By default this is + * the empty string '', which is a specific type. + * @see {Blockly.FieldVariable} + * @type {string} + */ + this.type = opt_type || ''; + + /** + * A unique id for the variable. This should be defined at creation and + * not change, even if the name changes. In most cases this should be a + * UUID. + * @type {string} + * @private + */ + this.id_ = opt_id || Blockly.utils.genUid(); + + Blockly.Events.fire(new Blockly.Events.VarCreate(this)); +}; + +/** + * @return {!string} The ID for the variable. + */ +Blockly.VariableModel.prototype.getId = function() { + return this.id_; +}; + +/** + * A custom compare function for the VariableModel objects. + * @param {Blockly.VariableModel} var1 First variable to compare. + * @param {Blockly.VariableModel} var2 Second variable to compare. + * @return {number} -1 if name of var1 is less than name of var2, 0 if equal, + * and 1 if greater. + * @package + */ +Blockly.VariableModel.compareByName = function(var1, var2) { + return goog.string.caseInsensitiveCompare(var1.name, var2.name); +}; diff --git a/core/variables.js b/core/variables.js index 12b8eeb..75209df 100644 --- a/core/variables.js +++ b/core/variables.js @@ -24,69 +24,123 @@ */ 'use strict'; +/** + * @name Blockly.Variables + * @namespace + **/ goog.provide('Blockly.Variables'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.VariableModel'); goog.require('Blockly.Workspace'); goog.require('goog.string'); /** - * Category to separate variable names from procedures and generated functions. + * Constant to separate variable names from procedures and generated functions + * when running generators. + * @deprecated Use Blockly.VARIABLE_CATEGORY_NAME */ -Blockly.Variables.NAME_TYPE = 'VARIABLE'; +Blockly.Variables.NAME_TYPE = Blockly.VARIABLE_CATEGORY_NAME; /** - * Find all user-created variables. - * @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace. - * @return {!Array.} Array of variable names. + * Find all user-created variables that are in use in the workspace. + * For use by generators. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @param {!Blockly.Workspace} ws The workspace to search for variables. + * @return {!Array.} Array of variable models. */ -Blockly.Variables.allVariables = function(root) { - var blocks; - if (root.getDescendants) { - // Root is Block. - blocks = root.getDescendants(); - } else if (root.getAllBlocks) { - // Root is Workspace. - blocks = root.getAllBlocks(); - } else { - throw 'Not Block or Workspace: ' + root; - } +Blockly.Variables.allUsedVarModels = function(ws) { + var blocks = ws.getAllBlocks(); var variableHash = Object.create(null); // Iterate through every block and add each variable to the hash. for (var x = 0; x < blocks.length; x++) { - var blockVariables = blocks[x].getVars(); - 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) { - variableHash[varName.toLowerCase()] = varName; + var blockVariables = blocks[x].getVarModels(); + if (blockVariables) { + for (var y = 0; y < blockVariables.length; y++) { + var variable = blockVariables[y]; + if (variable.getId()) { + variableHash[variable.getId()] = variable; + } } } } // Flatten the hash into a list. var variableList = []; - for (var name in variableHash) { - variableList.push(variableHash[name]); + for (var id in variableHash) { + variableList.push(variableHash[id]); } return variableList; }; /** - * Find all instances of the specified variable and rename them. - * @param {string} oldName Variable to rename. - * @param {string} newName New variable name. - * @param {!Blockly.Workspace} workspace Workspace rename variables in. + * Find all user-created variables that are in use in the workspace and return + * only their names. + * For use by generators. + * To get a list of all variables on a workspace, including unused variables, + * call Workspace.getAllVariables. + * @deprecated January 2018 */ -Blockly.Variables.renameVariable = function(oldName, newName, workspace) { - Blockly.Events.setGroup(true); +Blockly.Variables.allUsedVariables = function() { + console.warn('Deprecated call to Blockly.Variables.allUsedVariables. ' + + 'Use Blockly.Variables.allUsedVarModels instead.\nIf this is a major ' + + 'issue please file a bug on GitHub.'); +}; + +/** + * Find all developer variables used by blocks in the workspace. + * Developer variables are never shown to the user, but are declared as global + * variables in the generated code. + * To declare developer variables, define the getDeveloperVariables function on + * your block and return a list of variable names. + * For use by generators. + * @param {!Blockly.Workspace} workspace The workspace to search. + * @return {!Array.} A list of non-duplicated variable names. + */ +Blockly.Variables.allDeveloperVariables = function(workspace) { var blocks = workspace.getAllBlocks(); - // Iterate through every block. + var hash = {}; for (var i = 0; i < blocks.length; i++) { - blocks[i].renameVar(oldName, newName); + var block = blocks[i]; + if (block.getDeveloperVars) { + var devVars = block.getDeveloperVars(); + for (var j = 0; j < devVars.length; j++) { + hash[devVars[j]] = devVars[j]; + } + } + } + + // Flatten the hash into a list. + var list = []; + for (var name in hash) { + list.push(hash[name]); } - Blockly.Events.setGroup(false); + return list; +}; + +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!Blockly.Workspace} workspace The workspace contianing variables. + * @return {!Array.} Array of XML elements. + */ +Blockly.Variables.flyoutCategory = function(workspace) { + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE'); + + workspace.registerButtonCallback('CREATE_VARIABLE', function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace()); + }); + + xmlList.push(button); + + var blockList = Blockly.Variables.flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; }; /** @@ -94,59 +148,64 @@ Blockly.Variables.renameVariable = function(oldName, newName, workspace) { * @param {!Blockly.Workspace} workspace The workspace contianing variables. * @return {!Array.} Array of XML block elements. */ -Blockly.Variables.flyoutCategory = function(workspace) { - var variableList = Blockly.Variables.allVariables(workspace); - variableList.sort(goog.string.caseInsensitiveCompare); - // In addition to the user's variables, we also want to display the default - // variable name at the top. We also don't want this duplicated if the - // user has created a variable of the same name. - goog.array.remove(variableList, Blockly.Msg.VARIABLES_DEFAULT_NAME); - variableList.unshift(Blockly.Msg.VARIABLES_DEFAULT_NAME); +Blockly.Variables.flyoutCategoryBlocks = function(workspace) { + var variableModelList = workspace.getVariablesOfType(''); + variableModelList.sort(Blockly.VariableModel.compareByName); var xmlList = []; - for (var i = 0; i < variableList.length; i++) { + if (variableModelList.length > 0) { + var firstVariable = variableModelList[0]; if (Blockly.Blocks['variables_set']) { - // - // item - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'variables_set'); - if (Blockly.Blocks['variables_get']) { - block.setAttribute('gap', 8); - } - var field = goog.dom.createDom('field', null, variableList[i]); - field.setAttribute('name', 'VAR'); - block.appendChild(field); + var gap = Blockly.Blocks['math_change'] ? 8 : 24; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; xmlList.push(block); } - if (Blockly.Blocks['variables_get']) { - // - // item - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'variables_get'); - if (Blockly.Blocks['variables_set']) { - block.setAttribute('gap', 24); - } - var field = goog.dom.createDom('field', null, variableList[i]); - field.setAttribute('name', 'VAR'); - block.appendChild(field); + if (Blockly.Blocks['math_change']) { + var gap = Blockly.Blocks['variables_get'] ? 20 : 8; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + '' + + '1' + + '' + + '' + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; xmlList.push(block); } + + for (var i = 0, variable; variable = variableModelList[i]; i++) { + if (Blockly.Blocks['variables_get']) { + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(variable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + 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'. + * 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'. * @param {!Blockly.Workspace} workspace The workspace to be unique in. -* @return {string} New variable name. -*/ + * @return {string} New variable name. + */ Blockly.Variables.generateUniqueName = function(workspace) { - var variableList = Blockly.Variables.allVariables(workspace); + var variableList = workspace.getAllVariables(); var newName = ''; if (variableList.length) { var nameSuffix = 1; @@ -156,7 +215,7 @@ Blockly.Variables.generateUniqueName = function(workspace) { while (!newName) { var inUse = false; for (var i = 0; i < variableList.length; i++) { - if (variableList[i].toLowerCase() == potName) { + if (variableList[i].name.toLowerCase() == potName) { // This potential name is already used. inUse = true; break; @@ -185,3 +244,329 @@ Blockly.Variables.generateUniqueName = function(workspace) { } return newName; }; + +/** + * Handles "Create Variable" button in the default variables toolbox category. + * It will prompt the user for a varibale name, including re-prompts if a name + * is already in use among the workspace's variables. + * + * Custom button handlers can delegate to this function, allowing variables + * types and after-creation processing. More complex customization (e.g., + * prompting for variable type) is beyond the scope of this function. + * + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @param {function(?string=)=} opt_callback A callback. It will be passed an + * acceptable new variable name, or null if change is to be aborted (cancel + * button), or undefined if an existing variable was chosen. + * @param {string=} opt_type The type of the variable like 'int', 'string', or + * ''. This will default to '', which is a specific type. + */ +Blockly.Variables.createVariableButtonHandler = function( + workspace, opt_callback, opt_type) { + var type = opt_type || ''; + // This function needs to be named so it can be called recursively. + var promptAndCheckWithAlert = function(defaultName) { + Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName, + function(text) { + if (text) { + var existing = + Blockly.Variables.nameUsedWithAnyType_(text, workspace); + if (existing) { + var lowerCase = text.toLowerCase(); + if (existing.type == type) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace( + '%1', lowerCase); + } else { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE; + msg = msg.replace('%1', lowerCase).replace('%2', existing.type); + } + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(text); // Recurse + }); + } else { + // No conflict + workspace.createVariable(text, type); + if (opt_callback) { + opt_callback(text); + } + } + } else { + // User canceled prompt. + if (opt_callback) { + opt_callback(null); + } + } + }); + }; + promptAndCheckWithAlert(''); +}; +goog.exportSymbol('Blockly.Variables.createVariableButtonHandler', + Blockly.Variables.createVariableButtonHandler); + +/** + * Original name of Blockly.Variables.createVariableButtonHandler(..). + * @deprecated Use Blockly.Variables.createVariableButtonHandler(..). + * + * @param {!Blockly.Workspace} workspace The workspace on which to create the + * variable. + * @param {function(?string=)=} opt_callback A callback. It will be passed an + * acceptable new variable name, or null if change is to be aborted (cancel + * button), or undefined if an existing variable was chosen. + * @param {string=} opt_type The type of the variable like 'int', 'string', or + * ''. This will default to '', which is a specific type. + */ +Blockly.Variables.createVariable = + Blockly.Variables.createVariableButtonHandler; +goog.exportSymbol('Blockly.Variables.createVariable', + Blockly.Variables.createVariable); + +/** + * Rename a variable with the given workspace, variableType, and oldName. + * @param {!Blockly.Workspace} workspace The workspace on which to rename the + * variable. + * @param {Blockly.VariableModel} variable Variable to rename. + * @param {function(?string=)=} opt_callback A callback. It will + * be passed an acceptable new variable name, or null if change is to be + * aborted (cancel button), or undefined if an existing variable was chosen. + */ +Blockly.Variables.renameVariable = function(workspace, variable, + opt_callback) { + // This function needs to be named so it can be called recursively. + var promptAndCheckWithAlert = function(defaultName) { + var promptText = + Blockly.Msg.RENAME_VARIABLE_TITLE.replace('%1', variable.name); + Blockly.Variables.promptName(promptText, defaultName, + function(newName) { + if (newName) { + var existing = Blockly.Variables.nameUsedWithOtherType_(newName, + variable.type, workspace); + if (existing) { + var msg = Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE + .replace('%1', newName.toLowerCase()) + .replace('%2', existing.type); + Blockly.alert(msg, + function() { + promptAndCheckWithAlert(newName); // Recurse + }); + } else { + workspace.renameVariableById(variable.getId(), newName); + if (opt_callback) { + opt_callback(newName); + } + } + } else { + // User canceled prompt. + if (opt_callback) { + opt_callback(null); + } + } + }); + }; + promptAndCheckWithAlert(''); +}; + +/** + * Prompt the user for a new variable name. + * @param {string} promptText The string of the prompt. + * @param {string} defaultText The default value to show in the prompt's field. + * @param {function(?string)} callback A callback. It will return the new + * variable name, or null if the user picked something illegal. + */ +Blockly.Variables.promptName = function(promptText, defaultText, callback) { + Blockly.prompt(promptText, defaultText, function(newVar) { + // Merge runs of whitespace. Strip leading and trailing whitespace. + // Beyond this, all names are legal. + if (newVar) { + newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); + if (newVar == Blockly.Msg.RENAME_VARIABLE || + newVar == Blockly.Msg.NEW_VARIABLE) { + // Ok, not ALL names are legal... + newVar = null; + } + } + callback(newVar); + }); +}; + +/** + * Check whether there exists a variable with the given name but a different + * type. + * @param {string} name The name to search for. + * @param {string} type The type to exclude from the search. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name and a + * different type, or null if none was found. + * @private + */ +Blockly.Variables.nameUsedWithOtherType_ = function(name, type, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name && variable.type != type) { + return variable; + } + } + return null; +}; + +/** + * Check whether there exists a variable with the given name of any type. + * @param {string} name The name to search for. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. + * @return {?Blockly.VariableModel} The variable with the given name, or null if + * none was found. + * @private + */ +Blockly.Variables.nameUsedWithAnyType_ = function(name, workspace) { + var allVariables = workspace.getVariableMap().getAllVariables(); + + name = name.toLowerCase(); + for (var i = 0, variable; variable = allVariables[i]; i++) { + if (variable.name.toLowerCase() == name) { + return variable; + } + } + return null; +}; + +/** + * Generate XML string for variable field. + * @param {!Blockly.VariableModel} variableModel The variable model to generate + * an XML string from. + * @return {string} The generated XML. + * @private + */ +Blockly.Variables.generateVariableFieldXml_ = function(variableModel) { + // The variable name may be user input, so it may contain characters that need + // to be escaped to create valid XML. + var typeString = variableModel.type; + if (typeString == '') { + typeString = '\'\''; + } + var text = '' + goog.string.htmlEscape(variableModel.name) + ''; + return text; +}; + +/** + * Helper function to look up or create a variable on the given workspace. + * If no variable exists, creates and returns it. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up or create the variable, or null. + * @param {string=} opt_name The string to use to look up or create the + * variable. + * @param {string=} opt_type The type to use to look up or create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + */ +Blockly.Variables.getOrCreateVariablePackage = function(workspace, id, opt_name, + opt_type) { + var variable = Blockly.Variables.getVariable(workspace, id, opt_name, + opt_type); + if (!variable) { + variable = Blockly.Variables.createVariable_(workspace, id, opt_name, + opt_type); + } + return variable; +}; + +/** + * Look up a variable on the given workspace. + * Always looks in the main workspace before looking in the flyout workspace. + * Always prefers lookup by ID to lookup by name + type. + * @param {!Blockly.Workspace} workspace The workspace to search for the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to look up the variable, or null. + * @param {string=} opt_name The string to use to look up the variable. Only + * used if lookup by ID fails. + * @param {string=} opt_type The type to use to look up the variable. Only used + * if lookup by ID fails. + * @return {?Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination, or null if not found. + * @package + */ +Blockly.Variables.getVariable = function(workspace, id, opt_name, opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Try to just get the variable, by ID if possible. + if (id) { + // Look in the real variable map before checking the potential variable map. + var variable = workspace.getVariableById(id); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariableById(id); + } + } else if (opt_name) { + if (opt_type == undefined) { + throw new Error('Tried to look up a variable by name without a type'); + } + // Otherwise look up by name and type. + var variable = workspace.getVariable(opt_name, opt_type); + if (!variable && potentialVariableMap) { + variable = potentialVariableMap.getVariable(opt_name, opt_type); + } + } + return variable; +}; + +/** + * Helper function to create a variable on the given workspace. + * @param {!Blockly.Workspace} workspace The workspace in which to create the + * variable. It may be a flyout workspace or main workspace. + * @param {string} id The ID to use to create the variable, or null. + * @param {string=} opt_name The string to use to create the variable. + * @param {string=} opt_type The type to use to create the variable. + * @return {!Blockly.VariableModel} The variable corresponding to the given ID + * or name + type combination. + * @private + */ +Blockly.Variables.createVariable_ = function(workspace, id, opt_name, + opt_type) { + var potentialVariableMap = workspace.getPotentialVariableMap(); + // Variables without names get uniquely named for this workspace. + if (!opt_name) { + var ws = workspace.isFlyout ? workspace.targetWorkspace : workspace; + opt_name = Blockly.Variables.generateUniqueName(ws); + } + + // Create a potential variable if in the flyout. + if (potentialVariableMap) { + var variable = potentialVariableMap.createVariable(opt_name, opt_type, id); + } else { // In the main workspace, create a real variable. + var variable = workspace.createVariable(opt_name, opt_type, id); + } + return variable; +}; + +/** + * Helper function to get the list of variables that have been added to the + * workspace after adding a new block, using the given list of variables that + * were in the workspace before the new block was added. + * @param {!Blockly.Workspace} workspace The workspace to inspect. + * @param {!Array.} originalVariables The array of + * variables that existed in the workspace before adding the new block. + * @return {!Array.} The new array of variables that were + * freshly added to the workspace after creating the new block, or [] if no + * new variables were added to the workspace. + * @package + */ +Blockly.Variables.getAddedVariables = function(workspace, originalVariables) { + var allCurrentVariables = workspace.getAllVariables(); + var addedVariables = []; + if (originalVariables.length != allCurrentVariables.length) { + for (var i = 0; i < allCurrentVariables.length; i++) { + var variable = allCurrentVariables[i]; + // For any variable that is present in allCurrentVariables but not + // present in originalVariables, add the variable to addedVariables. + if (!originalVariables.includes(variable)) { + addedVariables.push(variable); + } + } + } + return addedVariables; +}; diff --git a/core/variables_dynamic.js b/core/variables_dynamic.js new file mode 100644 index 0000000..1a88e5e --- /dev/null +++ b/core/variables_dynamic.js @@ -0,0 +1,116 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 dynamic. + * + * @author duzc2dtw@gmail.com (Du Tian Wei) + */ +'use strict'; + +goog.provide('Blockly.VariablesDynamic'); + +goog.require('Blockly.Variables'); +goog.require('Blockly.Blocks'); +goog.require('Blockly.constants'); +goog.require('Blockly.VariableModel'); +// TODO Fix circular dependencies +// goog.require('Blockly.Workspace'); +goog.require('goog.string'); + + +Blockly.VariablesDynamic.onCreateVariableButtonClick_String = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'String'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Number = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Number'); +}; +Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour = function(button) { + Blockly.Variables.createVariableButtonHandler(button.getTargetWorkspace(), null, 'Colour'); +}; +/** + * Construct the elements (blocks and button) required by the flyout for the + * variable category. + * @param {!Blockly.Workspace} workspace The workspace containing variables. + * @return {!Array.} Array of XML elements. + */ +Blockly.VariablesDynamic.flyoutCategory = function(workspace) { + var xmlList = []; + var button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_STRING_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_STRING'); + xmlList.push(button); + button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_NUMBER_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_NUMBER'); + xmlList.push(button);button = goog.dom.createDom('button'); + button.setAttribute('text', Blockly.Msg.NEW_COLOUR_VARIABLE); + button.setAttribute('callbackKey', 'CREATE_VARIABLE_COLOUR'); + xmlList.push(button); + + workspace.registerButtonCallback('CREATE_VARIABLE_STRING', + Blockly.VariablesDynamic.onCreateVariableButtonClick_String); + workspace.registerButtonCallback('CREATE_VARIABLE_NUMBER', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Number); + workspace.registerButtonCallback('CREATE_VARIABLE_COLOUR', + Blockly.VariablesDynamic.onCreateVariableButtonClick_Colour); + + + var blockList = Blockly.VariablesDynamic.flyoutCategoryBlocks(workspace); + xmlList = xmlList.concat(blockList); + return xmlList; +}; + +/** + * Construct the blocks required by the flyout for the variable category. + * @param {!Blockly.Workspace} workspace The workspace containing variables. + * @return {!Array.} Array of XML block elements. + */ +Blockly.VariablesDynamic.flyoutCategoryBlocks = function(workspace) { + var variableModelList = workspace.getAllVariables(); + variableModelList.sort(Blockly.VariableModel.compareByName); + + var xmlList = []; + if (variableModelList.length > 0) { + if (Blockly.Blocks['variables_set_dynamic']) { + var firstVariable = variableModelList[0]; + var gap = 24; + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(firstVariable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + if (Blockly.Blocks['variables_get_dynamic']) { + for (var i = 0, variable; variable = variableModelList[i]; i++) { + var blockText = '' + + '' + + Blockly.Variables.generateVariableFieldXml_(variable) + + '' + + ''; + var block = Blockly.Xml.textToDom(blockText).firstChild; + xmlList.push(block); + } + } + } + return xmlList; +}; diff --git a/core/warning.js b/core/warning.js index bffbf06..6805d42 100644 --- a/core/warning.js +++ b/core/warning.js @@ -56,22 +56,28 @@ Blockly.Warning.prototype.collapseHidden = false; */ Blockly.Warning.prototype.drawIcon_ = function(group) { // Triangle with rounded corners. - Blockly.createSvgElement('path', - {'class': 'blocklyIconShape', - 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'}, - group); + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconShape', + 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z' + }, + group); // Can't use a real '!' text character since different browsers and operating // systems render it differently. // Body of exclamation point. - Blockly.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'}, - group); + Blockly.utils.createSvgElement('path', + { + 'class': 'blocklyIconSymbol', + 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z' + }, + group); // Dot of exclamation point. - Blockly.createSvgElement('rect', - {'class': 'blocklyIconSymbol', - 'x': '7', 'y': '11', 'height': '2', 'width': '2'}, - group); + Blockly.utils.createSvgElement('rect', + { + 'class': 'blocklyIconSymbol', + 'x': '7', 'y': '11', 'height': '2', 'width': '2' + }, + group); }; /** @@ -81,14 +87,18 @@ Blockly.Warning.prototype.drawIcon_ = function(group) { * @private */ Blockly.Warning.textToDom_ = function(text) { - var paragraph = /** @type {!SVGTextElement} */ ( - Blockly.createSvgElement('text', - {'class': 'blocklyText blocklyBubbleText', - 'y': Blockly.Bubble.BORDER_WIDTH}, - null)); + var paragraph = /** @type {!SVGTextElement} */ + (Blockly.utils.createSvgElement( + 'text', + { + 'class': 'blocklyText blocklyBubbleText', + 'y': Blockly.Bubble.BORDER_WIDTH + }, + null) + ); var lines = text.split('\n'); for (var i = 0; i < lines.length; i++) { - var tspanElement = Blockly.createSvgElement('tspan', + var tspanElement = Blockly.utils.createSvgElement('tspan', {'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph); var textNode = document.createTextNode(lines[i]); tspanElement.appendChild(textNode); @@ -139,7 +149,9 @@ Blockly.Warning.prototype.setVisible = function(visible) { * @param {!Event} e Mouse up event. * @private */ -Blockly.Warning.prototype.bodyFocus_ = function(e) { + +Blockly.Warning.prototype.bodyFocus_ = function( + /* eslint-disable no-unused-vars */ e /* eslint-enable no-unused-vars */) { this.bubble_.promote_(); }; diff --git a/core/widgetdiv.js b/core/widgetdiv.js index 817d15b..be8a0e1 100644 --- a/core/widgetdiv.js +++ b/core/widgetdiv.js @@ -26,10 +26,15 @@ */ 'use strict'; +/** + * @name Blockly.WidgetDiv + * @namespace + **/ goog.provide('Blockly.WidgetDiv'); goog.require('Blockly.Css'); goog.require('goog.dom'); +goog.require('goog.dom.TagName'); goog.require('goog.style'); @@ -61,7 +66,8 @@ Blockly.WidgetDiv.createDom = function() { return; // Already created. } // Create an HTML container for popup overlays (e.g. editor widgets). - Blockly.WidgetDiv.DIV = goog.dom.createDom('div', 'blocklyWidgetDiv'); + Blockly.WidgetDiv.DIV = + goog.dom.createDom(goog.dom.TagName.DIV, 'blocklyWidgetDiv'); document.body.appendChild(Blockly.WidgetDiv.DIV); }; @@ -93,7 +99,6 @@ Blockly.WidgetDiv.hide = function() { Blockly.WidgetDiv.DIV.style.display = 'none'; Blockly.WidgetDiv.DIV.style.left = ''; Blockly.WidgetDiv.DIV.style.top = ''; - Blockly.WidgetDiv.DIV.style.height = ''; Blockly.WidgetDiv.dispose_ && Blockly.WidgetDiv.dispose_(); Blockly.WidgetDiv.dispose_ = null; goog.dom.removeChildren(Blockly.WidgetDiv.DIV); @@ -122,14 +127,14 @@ Blockly.WidgetDiv.hideIfOwner = function(oldOwner) { /** * Position the widget at a given location. Prevent the widget from going * offscreen top or left (right in RTL). - * @param {number} anchorX Horizontal location (window coorditates, not body). - * @param {number} anchorY Vertical location (window coorditates, not body). + * @param {number} anchorX Horizontal location (window coordinates, not body). + * @param {number} anchorY Vertical location (window coordinates, not body). * @param {!goog.math.Size} windowSize Height/width of window. * @param {!goog.math.Coordinate} scrollOffset X/y of window scrollbars. * @param {boolean} rtl True if RTL, false if LTR. */ Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize, - scrollOffset, rtl) { + scrollOffset, rtl) { // Don't let the widget go above the top edge of the window. if (anchorY < scrollOffset.y) { anchorY = scrollOffset.y; @@ -145,8 +150,103 @@ Blockly.WidgetDiv.position = function(anchorX, anchorY, windowSize, anchorX = scrollOffset.x; } } - Blockly.WidgetDiv.DIV.style.left = anchorX + 'px'; - Blockly.WidgetDiv.DIV.style.top = anchorY + 'px'; - Blockly.WidgetDiv.DIV.style.height = - (windowSize.height - anchorY + scrollOffset.y) + 'px'; + Blockly.WidgetDiv.positionInternal_(anchorX, anchorY, windowSize.height); +}; + +/** + * Set the widget div's position and height. This function does nothing clever: + * it will not ensure that your widget div ends up in the visible window. + * @param {number} x Horizontal location (window coordinates, not body). + * @param {number} y Vertical location (window coordinates, not body). + * @param {number} height The height of the widget div (pixels). + * @private + */ +Blockly.WidgetDiv.positionInternal_ = function(x, y, height) { + Blockly.WidgetDiv.DIV.style.left = x + 'px'; + Blockly.WidgetDiv.DIV.style.top = y + 'px'; + Blockly.WidgetDiv.DIV.style.height = height + 'px'; +}; + +/** + * Position the widget div based on an anchor rectangle. + * The widget should be placed adjacent to but not overlapping the anchor + * rectangle. The preferred position is directly below and aligned to the left + * (ltr) or right (rtl) side of the anchor. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {!goog.math.Size} widgetSize The size of the widget that is inside the + * widget div, in window coordinates. + * @param {boolean} rtl Whether the workspace is in RTL mode. This determines + * horizontal alignment. + * @package + */ +Blockly.WidgetDiv.positionWithAnchor = function(viewportBBox, anchorBBox, + widgetSize, rtl) { + var y = Blockly.WidgetDiv.calculateY_(viewportBBox, anchorBBox, widgetSize); + var x = Blockly.WidgetDiv.calculateX_(viewportBBox, anchorBBox, widgetSize, + rtl); + + Blockly.WidgetDiv.positionInternal_(x, y, widgetSize.height); +}; + +/** + * Calculate an x position (in window coordinates) such that the widget will not + * be offscreen on the right or left. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {goog.math.Size} widgetSize The dimensions of the widget inside the + * widget div. + * @param {boolean} rtl Whether the Blockly workspace is in RTL mode. + * @return {number} A valid x-coordinate for the top left corner of the widget + * div, in window coordinates. + * @private + */ +Blockly.WidgetDiv.calculateX_ = function(viewportBBox, anchorBBox, widgetSize, + rtl) { + if (rtl) { + // Try to align the right side of the field and the right side of the widget. + var widgetLeft = anchorBBox.right - widgetSize.width; + // Don't go offscreen left. + var x = Math.max(widgetLeft, viewportBBox.left); + // But really don't go offscreen right: + return Math.min(x, viewportBBox.right - widgetSize.width); + } else { + // Try to align the left side of the field and the left side of the widget. + // Don't go offscreen right. + var x = Math.min(anchorBBox.left, + viewportBBox.right - widgetSize.width); + // But left is more important, because that's where the text is. + return Math.max(x, viewportBBox.left); + } +}; + +/** + * Calculate a y position (in window coordinates) such that the widget will not + * be offscreen on the top or bottom. + * @param {!Object} viewportBBox The bounding rectangle of the current viewport, + * in window coordinates. + * @param {!Object} anchorBBox The bounding rectangle of the anchor, in window + * coordinates. + * @param {goog.math.Size} widgetSize The dimensions of the widget inside the + * widget div. + * @return {number} A valid y-coordinate for the top left corner of the widget + * div, in window coordinates. + * @private + */ +Blockly.WidgetDiv.calculateY_ = function(viewportBBox, anchorBBox, widgetSize) { + // Flip the widget vertically if off the bottom. + if (anchorBBox.bottom + widgetSize.height >= + viewportBBox.bottom) { + // The bottom of the widget is at the top of the field. + return anchorBBox.top - widgetSize.height; + // The widget could go off the top of the window, but it would also go off + // the bottom. The window is just too small. + } else { + // The top of the widget is at the bottom of the field. + return anchorBBox.bottom; + } }; diff --git a/core/workspace.js b/core/workspace.js index 200196b..6f7b062 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -26,18 +26,20 @@ goog.provide('Blockly.Workspace'); +goog.require('Blockly.VariableMap'); +goog.require('goog.array'); goog.require('goog.math'); /** * Class for a workspace. This is a data structure that contains blocks. * There is no UI, and can be created headlessly. - * @param {Blockly.Options} opt_options Dictionary of options. + * @param {!Blockly.Options=} opt_options Dictionary of options. * @constructor */ Blockly.Workspace = function(opt_options) { /** @type {string} */ - this.id = Blockly.genUid(); + this.id = Blockly.utils.genUid(); Blockly.Workspace.WorkspaceDB_[this.id] = this; /** @type {!Blockly.Options} */ this.options = opt_options || {}; @@ -73,17 +75,38 @@ Blockly.Workspace = function(opt_options) { * @private */ this.blockDB_ = Object.create(null); + + /** + * @type {!Blockly.VariableMap} + * A map from variable type to list of variable names. The lists contain all + * of the named variables in the workspace, including variables + * that are not currently in use. + * @private + */ + this.variableMap_ = new Blockly.VariableMap(this); + + /** + * Blocks in the flyout can refer to variables that don't exist in the main + * workspace. For instance, the "get item in list" block refers to an "item" + * variable regardless of whether the variable has been created yet. + * A FieldVariable must always refer to a Blockly.VariableModel. We reconcile + * these by tracking "potential" variables in the flyout. These variables + * become real when references to them are dragged into the main workspace. + * @type {!Blockly.VariableMap} + * @private + */ + this.potentialVariableMap_ = null; }; /** - * Workspaces may be headless. - * @type {boolean} True if visible. False if headless. + * Returns `true` if the workspace is visible and `false` if it's headless. + * @type {boolean} */ Blockly.Workspace.prototype.rendered = false; /** - * Maximum number of undo events in stack. - * @type {number} 0 to turn off undo, Infinity for unlimited. + * Maximum number of undo events in stack. `0` turns off undo, `Infinity` sets it to unlimited. + * @type {number} */ Blockly.Workspace.prototype.MAX_UNDO = 1024; @@ -108,7 +131,7 @@ Blockly.Workspace.SCAN_ANGLE = 3; /** * Add a block to the list of top blocks. - * @param {!Blockly.Block} block Block to remove. + * @param {!Blockly.Block} block Block to add. */ Blockly.Workspace.prototype.addTopBlock = function(block) { this.topBlocks_.push(block); @@ -119,15 +142,7 @@ Blockly.Workspace.prototype.addTopBlock = function(block) { * @param {!Blockly.Block} block Block to remove. */ Blockly.Workspace.prototype.removeTopBlock = function(block) { - var found = false; - for (var child, i = 0; child = this.topBlocks_[i]; i++) { - if (child == block) { - this.topBlocks_.splice(i, 1); - found = true; - break; - } - } - if (!found) { + if (!goog.array.remove(this.topBlocks_, block)) { throw 'Block not present in workspace\'s list of top-most blocks.'; } }; @@ -181,8 +196,137 @@ Blockly.Workspace.prototype.clear = function() { if (!existingGroup) { Blockly.Events.setGroup(false); } + this.variableMap_.clear(); + if (this.potentialVariableMap_) { + this.potentialVariableMap_.clear(); + } +}; + +/* Begin functions that are just pass-throughs to the variable map. */ +/** + * Rename a variable by updating its name in the variable map. Identify the + * variable to rename with the given ID. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + */ +Blockly.Workspace.prototype.renameVariableById = function(id, newName) { + this.variableMap_.renameVariableById(id, newName); }; +/** + * Create a variable with a given name, optional type, and optional ID. + * @param {!string} name The name of the variable. This must be unique across + * variables and procedures. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {?Blockly.VariableModel} The newly created variable. + */ +Blockly.Workspace.prototype.createVariable = function(name, opt_type, opt_id) { + return this.variableMap_.createVariable(name, opt_type, opt_id); +}; + +/** + * Find all the uses of the given variable, which is identified by ID. + * @param {string} id ID of the variable to find. + * @return {!Array.} Array of block usages. + */ +Blockly.Workspace.prototype.getVariableUsesById = function(id) { + return this.variableMap_.getVariableUsesById(id); +}; + +/** + * Delete a variables by the passed in ID and all of its uses from this + * workspace. May prompt the user for confirmation. + * @param {string} id ID of variable to delete. + */ +Blockly.Workspace.prototype.deleteVariableById = function(id) { + this.variableMap_.deleteVariableById(id); +}; + +/** + * Deletes a variable and all of its uses from this workspace without asking the + * user for confirmation. + * @param {!Blockly.VariableModel} variable Variable to delete. + * @param {!Array.} uses An array of uses of the variable. + * @private + */ +Blockly.Workspace.prototype.deleteVariableInternal_ = function(variable, uses) { + this.variableMap_.deleteVariableInternal_(variable, uses); +}; + +/** + * Check whether a variable exists with the given name. The check is + * case-insensitive. + * @param {string} name The name to check for. + * @return {number} The index of the name in the variable list, or -1 if it is + * not present. + * @deprecated April 2017 + */ + +Blockly.Workspace.prototype.variableIndexOf = function( + /* eslint-disable no-unused-vars */ name + /* eslint-enable no-unused-vars */) { + console.warn( + 'Deprecated call to Blockly.Workspace.prototype.variableIndexOf'); + return -1; +}; + +/** + * Find the variable by the given name and return it. Return null if it is not + * found. + * TODO (#1199): Possibly delete this function. + * @param {!string} name The name to check for. + * @param {string=} opt_type The type of the variable. If not provided it + * defaults to the empty string, which is a specific type. + * @return {?Blockly.VariableModel} the variable with the given name. + */ +Blockly.Workspace.prototype.getVariable = function(name, opt_type) { + return this.variableMap_.getVariable(name, opt_type); +}; + +/** + * Find the variable by the given ID and return it. Return null if it is not + * found. + * @param {!string} id The ID to check for. + * @return {?Blockly.VariableModel} The variable with the given ID. + */ +Blockly.Workspace.prototype.getVariableById = function(id) { + return this.variableMap_.getVariableById(id); +}; + +/** + * Find the variable with the specified type. If type is null, return list of + * variables with empty string type. + * @param {?string} type Type of the variables to find. + * @return {Array.} The sought after variables of the + * passed in type. An empty array if none are found. + */ +Blockly.Workspace.prototype.getVariablesOfType = function(type) { + return this.variableMap_.getVariablesOfType(type); +}; + +/** + * Return all variable types. + * @return {!Array.} List of variable types. + * @package + */ +Blockly.Workspace.prototype.getVariableTypes = function() { + return this.variableMap_.getVariableTypes(); +}; + +/** + * Return all variables of all types. + * @return {!Array.} List of variable models. + */ +Blockly.Workspace.prototype.getAllVariables = function() { + return this.variableMap_.getAllVariables(); +}; + +/* End functions that are just pass-throughs to the variable map. */ + /** * Returns the horizontal offset of the workspace. * Intended for LTR/RTL compatibility in XML. @@ -197,8 +341,8 @@ Blockly.Workspace.prototype.getWidth = function() { * Obtain a newly created block. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. * @return {!Blockly.Block} The created block. */ Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) { @@ -240,10 +384,13 @@ Blockly.Workspace.prototype.undo = function(redo) { } events = Blockly.Events.filter(events, redo); Blockly.Events.recordUndo = false; - for (var i = 0, event; event = events[i]; i++) { - event.run(redo); + try { + for (var i = 0, event; event = events[i]; i++) { + event.run(redo); + } + } finally { + Blockly.Events.recordUndo = true; } - Blockly.Events.recordUndo = true; }; /** @@ -272,10 +419,7 @@ Blockly.Workspace.prototype.addChangeListener = function(func) { * @param {Function} func Function to stop calling. */ Blockly.Workspace.prototype.removeChangeListener = function(func) { - var i = this.listeners_.indexOf(func); - if (i != -1) { - this.listeners_.splice(i, 1); - } + goog.array.remove(this.listeners_, func); }; /** @@ -304,6 +448,49 @@ Blockly.Workspace.prototype.getBlockById = function(id) { return this.blockDB_[id] || null; }; +/** + * Checks whether all value and statement inputs in the workspace are filled + * with blocks. + * @param {boolean=} opt_shadowBlocksAreFilled An optional argument controlling + * whether shadow blocks are counted as filled. Defaults to true. + * @return {boolean} True if all inputs are filled, false otherwise. + */ +Blockly.Workspace.prototype.allInputsFilled = function(opt_shadowBlocksAreFilled) { + var blocks = this.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (!block.allInputsFilled(opt_shadowBlocksAreFilled)) { + return false; + } + } + return true; +}; + +/** + * Return the variable map that contains "potential" variables. These exist in + * the flyout but not in the workspace. + * @return {?Blockly.VariableMap} The potential variable map. + * @package + */ +Blockly.Workspace.prototype.getPotentialVariableMap = function() { + return this.potentialVariableMap_; +}; + +/** + * Create and store the potential variable map for this workspace. + * @package + */ +Blockly.Workspace.prototype.createPotentialVariableMap = function() { + this.potentialVariableMap_ = new Blockly.VariableMap(this); +}; + +/** + * Return the map of all variables on the workspace. + * @return {?Blockly.VariableMap} The variable map. + */ +Blockly.Workspace.prototype.getVariableMap = function() { + return this.variableMap_; +}; + /** * Database of all workspaces. * @private diff --git a/core/workspace_audio.js b/core/workspace_audio.js new file mode 100644 index 0000000..fe9cae2 --- /dev/null +++ b/core/workspace_audio.js @@ -0,0 +1,155 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Object in charge of loading, storing, and playing audio for a + * workspace. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceAudio'); + + +/** + * Class for loading, storing, and playing audio for a workspace. + * @param {Blockly.WorkspaceSvg} parentWorkspace The parent of the workspace + * this audio object belongs to, or null. + * @constructor + */ +Blockly.WorkspaceAudio = function(parentWorkspace) { + + /** + * The parent of the workspace this object belongs to, or null. May be + * checked for sounds that this object can't find. + * @type {Blockly.WorkspaceSvg} + * @private + */ + this.parentWorkspace_ = parentWorkspace; + + /** + * Database of pre-loaded sounds. + * @private + * @const + */ + this.SOUNDS_ = Object.create(null); +}; + +/** + * Time that the last sound was played. + * @type {Date} + * @private + */ +Blockly.WorkspaceAudio.prototype.lastSound_ = null; + +/** + * Dispose of this audio manager. + * @package + */ +Blockly.WorkspaceAudio.prototype.dispose = function() { + this.parentWorkspace_ = null; + this.SOUNDS_ = null; +}; + +/** + * Load an audio file. Cache it, ready for instantaneous playing. + * @param {!Array.} filenames List of file types in decreasing order of + * preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav'] + * Filenames include path from Blockly's root. File extensions matter. + * @param {string} name Name of sound. + */ +Blockly.WorkspaceAudio.prototype.load = function(filenames, name) { + if (!filenames.length) { + return; + } + try { + var audioTest = new window['Audio'](); + } catch (e) { + // No browser support for Audio. + // IE can throw an error even if the Audio object exists. + return; + } + var sound; + for (var i = 0; i < filenames.length; i++) { + var filename = filenames[i]; + var ext = filename.match(/\.(\w+)$/); + if (ext && audioTest.canPlayType('audio/' + ext[1])) { + // Found an audio format we can play. + sound = new window['Audio'](filename); + break; + } + } + if (sound && sound.play) { + this.SOUNDS_[name] = sound; + } +}; + +/** + * Preload all the audio files so that they play quickly when asked for. + * @package + */ +Blockly.WorkspaceAudio.prototype.preload = function() { + for (var name in this.SOUNDS_) { + var sound = this.SOUNDS_[name]; + sound.volume = 0.01; + sound.play(); + sound.pause(); + // iOS can only process one sound at a time. Trying to load more than one + // corrupts the earlier ones. Just load one and leave the others uncached. + if (goog.userAgent.IPAD || goog.userAgent.IPHONE) { + break; + } + } +}; + +/** + * Play a named sound at specified volume. If volume is not specified, + * use full volume (1). + * @param {string} name Name of sound. + * @param {number=} opt_volume Volume of sound (0-1). + */ +Blockly.WorkspaceAudio.prototype.play = function(name, opt_volume) { + var sound = this.SOUNDS_[name]; + if (sound) { + // Don't play one sound on top of another. + var now = new Date; + if (this.lastSound_ != null && + now - this.lastSound_ < Blockly.SOUND_LIMIT) { + return; + } + this.lastSound_ = now; + var mySound; + var ie9 = goog.userAgent.DOCUMENT_MODE && + goog.userAgent.DOCUMENT_MODE === 9; + if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) { + // Creating a new audio node causes lag in IE9, Android and iPad. Android + // and IE9 refetch the file from the server, iPad uses a singleton audio + // node which must be deleted and recreated for each new audio tag. + mySound = sound; + } else { + mySound = sound.cloneNode(); + } + mySound.volume = (opt_volume === undefined ? 1 : opt_volume); + mySound.play(); + } else if (this.parentWorkspace_) { + // Maybe a workspace on a lower level knows about this sound. + this.parentWorkspace_.getAudioManager().play(name, opt_volume); + } +}; diff --git a/core/workspace_drag_surface_svg.js b/core/workspace_drag_surface_svg.js new file mode 100644 index 0000000..3227d93 --- /dev/null +++ b/core/workspace_drag_surface_svg.js @@ -0,0 +1,192 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 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 An SVG that floats on top of the workspace. + * Blocks are moved into this SVG during a drag, improving performance. + * The entire SVG is translated using css translation instead of SVG so the + * blocks are never repainted during drag improving performance. + * @author katelyn@google.com (Katelyn Mann) + */ + +'use strict'; + +goog.provide('Blockly.WorkspaceDragSurfaceSvg'); + +goog.require('Blockly.utils'); + +goog.require('goog.asserts'); +goog.require('goog.math.Coordinate'); + + +/** + * Blocks are moved into this SVG during a drag, improving performance. + * The entire SVG is translated using css transforms instead of SVG so the + * blocks are never repainted during drag improving performance. + * @param {!Element} container Containing element. + * @constructor + */ +Blockly.WorkspaceDragSurfaceSvg = function(container) { + this.container_ = container; + this.createDom(); +}; + +/** + * The SVG drag surface. Set once by Blockly.WorkspaceDragSurfaceSvg.createDom. + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.SVG_ = null; + +/** + * SVG group inside the drag surface that holds blocks while a drag is in + * progress. Blocks are moved here by the workspace at start of a drag and moved + * back into the main SVG at the end of a drag. + * + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.dragGroup_ = null; + +/** + * Containing HTML element; parent of the workspace and the drag surface. + * @type {Element} + * @private + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.container_ = null; + +/** + * Create the drag surface and inject it into the container. + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.createDom = function() { + if (this.SVG_) { + return; // Already created. + } + + /** + * Dom structure when the workspace is being dragged. If there is no drag in + * progress, the SVG is empty and display: none. + * + * + * /g> + * + */ + this.SVG_ = Blockly.utils.createSvgElement('svg', + { + 'xmlns': Blockly.SVG_NS, + 'xmlns:html': Blockly.HTML_NS, + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'version': '1.1', + 'class': 'blocklyWsDragSurface blocklyOverflowVisible' + }, null); + this.container_.appendChild(this.SVG_); +}; + +/** + * Translate the entire drag surface during a drag. + * We translate the drag surface instead of the blocks inside the surface + * so that the browser avoids repainting the SVG. + * Because of this, the drag coordinates must be adjusted by scale. + * @param {number} x X translation for the entire surface + * @param {number} y Y translation for the entire surface + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) { + // This is a work-around to prevent a the blocks from rendering + // fuzzy while they are being moved on the drag surface. + x = x.toFixed(0); + y = y.toFixed(0); + + this.SVG_.style.display = 'block'; + Blockly.utils.setCssTransform( + this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0px)'); +}; + +/** + * Reports the surface translation in scaled workspace coordinates. + * Use this when finishing a drag to return blocks to the correct position. + * @return {!goog.math.Coordinate} Current translation of the surface + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() { + return Blockly.utils.getRelativeXY(this.SVG_); +}; + +/** + * Move the blockCanvas and bubbleCanvas out of the surface SVG and on to + * newSurface. + * @param {!SVGElement} newSurface The element to put the drag surface contents + * into. + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) { + var blockCanvas = this.SVG_.childNodes[0]; + var bubbleCanvas = this.SVG_.childNodes[1]; + if (!blockCanvas || !bubbleCanvas || + !Blockly.utils.hasClass(blockCanvas, 'blocklyBlockCanvas') || + !Blockly.utils.hasClass(bubbleCanvas, 'blocklyBubbleCanvas')) { + throw 'Couldn\'t clear and hide the drag surface. A node was missing.'; + } + + // If there is a previous sibling, put the blockCanvas back right afterwards, + // otherwise insert it as the first child node in newSurface. + if (this.previousSibling_ != null) { + Blockly.utils.insertAfter_(blockCanvas, this.previousSibling_); + } else { + newSurface.insertBefore(blockCanvas, newSurface.firstChild); + } + + // Reattach the bubble canvas after the blockCanvas. + Blockly.utils.insertAfter_(bubbleCanvas, blockCanvas); + // Hide the drag surface. + this.SVG_.style.display = 'none'; + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Drag surface was not cleared.'); + Blockly.utils.setCssTransform(this.SVG_, ''); + this.previousSibling_ = null; +}; + +/** + * Set the SVG to have the block canvas and bubble canvas in it and then + * show the surface. + * @param {!Element} blockCanvas The block canvas element from the workspace. + * @param {!Element} bubbleCanvas The element that contains the bubbles. + * @param {?Element} previousSibling The element to insert the block canvas & + bubble canvas after when it goes back in the DOM at the end of a drag. + * @param {number} width The width of the workspace SVG element. + * @param {number} height The height of the workspace SVG element. + * @param {number} scale The scale of the workspace being dragged. + * @package + */ +Blockly.WorkspaceDragSurfaceSvg.prototype.setContentsAndShow = function( + blockCanvas, bubbleCanvas, previousSibling, width, height, scale) { + goog.asserts.assert( + this.SVG_.childNodes.length == 0, 'Already dragging a block.'); + this.previousSibling_ = previousSibling; + // Make sure the blocks and bubble canvas are scaled appropriately. + blockCanvas.setAttribute('transform', 'translate(0, 0) scale(' + scale + ')'); + bubbleCanvas.setAttribute( + 'transform', 'translate(0, 0) scale(' + scale + ')'); + this.SVG_.setAttribute('width', width); + this.SVG_.setAttribute('height', height); + this.SVG_.appendChild(blockCanvas); + this.SVG_.appendChild(bubbleCanvas); + this.SVG_.style.display = 'block'; +}; diff --git a/core/workspace_dragger.js b/core/workspace_dragger.js new file mode 100644 index 0000000..09cb9b8 --- /dev/null +++ b/core/workspace_dragger.js @@ -0,0 +1,132 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2017 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 Methods for dragging a workspace visually. + * @author fenichel@google.com (Rachel Fenichel) + */ +'use strict'; + +goog.provide('Blockly.WorkspaceDragger'); + +goog.require('goog.math.Coordinate'); +goog.require('goog.asserts'); + + +/** + * Class for a workspace dragger. It moves the workspace around when it is + * being dragged by a mouse or touch. + * Note that the workspace itself manages whether or not it has a drag surface + * and how to do translations based on that. This simply passes the right + * commands based on events. + * @param {!Blockly.WorkspaceSvg} workspace The workspace to drag. + * @constructor + */ +Blockly.WorkspaceDragger = function(workspace) { + /** + * @type {!Blockly.WorkspaceSvg} + * @private + */ + this.workspace_ = workspace; + + /** + * The workspace's metrics object at the beginning of the drag. Contains size + * and position metrics of a workspace. + * Coordinate system: pixel coordinates. + * @type {!Object} + * @private + */ + this.startDragMetrics_ = workspace.getMetrics(); + + /** + * The scroll position of the workspace at the beginning of the drag. + * Coordinate system: pixel coordinates. + * @type {!goog.math.Coordinate} + * @private + */ + this.startScrollXY_ = new goog.math.Coordinate( + workspace.scrollX, workspace.scrollY); +}; + +/** + * Sever all links from this object. + * @package + */ +Blockly.WorkspaceDragger.prototype.dispose = function() { + this.workspace_ = null; +}; + +/** + * Start dragging the workspace. + * @package + */ +Blockly.WorkspaceDragger.prototype.startDrag = function() { + if (Blockly.selected) { + Blockly.selected.unselect(); + } + this.workspace_.setupDragSurface(); +}; + +/** + * Finish dragging the workspace and put everything back where it belongs. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ +Blockly.WorkspaceDragger.prototype.endDrag = function(currentDragDeltaXY) { + // Make sure everything is up to date. + this.drag(currentDragDeltaXY); + this.workspace_.resetDragSurface(); +}; + +/** + * Move the workspace based on the most recent mouse movements. + * @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has + * moved from the position at the start of the drag, in pixel coordinates. + * @package + */ +Blockly.WorkspaceDragger.prototype.drag = function(currentDragDeltaXY) { + var metrics = this.startDragMetrics_; + var newXY = goog.math.Coordinate.sum(this.startScrollXY_, currentDragDeltaXY); + + // Bound the new XY based on workspace bounds. + var x = Math.min(newXY.x, -metrics.contentLeft); + var y = Math.min(newXY.y, -metrics.contentTop); + x = Math.max(x, metrics.viewWidth - metrics.contentLeft - + metrics.contentWidth); + y = Math.max(y, metrics.viewHeight - metrics.contentTop - + metrics.contentHeight); + + x = -x - metrics.contentLeft; + y = -y - metrics.contentTop; + + this.updateScroll_(x, y); +}; + +/** + * Move the scrollbars to drag the workspace. + * x and y are in pixels. + * @param {number} x The new x position to move the scrollbar to. + * @param {number} y The new y position to move the scrollbar to. + * @private + */ +Blockly.WorkspaceDragger.prototype.updateScroll_ = function(x, y) { + this.workspace_.scrollbar.set(x, y); +}; diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 69ab56d..1675493 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -27,15 +27,23 @@ goog.provide('Blockly.WorkspaceSvg'); // TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); +//goog.require('Blockly.BlockSvg'); goog.require('Blockly.ConnectionDB'); +goog.require('Blockly.constants'); +goog.require('Blockly.Gesture'); +goog.require('Blockly.Grid'); goog.require('Blockly.Options'); goog.require('Blockly.ScrollbarPair'); +goog.require('Blockly.Touch'); goog.require('Blockly.Trashcan'); +goog.require('Blockly.VariablesDynamic'); goog.require('Blockly.Workspace'); +goog.require('Blockly.WorkspaceAudio'); +goog.require('Blockly.WorkspaceDragSurfaceSvg'); goog.require('Blockly.Xml'); goog.require('Blockly.ZoomControls'); +goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.math.Coordinate'); goog.require('goog.userAgent'); @@ -45,28 +53,83 @@ goog.require('goog.userAgent'); * Class for a workspace. This is an onscreen area with optional trashcan, * scrollbars, bubbles, and dragging. * @param {!Blockly.Options} options Dictionary of options. + * @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for + * blocks. + * @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for + * the workspace. * @extends {Blockly.Workspace} * @constructor */ -Blockly.WorkspaceSvg = function(options) { +Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface) { Blockly.WorkspaceSvg.superClass_.constructor.call(this, options); - this.getMetrics = options.getMetrics; - this.setMetrics = options.setMetrics; + this.getMetrics = + options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_; + this.setMetrics = + options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_; Blockly.ConnectionDB.init(this); + if (opt_blockDragSurface) { + this.blockDragSurface_ = opt_blockDragSurface; + } + + if (opt_wsDragSurface) { + this.workspaceDragSurface_ = opt_wsDragSurface; + } + + this.useWorkspaceDragSurface_ = + this.workspaceDragSurface_ && Blockly.utils.is3dSupported(); + + /** + * List of currently highlighted blocks. Block highlighting is often used to + * visually mark blocks currently being executed. + * @type !Array. + * @private + */ + this.highlightedBlocks_ = []; + + /** + * Object in charge of loading, storing, and playing audio for a workspace. + * @type {Blockly.WorkspaceAudio} + * @private + */ + this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace); + /** - * Database of pre-loaded sounds. + * This workspace's grid object or null. + * @type {Blockly.Grid} * @private - * @const */ - this.SOUNDS_ = Object.create(null); + this.grid_ = this.options.gridPattern ? + new Blockly.Grid(options.gridPattern, options.gridOptions) : null; + + if (Blockly.Variables && Blockly.Variables.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME, + Blockly.Variables.flyoutCategory); + } + if (Blockly.VariablesDynamic && Blockly.VariablesDynamic.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME, + Blockly.VariablesDynamic.flyoutCategory); + } + if (Blockly.Procedures && Blockly.Procedures.flyoutCategory) { + this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME, + Blockly.Procedures.flyoutCategory); + } }; goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace); /** - * Svg workspaces are user-visible (as opposed to a headless workspace). - * @type {boolean} True if visible. False if headless. + * A wrapper function called when a resize event occurs. + * You can pass the result to `unbindEvent_`. + * @type {Array.} + */ +Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null; + +/** + * The render status of an SVG workspace. + * Returns `true` for visible workspaces and `false` for non-visible, + * or headless, workspaces. + * @type {boolean} */ Blockly.WorkspaceSvg.prototype.rendered = true; @@ -77,31 +140,40 @@ Blockly.WorkspaceSvg.prototype.rendered = true; Blockly.WorkspaceSvg.prototype.isFlyout = false; /** - * Is this workspace currently being dragged around? + * Is this workspace the surface for a mutator? + * @type {boolean} + * @package + */ +Blockly.WorkspaceSvg.prototype.isMutator = false; + +/** + * Whether this workspace has resizes enabled. + * Disable during batch operations for a performance improvement. * @type {boolean} + * @private */ -Blockly.WorkspaceSvg.prototype.isScrolling = false; +Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true; /** - * Current horizontal scrolling offset. + * Current horizontal scrolling offset in pixel units. * @type {number} */ Blockly.WorkspaceSvg.prototype.scrollX = 0; /** - * Current vertical scrolling offset. + * Current vertical scrolling offset in pixel units. * @type {number} */ Blockly.WorkspaceSvg.prototype.scrollY = 0; /** - * Horizontal scroll value when scrolling started. + * Horizontal scroll value when scrolling started in pixel units. * @type {number} */ Blockly.WorkspaceSvg.prototype.startScrollX = 0; /** - * Vertical scroll value when scrolling started. + * Vertical scroll value when scrolling started in pixel units. * @type {number} */ Blockly.WorkspaceSvg.prototype.startScrollY = 0; @@ -131,6 +203,155 @@ Blockly.WorkspaceSvg.prototype.trashcan = null; */ Blockly.WorkspaceSvg.prototype.scrollbar = null; +/** + * The current gesture in progress on this workspace, if any. + * @type {Blockly.Gesture} + * @private + */ +Blockly.WorkspaceSvg.prototype.currentGesture_ = null; + +/** + * This workspace's surface for dragging blocks, if it exists. + * @type {Blockly.BlockDragSurfaceSvg} + * @private + */ +Blockly.WorkspaceSvg.prototype.blockDragSurface_ = null; + +/** + * This workspace's drag surface, if it exists. + * @type {Blockly.WorkspaceDragSurfaceSvg} + * @private + */ +Blockly.WorkspaceSvg.prototype.workspaceDragSurface_ = null; + +/** + * Whether to move workspace to the drag surface when it is dragged. + * True if it should move, false if it should be translated directly. + * @type {boolean} + * @private + */ +Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false; + +/** + * Whether the drag surface is actively in use. When true, calls to + * translate will translate the drag surface instead of the translating the + * workspace directly. + * This is set to true in setupDragSurface and to false in resetDragSurface. + * @type {boolean} + * @private + */ +Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false; + +/** + * Last known position of the page scroll. + * This is used to determine whether we have recalculated screen coordinate + * stuff since the page scrolled. + * @type {!goog.math.Coordinate} + * @private + */ +Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null; + +/** + * Map from function names to callbacks, for deciding what to do when a button + * is clicked. + * @type {!Object.} + * @private + */ +Blockly.WorkspaceSvg.prototype.flyoutButtonCallbacks_ = {}; + +/** + * Map from function names to callbacks, for deciding what to do when a custom + * toolbox category is opened. + * @type {!Object.>} + * @private + */ +Blockly.WorkspaceSvg.prototype.toolboxCategoryCallbacks_ = {}; + +/** + * In a flyout, the target workspace where blocks should be placed after a drag. + * Otherwise null. + * @type {?Blockly.WorkspaceSvg} + * @package + */ +Blockly.WorkspaceSvg.prototype.targetWorkspace = null; + +/** + * Inverted screen CTM, for use in mouseToSvg. + * @type {SVGMatrix} + * @private + */ +Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null; + +/** + * Getter for the inverted screen CTM. + * @return {SVGMatrix} The matrix to use in mouseToSvg + */ +Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() { + return this.inverseScreenCTM_; +}; + +/** + * Update the inverted screen CTM. + */ +Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() { + var ctm = this.getParentSvg().getScreenCTM(); + if (ctm) { + this.inverseScreenCTM_ = ctm.inverse(); + } +}; + +/** + * Return the absolute coordinates of the top-left corner of this element, + * scales that after canvas SVG element, if it's a descendant. + * The origin (0,0) is the top-left corner of the Blockly SVG. + * @param {!Element} element Element to find the coordinates of. + * @return {!goog.math.Coordinate} Object with .x and .y properties. + * @private + */ +Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) { + var x = 0; + var y = 0; + var scale = 1; + if (goog.dom.contains(this.getCanvas(), element) || + goog.dom.contains(this.getBubbleCanvas(), element)) { + // Before the SVG canvas, scale the coordinates. + scale = this.scale; + } + do { + // Loop through this block and every parent. + var xy = Blockly.utils.getRelativeXY(element); + if (element == this.getCanvas() || + element == this.getBubbleCanvas()) { + // After the SVG canvas, don't scale the coordinates. + scale = 1; + } + x += xy.x * scale; + y += xy.y * scale; + element = element.parentNode; + } while (element && element != this.getParentSvg()); + return new goog.math.Coordinate(x, y); +}; + +/** + * Return the position of the workspace origin relative to the injection div + * origin in pixels. + * The workspace origin is where a block would render at position (0, 0). + * It is not the upper left corner of the workspace SVG. + * @return {!goog.math.Coordinate} Offset in pixels. + * @package + */ +Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() { + return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_); +}; + +/** + * Save resize handler data so we can delete it later in dispose. + * @param {!Array.} handler Data that can be passed to unbindEvent_. + */ +Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) { + this.resizeHandlerWrapper_ = handler; +}; + /** * Create the workspace DOM elements. * @param {string=} opt_backgroundClass Either 'blocklyMainBackground' or @@ -144,52 +365,63 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { * [Trashcan and/or flyout may go here] * * - * [Scrollbars may go here] * * @type {SVGElement} */ - this.svgGroup_ = Blockly.createSvgElement('g', + this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': 'blocklyWorkspace'}, null); + + // Note that a alone does not receive mouse events--it must have a + // valid target inside it. If no background class is specified, as in the + // flyout, the workspace will not receive mouse events. if (opt_backgroundClass) { /** @type {SVGElement} */ - this.svgBackground_ = Blockly.createSvgElement('rect', + this.svgBackground_ = Blockly.utils.createSvgElement('rect', {'height': '100%', 'width': '100%', 'class': opt_backgroundClass}, this.svgGroup_); - if (opt_backgroundClass == 'blocklyMainBackground') { + + if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) { this.svgBackground_.style.fill = - 'url(#' + this.options.gridPattern.id + ')'; + 'url(#' + this.grid_.getPatternId() + ')'; } } /** @type {SVGElement} */ - this.svgBlockCanvas_ = Blockly.createSvgElement('g', - {'class': 'blocklyBlockCanvas'}, this.svgGroup_, this); + this.svgBlockCanvas_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyBlockCanvas'}, this.svgGroup_); /** @type {SVGElement} */ - this.svgBubbleCanvas_ = Blockly.createSvgElement('g', - {'class': 'blocklyBubbleCanvas'}, this.svgGroup_, this); + this.svgBubbleCanvas_ = Blockly.utils.createSvgElement('g', + {'class': 'blocklyBubbleCanvas'}, this.svgGroup_); var bottom = Blockly.Scrollbar.scrollbarThickness; if (this.options.hasTrashcan) { bottom = this.addTrashcan_(bottom); } if (this.options.zoomOptions && this.options.zoomOptions.controls) { - bottom = this.addZoomControls_(bottom); + this.addZoomControls_(bottom); } - Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_); - var thisWorkspace = this; - Blockly.bindEvent_(this.svgGroup_, 'touchstart', null, - function(e) {Blockly.longStart_(e, thisWorkspace);}); - if (this.options.zoomOptions && this.options.zoomOptions.wheel) { - // Mouse-wheel. - Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.onMouseWheel_); + + if (!this.isFlyout) { + Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this, + this.onMouseDown_); + if (this.options.zoomOptions && this.options.zoomOptions.wheel) { + // Mouse-wheel. + Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, + this.onMouseWheel_); + } } // Determine if there needs to be a category tree, or a simple list of // blocks. This cannot be changed later, since the UI is very different. if (this.options.hasCategories) { + /** + * @type {Blockly.Toolbox} + * @private + */ this.toolbox_ = new Blockly.Toolbox(this); - } else if (this.options.languageTree) { - this.addFlyout_(); } - this.updateGridPattern_(); + if (this.grid_) { + this.grid_.update(this.scale); + } + this.recordDeleteAreas(); return this.svgGroup_; }; @@ -200,6 +432,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { Blockly.WorkspaceSvg.prototype.dispose = function() { // Stop rerendering. this.rendered = false; + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } Blockly.WorkspaceSvg.superClass_.dispose.call(this); if (this.svgGroup_) { goog.dom.removeNode(this.svgGroup_); @@ -227,9 +462,31 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { this.zoomControls_.dispose(); this.zoomControls_ = null; } + + if (this.audioManager_) { + this.audioManager_.dispose(); + this.audioManager_ = null; + } + + if (this.grid_) { + this.grid_.dispose(); + this.grid_ = null; + } + + if (this.toolboxCategoryCallbacks_) { + this.toolboxCategoryCallbacks_ = null; + } + if (this.flyoutButtonCallbacks_) { + this.flyoutButtonCallbacks_ = null; + } if (!this.options.parentWorkspace) { - // Top-most workspace. Dispose of the SVG too. - goog.dom.removeNode(this.getParentSvg()); + // Top-most workspace. Dispose of the div that the + // SVG is injected into (i.e. injectionDiv). + goog.dom.removeNode(this.getParentSvg().parentNode); + } + if (this.resizeHandlerWrapper_) { + Blockly.unbindEvent_(this.resizeHandlerWrapper_); + this.resizeHandlerWrapper_ = null; } }; @@ -237,8 +494,8 @@ Blockly.WorkspaceSvg.prototype.dispose = function() { * Obtain a newly created block. * @param {?string} prototypeName Name of the language object containing * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. + * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise + * create a new ID. * @return {!Blockly.BlockSvg} The created block. */ Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName, opt_id) { @@ -274,26 +531,90 @@ Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) { }; /** - * Add a flyout. + * Add a flyout element in an element with the given tag name. + * @param {string} tagName What type of tag the flyout belongs in. + * @return {!Element} The element containing the flyout DOM. * @private */ -Blockly.WorkspaceSvg.prototype.addFlyout_ = function() { +Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) { var workspaceOptions = { disabledPatternId: this.options.disabledPatternId, parentWorkspace: this, RTL: this.RTL, + oneBasedIndex: this.options.oneBasedIndex, horizontalLayout: this.horizontalLayout, - toolboxPosition: this.options.toolboxPosition, + toolboxPosition: this.options.toolboxPosition }; - /** @type {Blockly.Flyout} */ - this.flyout_ = new Blockly.Flyout(workspaceOptions); + /** + * @type {!Blockly.Flyout} + * @private + */ + this.flyout_ = null; + if (this.horizontalLayout) { + this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions); + } else { + this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions); + } this.flyout_.autoClose = false; - var svgFlyout = this.flyout_.createDom(); - this.svgGroup_.insertBefore(svgFlyout, this.svgBlockCanvas_); + + // Return the element so that callers can place it in their desired + // spot in the DOM. For example, mutator flyouts do not go in the same place + // as main workspace flyouts. + return this.flyout_.createDom(tagName); +}; + +/** + * Getter for the flyout associated with this workspace. This flyout may be + * owned by either the toolbox or the workspace, depending on toolbox + * configuration. It will be null if there is no flyout. + * @return {Blockly.Flyout} The flyout on this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getFlyout_ = function() { + if (this.flyout_) { + return this.flyout_; + } + if (this.toolbox_) { + return this.toolbox_.flyout_; + } + return null; +}; + +/** + * Update items that use screen coordinate calculations + * because something has changed (e.g. scroll position, window size). + * @private + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() { + this.updateInverseScreenCTM(); + this.recordDeleteAreas(); }; /** - * Resize this workspace and its containing objects. + * If enabled, resize the parts of the workspace that change when the workspace + * contents (e.g. block positions) change. This will also scroll the + * workspace contents if needed. + * @package + */ +Blockly.WorkspaceSvg.prototype.resizeContents = function() { + if (!this.resizesEnabled_ || !this.rendered) { + return; + } + if (this.scrollbar) { + // TODO(picklesrus): Once rachel-fenichel's scrollbar refactoring + // is complete, call the method that only resizes scrollbar + // based on contents. + this.scrollbar.resize(); + } + this.updateInverseScreenCTM(); +}; + +/** + * Resize and reposition all of the workspace chrome (toolbox, + * trash, scrollbars etc.) + * This should be called when something changes that + * requires recalculating dimensions and positions of the + * trash, zoom, toolbox, etc. (e.g. window resize). */ Blockly.WorkspaceSvg.prototype.resize = function() { if (this.toolbox_) { @@ -311,8 +632,25 @@ Blockly.WorkspaceSvg.prototype.resize = function() { if (this.scrollbar) { this.scrollbar.resize(); } + this.updateScreenCalculations_(); }; +/** + * Resizes and repositions workspace chrome if the page has a new + * scroll position. + * @package + */ +Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled = + function() { + /* eslint-disable indent */ + var currScroll = goog.dom.getDocumentScroll(); + if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_, + currScroll)) { + this.lastRecordedPageScroll_ = currScroll; + this.updateScreenCalculations_(); + } +}; /* eslint-enable indent */ + /** * Get the SVG element that forms the drawing surface. * @return {!Element} SVG element. @@ -331,7 +669,7 @@ Blockly.WorkspaceSvg.prototype.getBubbleCanvas = function() { /** * Get the SVG element that contains this workspace. - * @return {!Element} SVG element. + * @return {Element} SVG element. */ Blockly.WorkspaceSvg.prototype.getParentSvg = function() { if (this.cachedParentSvg_) { @@ -354,12 +692,85 @@ Blockly.WorkspaceSvg.prototype.getParentSvg = function() { * @param {number} y Vertical translation. */ Blockly.WorkspaceSvg.prototype.translate = function(x, y) { - var translation = 'translate(' + x + ',' + y + ') ' + + if (this.useWorkspaceDragSurface_ && this.isDragSurfaceActive_) { + this.workspaceDragSurface_.translateSurface(x,y); + } else { + var translation = 'translate(' + x + ',' + y + ') ' + + 'scale(' + this.scale + ')'; + this.svgBlockCanvas_.setAttribute('transform', translation); + this.svgBubbleCanvas_.setAttribute('transform', translation); + } + // Now update the block drag surface if we're using one. + if (this.blockDragSurface_) { + this.blockDragSurface_.translateAndScaleGroup(x, y, this.scale); + } +}; + +/** + * Called at the end of a workspace drag to take the contents + * out of the drag surface and put them back into the workspace SVG. + * Does nothing if the workspace drag surface is not enabled. + * @package + */ +Blockly.WorkspaceSvg.prototype.resetDragSurface = function() { + // Don't do anything if we aren't using a drag surface. + if (!this.useWorkspaceDragSurface_) { + return; + } + + this.isDragSurfaceActive_ = false; + + var trans = this.workspaceDragSurface_.getSurfaceTranslation(); + this.workspaceDragSurface_.clearAndHide(this.svgGroup_); + var translation = 'translate(' + trans.x + ',' + trans.y + ') ' + 'scale(' + this.scale + ')'; this.svgBlockCanvas_.setAttribute('transform', translation); this.svgBubbleCanvas_.setAttribute('transform', translation); }; +/** + * Called at the beginning of a workspace drag to move contents of + * the workspace to the drag surface. + * Does nothing if the drag surface is not enabled. + * @package + */ +Blockly.WorkspaceSvg.prototype.setupDragSurface = function() { + // Don't do anything if we aren't using a drag surface. + if (!this.useWorkspaceDragSurface_) { + return; + } + + // This can happen if the user starts a drag, mouses up outside of the + // document where the mouseup listener is registered (e.g. outside of an + // iframe) and then moves the mouse back in the workspace. On mobile and ff, + // we get the mouseup outside the frame. On chrome and safari desktop we do + // not. + if (this.isDragSurfaceActive_) { + return; + } + + this.isDragSurfaceActive_ = true; + + // Figure out where we want to put the canvas back. The order + // in the is important because things are layered. + var previousElement = this.svgBlockCanvas_.previousSibling; + var width = this.getParentSvg().getAttribute('width'); + var height = this.getParentSvg().getAttribute('height'); + var coord = Blockly.utils.getRelativeXY(this.svgBlockCanvas_); + this.workspaceDragSurface_.setContentsAndShow(this.svgBlockCanvas_, + this.svgBubbleCanvas_, previousElement, width, height, this.scale); + this.workspaceDragSurface_.translateSurface(coord.x, coord.y); +}; + +/** + * @return {?Blockly.BlockDragSurfaceSvg} This workspace's block drag surface, + * if one is in use. + * @package + */ +Blockly.WorkspaceSvg.prototype.getBlockDragSurface = function() { + return this.blockDragSurface_; +}; + /** * Returns the horizontal offset of the workspace. * Intended for LTR/RTL compatibility in XML. @@ -376,6 +787,19 @@ Blockly.WorkspaceSvg.prototype.getWidth = function() { * @param {boolean} isVisible True if workspace should be visible. */ Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) { + + // Tell the scrollbar whether its container is visible so it can + // tell when to hide itself. + if (this.scrollbar) { + this.scrollbar.setContainerVisible(isVisible); + } + + // Tell the flyout whether its container is visible so it can + // tell when to hide itself. + if (this.getFlyout_()) { + this.getFlyout_().setContainerVisible(isVisible); + } + this.getParentSvg().style.display = isVisible ? 'block' : 'none'; if (this.toolbox_) { // Currently does not support toolboxes in mutators. @@ -404,53 +828,44 @@ Blockly.WorkspaceSvg.prototype.render = function() { }; /** - * Turn the visual trace functionality on or off. - * @param {boolean} armed True if the trace should be on. + * Was used back when block highlighting (for execution) and block selection + * (for editing) were the same thing. + * Any calls of this function can be deleted. + * @deprecated October 2016 */ -Blockly.WorkspaceSvg.prototype.traceOn = function(armed) { - this.traceOn_ = armed; - if (this.traceWrapper_) { - Blockly.unbindEvent_(this.traceWrapper_); - this.traceWrapper_ = null; - } - if (armed) { - this.traceWrapper_ = Blockly.bindEvent_(this.svgBlockCanvas_, - 'blocklySelectChange', this, function() {this.traceOn_ = false;}); - } +Blockly.WorkspaceSvg.prototype.traceOn = function() { + console.warn('Deprecated call to traceOn, delete this.'); }; /** - * Highlight a block in the workspace. - * @param {?string} id ID of block to find. + * Highlight or unhighlight a block in the workspace. Block highlighting is + * often used to visually mark blocks currently being executed. + * @param {?string} id ID of block to highlight/unhighlight, + * or null for no block (used to unhighlight all blocks). + * @param {boolean=} opt_state If undefined, highlight specified block and + * automatically unhighlight all others. If true or false, manually + * highlight/unhighlight the specified block. */ -Blockly.WorkspaceSvg.prototype.highlightBlock = function(id) { - if (this.traceOn_ && Blockly.dragMode_ != Blockly.DRAG_NONE) { - // The blocklySelectChange event normally prevents this, but sometimes - // there is a race condition on fast-executing apps. - this.traceOn(false); - } - if (!this.traceOn_) { - return; - } - var block = null; - if (id) { - block = this.getBlockById(id); - if (!block) { - return; +Blockly.WorkspaceSvg.prototype.highlightBlock = function(id, opt_state) { + if (opt_state === undefined) { + // Unhighlight all blocks. + for (var i = 0, block; block = this.highlightedBlocks_[i]; i++) { + block.setHighlighted(false); } + this.highlightedBlocks_.length = 0; } - // Temporary turn off the listener for selection changes, so that we don't - // trip the monitor for detecting user activity. - this.traceOn(false); - // Select the current block. + // Highlight/unhighlight the specified block. + var block = id ? this.getBlockById(id) : null; if (block) { - block.select(); - } else if (Blockly.selected) { - Blockly.selected.unselect(); + var state = (opt_state === undefined) || opt_state; + // Using Set here would be great, but at the cost of IE10 support. + if (!state) { + goog.array.remove(this.highlightedBlocks_, block); + } else if (this.highlightedBlocks_.indexOf(block) == -1) { + this.highlightedBlocks_.push(block); + } + block.setHighlighted(state); } - // Restore the monitor for user activity after the selection event has fired. - var thisWorkspace = this; - setTimeout(function() {thisWorkspace.traceOn(true);}, 1); }; /** @@ -462,59 +877,117 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { this.remainingCapacity()) { return; } - Blockly.terminateDrag_(); // Dragging while pasting? No. + if (this.currentGesture_) { + this.currentGesture_.cancel(); // Dragging while pasting? No. + } Blockly.Events.disable(); - var block = Blockly.Xml.domToBlock(xmlBlock, this); - // Move the duplicate to original position. - var blockX = parseInt(xmlBlock.getAttribute('x'), 10); - var blockY = parseInt(xmlBlock.getAttribute('y'), 10); - if (!isNaN(blockX) && !isNaN(blockY)) { - if (this.RTL) { - blockX = -blockX; - } - // Offset block until not clobbering another block and not in connection - // distance with neighbouring blocks. - do { - var collide = false; - var allBlocks = this.getAllBlocks(); - for (var i = 0, otherBlock; otherBlock = allBlocks[i]; i++) { - var otherXY = otherBlock.getRelativeToSurfaceXY(); - if (Math.abs(blockX - otherXY.x) <= 1 && - Math.abs(blockY - otherXY.y) <= 1) { - collide = true; - break; - } + try { + var block = Blockly.Xml.domToBlock(xmlBlock, this); + // Move the duplicate to original position. + var blockX = parseInt(xmlBlock.getAttribute('x'), 10); + var blockY = parseInt(xmlBlock.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + if (this.RTL) { + blockX = -blockX; } - if (!collide) { - // Check for blocks in snap range to any of its connections. - var connections = block.getConnections_(false); - for (var i = 0, connection; connection = connections[i]; i++) { - var neighbour = connection.closest(Blockly.SNAP_RADIUS, - new goog.math.Coordinate(blockX, blockY)); - if (neighbour.connection) { + // Offset block until not clobbering another block and not in connection + // distance with neighbouring blocks. + do { + var collide = false; + var allBlocks = this.getAllBlocks(); + for (var i = 0, otherBlock; otherBlock = allBlocks[i]; i++) { + var otherXY = otherBlock.getRelativeToSurfaceXY(); + if (Math.abs(blockX - otherXY.x) <= 1 && + Math.abs(blockY - otherXY.y) <= 1) { collide = true; break; } } - } - if (collide) { - if (this.RTL) { - blockX -= Blockly.SNAP_RADIUS; - } else { - blockX += Blockly.SNAP_RADIUS; + if (!collide) { + // Check for blocks in snap range to any of its connections. + var connections = block.getConnections_(false); + for (var i = 0, connection; connection = connections[i]; i++) { + var neighbour = connection.closest(Blockly.SNAP_RADIUS, + new goog.math.Coordinate(blockX, blockY)); + if (neighbour.connection) { + collide = true; + break; + } + } } - blockY += Blockly.SNAP_RADIUS * 2; - } - } while (collide); - block.moveBy(blockX, blockY); + if (collide) { + if (this.RTL) { + blockX -= Blockly.SNAP_RADIUS; + } else { + blockX += Blockly.SNAP_RADIUS; + } + blockY += Blockly.SNAP_RADIUS * 2; + } + } while (collide); + block.moveBy(blockX, blockY); + } + } finally { + Blockly.Events.enable(); } - Blockly.Events.enable(); if (Blockly.Events.isEnabled() && !block.isShadow()) { - Blockly.Events.fire(new Blockly.Events.Create(block)); + Blockly.Events.fire(new Blockly.Events.BlockCreate(block)); } block.select(); }; +/** + * Refresh the toolbox unless there's a drag in progress. + * @package + */ +Blockly.WorkspaceSvg.prototype.refreshToolboxSelection = function() { + var ws = this.isFlyout ? this.targetWorkspace : this; + if (ws && !ws.currentGesture_ && ws.toolbox_ && ws.toolbox_.flyout_) { + ws.toolbox_.refreshSelection(); + } +}; + +/** + * Rename a variable by updating its name in the variable map. Update the + * flyout to show the renamed variable immediately. + * @param {string} id ID of the variable to rename. + * @param {string} newName New variable name. + * @package + */ +Blockly.WorkspaceSvg.prototype.renameVariableById = function(id, newName) { + Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this, id, newName); + this.refreshToolboxSelection(); +}; + +/** + * Delete a variable by the passed in ID. Update the flyout to show + * immediately that the variable is deleted. + * @param {string} id ID of variable to delete. + * @package + */ +Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) { + Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id); + this.refreshToolboxSelection(); +}; + +/** + * Create a new variable with the given name. Update the flyout to show the new + * variable immediately. + * @param {string} name The new variable's name. + * @param {string=} opt_type The type of the variable like 'int' or 'string'. + * Does not need to be unique. Field_variable can filter variables based on + * their type. This will default to '' which is a specific type. + * @param {string=} opt_id The unique ID of the variable. This will default to + * a UUID. + * @return {?Blockly.VariableModel} The newly created variable. + * @package + */ +Blockly.WorkspaceSvg.prototype.createVariable = function(name, opt_type, opt_id) { + var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call( + this, name, opt_type, opt_id); + this.refreshToolboxSelection(); + return newVar; +}; + /** * Make a list of all the delete areas for this workspace. */ @@ -535,28 +1008,19 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { /** * Is the mouse event over a delete area (toolbox or non-closing flyout)? - * Opens or closes the trashcan and sets the cursor as a side effect. * @param {!Event} e Mouse move event. - * @return {boolean} True if event is in a delete area. + * @return {?number} Null if not over a delete area, or an enum representing + * which delete area the event is over. */ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { var xy = new goog.math.Coordinate(e.clientX, e.clientY); - if (this.deleteAreaTrash_) { - if (this.deleteAreaTrash_.contains(xy)) { - this.trashcan.setOpen_(true); - Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); - return true; - } - this.trashcan.setOpen_(false); + if (this.deleteAreaTrash_ && this.deleteAreaTrash_.contains(xy)) { + return Blockly.DELETE_AREA_TRASH; } - if (this.deleteAreaToolbox_) { - if (this.deleteAreaToolbox_.contains(xy)) { - Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); - return true; - } + if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) { + return Blockly.DELETE_AREA_TOOLBOX; } - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - return false; + return Blockly.DELETE_AREA_NONE; }; /** @@ -565,50 +1029,10 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { * @private */ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { - this.markFocused(); - if (Blockly.isTargetInput_(e)) { - return; - } - Blockly.svgResize(this); - Blockly.terminateDrag_(); // In case mouse-up event was lost. - Blockly.hideChaff(); - var isTargetWorkspace = e.target && e.target.nodeName && - (e.target.nodeName.toLowerCase() == 'svg' || - e.target == this.svgBackground_); - if (isTargetWorkspace && Blockly.selected && !this.options.readOnly) { - // Clicking on the document clears the selection. - Blockly.selected.unselect(); - } - if (Blockly.isRightButton(e)) { - // Right-click. - this.showContextMenu_(e); - } else if (this.scrollbar) { - // If the workspace is editable, only allow scrolling when gripping empty - // space. Otherwise, allow scrolling when gripping anywhere. - this.isScrolling = true; - // Record the current mouse position. - this.startDragMouseX = e.clientX; - this.startDragMouseY = e.clientY; - this.startDragMetrics = this.getMetrics(); - this.startScrollX = this.scrollX; - this.startScrollY = this.scrollY; - - // If this is a touch event then bind to the mouseup so workspace drag mode - // is turned off and double move events are not performed on a block. - // See comment in inject.js Blockly.init_ as to why mouseup events are - // bound to the document instead of the SVG's surface. - if ('mouseup' in Blockly.bindEvent_.TOUCH_MAP) { - Blockly.onTouchUpWrapper_ = Blockly.onTouchUpWrapper_ || []; - Blockly.onTouchUpWrapper_ = Blockly.onTouchUpWrapper_.concat( - Blockly.bindEvent_(document, 'mouseup', null, Blockly.onMouseUp_)); - } - Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_ || []; - Blockly.onMouseMoveWrapper_ = Blockly.onMouseMoveWrapper_.concat( - Blockly.bindEvent_(document, 'mousemove', null, Blockly.onMouseMove_)); + var gesture = this.getGesture(e); + if (gesture) { + gesture.handleWsStart(e, this); } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); }; /** @@ -618,7 +1042,8 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) { */ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { // Record the starting offset between the bubble's location and the mouse. - var point = Blockly.mouseToSvg(e, this.getParentSvg()); + var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; @@ -631,35 +1056,59 @@ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) { * @return {!goog.math.Coordinate} New location of object. */ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) { - var point = Blockly.mouseToSvg(e, this.getParentSvg()); + var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); // Fix scale of mouse event. point.x /= this.scale; point.y /= this.scale; return goog.math.Coordinate.sum(this.dragDeltaXY_, point); }; +/** + * Is the user currently dragging a block or scrolling the flyout/workspace? + * @return {boolean} True if currently dragging or scrolling. + */ +Blockly.WorkspaceSvg.prototype.isDragging = function() { + return this.currentGesture_ != null && this.currentGesture_.isDragging(); +}; + +/** + * Is this workspace draggable and scrollable? + * @return {boolean} True if this workspace may be dragged. + */ +Blockly.WorkspaceSvg.prototype.isDraggable = function() { + return !!this.scrollbar; +}; + /** * Handle a mouse-wheel on SVG drawing surface. * @param {!Event} e Mouse wheel event. * @private */ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) { - // TODO: Remove terminateDrag and compensate for coordinate skew during zoom. - Blockly.terminateDrag_(); - var delta = e.deltaY > 0 ? -1 : 1; - var position = Blockly.mouseToSvg(e, this.getParentSvg()); + // TODO: Remove gesture cancellation and compensate for coordinate skew during + // zoom. + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } + // The vertical scroll distance that corresponds to a click of a zoom button. + var PIXELS_PER_ZOOM_STEP = 50; + var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP; + var position = Blockly.utils.mouseToSvg(e, this.getParentSvg(), + this.getInverseScreenCTM()); this.zoom(position.x, position.y, delta); e.preventDefault(); }; /** * Calculate the bounding box for the blocks on the workspace. + * Coordinate system: workspace coordinates. * * @return {Object} Contains the position and size of the bounding box * containing the blocks on the workspace. */ Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() { - var topBlocks = this.getTopBlocks(); + var topBlocks = this.getTopBlocks(false); // There are no blocks, return empty rectangle. if (!topBlocks.length) { return {x: 0, y: 0, width: 0, height: 0}; @@ -694,9 +1143,9 @@ Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() { /** * Clean up the workspace by ordering all the blocks in a column. - * @private */ -Blockly.WorkspaceSvg.prototype.cleanUp_ = function() { +Blockly.WorkspaceSvg.prototype.cleanUp = function() { + this.setResizesEnabled(false); Blockly.Events.setGroup(true); var topBlocks = this.getTopBlocks(true); var cursorY = 0; @@ -708,8 +1157,7 @@ Blockly.WorkspaceSvg.prototype.cleanUp_ = function() { block.getHeightWidth().height + Blockly.BlockSvg.MIN_BLOCK_Y; } Blockly.Events.setGroup(false); - // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(this); + this.setResizesEnabled(true); }; /** @@ -723,7 +1171,8 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { } var menuOptions = []; var topBlocks = this.getTopBlocks(true); - var eventGroup = Blockly.genUid(); + var eventGroup = Blockly.utils.genUid(); + var ws = this; // Options to undo/redo previous action. var undoOption = {}; @@ -742,7 +1191,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { var cleanOption = {}; cleanOption.text = Blockly.Msg.CLEAN_UP; cleanOption.enabled = topBlocks.length > 1; - cleanOption.callback = this.cleanUp_.bind(this); + cleanOption.callback = this.cleanUp.bind(this); menuOptions.push(cleanOption); } @@ -813,18 +1262,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { for (var i = 0; i < topBlocks.length; i++) { addDeletableBlocks(topBlocks[i]); } - var deleteOption = { - text: deleteList.length == 1 ? Blockly.Msg.DELETE_BLOCK : - Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)), - enabled: deleteList.length > 0, - callback: function() { - if (deleteList.length < 2 || - window.confirm(Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', - String(deleteList.length)))) { - deleteNext(); - } - } - }; + function deleteNext() { Blockly.Events.setGroup(eventGroup); var block = deleteList.shift(); @@ -838,89 +1276,31 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { } Blockly.Events.setGroup(false); } - menuOptions.push(deleteOption); - Blockly.ContextMenu.show(e, menuOptions, this.RTL); -}; - -/** - * Load an audio file. Cache it, ready for instantaneous playing. - * @param {!Array.} filenames List of file types in decreasing order of - * preference (i.e. increasing size). E.g. ['media/go.mp3', 'media/go.wav'] - * Filenames include path from Blockly's root. File extensions matter. - * @param {string} name Name of sound. - * @private - */ -Blockly.WorkspaceSvg.prototype.loadAudio_ = function(filenames, name) { - if (!filenames.length) { - return; - } - try { - var audioTest = new window['Audio'](); - } catch (e) { - // No browser support for Audio. - // IE can throw an error even if the Audio object exists. - return; - } - var sound; - for (var i = 0; i < filenames.length; i++) { - var filename = filenames[i]; - var ext = filename.match(/\.(\w+)$/); - if (ext && audioTest.canPlayType('audio/' + ext[1])) { - // Found an audio format we can play. - sound = new window['Audio'](filename); - break; - } - } - if (sound && sound.play) { - this.SOUNDS_[name] = sound; - } -}; - -/** - * Preload all the audio files so that they play quickly when asked for. - * @private - */ -Blockly.WorkspaceSvg.prototype.preloadAudio_ = function() { - for (var name in this.SOUNDS_) { - var sound = this.SOUNDS_[name]; - sound.volume = .01; - sound.play(); - sound.pause(); - // iOS can only process one sound at a time. Trying to load more than one - // corrupts the earlier ones. Just load one and leave the others uncached. - if (goog.userAgent.IPAD || goog.userAgent.IPHONE) { - break; + var deleteOption = { + text: deleteList.length == 1 ? Blockly.Msg.DELETE_BLOCK : + Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteList.length)), + enabled: deleteList.length > 0, + callback: function() { + if (ws.currentGesture_) { + ws.currentGesture_.cancel(); + } + if (deleteList.length < 2 ) { + deleteNext(); + } else { + Blockly.confirm( + Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', deleteList.length), + function(ok) { + if (ok) { + deleteNext(); + } + }); + } } - } -}; + }; + menuOptions.push(deleteOption); -/** - * Play an audio file at specified value. If volume is not specified, - * use full volume (1). - * @param {string} name Name of sound. - * @param {number=} opt_volume Volume of sound (0-1). - */ -Blockly.WorkspaceSvg.prototype.playAudio = function(name, opt_volume) { - var sound = this.SOUNDS_[name]; - if (sound) { - var mySound; - var ie9 = goog.userAgent.DOCUMENT_MODE && - goog.userAgent.DOCUMENT_MODE === 9; - if (ie9 || goog.userAgent.IPAD || goog.userAgent.ANDROID) { - // Creating a new audio node causes lag in IE9, Android and iPad. Android - // and IE9 refetch the file from the server, iPad uses a singleton audio - // node which must be deleted and recreated for each new audio tag. - mySound = sound; - } else { - mySound = sound.cloneNode(); - } - mySound.volume = (opt_volume === undefined ? 1 : opt_volume); - mySound.play(); - } else if (this.options.parentWorkspace) { - // Maybe a workspace on a lower level knows about this sound. - this.options.parentWorkspace.playAudio(name, opt_volume); - } + Blockly.ContextMenu.show(e, menuOptions, this.RTL); }; /** @@ -962,6 +1342,39 @@ Blockly.WorkspaceSvg.prototype.markFocused = function() { this.options.parentWorkspace.markFocused(); } else { Blockly.mainWorkspace = this; + // We call e.preventDefault in many event handlers which means we + // need to explicitly grab focus (e.g from a textarea) because + // the browser will not do it for us. How to do this is browser dependant. + this.setBrowserFocus(); + } +}; + +/** + * Set the workspace to have focus in the browser. + * @private + */ +Blockly.WorkspaceSvg.prototype.setBrowserFocus = function() { + // Blur whatever was focused since explcitly grabbing focus below does not + // work in Edge. + if (document.activeElement) { + document.activeElement.blur(); + } + try { + // Focus the workspace SVG - this is for Chrome and Firefox. + this.getParentSvg().focus(); + } catch (e) { + // IE and Edge do not support focus on SVG elements. When that fails + // above, get the injectionDiv (the workspace's parent) and focus that + // instead. This doesn't work in Chrome. + try { + // In IE11, use setActive (which is IE only) so the page doesn't scroll + // to the workspace gaining focus. + this.getParentSvg().parentNode.setActive(); + } catch (e) { + // setActive support was discontinued in Edge so when that fails, call + // focus instead. + this.getParentSvg().parentNode.focus(); + } } }; @@ -969,9 +1382,10 @@ Blockly.WorkspaceSvg.prototype.markFocused = function() { * Zooming the blocks centered in (x, y) coordinate with zooming in or out. * @param {number} x X coordinate of center. * @param {number} y Y coordinate of center. - * @param {number} type Type of zooming (-1 zooming out and 1 zooming in). + * @param {number} amount Amount of zooming + * (negative zooms out and positive zooms in). */ -Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) { +Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) { var speed = this.options.zoomOptions.scaleSpeed; var metrics = this.getMetrics(); var center = this.getParentSvg().createSVGPoint(); @@ -982,7 +1396,7 @@ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) { y = center.y; var canvas = this.getCanvas(); // Scale factor. - var scaleChange = (type == 1) ? speed : 1 / speed; + var scaleChange = Math.pow(speed, amount); // Clamp scale within valid range. var newScale = this.scale * scaleChange; if (newScale > this.options.zoomOptions.maxScale) { @@ -998,6 +1412,7 @@ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) { .translate(x * (1 - scaleChange), y * (1 - scaleChange)) .scale(scaleChange); // newScale and matrix.a should be identical (within a rounding error). + // ScrollX and scrollY are in pixels. this.scrollX = matrix.e - metrics.absoluteLeft; this.scrollY = matrix.f - metrics.absoluteTop; } @@ -1032,7 +1447,7 @@ Blockly.WorkspaceSvg.prototype.zoomToFit = function() { workspaceWidth -= this.flyout_.width_; } if (!this.scrollbar) { - // Orgin point of 0,0 is fixed, blocks will not scroll to center. + // Origin point of 0,0 is fixed, blocks will not scroll to center. blocksWidth += metrics.contentLeft; blocksHeight += metrics.contentTop; } @@ -1072,7 +1487,9 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { newScale = this.options.zoomOptions.minScale; } this.scale = newScale; - this.updateGridPattern_(); + if (this.grid_) { + this.grid_.update(this.scale); + } if (this.scrollbar) { this.scrollbar.resize(); } else { @@ -1086,39 +1503,390 @@ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) { }; /** - * Updates the grid pattern. + * Get the dimensions of the given workspace component, in pixels. + * @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the + * dimensions of, or null. It should be a toolbox or flyout, and should + * implement getWidth() and getHeight(). + * @return {!Object} An object containing width and height attributes, which + * will both be zero if elem did not exist. + * @private + */ +Blockly.WorkspaceSvg.getDimensionsPx_ = function(elem) { + var width = 0; + var height = 0; + if (elem) { + width = elem.getWidth(); + height = elem.getHeight(); + } + return { + width: width, + height: height + }; +}; + +/** + * Get the content dimensions of the given workspace, taking into account + * whether or not it is scrollable and what size the workspace div is on screen. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing at least + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensions_ = function(ws, svgSize) { + if (ws.scrollbar) { + return Blockly.WorkspaceSvg.getContentDimensionsBounded_(ws, svgSize); + } else { + return Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + } +}; + +/** + * Get the bounding box for all workspace contents, in pixels. + * @param {!Blockly.WorkspaceSvg} ws The workspace to inspect. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left, right, top and bottom in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) { + // Block bounding box is in workspace coordinates. + var blockBox = ws.getBlocksBoundingBox(); + var scale = ws.scale; + + // Convert to pixels. + var width = blockBox.width * scale; + var height = blockBox.height * scale; + var left = blockBox.x * scale; + var top = blockBox.y * scale; + + return { + left: left, + top: top, + right: left + width, + bottom: top + height, + width: width, + height: height + }; +}; + +/** + * Calculate the size of a scrollable workspace, which should include room for a + * half screen border around the workspace contents. + * @param {!Blockly.WorkspaceSvg} ws The workspace to measure. + * @param {!Object} svgSize An object containing height and width attributes in + * CSS pixels. Together they specify the size of the visible workspace, not + * including areas covered up by the toolbox. + * @return {!Object} The dimensions of the contents of the given workspace, as + * an object containing + * - height and width in pixels + * - left and top in pixels relative to the workspace origin. + * @private + */ +Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) { + var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws); + + // View height and width are both in pixels, and are the same as the SVG size. + var viewWidth = svgSize.width; + var viewHeight = svgSize.height; + var halfWidth = viewWidth / 2; + var halfHeight = viewHeight / 2; + + // Add a border around the content that is at least half a screenful wide. + // Ensure border is wide enough that blocks can scroll over entire screen. + var left = Math.min(content.left - halfWidth, content.right - viewWidth); + var right = Math.max(content.right + halfWidth, content.left + viewWidth); + + var top = Math.min(content.top - halfHeight, content.bottom - viewHeight); + var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight); + + var dimensions = { + left: left, + top: top, + height: bottom - top, + width: right - left + }; + return dimensions; +}; + +/** + * Return an object with all the metrics required to size scrollbars for a + * top level workspace. The following properties are computed: + * Coordinate system: pixel coordinates. + * .viewHeight: Height of the visible rectangle, + * .viewWidth: Width of the visible rectangle, + * .contentHeight: Height of the contents, + * .contentWidth: Width of the content, + * .viewTop: Offset of top edge of visible rectangle from parent, + * .viewLeft: Offset of left edge of visible rectangle from parent, + * .contentTop: Offset of the top-most content from the y=0 coordinate, + * .contentLeft: Offset of the left-most content from the x=0 coordinate. + * .absoluteTop: Top-edge of view. + * .absoluteLeft: Left-edge of view. + * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero. + * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero. + * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero. + * .flyoutHeight: Height of flyout if it is always open. Otherwise zero. + * .toolboxPosition: Top, bottom, left or right. + * @return {!Object} Contains size and position metrics of a top level + * workspace. + * @private + * @this Blockly.WorkspaceSvg + */ +Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() { + + var toolboxDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.toolbox_); + var flyoutDimensions = + Blockly.WorkspaceSvg.getDimensionsPx_(this.flyout_); + + // Contains height and width in CSS pixels. + // svgSize is equivalent to the size of the injectionDiv at this point. + var svgSize = Blockly.svgSize(this.getParentSvg()); + if (this.toolbox_) { + if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP || + this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { + svgSize.height -= toolboxDimensions.height; + } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT || + this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { + svgSize.width -= toolboxDimensions.width; + } + } + + // svgSize is now the space taken up by the Blockly workspace, not including + // the toolbox. + var contentDimensions = + Blockly.WorkspaceSvg.getContentDimensions_(this, svgSize); + + var absoluteLeft = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { + absoluteLeft = toolboxDimensions.width; + } + var absoluteTop = 0; + if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { + absoluteTop = toolboxDimensions.height; + } + + var metrics = { + contentHeight: contentDimensions.height, + contentWidth: contentDimensions.width, + contentTop: contentDimensions.top, + contentLeft: contentDimensions.left, + + viewHeight: svgSize.height, + viewWidth: svgSize.width, + viewTop: -this.scrollY, // Must be in pixels, somehow. + viewLeft: -this.scrollX, // Must be in pixels, somehow. + + absoluteTop: absoluteTop, + absoluteLeft: absoluteLeft, + + toolboxWidth: toolboxDimensions.width, + toolboxHeight: toolboxDimensions.height, + + flyoutWidth: flyoutDimensions.width, + flyoutHeight: flyoutDimensions.height, + + toolboxPosition: this.toolboxPosition + }; + return metrics; +}; + +/** + * Sets the X/Y translations of a top level workspace to match the scrollbars. + * @param {!Object} xyRatio Contains an x and/or y property which is a float + * between 0 and 1 specifying the degree of scrolling. * @private + * @this Blockly.WorkspaceSvg */ -Blockly.WorkspaceSvg.prototype.updateGridPattern_ = function() { - if (!this.options.gridPattern) { - return; // No grid. - } - // MSIE freaks if it sees a 0x0 pattern, so set empty patterns to 100x100. - var safeSpacing = (this.options.gridOptions['spacing'] * this.scale) || 100; - this.options.gridPattern.setAttribute('width', safeSpacing); - this.options.gridPattern.setAttribute('height', safeSpacing); - var half = Math.floor(this.options.gridOptions['spacing'] / 2) + 0.5; - var start = half - this.options.gridOptions['length'] / 2; - var end = half + this.options.gridOptions['length'] / 2; - var line1 = this.options.gridPattern.firstChild; - var line2 = line1 && line1.nextSibling; - half *= this.scale; - start *= this.scale; - end *= this.scale; - if (line1) { - line1.setAttribute('stroke-width', this.scale); - line1.setAttribute('x1', start); - line1.setAttribute('y1', half); - line1.setAttribute('x2', end); - line1.setAttribute('y2', half); - } - if (line2) { - line2.setAttribute('stroke-width', this.scale); - line2.setAttribute('x1', half); - line2.setAttribute('y1', start); - line2.setAttribute('x2', half); - line2.setAttribute('y2', end); +Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) { + if (!this.scrollbar) { + throw 'Attempt to set top level workspace scroll without scrollbars.'; + } + var metrics = this.getMetrics(); + if (goog.isNumber(xyRatio.x)) { + this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; } + if (goog.isNumber(xyRatio.y)) { + this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; + } + var x = this.scrollX + metrics.absoluteLeft; + var y = this.scrollY + metrics.absoluteTop; + this.translate(x, y); + if (this.grid_) { + this.grid_.moveTo(x, y); + } +}; + +/** + * Update whether this workspace has resizes enabled. + * If enabled, workspace will resize when appropriate. + * If disabled, workspace will not resize until re-enabled. + * Use to avoid resizing during a batch operation, for performance. + * @param {boolean} enabled Whether resizes should be enabled. + */ +Blockly.WorkspaceSvg.prototype.setResizesEnabled = function(enabled) { + var reenabled = (!this.resizesEnabled_ && enabled); + this.resizesEnabled_ = enabled; + if (reenabled) { + // Newly enabled. Trigger a resize. + this.resizeContents(); + } +}; + +/** + * Dispose of all blocks in workspace, with an optimization to prevent resizes. + */ +Blockly.WorkspaceSvg.prototype.clear = function() { + this.setResizesEnabled(false); + Blockly.WorkspaceSvg.superClass_.clear.call(this); + this.setResizesEnabled(true); +}; + +/** + * Register a callback function associated with a given key, for clicks on + * buttons and labels in the flyout. + * For instance, a button specified by the XML + * + * should be matched by a call to + * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction). + * @param {string} key The name to use to look up this function. + * @param {function(!Blockly.FlyoutButton)} func The function to call when the + * given button is clicked. + */ +Blockly.WorkspaceSvg.prototype.registerButtonCallback = function(key, func) { + goog.asserts.assert(goog.isFunction(func), + 'Button callbacks must be functions.'); + this.flyoutButtonCallbacks_[key] = func; +}; + +/** + * Get the callback function associated with a given key, for clicks on buttons + * and labels in the flyout. + * @param {string} key The name to use to look up the function. + * @return {?function(!Blockly.FlyoutButton)} The function corresponding to the + * given key for this workspace; null if no callback is registered. + */ +Blockly.WorkspaceSvg.prototype.getButtonCallback = function(key) { + var result = this.flyoutButtonCallbacks_[key]; + return result ? result : null; +}; + +/** + * Remove a callback for a click on a button in the flyout. + * @param {string} key The name associated with the callback function. + */ +Blockly.WorkspaceSvg.prototype.removeButtonCallback = function(key) { + this.flyoutButtonCallbacks_[key] = null; +}; + +/** + * Register a callback function associated with a given key, for populating + * custom toolbox categories in this workspace. See the variable and procedure + * categories as an example. + * @param {string} key The name to use to look up this function. + * @param {function(!Blockly.Workspace):!Array.} func The function to + * call when the given toolbox category is opened. + */ +Blockly.WorkspaceSvg.prototype.registerToolboxCategoryCallback = function(key, + func) { + goog.asserts.assert(goog.isFunction(func), + 'Toolbox category callbacks must be functions.'); + this.toolboxCategoryCallbacks_[key] = func; +}; + +/** + * Get the callback function associated with a given key, for populating + * custom toolbox categories in this workspace. + * @param {string} key The name to use to look up the function. + * @return {?function(!Blockly.Workspace):!Array.} The function + * corresponding to the given key for this workspace, or null if no function + * is registered. + */ +Blockly.WorkspaceSvg.prototype.getToolboxCategoryCallback = function(key) { + var result = this.toolboxCategoryCallbacks_[key]; + return result ? result : null; +}; + +/** + * Remove a callback for a click on a custom category's name in the toolbox. + * @param {string} key The name associated with the callback function. + */ +Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) { + this.toolboxCategoryCallbacks_[key] = null; +}; + +/** + * Look up the gesture that is tracking this touch stream on this workspace. + * May create a new gesture. + * @param {!Event} e Mouse event or touch event. + * @return {Blockly.Gesture} The gesture that is tracking this touch stream, + * or null if no valid gesture exists. + * @package + */ +Blockly.WorkspaceSvg.prototype.getGesture = function(e) { + var isStart = (e.type == 'mousedown' || e.type == 'touchstart' || e.type == 'pointerdown'); + + var gesture = this.currentGesture_; + if (gesture) { + if (isStart && gesture.hasStarted()) { + console.warn('tried to start the same gesture twice'); + // That's funny. We must have missed a mouse up. + // Cancel it, rather than try to retrieve all of the state we need. + gesture.cancel(); + return null; + } + return gesture; + } + + // No gesture existed on this workspace, but this looks like the start of a + // new gesture. + if (isStart) { + this.currentGesture_ = new Blockly.Gesture(e, this); + return this.currentGesture_; + } + // No gesture existed and this event couldn't be the start of a new gesture. + return null; +}; + +/** + * Clear the reference to the current gesture. + * @package + */ +Blockly.WorkspaceSvg.prototype.clearGesture = function() { + this.currentGesture_ = null; +}; + +/** + * Cancel the current gesture, if one exists. + * @package + */ +Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() { + if (this.currentGesture_) { + this.currentGesture_.cancel(); + } +}; + +/** + * Get the audio manager for this workspace. + * @return {Blockly.WorkspaceAudio} The audio manager for this workspace. + */ +Blockly.WorkspaceSvg.prototype.getAudioManager = function() { + return this.audioManager_; +}; + +/** + * Get the grid object for this workspace, or null if there is none. + * @return {Blockly.Grid} The grid object for this workspace. + * @package + */ +Blockly.WorkspaceSvg.prototype.getGrid = function() { + return this.grid_; }; // Export symbols that would otherwise be renamed by Closure compiler. diff --git a/core/xml.js b/core/xml.js index e2651e0..e99a95a 100644 --- a/core/xml.js +++ b/core/xml.js @@ -24,38 +24,62 @@ */ 'use strict'; +/** + * @name Blockly.Xml + * @namespace + **/ goog.provide('Blockly.Xml'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); +goog.require('goog.asserts'); goog.require('goog.dom'); /** * Encode a block tree as XML. * @param {!Blockly.Workspace} workspace The workspace containing blocks. + * @param {boolean=} opt_noId True if the encoder should skip the block IDs. * @return {!Element} XML document. */ -Blockly.Xml.workspaceToDom = function(workspace) { +Blockly.Xml.workspaceToDom = function(workspace, opt_noId) { var xml = goog.dom.createDom('xml'); + xml.appendChild(Blockly.Xml.variablesToDom( + Blockly.Variables.allUsedVarModels(workspace))); var blocks = workspace.getTopBlocks(true); for (var i = 0, block; block = blocks[i]; i++) { - xml.appendChild(Blockly.Xml.blockToDomWithXY(block)); + xml.appendChild(Blockly.Xml.blockToDomWithXY(block, opt_noId)); } return xml; }; +/** + * Encode a list of variables as XML. + * @param {!Array.} variableList List of all variable + * models. + * @return {!Element} List of XML elements. + */ +Blockly.Xml.variablesToDom = function(variableList) { + var variables = goog.dom.createDom('variables'); + for (var i = 0, variable; variable = variableList[i]; i++) { + var element = goog.dom.createDom('variable', null, variable.name); + element.setAttribute('type', variable.type); + element.setAttribute('id', variable.getId()); + variables.appendChild(element); + } + return variables; +}; + /** * Encode a block subtree as XML with XY coordinates. * @param {!Blockly.Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. * @return {!Element} Tree of XML elements. */ -Blockly.Xml.blockToDomWithXY = function(block) { +Blockly.Xml.blockToDomWithXY = function(block, opt_noId) { var width; // Not used in LTR. if (block.workspace.RTL) { width = block.workspace.getWidth(); } - var element = Blockly.Xml.blockToDom(block); + var element = Blockly.Xml.blockToDom(block, opt_noId); var xy = block.getRelativeToSurfaceXY(); element.setAttribute('x', Math.round(block.workspace.RTL ? width - xy.x : xy.x)); @@ -63,15 +87,92 @@ Blockly.Xml.blockToDomWithXY = function(block) { return element; }; +/** + * Encode a variable field as XML. + * @param {!Blockly.FieldVariable} field The field to encode. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDomVariable_ = function(field) { + var id = field.getValue(); + // The field had not been initialized fully before being serialized. + // This can happen if a block is created directly through a call to + // workspace.newBlock instead of from XML. + // The new block will be serialized for the first time when firing a block + // creation event. + if (id == null) { + field.initModel(); + id = field.getValue(); + } + // Get the variable directly from the field, instead of doing a lookup. This + // will work even if the variable has already been deleted. This can happen + // because the flyout defers deleting blocks until the next time the flyout is + // opened. + var variable = field.getVariable(); + + if (!variable) { + throw Error('Tried to serialize a variable field with no variable.'); + } + var container = goog.dom.createDom('field', null, variable.name); + container.setAttribute('name', field.name); + container.setAttribute('id', variable.getId()); + container.setAttribute('variabletype', variable.type); + return container; +}; + +/** + * Encode a field as XML. + * @param {!Blockly.Field} field The field to encode. + * @param {!Blockly.Workspace} workspace The workspace that the field is in. + * @return {?Element} XML element, or null if the field did not need to be + * serialized. + * @private + */ +Blockly.Xml.fieldToDom_ = function(field) { + if (field.name && field.EDITABLE) { + if (field instanceof Blockly.FieldVariable) { + return Blockly.Xml.fieldToDomVariable_(field); + } else { + var container = goog.dom.createDom('field', null, field.getValue()); + container.setAttribute('name', field.name); + return container; + } + } + return null; +}; + +/** + * Encode all of a block's fields as XML and attach them to the given tree of + * XML elements. + * @param {!Blockly.Block} block A block with fields to be encoded. + * @param {!Element} element The XML element to which the field DOM should be + * attached. + * @private + */ +Blockly.Xml.allFieldsToDom_ = function(block, element) { + for (var i = 0, input; input = block.inputList[i]; i++) { + for (var j = 0, field; field = input.fieldRow[j]; j++) { + var fieldDom = Blockly.Xml.fieldToDom_(field); + if (fieldDom) { + element.appendChild(fieldDom); + } + } + } +}; + /** * Encode a block subtree as XML. * @param {!Blockly.Block} block The root block to encode. + * @param {boolean=} opt_noId True if the encoder should skip the block ID. * @return {!Element} Tree of XML elements. */ -Blockly.Xml.blockToDom = function(block) { +Blockly.Xml.blockToDom = function(block, opt_noId) { var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block'); element.setAttribute('type', block.type); - element.setAttribute('id', block.id); + if (!opt_noId) { + element.setAttribute('id', block.id); + } if (block.mutationToDom) { // Custom data for an advanced block. var mutation = block.mutationToDom(); @@ -79,18 +180,8 @@ Blockly.Xml.blockToDom = function(block) { element.appendChild(mutation); } } - function fieldToDom(field) { - if (field.name && field.EDITABLE) { - var container = goog.dom.createDom('field', null, field.getValue()); - container.setAttribute('name', field.name); - element.appendChild(container); - } - } - for (var i = 0, input; input = block.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - fieldToDom(field); - } - } + + Blockly.Xml.allFieldsToDom_(block, element); var commentText = block.getCommentText(); if (commentText) { @@ -126,7 +217,7 @@ Blockly.Xml.blockToDom = function(block) { container.appendChild(Blockly.Xml.cloneShadow_(shadow)); } if (childBlock) { - container.appendChild(Blockly.Xml.blockToDom(childBlock)); + container.appendChild(Blockly.Xml.blockToDom(childBlock, opt_noId)); empty = false; } } @@ -157,7 +248,7 @@ Blockly.Xml.blockToDom = function(block) { var nextBlock = block.getNextBlock(); if (nextBlock) { var container = goog.dom.createDom('next', null, - Blockly.Xml.blockToDom(nextBlock)); + Blockly.Xml.blockToDom(nextBlock, opt_noId)); element.appendChild(container); } var shadow = block.nextConnection && block.nextConnection.getShadowDom(); @@ -261,7 +352,7 @@ Blockly.Xml.textToDom = function(text) { dom.firstChild.nodeName.toLowerCase() != 'xml' || dom.firstChild !== dom.lastChild) { // Whatever we got back from the parser is not XML. - throw 'Blockly.Xml.textToDom did not obtain a valid XML tree.'; + goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.'); } return dom.firstChild; }; @@ -270,6 +361,7 @@ Blockly.Xml.textToDom = function(text) { * Decode an XML DOM and create blocks on the workspace. * @param {!Element} xml XML DOM. * @param {!Blockly.Workspace} workspace The workspace. + * @return {Array.} An array containing new block IDs. */ Blockly.Xml.domToWorkspace = function(xml, workspace) { if (xml instanceof Blockly.Workspace) { @@ -283,6 +375,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { if (workspace.RTL) { width = workspace.getWidth(); } + var newBlockIds = []; // A list of block IDs added by this call. Blockly.Field.startCache(); // Safari 7.1.3 is known to provide node lists with extra references to // children beyond the lists' length. Trust the length, do not use the @@ -292,22 +385,108 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) { if (!existingGroup) { Blockly.Events.setGroup(true); } - for (var i = 0; i < childCount; i++) { - var xmlChild = xml.childNodes[i]; - var name = xmlChild.nodeName.toLowerCase(); - if (name == 'block' || name == 'shadow') { - var block = Blockly.Xml.domToBlock(xmlChild, workspace); - var blockX = parseInt(xmlChild.getAttribute('x'), 10); - var blockY = parseInt(xmlChild.getAttribute('y'), 10); - if (!isNaN(blockX) && !isNaN(blockY)) { - block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); + + // Disable workspace resizes as an optimization. + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(false); + } + var variablesFirst = true; + try { + for (var i = 0; i < childCount; i++) { + var xmlChild = xml.childNodes[i]; + var name = xmlChild.nodeName.toLowerCase(); + if (name == 'block' || + (name == 'shadow' && !Blockly.Events.recordUndo)) { + // Allow top-level shadow blocks if recordUndo is disabled since + // that means an undo is in progress. Such a block is expected + // to be moved to a nested destination in the next operation. + var block = Blockly.Xml.domToBlock(xmlChild, workspace); + newBlockIds.push(block.id); + var blockX = parseInt(xmlChild.getAttribute('x'), 10); + var blockY = parseInt(xmlChild.getAttribute('y'), 10); + if (!isNaN(blockX) && !isNaN(blockY)) { + block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); + } + variablesFirst = false; + } else if (name == 'shadow') { + goog.asserts.fail('Shadow block cannot be a top-level block.'); + variablesFirst = false; + } else if (name == 'variables') { + if (variablesFirst) { + Blockly.Xml.domToVariables(xmlChild, workspace); + } else { + throw Error('\'variables\' tag must exist once before block and ' + + 'shadow tag elements in the workspace XML, but it was found in ' + + 'another location.'); + } + variablesFirst = false; } } + } finally { + if (!existingGroup) { + Blockly.Events.setGroup(false); + } + Blockly.Field.stopCache(); } - if (!existingGroup) { - Blockly.Events.setGroup(false); + // Re-enable workspace resizing. + if (workspace.setResizesEnabled) { + workspace.setResizesEnabled(true); + } + return newBlockIds; +}; + +/** + * Decode an XML DOM and create blocks on the workspace. Position the new + * blocks immediately below prior blocks, aligned by their starting edge. + * @param {!Element} xml The XML DOM. + * @param {!Blockly.Workspace} workspace The workspace to add to. + * @return {Array.} An array containing new block IDs. + */ +Blockly.Xml.appendDomToWorkspace = function(xml, workspace) { + var bbox; // Bounding box of the current blocks. + // First check if we have a workspaceSvg, otherwise the blocks have no shape + // and the position does not matter. + if (workspace.hasOwnProperty('scale')) { + var savetab = Blockly.BlockSvg.TAB_WIDTH; + try { + Blockly.BlockSvg.TAB_WIDTH = 0; + bbox = workspace.getBlocksBoundingBox(); + } finally { + Blockly.BlockSvg.TAB_WIDTH = savetab; + } } - Blockly.Field.stopCache(); + // Load the new blocks into the workspace and get the IDs of the new blocks. + var newBlockIds = Blockly.Xml.domToWorkspace(xml,workspace); + if (bbox && bbox.height) { // check if any previous block + var offsetY = 0; // offset to add to y of the new block + var offsetX = 0; + var farY = bbox.y + bbox.height; // bottom position + var topX = bbox.x; // x of bounding box + // Check position of the new blocks. + var newX = Infinity; // x of top corner + var newY = Infinity; // y of top corner + for (var i = 0; i < newBlockIds.length; i++) { + var blockXY = workspace.getBlockById(newBlockIds[i]).getRelativeToSurfaceXY(); + if (blockXY.y < newY) { + newY = blockXY.y; + } + if (blockXY.x < newX) { // if we align also on x + newX = blockXY.x; + } + } + offsetY = farY - newY + Blockly.BlockSvg.SEP_SPACE_Y; + offsetX = topX - newX; + // move the new blocks to append them at the bottom + var width; // Not used in LTR. + if (workspace.RTL) { + width = workspace.getWidth(); + } + for (var i = 0; i < newBlockIds.length; i++) { + var block = workspace.getBlockById(newBlockIds[i]); + block.moveBy(workspace.RTL ? width - offsetX : offsetX, offsetY); + } + } + return newBlockIds; }; /** @@ -327,37 +506,74 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { } // Create top-level block. Blockly.Events.disable(); - var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); - if (workspace.rendered) { - // Hide connections to speed up assembly. - topBlock.setConnectionsHidden(true); + var variablesBeforeCreation = workspace.getAllVariables(); + try { + var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); // Generate list of all blocks. var blocks = topBlock.getDescendants(); - // Render each block. - for (var i = blocks.length - 1; i >= 0; i--) { - blocks[i].initSvg(); - } - for (var i = blocks.length - 1; i >= 0; i--) { - blocks[i].render(false); - } - // Populating the connection database may be defered until after the blocks - // have renderend. - setTimeout(function() { - if (topBlock.workspace) { // Check that the block hasn't been deleted. - topBlock.setConnectionsHidden(false); + if (workspace.rendered) { + // Hide connections to speed up assembly. + topBlock.setConnectionsHidden(true); + // Render each block. + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initSvg(); + } + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].render(false); } - }, 1); - topBlock.updateDisabled(); - // Fire an event to allow scrollbars to resize. - Blockly.asyncSvgResize(workspace); + // Populating the connection database may be deferred until after the + // blocks have rendered. + setTimeout(function() { + if (topBlock.workspace) { // Check that the block hasn't been deleted. + topBlock.setConnectionsHidden(false); + } + }, 1); + topBlock.updateDisabled(); + // Allow the scrollbars to resize and move based on the new contents. + // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. + workspace.resizeContents(); + } else { + for (var i = blocks.length - 1; i >= 0; i--) { + blocks[i].initModel(); + } + } + } finally { + Blockly.Events.enable(); } - Blockly.Events.enable(); if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Create(topBlock)); + var newVariables = Blockly.Variables.getAddedVariables(workspace, + variablesBeforeCreation); + // Fire a VarCreate event for each (if any) new variable created. + for(var i = 0; i < newVariables.length; i++) { + var thisVariable = newVariables[i]; + Blockly.Events.fire(new Blockly.Events.VarCreate(thisVariable)); + } + // Block events come after var events, in case they refer to newly created + // variables. + Blockly.Events.fire(new Blockly.Events.BlockCreate(topBlock)); } return topBlock; }; +/** + * Decode an XML list of variables and add the variables to the workspace. + * @param {!Element} xmlVariables List of XML variable elements. + * @param {!Blockly.Workspace} workspace The workspace to which the variable + * should be added. + */ +Blockly.Xml.domToVariables = function(xmlVariables, workspace) { + for (var i = 0, xmlChild; xmlChild = xmlVariables.children[i]; i++) { + var type = xmlChild.getAttribute('type'); + var id = xmlChild.getAttribute('id'); + var name = xmlChild.textContent; + + if (type == null) { + throw Error('Variable with id, ' + id + ' is without a type'); + } + workspace.createVariable(name, type, id); + } +}; + /** * Decode an XML block tag and create a block (and possibly sub blocks) on the * workspace. @@ -369,9 +585,8 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) { Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { var block = null; var prototypeName = xmlBlock.getAttribute('type'); - if (!prototypeName) { - throw 'Block type unspecified: \n' + xmlBlock.outerHTML; - } + goog.asserts.assert( + prototypeName, 'Block type unspecified: %s', xmlBlock.outerHTML); var id = xmlBlock.getAttribute('id'); block = workspace.newBlock(prototypeName, id); @@ -384,21 +599,20 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { var input; // Find any enclosed blocks or shadows in this tag. - var childBlockNode = null; - var childShadowNode = null; - for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j]; - j++) { - if (grandchildNode.nodeType == 1) { - if (grandchildNode.nodeName.toLowerCase() == 'block') { - childBlockNode = grandchildNode; - } else if (grandchildNode.nodeName.toLowerCase() == 'shadow') { - childShadowNode = grandchildNode; + var childBlockElement = null; + var childShadowElement = null; + for (var j = 0, grandchild; grandchild = xmlChild.childNodes[j]; j++) { + if (grandchild.nodeType == 1) { + if (grandchild.nodeName.toLowerCase() == 'block') { + childBlockElement = /** @type {!Element} */ (grandchild); + } else if (grandchild.nodeName.toLowerCase() == 'shadow') { + childShadowElement = /** @type {!Element} */ (grandchild); } } } // Use the shadow block if there is no child block. - if (!childBlockNode && childShadowNode) { - childBlockNode = childShadowNode; + if (!childBlockElement && childShadowElement) { + childBlockElement = childShadowElement; } var name = xmlChild.getAttribute('name'); @@ -408,7 +622,7 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { if (block.domToMutation) { block.domToMutation(xmlChild); if (block.initSvg) { - // Mutation may have added some elements that need initalizing. + // Mutation may have added some elements that need initializing. block.initSvg(); } } @@ -439,13 +653,7 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { // Titles were renamed to field in December 2013. // Fall through. case 'field': - var field = block.getField(name); - if (!field) { - console.warn('Ignoring non-existent field ' + name + ' in block ' + - prototypeName); - break; - } - field.setValue(xmlChild.textContent); + Blockly.Xml.domToField_(block, name, xmlChild); break; case 'value': case 'statement': @@ -455,37 +663,36 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { prototypeName); break; } - if (childShadowNode) { - input.connection.setShadowDom(childShadowNode); + if (childShadowElement) { + input.connection.setShadowDom(childShadowElement); } - if (childBlockNode) { - blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, + if (childBlockElement) { + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockElement, workspace); if (blockChild.outputConnection) { input.connection.connect(blockChild.outputConnection); } else if (blockChild.previousConnection) { input.connection.connect(blockChild.previousConnection); } else { - throw 'Child block does not have output or previous statement.'; + goog.asserts.fail( + 'Child block does not have output or previous statement.'); } } break; case 'next': - if (childShadowNode && block.nextConnection) { - block.nextConnection.setShadowDom(childShadowNode); + if (childShadowElement && block.nextConnection) { + block.nextConnection.setShadowDom(childShadowElement); } - if (childBlockNode) { - if (!block.nextConnection) { - throw 'Next statement does not exist.'; - } else if (block.nextConnection.isConnected()) { - // This could happen if there is more than one XML 'next' tag. - throw 'Next statement is already connected.'; - } - blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, + if (childBlockElement) { + goog.asserts.assert(block.nextConnection, + 'Next statement does not exist.'); + // If there is more than one XML 'next' tag. + goog.asserts.assert(!block.nextConnection.isConnected(), + 'Next statement is already connected.'); + blockChild = Blockly.Xml.domToBlockHeadless_(childBlockElement, workspace); - if (!blockChild.previousConnection) { - throw 'Next block does not have previous statement.'; - } + goog.asserts.assert(blockChild.previousConnection, + 'Next block does not have previous statement.'); block.nextConnection.connect(blockChild.previousConnection); } break; @@ -520,15 +727,75 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { block.setCollapsed(collapsed == 'true'); } if (xmlBlock.nodeName.toLowerCase() == 'shadow') { + // Ensure all children are also shadows. + var children = block.getChildren(); + for (var i = 0, child; child = children[i]; i++) { + goog.asserts.assert( + child.isShadow(), 'Shadow block not allowed non-shadow child.'); + } + // Ensure this block doesn't have any variable inputs. + goog.asserts.assert(block.getVarModels().length == 0, + 'Shadow blocks cannot have variable references.'); block.setShadow(true); } - // Give the block a chance to clean up any initial inputs. - if (block.validate) { - block.validate(); - } return block; }; +/** + * Decode an XML variable field tag and set the value of that field. + * @param {!Blockly.Workspace} workspace The workspace that is currently being + * deserialized. + * @param {!Element} xml The field tag to decode. + * @param {string} text The text content of the XML tag. + * @param {!Blockly.FieldVariable} field The field on which the value will be + * set. + * @private + */ +Blockly.Xml.domToFieldVariable_ = function(workspace, xml, text, field) { + var type = xml.getAttribute('variabletype') || ''; + // TODO (fenichel): Does this need to be explicit or not? + if (type == '\'\'') { + type = ''; + } + + var variable = Blockly.Variables.getOrCreateVariablePackage(workspace, xml.id, + text, type); + + // This should never happen :) + if (type != null && type !== variable.type) { + throw Error('Serialized variable type with id \'' + + variable.getId() + '\' had type ' + variable.type + ', and ' + + 'does not match variable field that references it: ' + + Blockly.Xml.domToText(xml) + '.'); + } + + field.setValue(variable.getId()); +}; + +/** + * Decode an XML field tag and set the value of that field on the given block. + * @param {!Blockly.Block} block The block that is currently being deserialized. + * @param {string} fieldName The name of the field on the block. + * @param {!Element} xml The field tag to decode. + * @private + */ +Blockly.Xml.domToField_ = function(block, fieldName, xml) { + var field = block.getField(fieldName); + if (!field) { + console.warn('Ignoring non-existent field ' + fieldName + ' in block ' + + block.type); + return; + } + + var workspace = block.workspace; + var text = xml.textContent; + if (field instanceof Blockly.FieldVariable) { + Blockly.Xml.domToFieldVariable_(workspace, xml, text, field); + } else { + field.setValue(text); + } +}; + /** * Remove any 'next' block (statements in a stack). * @param {!Element} xmlBlock XML block element. diff --git a/core/zoom_controls.js b/core/zoom_controls.js index 8edb9eb..8d8b085 100644 --- a/core/zoom_controls.js +++ b/core/zoom_controls.js @@ -26,6 +26,7 @@ goog.provide('Blockly.ZoomControls'); +goog.require('Blockly.Touch'); goog.require('goog.dom'); @@ -64,7 +65,7 @@ Blockly.ZoomControls.prototype.MARGIN_BOTTOM_ = 20; * @type {number} * @private */ -Blockly.ZoomControls.prototype.MARGIN_SIDE_ = 28; +Blockly.ZoomControls.prototype.MARGIN_SIDE_ = 20; /** * The SVG group containing the zoom controls. @@ -112,69 +113,82 @@ Blockly.ZoomControls.prototype.createDom = function() { clip-path="url(#blocklyZoomresetClipPath837493)"> */ - this.svgGroup_ = Blockly.createSvgElement('g', + this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': 'blocklyZoom'}, null); + var clip; var rnd = String(Math.random()).substring(2); - var clip = Blockly.createSvgElement('clipPath', + clip = Blockly.utils.createSvgElement('clipPath', {'id': 'blocklyZoomoutClipPath' + rnd}, this.svgGroup_); - Blockly.createSvgElement('rect', + Blockly.utils.createSvgElement('rect', {'width': 32, 'height': 32, 'y': 77}, clip); - var zoomoutSvg = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, 'x': -64, - 'y': -15, - 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')'}, + var zoomoutSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'x': -64, + 'y': -15, + 'clip-path': 'url(#blocklyZoomoutClipPath' + rnd + ')' + }, this.svgGroup_); zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); - var clip = Blockly.createSvgElement('clipPath', + clip = Blockly.utils.createSvgElement('clipPath', {'id': 'blocklyZoominClipPath' + rnd}, this.svgGroup_); - Blockly.createSvgElement('rect', + Blockly.utils.createSvgElement('rect', {'width': 32, 'height': 32, 'y': 43}, clip); - var zoominSvg = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, - 'x': -32, - 'y': -49, - 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')'}, + var zoominSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, + 'x': -32, + 'y': -49, + 'clip-path': 'url(#blocklyZoominClipPath' + rnd + ')' + }, this.svgGroup_); zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); - var clip = Blockly.createSvgElement('clipPath', + clip = Blockly.utils.createSvgElement('clipPath', {'id': 'blocklyZoomresetClipPath' + rnd}, this.svgGroup_); - Blockly.createSvgElement('rect', + Blockly.utils.createSvgElement('rect', {'width': 32, 'height': 32}, clip); - var zoomresetSvg = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, - 'height': Blockly.SPRITE.height, 'y': -92, - 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')'}, + var zoomresetSvg = Blockly.utils.createSvgElement('image', + { + 'width': Blockly.SPRITE.width, + 'height': Blockly.SPRITE.height, 'y': -92, + 'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')' + }, this.svgGroup_); zoomresetSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', workspace.options.pathToMedia + Blockly.SPRITE.url); // Attach event listeners. - Blockly.bindEvent_(zoomresetSvg, 'mousedown', null, function(e) { - workspace.setScale(1); + Blockly.bindEventWithChecks_(zoomresetSvg, 'mousedown', null, function(e) { + workspace.markFocused(); + workspace.setScale(workspace.options.zoomOptions.startScale); workspace.scrollCenter(); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. e.stopPropagation(); // Don't start a workspace scroll. e.preventDefault(); // Stop double-clicking from selecting text. }); - Blockly.bindEvent_(zoominSvg, 'mousedown', null, function(e) { + Blockly.bindEventWithChecks_(zoominSvg, 'mousedown', null, function(e) { + workspace.markFocused(); workspace.zoomCenter(1); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. e.stopPropagation(); // Don't start a workspace scroll. e.preventDefault(); // Stop double-clicking from selecting text. }); - Blockly.bindEvent_(zoomoutSvg, 'mousedown', null, function(e) { + Blockly.bindEventWithChecks_(zoomoutSvg, 'mousedown', null, function(e) { + workspace.markFocused(); workspace.zoomCenter(-1); + Blockly.Touch.clearTouchIdentifier(); // Don't block future drags. e.stopPropagation(); // Don't start a workspace scroll. e.preventDefault(); // Stop double-clicking from selecting text. }); diff --git a/core_build_list.json b/core_build_list.json index 62b017f..6e3e36d 100644 --- a/core_build_list.json +++ b/core_build_list.json @@ -1 +1 @@ -["..\\closure-library\\closure\\goog\\base.js", "core\\blocks.js", "..\\closure-library\\closure\\goog\\debug\\error.js", "..\\closure-library\\closure\\goog\\dom\\nodetype.js", "..\\closure-library\\closure\\goog\\string\\string.js", "..\\closure-library\\closure\\goog\\asserts\\asserts.js", "..\\closure-library\\closure\\goog\\array\\array.js", "..\\closure-library\\closure\\goog\\math\\math.js", "core\\workspace.js", "..\\closure-library\\closure\\goog\\labs\\useragent\\util.js", "..\\closure-library\\closure\\goog\\object\\object.js", "..\\closure-library\\closure\\goog\\labs\\useragent\\browser.js", "..\\closure-library\\closure\\goog\\labs\\useragent\\engine.js", "..\\closure-library\\closure\\goog\\labs\\useragent\\platform.js", "..\\closure-library\\closure\\goog\\reflect\\reflect.js", "..\\closure-library\\closure\\goog\\useragent\\useragent.js", "..\\closure-library\\closure\\goog\\dom\\browserfeature.js", "..\\closure-library\\closure\\goog\\dom\\htmlelement.js", "..\\closure-library\\closure\\goog\\dom\\tagname.js", "..\\closure-library\\closure\\goog\\dom\\asserts.js", "..\\closure-library\\closure\\goog\\dom\\tags.js", "..\\closure-library\\closure\\goog\\string\\typedstring.js", "..\\closure-library\\closure\\goog\\string\\const.js", "..\\closure-library\\closure\\goog\\html\\safescript.js", "..\\closure-library\\closure\\goog\\fs\\url.js", "..\\closure-library\\closure\\goog\\i18n\\bidi.js", "..\\closure-library\\closure\\goog\\html\\trustedresourceurl.js", "..\\closure-library\\closure\\goog\\html\\safeurl.js", "..\\closure-library\\closure\\goog\\html\\safestyle.js", "..\\closure-library\\closure\\goog\\html\\safestylesheet.js", "..\\closure-library\\closure\\goog\\html\\safehtml.js", "..\\closure-library\\closure\\goog\\dom\\safe.js", "..\\closure-library\\closure\\goog\\html\\uncheckedconversions.js", "..\\closure-library\\closure\\goog\\math\\coordinate.js", "..\\closure-library\\closure\\goog\\math\\size.js", "..\\closure-library\\closure\\goog\\dom\\dom.js", "core\\bubble.js", "core\\icon.js", "core\\comment.js", "core\\connection.js", "..\\closure-library\\closure\\goog\\dom\\vendor.js", "..\\closure-library\\closure\\goog\\html\\legacyconversions.js", "..\\closure-library\\closure\\goog\\math\\box.js", "..\\closure-library\\closure\\goog\\math\\irect.js", "..\\closure-library\\closure\\goog\\math\\rect.js", "..\\closure-library\\closure\\goog\\style\\style.js", "core\\field.js", "core\\tooltip.js", "core\\field_label.js", "core\\input.js", "core\\connection_db.js", "core\\options.js", "..\\closure-library\\closure\\goog\\debug\\entrypointregistry.js", "..\\closure-library\\closure\\goog\\events\\browserfeature.js", "..\\closure-library\\closure\\goog\\disposable\\idisposable.js", "..\\closure-library\\closure\\goog\\disposable\\disposable.js", "..\\closure-library\\closure\\goog\\events\\eventid.js", "..\\closure-library\\closure\\goog\\events\\event.js", "..\\closure-library\\closure\\goog\\events\\eventtype.js", "..\\closure-library\\closure\\goog\\events\\browserevent.js", "..\\closure-library\\closure\\goog\\events\\listenable.js", "..\\closure-library\\closure\\goog\\events\\listener.js", "..\\closure-library\\closure\\goog\\events\\listenermap.js", "..\\closure-library\\closure\\goog\\events\\events.js", "core\\scrollbar.js", "..\\closure-library\\closure\\goog\\promise\\thenable.js", "..\\closure-library\\closure\\goog\\async\\freelist.js", "..\\closure-library\\closure\\goog\\async\\workqueue.js", "..\\closure-library\\closure\\goog\\functions\\functions.js", "..\\closure-library\\closure\\goog\\async\\nexttick.js", "..\\closure-library\\closure\\goog\\async\\run.js", "..\\closure-library\\closure\\goog\\promise\\resolver.js", "..\\closure-library\\closure\\goog\\promise\\promise.js", "..\\closure-library\\closure\\goog\\events\\eventtarget.js", "..\\closure-library\\closure\\goog\\timer\\timer.js", "core\\trashcan.js", "core\\xml.js", "core\\zoom_controls.js", "core\\workspace_svg.js", "core\\mutator.js", "core\\warning.js", "core\\block.js", "..\\closure-library\\closure\\goog\\events\\eventhandler.js", "..\\closure-library\\closure\\goog\\ui\\idgenerator.js", "..\\closure-library\\closure\\goog\\ui\\component.js", "..\\closure-library\\closure\\goog\\a11y\\aria\\roles.js", "..\\closure-library\\closure\\goog\\a11y\\aria\\attributes.js", "..\\closure-library\\closure\\goog\\a11y\\aria\\datatables.js", "..\\closure-library\\closure\\goog\\a11y\\aria\\aria.js", "..\\closure-library\\closure\\goog\\events\\keycodes.js", "..\\closure-library\\closure\\goog\\events\\keyhandler.js", "..\\closure-library\\closure\\goog\\dom\\classlist.js", "..\\closure-library\\closure\\goog\\ui\\registry.js", "..\\closure-library\\closure\\goog\\ui\\containerrenderer.js", "..\\closure-library\\closure\\goog\\ui\\controlcontent.js", "..\\closure-library\\closure\\goog\\ui\\controlrenderer.js", "..\\closure-library\\closure\\goog\\ui\\control.js", "..\\closure-library\\closure\\goog\\ui\\container.js", "..\\closure-library\\closure\\goog\\ui\\menuheaderrenderer.js", "..\\closure-library\\closure\\goog\\ui\\menuheader.js", "..\\closure-library\\closure\\goog\\ui\\menuitemrenderer.js", "..\\closure-library\\closure\\goog\\ui\\menuitem.js", "..\\closure-library\\closure\\goog\\ui\\menuseparatorrenderer.js", "..\\closure-library\\closure\\goog\\ui\\separator.js", "..\\closure-library\\closure\\goog\\ui\\menurenderer.js", "..\\closure-library\\closure\\goog\\ui\\menuseparator.js", "..\\closure-library\\closure\\goog\\ui\\menu.js", "core\\contextmenu.js", "core\\rendered_connection.js", "core\\block_svg.js", "core\\block_render_svg.js", "core\\events.js", "core\\msg.js", "core\\field_textinput.js", "core\\field_angle.js", "core\\field_checkbox.js", "..\\closure-library\\closure\\goog\\color\\names.js", "..\\closure-library\\closure\\goog\\color\\color.js", "..\\closure-library\\closure\\goog\\iter\\iter.js", "..\\closure-library\\closure\\goog\\dom\\tagiterator.js", "..\\closure-library\\closure\\goog\\dom\\nodeiterator.js", "..\\closure-library\\closure\\goog\\ui\\paletterenderer.js", "..\\closure-library\\closure\\goog\\ui\\selectionmodel.js", "..\\closure-library\\closure\\goog\\ui\\palette.js", "..\\closure-library\\closure\\goog\\ui\\colorpalette.js", "..\\closure-library\\closure\\goog\\ui\\colorpicker.js", "core\\field_colour.js", "core\\field_dropdown.js", "core\\instances.js", "core\\utils.js", "core\\field_instance.js", "core\\field_image.js", "core\\field_number.js", "core\\variables.js", "core\\field_variable.js","core\\macro.js", "core\\field_macro.js" ,"core\\generator.js", "core\\type.js", "core\\types.js", "core\\static_typing.js", "core\\names.js", "core\\procedures.js", "core\\flyout.js", "..\\closure-library\\closure\\goog\\events\\focushandler.js", "..\\closure-library\\closure\\goog\\debug\\debug.js", "..\\closure-library\\closure\\goog\\debug\\logrecord.js", "..\\closure-library\\closure\\goog\\debug\\logbuffer.js", "..\\closure-library\\closure\\goog\\debug\\logger.js", "..\\closure-library\\closure\\goog\\log\\log.js", "..\\closure-library\\closure\\goog\\string\\stringbuffer.js", "..\\closure-library\\closure\\goog\\ui\\tree\\basenode.js", "..\\closure-library\\closure\\goog\\ui\\tree\\treenode.js", "..\\closure-library\\closure\\goog\\structs\\structs.js", "..\\closure-library\\closure\\goog\\structs\\trie.js", "..\\closure-library\\closure\\goog\\ui\\tree\\typeahead.js", "..\\closure-library\\closure\\goog\\ui\\tree\\treecontrol.js", "core\\toolbox.js", "core\\css.js", "core\\widgetdiv.js", "core\\constants.js", "core\\inject.js", "core\\blockly.js"] \ No newline at end of file +["../closure-library/closure/goog/base.js", "core/blocks.js", "../closure-library/closure/goog/debug/error.js", "../closure-library/closure/goog/dom/nodetype.js", "../closure-library/closure/goog/asserts/asserts.js", "../closure-library/closure/goog/array/array.js", "../closure-library/closure/goog/math/math.js", "core/workspace.js", "../closure-library/closure/goog/string/string.js", "../closure-library/closure/goog/labs/useragent/util.js", "../closure-library/closure/goog/object/object.js", "../closure-library/closure/goog/labs/useragent/browser.js", "../closure-library/closure/goog/labs/useragent/engine.js", "../closure-library/closure/goog/labs/useragent/platform.js", "../closure-library/closure/goog/reflect/reflect.js", "../closure-library/closure/goog/useragent/useragent.js", "../closure-library/closure/goog/dom/browserfeature.js", "../closure-library/closure/goog/dom/htmlelement.js", "../closure-library/closure/goog/dom/tagname.js", "../closure-library/closure/goog/dom/asserts.js", "../closure-library/closure/goog/dom/tags.js", "../closure-library/closure/goog/string/typedstring.js", "../closure-library/closure/goog/string/const.js", "../closure-library/closure/goog/html/safescript.js", "../closure-library/closure/goog/fs/url.js", "../closure-library/closure/goog/i18n/bidi.js", "../closure-library/closure/goog/html/trustedresourceurl.js", "../closure-library/closure/goog/html/safeurl.js", "../closure-library/closure/goog/html/safestyle.js", "../closure-library/closure/goog/html/safestylesheet.js", "../closure-library/closure/goog/html/safehtml.js", "../closure-library/closure/goog/dom/safe.js", "../closure-library/closure/goog/html/uncheckedconversions.js", "../closure-library/closure/goog/math/coordinate.js", "../closure-library/closure/goog/math/size.js", "../closure-library/closure/goog/dom/dom.js", "core/bubble.js", "core/icon.js", "core/comment.js", "core/connection.js", "../closure-library/closure/goog/dom/vendor.js", "../closure-library/closure/goog/math/box.js", "../closure-library/closure/goog/math/irect.js", "../closure-library/closure/goog/math/rect.js", "../closure-library/closure/goog/style/style.js", "core/field.js", "core/tooltip.js", "core/field_label.js", "core/input.js", "core/connection_db.js", "core/options.js", "../closure-library/closure/goog/debug/entrypointregistry.js", "../closure-library/closure/goog/debug/errorcontext.js", "../closure-library/closure/goog/debug/debug.js", "../closure-library/closure/goog/events/browserfeature.js", "../closure-library/closure/goog/disposable/idisposable.js", "../closure-library/closure/goog/disposable/disposable.js", "../closure-library/closure/goog/events/eventid.js", "../closure-library/closure/goog/events/event.js", "../closure-library/closure/goog/events/eventtype.js", "../closure-library/closure/goog/events/browserevent.js", "../closure-library/closure/goog/events/listenable.js", "../closure-library/closure/goog/events/listener.js", "../closure-library/closure/goog/events/listenermap.js", "../closure-library/closure/goog/events/events.js", "core/scrollbar.js", "../closure-library/closure/goog/promise/thenable.js", "../closure-library/closure/goog/async/freelist.js", "../closure-library/closure/goog/async/workqueue.js", "../closure-library/closure/goog/functions/functions.js", "../closure-library/closure/goog/async/nexttick.js", "../closure-library/closure/goog/async/run.js", "../closure-library/closure/goog/promise/resolver.js", "../closure-library/closure/goog/promise/promise.js", "../closure-library/closure/goog/events/eventtarget.js", "../closure-library/closure/goog/timer/timer.js", "core/trashcan.js", "core/xml.js", "core/zoom_controls.js", "core/workspace_svg.js", "core/mutator.js", "core/warning.js", "core/block.js", "../closure-library/closure/goog/events/eventhandler.js", "../closure-library/closure/goog/ui/idgenerator.js", "../closure-library/closure/goog/ui/component.js", "../closure-library/closure/goog/a11y/aria/roles.js", "../closure-library/closure/goog/a11y/aria/attributes.js", "../closure-library/closure/goog/a11y/aria/datatables.js", "../closure-library/closure/goog/a11y/aria/aria.js", "../closure-library/closure/goog/events/keycodes.js", "../closure-library/closure/goog/events/keyhandler.js", "../closure-library/closure/goog/dom/classlist.js", "../closure-library/closure/goog/ui/registry.js", "../closure-library/closure/goog/ui/containerrenderer.js", "../closure-library/closure/goog/ui/controlcontent.js", "../closure-library/closure/goog/ui/controlrenderer.js", "../closure-library/closure/goog/ui/control.js", "../closure-library/closure/goog/ui/container.js", "../closure-library/closure/goog/ui/menuheaderrenderer.js", "../closure-library/closure/goog/ui/menuheader.js", "../closure-library/closure/goog/ui/menuitemrenderer.js", "../closure-library/closure/goog/ui/menuitem.js", "../closure-library/closure/goog/ui/menuseparatorrenderer.js", "../closure-library/closure/goog/ui/separator.js", "../closure-library/closure/goog/ui/menurenderer.js", "../closure-library/closure/goog/ui/menuseparator.js", "../closure-library/closure/goog/ui/menu.js", "core/contextmenu.js", "core/rendered_connection.js", "core/block_svg.js", "core/block_render_svg.js", "core/events.js", "core/msg.js", "core/field_textinput.js", "core/field_angle.js", "core/field_checkbox.js", "../closure-library/closure/goog/color/names.js", "../closure-library/closure/goog/color/color.js", "../closure-library/closure/goog/iter/iter.js", "../closure-library/closure/goog/dom/tagiterator.js", "../closure-library/closure/goog/dom/nodeiterator.js", "../closure-library/closure/goog/ui/paletterenderer.js", "../closure-library/closure/goog/ui/selectionmodel.js", "../closure-library/closure/goog/ui/palette.js", "../closure-library/closure/goog/ui/colorpalette.js", "../closure-library/closure/goog/ui/colorpicker.js", "core/field_colour.js", "core/field_dropdown.js", "core/instances.js", "core/utils.js", "core/field_instance.js", "core/field_image.js", "core/field_number.js", "core/variables.js", "core/field_variable.js", "core/generator.js", "core/names.js", "core/procedures.js", "core/flyout.js", "../closure-library/closure/goog/events/focushandler.js", "../closure-library/closure/goog/debug/logrecord.js", "../closure-library/closure/goog/debug/logbuffer.js", "../closure-library/closure/goog/debug/logger.js", "../closure-library/closure/goog/log/log.js", "../closure-library/closure/goog/string/stringbuffer.js", "../closure-library/closure/goog/ui/tree/basenode.js", "../closure-library/closure/goog/ui/tree/treenode.js", "../closure-library/closure/goog/structs/structs.js", "../closure-library/closure/goog/structs/trie.js", "../closure-library/closure/goog/ui/tree/typeahead.js", "../closure-library/closure/goog/ui/tree/treecontrol.js", "core/toolbox.js", "core/css.js", "core/widgetdiv.js", "core/constants.js", "core/inject.js", "core/blockly.js", "c_core/type.js", "c_core/types.js", "c_core/static_typing.js", "c_core/macro.js", "c_core/field_macro.js", "c_core/c_blockly.js"] \ No newline at end of file diff --git a/core_build_list_generator.py b/core_build_list_generator.py new file mode 100644 index 0000000..5659564 --- /dev/null +++ b/core_build_list_generator.py @@ -0,0 +1,94 @@ +#!/usr/bin/python2.7 +# Generator core build list for further processing by nodejs +# This script generates core_build_list.json + + +import sys +if sys.version_info[0] != 2: + raise Exception("Blockly build only compatible with Python 2.x.\n" + "You are using: " + sys.version) + + +import json, os, threading + + +def import_path(fullpath): + """Import a file with full path specification. + Allows one to import from any directory, something __import__ does not do. + + Args: + fullpath: Path and filename of import. + + Returns: + An imported module. + """ + path, filename = os.path.split(fullpath) + filename, ext = os.path.splitext(filename) + sys.path.append(path) + module = __import__(filename) + reload(module) # Might be out of date. + del sys.path[-1] + return module + + + + + + + +class Gen_compressed(threading.Thread): + """Generate a json list file that contains all of Blockly's core, + its extension c_core, and all + required parts of Closure files + """ + def __init__(self, search_paths): + threading.Thread.__init__(self) + self.search_paths = search_paths + + def run(self): + self.gen_core_build_list() + + + + def gen_core_build_list(self): + target_filename = "core_build_list.json" + + # Read in all the source files. + filenames = calcdeps.CalculateDependencies(self.search_paths, + [os.path.join("core", "blockly.js"),os.path.join("c_core","c_blockly.js")]) + core_build_list_json_string=json.dumps(filenames) + with open(target_filename,'w') as f: + f.write(core_build_list_json_string) + f.close() + + + +if __name__ == "__main__": + try: + calcdeps = import_path(os.path.join( + os.path.pardir, "closure-library", "closure", "bin", "calcdeps.py")) + except ImportError: + if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")): + # Dir got renamed when Closure moved from Google Code to GitHub in 2014. + print("Error: Closure directory needs to be renamed from" + "'closure-library-read-only' to 'closure-library'.\n" + "Please rename this directory.") + elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")): + # When Closure is installed by npm, it is named "google-closure-library". + #calcdeps = import_path(os.path.join( + # os.path.pardir, "google-closure-library", "closure", "bin", "calcdeps.py")) + print("Error: Closure directory needs to be renamed from" + "'google-closure-library' to 'closure-library'.\n" + "Please rename this directory.") + else: + print("""Error: Closure not found. Read this: +developers.google.com/blockly/guides/modify/web/closure""") + sys.exit(1) + + core_search_paths = calcdeps.ExpandDirectories( + ["core","c_core",os.path.join(os.path.pardir, "closure-library")]) + core_search_paths.sort() # Deterministic build. + + Gen_compressed(core_search_paths).start() + + diff --git a/generators/mbed/stepper.js b/generators/mbed/stepper.js deleted file mode 100644 index 31a131e..0000000 --- a/generators/mbed/stepper.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @license Licensed under the Apache License, Version 2.0 (the "License"): - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -/** - * @fileoverview mbed code generator for the Stepper library blocks. - * The mbed Stepper library docs: http://mbed.cc/en/Reference/Stepper - */ -'use strict'; - -goog.provide('Blockly.mbed.stepper'); - -goog.require('Blockly.mbed'); - - -/** - * Code generator for the stepper generator configuration. Nothing is added - * to the 'loop()' function. Sets the pins (X and Y), steps per revolution (Z), - * speed(A) and instance name (B). - * mbed code: #include - * Stepper B(Z, X, Y); - * setup() { B.setSpeed(A); } - * @param {!Blockly.Block} block Block to generate the code from. - * @return {string} Empty string as no code goes into 'loop()'. - */ -Blockly.mbed['stepper_config'] = function(block) { - var pin1 = block.getFieldValue('STEPPER_PIN1'); - var pin2 = block.getFieldValue('STEPPER_PIN2'); - var pinType = Blockly.mbed.PinTypes.STEPPER; - var stepperName = block.getFieldValue('STEPPER_NAME'); - var stepperSteps = Blockly.mbed.valueToCode(block, 'STEPPER_STEPS', - Blockly.mbed.ORDER_ATOMIC) || '360'; - var stepperSpeed = Blockly.mbed.valueToCode(block, 'STEPPER_SPEED', - Blockly.mbed.ORDER_ATOMIC) || '90'; - - //stepper is a variable containing the used pins - Blockly.mbed.addVariable(stepperName, - 'int ' + stepperName + '[2] = {' + pin1 + ', ' + pin2 + '};', true); - stepperName = 'stepper_' + stepperName - - Blockly.mbed.reservePin(block, pin1, pinType, 'Stepper'); - Blockly.mbed.reservePin(block, pin2, pinType, 'Stepper'); - - Blockly.mbed.addInclude('stepper', '#include '); - - var globalCode = 'Stepper ' + stepperName + '(' + stepperSteps + ', ' + - pin1 + ', ' + pin2 + ');'; - Blockly.mbed.addDeclaration(stepperName, globalCode); - - var setupCode = stepperName + '.setSpeed(' + stepperSpeed + ');'; - Blockly.mbed.addSetup(stepperName, setupCode, true); - - return ''; -}; - -/** - * Code generator for moving the stepper instance (X) a number of steps (Y). - * Library info in the setHelpUrl link. - * This block requires the stepper_config block to be present. - * mbed code: loop { X.steps(Y) } - * @param {!Blockly.Block} block Block to generate the code from. - * @return {array} Completed code with order of operation. - */ -Blockly.mbed['stepper_step'] = function(block) { - var stepperInstanceName = 'stepper_' + block.getFieldValue('STEPPER_NAME'); - var stepperSteps = Blockly.mbed.valueToCode(block, 'STEPPER_STEPS', - Blockly.mbed.ORDER_ATOMIC) || '0'; - var code = stepperInstanceName + '.step(' + stepperSteps + ');\n'; - return code; -}; diff --git a/index.html b/index.html index d59efca..7c730ba 100644 --- a/index.html +++ b/index.html @@ -112,7 +112,7 @@ @@ -358,27 +358,6 @@

BlocklyMbed - - 1 - 2 - - - 100 - - - - - 10 - - - - - - - 10 - - - diff --git a/index_debug.html b/index_debug.html index e6cdaf8..29c3491 100644 --- a/index_debug.html +++ b/index_debug.html @@ -20,7 +20,6 @@ - @@ -38,7 +37,6 @@ - @@ -166,21 +164,12 @@

- -
-

BlocklyMbed +

BlocklyMbed > web-based visual programming editor for mbed

Blocks  mbed   XML - @@ -404,27 +393,6 @@

- - 1 - 2 - - - 100 - - - - - 10 - - - - - - - 10 - - - diff --git a/index_utility.js b/index_utility.js index 336c630..21035aa 100644 --- a/index_utility.js +++ b/index_utility.js @@ -5,7 +5,7 @@ * {@link http://www.apache.org/licenses/LICENSE-2.0} * @fileoverview Manages the dom interaction of home page. * @version 1.0 - * Last modified on 23/02/2018 + * Last modified on 11/03/2018 */ @@ -133,7 +133,7 @@ function tabClick(clickedName) { } if (xmlDom) { Blockly.mainWorkspace.clear(); - Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xmlDom); + Blockly.Xml.domToWorkspace(xmlDom,Blockly.mainWorkspace); } } @@ -204,7 +204,7 @@ function renderContent() { function restore_blocks() { if ('localStorage' in window && window.localStorage.mbed) { var xml = Blockly.Xml.textToDom(window.localStorage.mbed); - Blockly.Xml.domToWorkspace(Blockly.mainWorkspace, xml); + Blockly.Xml.domToWorkspace(xml,Blockly.mainWorkspace); } } /** diff --git a/msg/js/en.js b/msg/js/en.js index 440a9ac..f8263b0 100644 --- a/msg/js/en.js +++ b/msg/js/en.js @@ -6,513 +6,425 @@ goog.provide('Blockly.Msg.en'); goog.require('Blockly.Msg'); -Blockly.Msg.ADD_COMMENT = "Add Comment"; -Blockly.Msg.ARD_ANALOGREAD = "read analog pin#"; -Blockly.Msg.ARD_ANALOGREAD_TIP = "Return value between 0 and 1024"; -Blockly.Msg.ARD_ANALOGWRITE = "set analog pin#"; -Blockly.Msg.ARD_ANALOGWRITE_TIP = "Write analog value between 0 and 255 to a specific PWM Port"; -Blockly.Msg.ARD_BUILTIN_LED = "set built-in LED"; -Blockly.Msg.ARD_BUILTIN_LED_TIP = "Light on or off for the built-in LED of the Arduino"; -Blockly.Msg.ARD_COMPONENT_WARN1 = "A %1 configuration block with the same %2 name must be added to use this block!"; -Blockly.Msg.ARD_DEFINE = "Define"; -Blockly.Msg.ARD_DIGITALREAD = "read digital pin#"; -Blockly.Msg.ARD_DIGITALREAD_TIP = "Read digital value on a pin: HIGH or LOW"; -Blockly.Msg.ARD_DIGITALWRITE = "set digitial pin#"; -Blockly.Msg.ARD_DIGITALWRITE_TIP = "Write digital value HIGH or LOW to a specific Port"; -Blockly.Msg.ARD_FUN_RUN_LOOP = "Arduino loop forever:"; -Blockly.Msg.ARD_FUN_RUN_SETUP = "Arduino run first:"; -Blockly.Msg.ARD_FUN_RUN_TIP = "Defines the Arduino setup() and loop() functions."; -Blockly.Msg.ARD_HIGH = "HIGH"; -Blockly.Msg.ARD_HIGHLOW_TIP = "Set a pin state logic High or Low."; -Blockly.Msg.ARD_LOW = "LOW"; -Blockly.Msg.ARD_MAP = "Map"; -Blockly.Msg.ARD_MAP_TIP = "Re-maps a number from [0-1024] to another."; -Blockly.Msg.ARD_MAP_VAL = "value to [0-"; -Blockly.Msg.ARD_NOTONE = "Turn off tone on pin #"; -Blockly.Msg.ARD_NOTONE_PIN = "No tone PIN#"; -Blockly.Msg.ARD_NOTONE_PIN_TIP = "Stop generating a tone on a pin"; -Blockly.Msg.ARD_NOTONE_TIP = "Turns the tone off on the selected pin"; -Blockly.Msg.ARD_PIN_WARN1 = "Pin %1 is needed for %2 as pin %3. Already used as %4."; -Blockly.Msg.ARD_PULSEON = "pulse on pin #"; -Blockly.Msg.ARD_PULSEREAD = "Read"; -Blockly.Msg.ARD_PULSETIMEOUT = "timeout after"; -Blockly.Msg.ARD_PULSETIMEOUT_MS = ""; -Blockly.Msg.ARD_PULSETIMEOUT_TIP = "Measures the duration of a pulse on the selected pin, if it is within the timeout."; -Blockly.Msg.ARD_PULSE_TIP = "Measures the duration of a pulse on the selected pin."; -Blockly.Msg.ARD_SERIAL_BPS = "bps"; -Blockly.Msg.ARD_SERIAL_PRINT = "print"; -Blockly.Msg.ARD_SERIAL_PRINT_NEWLINE = "add new line"; -Blockly.Msg.ARD_SERIAL_PRINT_TIP = "Prints data to the console/serial port as human-readable ASCII text."; -Blockly.Msg.ARD_SERIAL_PRINT_WARN = "A setup block for %1 must be added to the workspace to use this block!"; -Blockly.Msg.ARD_SERIAL_SETUP = "Setup"; -Blockly.Msg.ARD_SERIAL_SETUP_TIP = "Selects the speed for a specific Serial peripheral"; -Blockly.Msg.ARD_SERIAL_SPEED = ": speed to"; -Blockly.Msg.ARD_SERVO_READ = "read SERVO from PIN#"; -Blockly.Msg.ARD_SERVO_READ_TIP = "Read a Servo angle"; -Blockly.Msg.ARD_SERVO_WRITE = "set SERVO from Pin"; -Blockly.Msg.ARD_SERVO_WRITE_DEG_180 = "Degrees (0~180)"; -Blockly.Msg.ARD_SERVO_WRITE_TIP = "Set a Servo to an specified angle"; -Blockly.Msg.ARD_SERVO_WRITE_TO = "to"; -Blockly.Msg.ARD_SETTONE = "Set tone on pin #"; -Blockly.Msg.ARD_SPI_SETUP = "Setup"; -Blockly.Msg.ARD_SPI_SETUP_CONF = "configuration:"; -Blockly.Msg.ARD_SPI_SETUP_DIVIDE = "clock divide"; -Blockly.Msg.ARD_SPI_SETUP_LSBFIRST = "LSBFIRST"; -Blockly.Msg.ARD_SPI_SETUP_MODE = "SPI mode (idle - edge)"; -Blockly.Msg.ARD_SPI_SETUP_MODE0 = "0 (Low - Falling)"; -Blockly.Msg.ARD_SPI_SETUP_MODE1 = "1 (Low - Rising)"; -Blockly.Msg.ARD_SPI_SETUP_MODE2 = "2 (High - Falling)"; -Blockly.Msg.ARD_SPI_SETUP_MODE3 = "3 (High - Rising)"; -Blockly.Msg.ARD_SPI_SETUP_MSBFIRST = "MSBFIRST"; -Blockly.Msg.ARD_SPI_SETUP_SHIFT = "data shift"; -Blockly.Msg.ARD_SPI_SETUP_TIP = "Configures the SPI peripheral."; -Blockly.Msg.ARD_SPI_TRANSRETURN_TIP = "Send a SPI message to an specified slave device and get data back."; -Blockly.Msg.ARD_SPI_TRANS_NONE = "none"; -Blockly.Msg.ARD_SPI_TRANS_SLAVE = "to slave pin"; -Blockly.Msg.ARD_SPI_TRANS_TIP = "Send a SPI message to an specified slave device."; -Blockly.Msg.ARD_SPI_TRANS_VAL = "transfer"; -Blockly.Msg.ARD_SPI_TRANS_WARN1 = "A setup block for %1 must be added to the workspace to use this block!"; -Blockly.Msg.ARD_SPI_TRANS_WARN2 = "Old pin value %1 is no longer available."; -Blockly.Msg.ARD_STEPPER_COMPONENT = "stepper"; -Blockly.Msg.ARD_STEPPER_DEFAULT_NAME = "MyStepper"; -Blockly.Msg.ARD_STEPPER_MOTOR = "stepper motor:"; -Blockly.Msg.ARD_STEPPER_PIN1 = "pin1#"; -Blockly.Msg.ARD_STEPPER_PIN2 = "pin2#"; -Blockly.Msg.ARD_STEPPER_REVOLVS = "how many steps per revolution"; -Blockly.Msg.ARD_STEPPER_SETUP = "Setup stepper motor"; -Blockly.Msg.ARD_STEPPER_SETUP_TIP = "Configures a stepper motor pinout and other settings."; -Blockly.Msg.ARD_STEPPER_SPEED = "set speed (rpm) to"; -Blockly.Msg.ARD_STEPPER_STEP = "move stepper"; -Blockly.Msg.ARD_STEPPER_STEPS = "steps"; -Blockly.Msg.ARD_STEPPER_STEP_TIP = "Turns the stepper motor a specific number of steps."; -Blockly.Msg.ARD_TIME_DELAY = "wait"; -Blockly.Msg.ARD_TIME_DELAY_MICROS = "microseconds"; -Blockly.Msg.ARD_TIME_DELAY_MICRO_TIP = "Wait specific time in microseconds"; -Blockly.Msg.ARD_TIME_DELAY_TIP = "Wait specific time in milliseconds"; -Blockly.Msg.ARD_TIME_INF = "wait forever (end program)"; -Blockly.Msg.ARD_TIME_INF_TIP = "Wait indefinitely, stopping the program."; -Blockly.Msg.ARD_TIME_MICROS = "current elapsed Time (microseconds)"; -Blockly.Msg.ARD_TIME_MICROS_TIP = "Returns the number of microseconds since the Arduino board began running the current program. Has to be stored in a positive long integer"; -Blockly.Msg.ARD_TIME_MILLIS = "current elapsed Time (milliseconds)"; -Blockly.Msg.ARD_TIME_MILLIS_TIP = "Returns the number of milliseconds since the Arduino board began running the current program. Has to be stored in a positive long integer"; -Blockly.Msg.ARD_TIME_MS = "milliseconds"; -Blockly.Msg.ARD_TONEFREQ = "at frequency"; -Blockly.Msg.ARD_TONE_FREQ = "frequency"; -Blockly.Msg.ARD_TONE_PIN = "Tone PIN#"; -Blockly.Msg.ARD_TONE_PIN_TIP = "Generate audio tones on a pin"; -Blockly.Msg.ARD_TONE_TIP = "Sets tone on pin to specified frequency within range 31 - 65535"; -Blockly.Msg.ARD_TONE_WARNING = "Frequency must be in range 31 - 65535"; -Blockly.Msg.ARD_TYPE_ARRAY = "Array"; -Blockly.Msg.ARD_TYPE_BOOL = "Boolean"; -Blockly.Msg.ARD_TYPE_CHAR = "Character"; -Blockly.Msg.ARD_TYPE_CHILDBLOCKMISSING = "ChildBlockMissing"; -Blockly.Msg.ARD_TYPE_DECIMAL = "Decimal"; -Blockly.Msg.ARD_TYPE_LONG = "Large Number"; -Blockly.Msg.ARD_TYPE_NULL = "Null"; -Blockly.Msg.ARD_TYPE_NUMBER = "Number"; -Blockly.Msg.ARD_TYPE_SHORT = "Short Number"; -Blockly.Msg.ARD_TYPE_TEXT = "Text"; -Blockly.Msg.ARD_TYPE_UNDEF = "Undefined"; -Blockly.Msg.ARD_VAR_AS = "as"; -Blockly.Msg.ARD_VAR_AS_TIP = "Sets a value to a specific type"; -Blockly.Msg.ARD_WRITE_TO = "to"; -Blockly.Msg.AUTH = "Please authorize this app to enable your work to be saved and to allow it to be shared by you."; -Blockly.Msg.CHANGE_VALUE_TITLE = "Change value:"; -Blockly.Msg.CHAT = "Chat with your collaborator by typing in this box!"; -Blockly.Msg.CLEAN_UP = "Clean up Blocks"; -Blockly.Msg.COLLAPSE_ALL = "Collapse Blocks"; -Blockly.Msg.COLLAPSE_BLOCK = "Collapse Block"; -Blockly.Msg.COLOUR_BLEND_COLOUR1 = "colour 1"; -Blockly.Msg.COLOUR_BLEND_COLOUR2 = "colour 2"; -Blockly.Msg.COLOUR_BLEND_HELPURL = "http://meyerweb.com/eric/tools/color-blend/"; -Blockly.Msg.COLOUR_BLEND_RATIO = "ratio"; -Blockly.Msg.COLOUR_BLEND_TITLE = "blend"; -Blockly.Msg.COLOUR_BLEND_TOOLTIP = "Blends two colours together with a given ratio (0.0 - 1.0)."; -Blockly.Msg.COLOUR_PICKER_HELPURL = "https://en.wikipedia.org/wiki/Color"; -Blockly.Msg.COLOUR_PICKER_TOOLTIP = "Choose a colour from the palette."; -Blockly.Msg.COLOUR_RANDOM_HELPURL = "http://randomcolour.com"; -Blockly.Msg.COLOUR_RANDOM_TITLE = "random colour"; -Blockly.Msg.COLOUR_RANDOM_TOOLTIP = "Choose a colour at random."; -Blockly.Msg.COLOUR_RGB_BLUE = "blue"; -Blockly.Msg.COLOUR_RGB_GREEN = "green"; -Blockly.Msg.COLOUR_RGB_HELPURL = "http://www.december.com/html/spec/colorper.html"; -Blockly.Msg.COLOUR_RGB_RED = "red"; -Blockly.Msg.COLOUR_RGB_TITLE = "colour with"; -Blockly.Msg.COLOUR_RGB_TOOLTIP = "Create a colour with the specified amount of red, green, and blue. All values must be between 0 and 100."; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL = "https://github.com/google/blockly/wiki/Loops#loop-termination-blocks"; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK = "break out of loop"; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE = "continue with next iteration of loop"; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK = "Break out of the containing loop."; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE = "Skip the rest of this loop, and continue with the next iteration."; -Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING = "Warning: This block may only be used within a loop."; -Blockly.Msg.CONTROLS_FOREACH_HELPURL = "https://github.com/google/blockly/wiki/Loops#for-each"; -Blockly.Msg.CONTROLS_FOREACH_TITLE = "for each item %1 in list %2"; -Blockly.Msg.CONTROLS_FOREACH_TOOLTIP = "For each item in a list, set the variable '%1' to the item, and then do some statements."; -Blockly.Msg.CONTROLS_FOR_HELPURL = "https://github.com/google/blockly/wiki/Loops#count-with"; -Blockly.Msg.CONTROLS_FOR_TITLE = "count with %1 from %2 to %3 by %4"; -Blockly.Msg.CONTROLS_FOR_TOOLTIP = "Have the variable '%1' take on the values from the start number to the end number, counting by the specified interval, and do the specified blocks."; -Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP = "Add a condition to the if block."; -Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = "Add a final, catch-all condition to the if block."; -Blockly.Msg.CONTROLS_IF_HELPURL = "https://github.com/google/blockly/wiki/IfElse"; -Blockly.Msg.CONTROLS_IF_IF_TOOLTIP = "Add, remove, or reorder sections to reconfigure this if block."; -Blockly.Msg.CONTROLS_IF_MSG_ELSE = "else"; -Blockly.Msg.CONTROLS_IF_MSG_ELSEIF = "else if"; -Blockly.Msg.CONTROLS_IF_MSG_IF = "if"; -Blockly.Msg.CONTROLS_IF_TOOLTIP_1 = "If a value is true, then do some statements."; -Blockly.Msg.CONTROLS_IF_TOOLTIP_2 = "If a value is true, then do the first block of statements. Otherwise, do the second block of statements."; -Blockly.Msg.CONTROLS_IF_TOOLTIP_3 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements."; -Blockly.Msg.CONTROLS_IF_TOOLTIP_4 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements. If none of the values are true, do the last block of statements."; -Blockly.Msg.CONTROLS_REPEAT_HELPURL = "https://en.wikipedia.org/wiki/For_loop"; -Blockly.Msg.CONTROLS_REPEAT_INPUT_DO = "do"; -Blockly.Msg.CONTROLS_REPEAT_TITLE = "repeat %1 times"; -Blockly.Msg.CONTROLS_REPEAT_TOOLTIP = "Do some statements several times."; -Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL = "https://github.com/google/blockly/wiki/Loops#repeat"; -Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL = "repeat until"; -Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE = "repeat while"; -Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = "While a value is false, then do some statements."; -Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = "While a value is true, then do some statements."; -Blockly.Msg.DELETE_ALL_BLOCKS = "Delete all %1 blocks?"; -Blockly.Msg.DELETE_BLOCK = "Delete Block"; -Blockly.Msg.DELETE_X_BLOCKS = "Delete %1 Blocks"; -Blockly.Msg.DISABLE_BLOCK = "Disable Block"; -Blockly.Msg.DUPLICATE_BLOCK = "Duplicate"; -Blockly.Msg.ENABLE_BLOCK = "Enable Block"; -Blockly.Msg.EXPAND_ALL = "Expand Blocks"; -Blockly.Msg.EXPAND_BLOCK = "Expand Block"; -Blockly.Msg.EXTERNAL_INPUTS = "External Inputs"; -Blockly.Msg.HELP = "Help"; -Blockly.Msg.INLINE_INPUTS = "Inline Inputs"; -Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-empty-list"; -Blockly.Msg.LISTS_CREATE_EMPTY_TITLE = "create empty list"; -Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP = "Returns a list, of length 0, containing no data records"; -Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD = "list"; -Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP = "Add, remove, or reorder sections to reconfigure this list block."; -Blockly.Msg.LISTS_CREATE_WITH_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; -Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH = "create list with"; -Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP = "Add an item to the list."; -Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP = "Create a list with any number of items."; -Blockly.Msg.LISTS_GET_INDEX_FIRST = "first"; -Blockly.Msg.LISTS_GET_INDEX_FROM_END = "# from end"; -Blockly.Msg.LISTS_GET_INDEX_FROM_START = "#"; -Blockly.Msg.LISTS_GET_INDEX_GET = "get"; -Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE = "get and remove"; -Blockly.Msg.LISTS_GET_INDEX_LAST = "last"; -Blockly.Msg.LISTS_GET_INDEX_RANDOM = "random"; -Blockly.Msg.LISTS_GET_INDEX_REMOVE = "remove"; -Blockly.Msg.LISTS_GET_INDEX_TAIL = ""; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST = "Returns the first item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM_END = "Returns the item at the specified position in a list. #1 is the last item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM_START = "Returns the item at the specified position in a list. #1 is the first item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST = "Returns the last item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM = "Returns a random item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST = "Removes and returns the first item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_END = "Removes and returns the item at the specified position in a list. #1 is the last item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_START = "Removes and returns the item at the specified position in a list. #1 is the first item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST = "Removes and returns the last item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM = "Removes and returns a random item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST = "Removes the first item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_END = "Removes the item at the specified position in a list. #1 is the last item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_START = "Removes the item at the specified position in a list. #1 is the first item."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST = "Removes the last item in a list."; -Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM = "Removes a random item in a list."; -Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END = "to # from end"; -Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START = "to #"; -Blockly.Msg.LISTS_GET_SUBLIST_END_LAST = "to last"; -Blockly.Msg.LISTS_GET_SUBLIST_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-a-sublist"; -Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST = "get sub-list from first"; -Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END = "get sub-list from # from end"; -Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START = "get sub-list from #"; -Blockly.Msg.LISTS_GET_SUBLIST_TAIL = ""; -Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP = "Creates a copy of the specified portion of a list."; -Blockly.Msg.LISTS_INDEX_OF_FIRST = "find first occurrence of item"; -Blockly.Msg.LISTS_INDEX_OF_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list"; -Blockly.Msg.LISTS_INDEX_OF_LAST = "find last occurrence of item"; -Blockly.Msg.LISTS_INDEX_OF_TOOLTIP = "Returns the index of the first/last occurrence of the item in the list. Returns 0 if item is not found."; -Blockly.Msg.LISTS_INLIST = "in list"; -Blockly.Msg.LISTS_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#is-empty"; -Blockly.Msg.LISTS_ISEMPTY_TITLE = "%1 is empty"; -Blockly.Msg.LISTS_ISEMPTY_TOOLTIP = "Returns true if the list is empty."; -Blockly.Msg.LISTS_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Lists#length-of"; -Blockly.Msg.LISTS_LENGTH_TITLE = "length of %1"; -Blockly.Msg.LISTS_LENGTH_TOOLTIP = "Returns the length of a list."; -Blockly.Msg.LISTS_REPEAT_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; -Blockly.Msg.LISTS_REPEAT_TITLE = "create list with item %1 repeated %2 times"; -Blockly.Msg.LISTS_REPEAT_TOOLTIP = "Creates a list consisting of the given value repeated the specified number of times."; -Blockly.Msg.LISTS_SET_INDEX_HELPURL = "https://github.com/google/blockly/wiki/Lists#in-list--set"; -Blockly.Msg.LISTS_SET_INDEX_INPUT_TO = "as"; -Blockly.Msg.LISTS_SET_INDEX_INSERT = "insert at"; -Blockly.Msg.LISTS_SET_INDEX_SET = "set"; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST = "Inserts the item at the start of a list."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM_END = "Inserts the item at the specified position in a list. #1 is the last item."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM_START = "Inserts the item at the specified position in a list. #1 is the first item."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST = "Append the item to the end of a list."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM = "Inserts the item randomly in a list."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST = "Sets the first item in a list."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM_END = "Sets the item at the specified position in a list. #1 is the last item."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM_START = "Sets the item at the specified position in a list. #1 is the first item."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST = "Sets the last item in a list."; -Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM = "Sets a random item in a list."; -Blockly.Msg.LISTS_SORT_HELPURL = "https://github.com/google/blockly/wiki/Lists#sorting-a-list"; -Blockly.Msg.LISTS_SORT_ORDER_ASCENDING = "ascending"; -Blockly.Msg.LISTS_SORT_ORDER_DESCENDING = "descending"; -Blockly.Msg.LISTS_SORT_TITLE = "sort %1 %2 %3"; -Blockly.Msg.LISTS_SORT_TOOLTIP = "Sort a copy of a list."; -Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE = "alphabetic, ignore case"; -Blockly.Msg.LISTS_SORT_TYPE_NUMERIC = "numeric"; -Blockly.Msg.LISTS_SORT_TYPE_TEXT = "alphabetic"; -Blockly.Msg.LISTS_SPLIT_HELPURL = "https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists"; -Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT = "make list from text"; -Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST = "make text from list"; -Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN = "Join a list of texts into one text, separated by a delimiter."; -Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT = "Split text into a list of texts, breaking at each delimiter."; -Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER = "with delimiter"; -Blockly.Msg.LOGIC_BOOLEAN_FALSE = "false"; -Blockly.Msg.LOGIC_BOOLEAN_HELPURL = "https://github.com/google/blockly/wiki/Logic#values"; -Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP = "Returns either true or false."; -Blockly.Msg.LOGIC_BOOLEAN_TRUE = "true"; -Blockly.Msg.LOGIC_COMPARE_HELPURL = "https://en.wikipedia.org/wiki/Inequality_(mathematics)"; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ = "Return true if both inputs equal each other."; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT = "Return true if the first input is greater than the second input."; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE = "Return true if the first input is greater than or equal to the second input."; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT = "Return true if the first input is smaller than the second input."; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE = "Return true if the first input is smaller than or equal to the second input."; -Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ = "Return true if both inputs are not equal to each other."; -Blockly.Msg.LOGIC_NEGATE_HELPURL = "https://github.com/google/blockly/wiki/Logic#not"; -Blockly.Msg.LOGIC_NEGATE_TITLE = "not %1"; -Blockly.Msg.LOGIC_NEGATE_TOOLTIP = "Returns true if the input is false. Returns false if the input is true."; -Blockly.Msg.LOGIC_NULL = "null"; -Blockly.Msg.LOGIC_NULL_HELPURL = "https://en.wikipedia.org/wiki/Nullable_type"; -Blockly.Msg.LOGIC_NULL_TOOLTIP = "Returns null."; -Blockly.Msg.LOGIC_OPERATION_AND = "and"; -Blockly.Msg.LOGIC_OPERATION_HELPURL = "https://github.com/google/blockly/wiki/Logic#logical-operations"; -Blockly.Msg.LOGIC_OPERATION_OR = "or"; -Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND = "Return true if both inputs are true."; -Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR = "Return true if at least one of the inputs is true."; -Blockly.Msg.LOGIC_TERNARY_CONDITION = "test"; -Blockly.Msg.LOGIC_TERNARY_HELPURL = "https://en.wikipedia.org/wiki/%3F:"; -Blockly.Msg.LOGIC_TERNARY_IF_FALSE = "if false"; -Blockly.Msg.LOGIC_TERNARY_IF_TRUE = "if true"; -Blockly.Msg.LOGIC_TERNARY_TOOLTIP = "Check the condition in 'test'. If the condition is true, returns the 'if true' value; otherwise returns the 'if false' value."; -Blockly.Msg.MATH_ADDITION_SYMBOL = "+"; -Blockly.Msg.MATH_ARITHMETIC_HELPURL = "https://en.wikipedia.org/wiki/Arithmetic"; -Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD = "Return the sum of the two numbers."; -Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE = "Return the quotient of the two numbers."; -Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS = "Return the difference of the two numbers."; -Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY = "Return the product of the two numbers."; -Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER = "Return the first number raised to the power of the second number."; -Blockly.Msg.MATH_CHANGE_HELPURL = "https://en.wikipedia.org/wiki/Programming_idiom#Incrementing_a_counter"; -Blockly.Msg.MATH_CHANGE_TITLE = "change %1 by %2"; -Blockly.Msg.MATH_CHANGE_TOOLTIP = "Add a number to variable '%1'."; -Blockly.Msg.MATH_CONSTANT_HELPURL = "https://en.wikipedia.org/wiki/Mathematical_constant"; -Blockly.Msg.MATH_CONSTANT_TOOLTIP = "Return one of the common constants: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…), or ∞ (infinity)."; -Blockly.Msg.MATH_CONSTRAIN_HELPURL = "https://en.wikipedia.org/wiki/Clamping_%28graphics%29"; -Blockly.Msg.MATH_CONSTRAIN_TITLE = "constrain %1 low %2 high %3"; -Blockly.Msg.MATH_CONSTRAIN_TOOLTIP = "Constrain a number to be between the specified limits (inclusive)."; -Blockly.Msg.MATH_DIVISION_SYMBOL = "÷"; -Blockly.Msg.MATH_IS_DIVISIBLE_BY = "is divisible by"; -Blockly.Msg.MATH_IS_EVEN = "is even"; -Blockly.Msg.MATH_IS_NEGATIVE = "is negative"; -Blockly.Msg.MATH_IS_ODD = "is odd"; -Blockly.Msg.MATH_IS_POSITIVE = "is positive"; -Blockly.Msg.MATH_IS_PRIME = "is prime"; -Blockly.Msg.MATH_IS_TOOLTIP = "Check if a number is an even, odd, prime, whole, positive, negative, or if it is divisible by certain number. Returns true or false."; -Blockly.Msg.MATH_IS_WHOLE = "is whole"; -Blockly.Msg.MATH_MODULO_HELPURL = "https://en.wikipedia.org/wiki/Modulo_operation"; -Blockly.Msg.MATH_MODULO_TITLE = "remainder of %1 ÷ %2"; -Blockly.Msg.MATH_MODULO_TOOLTIP = "Return the remainder from dividing the two numbers."; -Blockly.Msg.MATH_MULTIPLICATION_SYMBOL = "×"; -Blockly.Msg.MATH_NUMBER_HELPURL = "https://en.wikipedia.org/wiki/Number"; -Blockly.Msg.MATH_NUMBER_TOOLTIP = "A number."; -Blockly.Msg.MATH_ONLIST_HELPURL = ""; -Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE = "average of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_MAX = "max of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN = "median of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_MIN = "min of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_MODE = "modes of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM = "random item of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV = "standard deviation of list"; -Blockly.Msg.MATH_ONLIST_OPERATOR_SUM = "sum of list"; -Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE = "Return the average (arithmetic mean) of the numeric values in the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX = "Return the largest number in the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN = "Return the median number in the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN = "Return the smallest number in the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE = "Return a list of the most common item(s) in the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM = "Return a random element from the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV = "Return the standard deviation of the list."; -Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM = "Return the sum of all the numbers in the list."; -Blockly.Msg.MATH_POWER_SYMBOL = "^"; -Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; -Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM = "random fraction"; -Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP = "Return a random fraction between 0.0 (inclusive) and 1.0 (exclusive)."; -Blockly.Msg.MATH_RANDOM_INT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; -Blockly.Msg.MATH_RANDOM_INT_TITLE = "random integer from %1 to %2"; -Blockly.Msg.MATH_RANDOM_INT_TOOLTIP = "Return a random integer between the two specified limits, inclusive."; -Blockly.Msg.MATH_ROUND_HELPURL = "https://en.wikipedia.org/wiki/Rounding"; -Blockly.Msg.MATH_ROUND_OPERATOR_ROUND = "round"; -Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN = "round down"; -Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP = "round up"; -Blockly.Msg.MATH_ROUND_TOOLTIP = "Round a number up or down."; -Blockly.Msg.MATH_SINGLE_HELPURL = "https://en.wikipedia.org/wiki/Square_root"; -Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE = "absolute"; -Blockly.Msg.MATH_SINGLE_OP_ROOT = "square root"; -Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS = "Return the absolute value of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP = "Return e to the power of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_LN = "Return the natural logarithm of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10 = "Return the base 10 logarithm of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG = "Return the negation of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10 = "Return 10 to the power of a number."; -Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT = "Return the square root of a number."; -Blockly.Msg.MATH_SUBTRACTION_SYMBOL = "-"; -Blockly.Msg.MATH_TRIG_ACOS = "acos"; -Blockly.Msg.MATH_TRIG_ASIN = "asin"; -Blockly.Msg.MATH_TRIG_ATAN = "atan"; -Blockly.Msg.MATH_TRIG_COS = "cos"; -Blockly.Msg.MATH_TRIG_HELPURL = "https://en.wikipedia.org/wiki/Trigonometric_functions"; -Blockly.Msg.MATH_TRIG_SIN = "sin"; -Blockly.Msg.MATH_TRIG_TAN = "tan"; -Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS = "Return the arccosine of a number."; -Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN = "Return the arcsine of a number."; -Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN = "Return the arctangent of a number."; -Blockly.Msg.MATH_TRIG_TOOLTIP_COS = "Return the cosine of a degree (not radian)."; -Blockly.Msg.MATH_TRIG_TOOLTIP_SIN = "Return the sine of a degree (not radian)."; -Blockly.Msg.MATH_TRIG_TOOLTIP_TAN = "Return the tangent of a degree (not radian)."; -Blockly.Msg.ME = "Me"; -Blockly.Msg.NEW_INSTANCE = "New instance..."; -Blockly.Msg.NEW_INSTANCE_TITLE = "New instance name:"; -Blockly.Msg.NEW_VARIABLE = "New variable..."; -Blockly.Msg.NEW_VARIABLE_TITLE = "New variable name:"; -Blockly.Msg.NEW_MACRO = "New macro..."; -Blockly.Msg.NEW_MACRO_TITLE = "New macro name:"; -Blockly.Msg.ORDINAL_NUMBER_SUFFIX = ""; -Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS = "allow statements"; -Blockly.Msg.PROCEDURES_BEFORE_PARAMS = "with:"; -Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; -Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP = "Run the user-defined function '%1'."; -Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; -Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP = "Run the user-defined function '%1' and use its output."; -Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS = "with:"; -Blockly.Msg.PROCEDURES_CREATE_DO = "Create '%1'"; -Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT = "Describe this function..."; -Blockly.Msg.PROCEDURES_DEFNORETURN_DO = ""; -Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; -Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE = "do something"; -Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE = "to"; -Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP = "Creates a function with no output."; -Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL = "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29"; -Blockly.Msg.PROCEDURES_DEFRETURN_RETURN = "return"; -Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP = "Creates a function with an output."; -Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING = "Warning: This function has duplicate parameters."; -Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF = "Highlight function definition"; -Blockly.Msg.PROCEDURES_IFRETURN_HELPURL = "http://c2.com/cgi/wiki?GuardClause"; -Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP = "If a value is true, then return a second value."; -Blockly.Msg.PROCEDURES_IFRETURN_WARNING = "Warning: This block may be used only within a function definition."; -Blockly.Msg.PROCEDURES_MUTATORARG_TITLE = "input name:"; -Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP = "Add an input to the function."; -Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE = "inputs"; -Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP = "Add, remove, or reorder inputs to this function."; -Blockly.Msg.REDO = "Redo"; -Blockly.Msg.REMOVE_COMMENT = "Remove Comment"; -Blockly.Msg.RENAME_INSTANCE = "Rename instance..."; -Blockly.Msg.RENAME_INSTANCE_TITLE = "Rename all '%1' instances to:"; -Blockly.Msg.RENAME_VARIABLE = "Rename variable..."; -Blockly.Msg.RENAME_MACRO = "Rename macro..."; -Blockly.Msg.RENAME_VARIABLE_TITLE = "Rename all '%1' variables to:"; -Blockly.Msg.RENAME_MACRO_TITLE = "Rename all '%1' macros to:"; -Blockly.Msg.TEXT_APPEND_APPENDTEXT = "append text"; -Blockly.Msg.TEXT_APPEND_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; -Blockly.Msg.TEXT_APPEND_TO = "to"; -Blockly.Msg.TEXT_APPEND_TOOLTIP = "Append some text to variable '%1'."; -Blockly.Msg.TEXT_CHANGECASE_HELPURL = "https://github.com/google/blockly/wiki/Text#adjusting-text-case"; -Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE = "to lower case"; -Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE = "to Title Case"; -Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE = "to UPPER CASE"; -Blockly.Msg.TEXT_CHANGECASE_TOOLTIP = "Return a copy of the text in a different case."; -Blockly.Msg.TEXT_CHARAT_FIRST = "get first letter"; -Blockly.Msg.TEXT_CHARAT_FROM_END = "get letter # from end"; -Blockly.Msg.TEXT_CHARAT_FROM_START = "get letter #"; -Blockly.Msg.TEXT_CHARAT_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-text"; -Blockly.Msg.TEXT_CHARAT_INPUT_INTEXT = "in text"; -Blockly.Msg.TEXT_CHARAT_LAST = "get last letter"; -Blockly.Msg.TEXT_CHARAT_RANDOM = "get random letter"; -Blockly.Msg.TEXT_CHARAT_TAIL = ""; -Blockly.Msg.TEXT_CHARAT_TOOLTIP = "Returns the letter at the specified position."; -Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP = "Add an item to the text."; -Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN = "join"; -Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP = "Add, remove, or reorder sections to reconfigure this text block."; -Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END = "to letter # from end"; -Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START = "to letter #"; -Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST = "to last letter"; -Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text"; -Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT = "in text"; -Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST = "get substring from first letter"; -Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END = "get substring from letter # from end"; -Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START = "get substring from letter #"; -Blockly.Msg.TEXT_GET_SUBSTRING_TAIL = ""; -Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP = "Returns a specified portion of the text."; -Blockly.Msg.TEXT_INDEXOF_HELPURL = "https://github.com/google/blockly/wiki/Text#finding-text"; -Blockly.Msg.TEXT_INDEXOF_INPUT_INTEXT = "in text"; -Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST = "find first occurrence of text"; -Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST = "find last occurrence of text"; -Blockly.Msg.TEXT_INDEXOF_TAIL = ""; -Blockly.Msg.TEXT_INDEXOF_TOOLTIP = "Returns the index of the first/last occurrence of the first text in the second text. Returns 0 if text is not found."; -Blockly.Msg.TEXT_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Text#checking-for-empty-text"; -Blockly.Msg.TEXT_ISEMPTY_TITLE = "%1 is empty"; -Blockly.Msg.TEXT_ISEMPTY_TOOLTIP = "Returns true if the provided text is empty."; -Blockly.Msg.TEXT_JOIN_HELPURL = "https://github.com/google/blockly/wiki/Text#text-creation"; -Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH = "create text with"; -Blockly.Msg.TEXT_JOIN_TOOLTIP = "Create a piece of text by joining together any number of items."; -Blockly.Msg.TEXT_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; -Blockly.Msg.TEXT_LENGTH_TITLE = "length of %1"; -Blockly.Msg.TEXT_LENGTH_TOOLTIP = "Returns the number of letters (including spaces) in the provided text."; -Blockly.Msg.TEXT_PRINT_HELPURL = "https://github.com/google/blockly/wiki/Text#printing-text"; -Blockly.Msg.TEXT_PRINT_TITLE = "print %1"; -Blockly.Msg.TEXT_PRINT_TOOLTIP = "Print the specified text, number or other value."; -Blockly.Msg.TEXT_PROMPT_HELPURL = "https://github.com/google/blockly/wiki/Text#getting-input-from-the-user"; -Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER = "Prompt for user for a number."; -Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT = "Prompt for user for some text."; -Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER = "prompt for number with message"; -Blockly.Msg.TEXT_PROMPT_TYPE_TEXT = "prompt for text with message"; -Blockly.Msg.TEXT_TEXT_HELPURL = "https://en.wikipedia.org/wiki/String_(computer_science)"; -Blockly.Msg.TEXT_TEXT_TOOLTIP = "A letter, word, or line of text."; -Blockly.Msg.TEXT_TRIM_HELPURL = "https://github.com/google/blockly/wiki/Text#trimming-removing-spaces"; -Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH = "trim spaces from both sides of"; -Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT = "trim spaces from left side of"; -Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT = "trim spaces from right side of"; -Blockly.Msg.TEXT_TRIM_TOOLTIP = "Return a copy of the text with spaces removed from one or both ends."; -Blockly.Msg.TODAY = "Today"; -Blockly.Msg.UNDO = "Undo"; -Blockly.Msg.MACRO_DEFAULT_NAME = "newMacro"; -Blockly.Msg.VARIABLES_DEFAULT_NAME = "item"; -Blockly.Msg.VARIABLES_GET_CREATE_SET = "Create 'set %1'"; -Blockly.Msg.MACRO_DEFINE_CREATE_SET = "Create 'define %1'"; -Blockly.Msg.VARIABLES_GET_HELPURL = "https://github.com/google/blockly/wiki/Variables#get"; -Blockly.Msg.MACRO_GET_HELPURL = "" -Blockly.Msg.VARIABLES_GET_TOOLTIP = "Returns the value of this variable."; -Blockly.Msg.MACRO_GET_TOOLTIP = "get the value of this macro"; -Blockly.Msg.VARIABLES_SET = "set %1 to %2"; -Blockly.Msg.MACRO_DEFINE = "define %1 as %2"; -Blockly.Msg.VARIABLES_SET_CREATE_GET = "Create 'get %1'"; -Blockly.Msg.VARIABLES_SET_HELPURL = "https://github.com/google/blockly/wiki/Variables#set"; -Blockly.Msg.VARIABLES_SET_TOOLTIP = "Sets this variable to be equal to the input."; -Blockly.Msg.MACRO_DEFINE_HELPURL = ""; -Blockly.Msg.MACRO_DEFINE_TOOLTIP = "Defines this macro to be equal to the input."; -Blockly.Msg.MATH_CHANGE_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; -Blockly.Msg.PROCEDURES_DEFRETURN_TITLE = Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE; -Blockly.Msg.CONTROLS_IF_IF_TITLE_IF = Blockly.Msg.CONTROLS_IF_MSG_IF; -Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; -Blockly.Msg.CONTROLS_IF_MSG_THEN = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; -Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE = Blockly.Msg.CONTROLS_IF_MSG_ELSE; -Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE = Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE; -Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; -Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; -Blockly.Msg.PROCEDURES_DEFRETURN_DO = Blockly.Msg.PROCEDURES_DEFNORETURN_DO; -Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF = Blockly.Msg.CONTROLS_IF_MSG_ELSEIF; -Blockly.Msg.LISTS_GET_INDEX_HELPURL = Blockly.Msg.LISTS_INDEX_OF_HELPURL; -Blockly.Msg.CONTROLS_FOREACH_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; -Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; -Blockly.Msg.CONTROLS_FOR_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; -Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; -Blockly.Msg.TEXT_APPEND_VARIABLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; -Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; -Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; -Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT = Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT; \ No newline at end of file +/** @export */ Blockly.Msg.ADD_COMMENT = "Add Comment"; +/** @export */ Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE = "Can't delete the variable '%1' because it's part of the definition of the function '%2'"; +/** @export */ Blockly.Msg.CHANGE_VALUE_TITLE = "Change value:"; +/** @export */ Blockly.Msg.CLEAN_UP = "Clean up Blocks"; +/** @export */ Blockly.Msg.COLLAPSE_ALL = "Collapse Blocks"; +/** @export */ Blockly.Msg.COLLAPSE_BLOCK = "Collapse Block"; +/** @export */ Blockly.Msg.COLOUR_BLEND_COLOUR1 = "colour 1"; +/** @export */ Blockly.Msg.COLOUR_BLEND_COLOUR2 = "colour 2"; +/** @export */ Blockly.Msg.COLOUR_BLEND_HELPURL = "http://meyerweb.com/eric/tools/color-blend/"; +/** @export */ Blockly.Msg.COLOUR_BLEND_RATIO = "ratio"; +/** @export */ Blockly.Msg.COLOUR_BLEND_TITLE = "blend"; +/** @export */ Blockly.Msg.COLOUR_BLEND_TOOLTIP = "Blends two colours together with a given ratio (0.0 - 1.0)."; +/** @export */ Blockly.Msg.COLOUR_PICKER_HELPURL = "https://en.wikipedia.org/wiki/Color"; +/** @export */ Blockly.Msg.COLOUR_PICKER_TOOLTIP = "Choose a colour from the palette."; +/** @export */ Blockly.Msg.COLOUR_RANDOM_HELPURL = "http://randomcolour.com"; +/** @export */ Blockly.Msg.COLOUR_RANDOM_TITLE = "random colour"; +/** @export */ Blockly.Msg.COLOUR_RANDOM_TOOLTIP = "Choose a colour at random."; +/** @export */ Blockly.Msg.COLOUR_RGB_BLUE = "blue"; +/** @export */ Blockly.Msg.COLOUR_RGB_GREEN = "green"; +/** @export */ Blockly.Msg.COLOUR_RGB_HELPURL = "http://www.december.com/html/spec/colorper.html"; +/** @export */ Blockly.Msg.COLOUR_RGB_RED = "red"; +/** @export */ Blockly.Msg.COLOUR_RGB_TITLE = "colour with"; +/** @export */ Blockly.Msg.COLOUR_RGB_TOOLTIP = "Create a colour with the specified amount of red, green, and blue. All values must be between 0 and 100."; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL = "https://github.com/google/blockly/wiki/Loops#loop-termination-blocks"; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK = "break out of loop"; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE = "continue with next iteration of loop"; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK = "Break out of the containing loop."; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE = "Skip the rest of this loop, and continue with the next iteration."; +/** @export */ Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING = "Warning: This block may only be used within a loop."; +/** @export */ Blockly.Msg.CONTROLS_FOREACH_HELPURL = "https://github.com/google/blockly/wiki/Loops#for-each"; +/** @export */ Blockly.Msg.CONTROLS_FOREACH_TITLE = "for each item %1 in list %2"; +/** @export */ Blockly.Msg.CONTROLS_FOREACH_TOOLTIP = "For each item in a list, set the variable '%1' to the item, and then do some statements."; +/** @export */ Blockly.Msg.CONTROLS_FOR_HELPURL = "https://github.com/google/blockly/wiki/Loops#count-with"; +/** @export */ Blockly.Msg.CONTROLS_FOR_TITLE = "count with %1 from %2 to %3 by %4"; +/** @export */ Blockly.Msg.CONTROLS_FOR_TOOLTIP = "Have the variable '%1' take on the values from the start number to the end number, counting by the specified interval, and do the specified blocks."; +/** @export */ Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP = "Add a condition to the if block."; +/** @export */ Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = "Add a final, catch-all condition to the if block."; +/** @export */ Blockly.Msg.CONTROLS_IF_HELPURL = "https://github.com/google/blockly/wiki/IfElse"; +/** @export */ Blockly.Msg.CONTROLS_IF_IF_TOOLTIP = "Add, remove, or reorder sections to reconfigure this if block."; +/** @export */ Blockly.Msg.CONTROLS_IF_MSG_ELSE = "else"; +/** @export */ Blockly.Msg.CONTROLS_IF_MSG_ELSEIF = "else if"; +/** @export */ Blockly.Msg.CONTROLS_IF_MSG_IF = "if"; +/** @export */ Blockly.Msg.CONTROLS_IF_TOOLTIP_1 = "If a value is true, then do some statements."; +/** @export */ Blockly.Msg.CONTROLS_IF_TOOLTIP_2 = "If a value is true, then do the first block of statements. Otherwise, do the second block of statements."; +/** @export */ Blockly.Msg.CONTROLS_IF_TOOLTIP_3 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements."; +/** @export */ Blockly.Msg.CONTROLS_IF_TOOLTIP_4 = "If the first value is true, then do the first block of statements. Otherwise, if the second value is true, do the second block of statements. If none of the values are true, do the last block of statements."; +/** @export */ Blockly.Msg.CONTROLS_REPEAT_HELPURL = "https://en.wikipedia.org/wiki/For_loop"; +/** @export */ Blockly.Msg.CONTROLS_REPEAT_INPUT_DO = "do"; +/** @export */ Blockly.Msg.CONTROLS_REPEAT_TITLE = "repeat %1 times"; +/** @export */ Blockly.Msg.CONTROLS_REPEAT_TOOLTIP = "Do some statements several times."; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL = "https://github.com/google/blockly/wiki/Loops#repeat"; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL = "repeat until"; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE = "repeat while"; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = "While a value is false, then do some statements."; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = "While a value is true, then do some statements."; +/** @export */ Blockly.Msg.DELETE_ALL_BLOCKS = "Delete all %1 blocks?"; +/** @export */ Blockly.Msg.DELETE_BLOCK = "Delete Block"; +/** @export */ Blockly.Msg.DELETE_VARIABLE = "Delete the '%1' variable"; +/** @export */ Blockly.Msg.DELETE_VARIABLE_CONFIRMATION = "Delete %1 uses of the '%2' variable?"; +/** @export */ Blockly.Msg.DELETE_X_BLOCKS = "Delete %1 Blocks"; +/** @export */ Blockly.Msg.DISABLE_BLOCK = "Disable Block"; +/** @export */ Blockly.Msg.DUPLICATE_BLOCK = "Duplicate"; +/** @export */ Blockly.Msg.ENABLE_BLOCK = "Enable Block"; +/** @export */ Blockly.Msg.EXPAND_ALL = "Expand Blocks"; +/** @export */ Blockly.Msg.EXPAND_BLOCK = "Expand Block"; +/** @export */ Blockly.Msg.EXTERNAL_INPUTS = "External Inputs"; +/** @export */ Blockly.Msg.HELP = "Help"; +/** @export */ Blockly.Msg.INLINE_INPUTS = "Inline Inputs"; +/** @export */ Blockly.Msg.IOS_CANCEL = "Cancel"; +/** @export */ Blockly.Msg.IOS_ERROR = "Error"; +/** @export */ Blockly.Msg.IOS_OK = "OK"; +/** @export */ Blockly.Msg.IOS_PROCEDURES_ADD_INPUT = "+ Add Input"; +/** @export */ Blockly.Msg.IOS_PROCEDURES_ALLOW_STATEMENTS = "Allow statements"; +/** @export */ Blockly.Msg.IOS_PROCEDURES_DUPLICATE_INPUTS_ERROR = "This function has duplicate inputs."; +/** @export */ Blockly.Msg.IOS_PROCEDURES_INPUTS = "INPUTS"; +/** @export */ Blockly.Msg.IOS_VARIABLES_ADD_BUTTON = "Add"; +/** @export */ Blockly.Msg.IOS_VARIABLES_ADD_VARIABLE = "+ Add Variable"; +/** @export */ Blockly.Msg.IOS_VARIABLES_DELETE_BUTTON = "Delete"; +/** @export */ Blockly.Msg.IOS_VARIABLES_EMPTY_NAME_ERROR = "You can't use an empty variable name."; +/** @export */ Blockly.Msg.IOS_VARIABLES_RENAME_BUTTON = "Rename"; +/** @export */ Blockly.Msg.IOS_VARIABLES_VARIABLE_NAME = "Variable name"; +/** @export */ Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-empty-list"; +/** @export */ Blockly.Msg.LISTS_CREATE_EMPTY_TITLE = "create empty list"; +/** @export */ Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP = "Returns a list, of length 0, containing no data records"; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD = "list"; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP = "Add, remove, or reorder sections to reconfigure this list block."; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH = "create list with"; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP = "Add an item to the list."; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP = "Create a list with any number of items."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_FIRST = "first"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_FROM_END = "# from end"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_FROM_START = "#"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_GET = "get"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE = "get and remove"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_LAST = "last"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_RANDOM = "random"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_REMOVE = "remove"; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TAIL = ""; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST = "Returns the first item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM = "Returns the item at the specified position in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST = "Returns the last item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM = "Returns a random item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST = "Removes and returns the first item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM = "Removes and returns the item at the specified position in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST = "Removes and returns the last item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM = "Removes and returns a random item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST = "Removes the first item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM = "Removes the item at the specified position in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST = "Removes the last item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM = "Removes a random item in a list."; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END = "to # from end"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START = "to #"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_END_LAST = "to last"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-a-sublist"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST = "get sub-list from first"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END = "get sub-list from # from end"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START = "get sub-list from #"; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_TAIL = ""; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP = "Creates a copy of the specified portion of a list."; +/** @export */ Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP = "%1 is the last item."; +/** @export */ Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP = "%1 is the first item."; +/** @export */ Blockly.Msg.LISTS_INDEX_OF_FIRST = "find first occurrence of item"; +/** @export */ Blockly.Msg.LISTS_INDEX_OF_HELPURL = "https://github.com/google/blockly/wiki/Lists#getting-items-from-a-list"; +/** @export */ Blockly.Msg.LISTS_INDEX_OF_LAST = "find last occurrence of item"; +/** @export */ Blockly.Msg.LISTS_INDEX_OF_TOOLTIP = "Returns the index of the first/last occurrence of the item in the list. Returns %1 if item is not found."; +/** @export */ Blockly.Msg.LISTS_INLIST = "in list"; +/** @export */ Blockly.Msg.LISTS_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Lists#is-empty"; +/** @export */ Blockly.Msg.LISTS_ISEMPTY_TITLE = "%1 is empty"; +/** @export */ Blockly.Msg.LISTS_ISEMPTY_TOOLTIP = "Returns true if the list is empty."; +/** @export */ Blockly.Msg.LISTS_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Lists#length-of"; +/** @export */ Blockly.Msg.LISTS_LENGTH_TITLE = "length of %1"; +/** @export */ Blockly.Msg.LISTS_LENGTH_TOOLTIP = "Returns the length of a list."; +/** @export */ Blockly.Msg.LISTS_REPEAT_HELPURL = "https://github.com/google/blockly/wiki/Lists#create-list-with"; +/** @export */ Blockly.Msg.LISTS_REPEAT_TITLE = "create list with item %1 repeated %2 times"; +/** @export */ Blockly.Msg.LISTS_REPEAT_TOOLTIP = "Creates a list consisting of the given value repeated the specified number of times."; +/** @export */ Blockly.Msg.LISTS_REVERSE_HELPURL = "https://github.com/google/blockly/wiki/Lists#reversing-a-list"; +/** @export */ Blockly.Msg.LISTS_REVERSE_MESSAGE0 = "reverse %1"; +/** @export */ Blockly.Msg.LISTS_REVERSE_TOOLTIP = "Reverse a copy of a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_HELPURL = "https://github.com/google/blockly/wiki/Lists#in-list--set"; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_INPUT_TO = "as"; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_INSERT = "insert at"; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_SET = "set"; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST = "Inserts the item at the start of a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM = "Inserts the item at the specified position in a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST = "Append the item to the end of a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM = "Inserts the item randomly in a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST = "Sets the first item in a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM = "Sets the item at the specified position in a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST = "Sets the last item in a list."; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM = "Sets a random item in a list."; +/** @export */ Blockly.Msg.LISTS_SORT_HELPURL = "https://github.com/google/blockly/wiki/Lists#sorting-a-list"; +/** @export */ Blockly.Msg.LISTS_SORT_ORDER_ASCENDING = "ascending"; +/** @export */ Blockly.Msg.LISTS_SORT_ORDER_DESCENDING = "descending"; +/** @export */ Blockly.Msg.LISTS_SORT_TITLE = "sort %1 %2 %3"; +/** @export */ Blockly.Msg.LISTS_SORT_TOOLTIP = "Sort a copy of a list."; +/** @export */ Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE = "alphabetic, ignore case"; +/** @export */ Blockly.Msg.LISTS_SORT_TYPE_NUMERIC = "numeric"; +/** @export */ Blockly.Msg.LISTS_SORT_TYPE_TEXT = "alphabetic"; +/** @export */ Blockly.Msg.LISTS_SPLIT_HELPURL = "https://github.com/google/blockly/wiki/Lists#splitting-strings-and-joining-lists"; +/** @export */ Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT = "make list from text"; +/** @export */ Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST = "make text from list"; +/** @export */ Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN = "Join a list of texts into one text, separated by a delimiter."; +/** @export */ Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT = "Split text into a list of texts, breaking at each delimiter."; +/** @export */ Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER = "with delimiter"; +/** @export */ Blockly.Msg.LOGIC_BOOLEAN_FALSE = "false"; +/** @export */ Blockly.Msg.LOGIC_BOOLEAN_HELPURL = "https://github.com/google/blockly/wiki/Logic#values"; +/** @export */ Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP = "Returns either true or false."; +/** @export */ Blockly.Msg.LOGIC_BOOLEAN_TRUE = "true"; +/** @export */ Blockly.Msg.LOGIC_COMPARE_HELPURL = "https://en.wikipedia.org/wiki/Inequality_(mathematics)"; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ = "Return true if both inputs equal each other."; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT = "Return true if the first input is greater than the second input."; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE = "Return true if the first input is greater than or equal to the second input."; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT = "Return true if the first input is smaller than the second input."; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE = "Return true if the first input is smaller than or equal to the second input."; +/** @export */ Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ = "Return true if both inputs are not equal to each other."; +/** @export */ Blockly.Msg.LOGIC_NEGATE_HELPURL = "https://github.com/google/blockly/wiki/Logic#not"; +/** @export */ Blockly.Msg.LOGIC_NEGATE_TITLE = "not %1"; +/** @export */ Blockly.Msg.LOGIC_NEGATE_TOOLTIP = "Returns true if the input is false. Returns false if the input is true."; +/** @export */ Blockly.Msg.LOGIC_NULL = "null"; +/** @export */ Blockly.Msg.LOGIC_NULL_HELPURL = "https://en.wikipedia.org/wiki/Nullable_type"; +/** @export */ Blockly.Msg.LOGIC_NULL_TOOLTIP = "Returns null."; +/** @export */ Blockly.Msg.LOGIC_OPERATION_AND = "and"; +/** @export */ Blockly.Msg.LOGIC_OPERATION_HELPURL = "https://github.com/google/blockly/wiki/Logic#logical-operations"; +/** @export */ Blockly.Msg.LOGIC_OPERATION_OR = "or"; +/** @export */ Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND = "Return true if both inputs are true."; +/** @export */ Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR = "Return true if at least one of the inputs is true."; +/** @export */ Blockly.Msg.LOGIC_TERNARY_CONDITION = "test"; +/** @export */ Blockly.Msg.LOGIC_TERNARY_HELPURL = "https://en.wikipedia.org/wiki/%3F:"; +/** @export */ Blockly.Msg.LOGIC_TERNARY_IF_FALSE = "if false"; +/** @export */ Blockly.Msg.LOGIC_TERNARY_IF_TRUE = "if true"; +/** @export */ Blockly.Msg.LOGIC_TERNARY_TOOLTIP = "Check the condition in 'test'. If the condition is true, returns the 'if true' value; otherwise returns the 'if false' value."; +/** @export */ Blockly.Msg.MATH_ADDITION_SYMBOL = "+"; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_HELPURL = "https://en.wikipedia.org/wiki/Arithmetic"; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD = "Return the sum of the two numbers."; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE = "Return the quotient of the two numbers."; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS = "Return the difference of the two numbers."; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY = "Return the product of the two numbers."; +/** @export */ Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER = "Return the first number raised to the power of the second number."; +/** @export */ Blockly.Msg.MATH_CHANGE_HELPURL = "https://en.wikipedia.org/wiki/Programming_idiom#Incrementing_a_counter"; +/** @export */ Blockly.Msg.MATH_CHANGE_TITLE = "change %1 by %2"; +/** @export */ Blockly.Msg.MATH_CHANGE_TOOLTIP = "Add a number to variable '%1'."; +/** @export */ Blockly.Msg.MATH_CONSTANT_HELPURL = "https://en.wikipedia.org/wiki/Mathematical_constant"; +/** @export */ Blockly.Msg.MATH_CONSTANT_TOOLTIP = "Return one of the common constants: π (3.141…), e (2.718…), φ (1.618…), sqrt(2) (1.414…), sqrt(½) (0.707…), or ∞ (infinity)."; +/** @export */ Blockly.Msg.MATH_CONSTRAIN_HELPURL = "https://en.wikipedia.org/wiki/Clamping_(graphics)"; +/** @export */ Blockly.Msg.MATH_CONSTRAIN_TITLE = "constrain %1 low %2 high %3"; +/** @export */ Blockly.Msg.MATH_CONSTRAIN_TOOLTIP = "Constrain a number to be between the specified limits (inclusive)."; +/** @export */ Blockly.Msg.MATH_DIVISION_SYMBOL = "÷"; +/** @export */ Blockly.Msg.MATH_IS_DIVISIBLE_BY = "is divisible by"; +/** @export */ Blockly.Msg.MATH_IS_EVEN = "is even"; +/** @export */ Blockly.Msg.MATH_IS_NEGATIVE = "is negative"; +/** @export */ Blockly.Msg.MATH_IS_ODD = "is odd"; +/** @export */ Blockly.Msg.MATH_IS_POSITIVE = "is positive"; +/** @export */ Blockly.Msg.MATH_IS_PRIME = "is prime"; +/** @export */ Blockly.Msg.MATH_IS_TOOLTIP = "Check if a number is an even, odd, prime, whole, positive, negative, or if it is divisible by certain number. Returns true or false."; +/** @export */ Blockly.Msg.MATH_IS_WHOLE = "is whole"; +/** @export */ Blockly.Msg.MATH_MODULO_HELPURL = "https://en.wikipedia.org/wiki/Modulo_operation"; +/** @export */ Blockly.Msg.MATH_MODULO_TITLE = "remainder of %1 ÷ %2"; +/** @export */ Blockly.Msg.MATH_MODULO_TOOLTIP = "Return the remainder from dividing the two numbers."; +/** @export */ Blockly.Msg.MATH_MULTIPLICATION_SYMBOL = "×"; +/** @export */ Blockly.Msg.MATH_NUMBER_HELPURL = "https://en.wikipedia.org/wiki/Number"; +/** @export */ Blockly.Msg.MATH_NUMBER_TOOLTIP = "A number."; +/** @export */ Blockly.Msg.MATH_ONLIST_HELPURL = ""; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE = "average of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_MAX = "max of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN = "median of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_MIN = "min of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_MODE = "modes of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM = "random item of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV = "standard deviation of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_OPERATOR_SUM = "sum of list"; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE = "Return the average (arithmetic mean) of the numeric values in the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX = "Return the largest number in the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN = "Return the median number in the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN = "Return the smallest number in the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE = "Return a list of the most common item(s) in the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM = "Return a random element from the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV = "Return the standard deviation of the list."; +/** @export */ Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM = "Return the sum of all the numbers in the list."; +/** @export */ Blockly.Msg.MATH_POWER_SYMBOL = "^"; +/** @export */ Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; +/** @export */ Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM = "random fraction"; +/** @export */ Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP = "Return a random fraction between 0.0 (inclusive) and 1.0 (exclusive)."; +/** @export */ Blockly.Msg.MATH_RANDOM_INT_HELPURL = "https://en.wikipedia.org/wiki/Random_number_generation"; +/** @export */ Blockly.Msg.MATH_RANDOM_INT_TITLE = "random integer from %1 to %2"; +/** @export */ Blockly.Msg.MATH_RANDOM_INT_TOOLTIP = "Return a random integer between the two specified limits, inclusive."; +/** @export */ Blockly.Msg.MATH_ROUND_HELPURL = "https://en.wikipedia.org/wiki/Rounding"; +/** @export */ Blockly.Msg.MATH_ROUND_OPERATOR_ROUND = "round"; +/** @export */ Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN = "round down"; +/** @export */ Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP = "round up"; +/** @export */ Blockly.Msg.MATH_ROUND_TOOLTIP = "Round a number up or down."; +/** @export */ Blockly.Msg.MATH_SINGLE_HELPURL = "https://en.wikipedia.org/wiki/Square_root"; +/** @export */ Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE = "absolute"; +/** @export */ Blockly.Msg.MATH_SINGLE_OP_ROOT = "square root"; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS = "Return the absolute value of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP = "Return e to the power of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_LN = "Return the natural logarithm of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10 = "Return the base 10 logarithm of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG = "Return the negation of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10 = "Return 10 to the power of a number."; +/** @export */ Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT = "Return the square root of a number."; +/** @export */ Blockly.Msg.MATH_SUBTRACTION_SYMBOL = "-"; +/** @export */ Blockly.Msg.MATH_TRIG_ACOS = "acos"; +/** @export */ Blockly.Msg.MATH_TRIG_ASIN = "asin"; +/** @export */ Blockly.Msg.MATH_TRIG_ATAN = "atan"; +/** @export */ Blockly.Msg.MATH_TRIG_COS = "cos"; +/** @export */ Blockly.Msg.MATH_TRIG_HELPURL = "https://en.wikipedia.org/wiki/Trigonometric_functions"; +/** @export */ Blockly.Msg.MATH_TRIG_SIN = "sin"; +/** @export */ Blockly.Msg.MATH_TRIG_TAN = "tan"; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS = "Return the arccosine of a number."; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN = "Return the arcsine of a number."; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN = "Return the arctangent of a number."; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_COS = "Return the cosine of a degree (not radian)."; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_SIN = "Return the sine of a degree (not radian)."; +/** @export */ Blockly.Msg.MATH_TRIG_TOOLTIP_TAN = "Return the tangent of a degree (not radian)."; +/** @export */ Blockly.Msg.NEW_COLOUR_VARIABLE = "Create colour variable..."; +/** @export */ Blockly.Msg.NEW_NUMBER_VARIABLE = "Create number variable..."; +/** @export */ Blockly.Msg.NEW_STRING_VARIABLE = "Create string variable..."; +/** @export */ Blockly.Msg.NEW_VARIABLE = "Create variable..."; +/** @export */ Blockly.Msg.NEW_VARIABLE_TITLE = "New variable name:"; +/** @export */ Blockly.Msg.NEW_VARIABLE_TYPE_TITLE = "New variable type:"; +/** @export */ Blockly.Msg.ORDINAL_NUMBER_SUFFIX = ""; +/** @export */ Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS = "allow statements"; +/** @export */ Blockly.Msg.PROCEDURES_BEFORE_PARAMS = "with:"; +/** @export */ Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Subroutine"; +/** @export */ Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP = "Run the user-defined function '%1'."; +/** @export */ Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL = "https://en.wikipedia.org/wiki/Subroutine"; +/** @export */ Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP = "Run the user-defined function '%1' and use its output."; +/** @export */ Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS = "with:"; +/** @export */ Blockly.Msg.PROCEDURES_CREATE_DO = "Create '%1'"; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT = "Describe this function..."; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_DO = ""; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL = "https://en.wikipedia.org/wiki/Subroutine"; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE = "do something"; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE = "to"; +/** @export */ Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP = "Creates a function with no output."; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL = "https://en.wikipedia.org/wiki/Subroutine"; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_RETURN = "return"; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP = "Creates a function with an output."; +/** @export */ Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING = "Warning: This function has duplicate parameters."; +/** @export */ Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF = "Highlight function definition"; +/** @export */ Blockly.Msg.PROCEDURES_IFRETURN_HELPURL = "http://c2.com/cgi/wiki?GuardClause"; +/** @export */ Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP = "If a value is true, then return a second value."; +/** @export */ Blockly.Msg.PROCEDURES_IFRETURN_WARNING = "Warning: This block may be used only within a function definition."; +/** @export */ Blockly.Msg.PROCEDURES_MUTATORARG_TITLE = "input name:"; +/** @export */ Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP = "Add an input to the function."; +/** @export */ Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE = "inputs"; +/** @export */ Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP = "Add, remove, or reorder inputs to this function."; +/** @export */ Blockly.Msg.REDO = "Redo"; +/** @export */ Blockly.Msg.REMOVE_COMMENT = "Remove Comment"; +/** @export */ Blockly.Msg.RENAME_VARIABLE = "Rename variable..."; +/** @export */ Blockly.Msg.RENAME_VARIABLE_TITLE = "Rename all '%1' variables to:"; +/** @export */ Blockly.Msg.TEXT_APPEND_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; +/** @export */ Blockly.Msg.TEXT_APPEND_TITLE = "to %1 append text %2"; +/** @export */ Blockly.Msg.TEXT_APPEND_TOOLTIP = "Append some text to variable '%1'."; +/** @export */ Blockly.Msg.TEXT_CHANGECASE_HELPURL = "https://github.com/google/blockly/wiki/Text#adjusting-text-case"; +/** @export */ Blockly.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE = "to lower case"; +/** @export */ Blockly.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE = "to Title Case"; +/** @export */ Blockly.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE = "to UPPER CASE"; +/** @export */ Blockly.Msg.TEXT_CHANGECASE_TOOLTIP = "Return a copy of the text in a different case."; +/** @export */ Blockly.Msg.TEXT_CHARAT_FIRST = "get first letter"; +/** @export */ Blockly.Msg.TEXT_CHARAT_FROM_END = "get letter # from end"; +/** @export */ Blockly.Msg.TEXT_CHARAT_FROM_START = "get letter #"; +/** @export */ Blockly.Msg.TEXT_CHARAT_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-text"; +/** @export */ Blockly.Msg.TEXT_CHARAT_LAST = "get last letter"; +/** @export */ Blockly.Msg.TEXT_CHARAT_RANDOM = "get random letter"; +/** @export */ Blockly.Msg.TEXT_CHARAT_TAIL = ""; +/** @export */ Blockly.Msg.TEXT_CHARAT_TITLE = "in text %1 %2"; +/** @export */ Blockly.Msg.TEXT_CHARAT_TOOLTIP = "Returns the letter at the specified position."; +/** @export */ Blockly.Msg.TEXT_COUNT_HELPURL = "https://github.com/google/blockly/wiki/Text#counting-substrings"; +/** @export */ Blockly.Msg.TEXT_COUNT_MESSAGE0 = "count %1 in %2"; +/** @export */ Blockly.Msg.TEXT_COUNT_TOOLTIP = "Count how many times some text occurs within some other text."; +/** @export */ Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TOOLTIP = "Add an item to the text."; +/** @export */ Blockly.Msg.TEXT_CREATE_JOIN_TITLE_JOIN = "join"; +/** @export */ Blockly.Msg.TEXT_CREATE_JOIN_TOOLTIP = "Add, remove, or reorder sections to reconfigure this text block."; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_END = "to letter # from end"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_END_FROM_START = "to letter #"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_END_LAST = "to last letter"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_HELPURL = "https://github.com/google/blockly/wiki/Text#extracting-a-region-of-text"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT = "in text"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_START_FIRST = "get substring from first letter"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_END = "get substring from letter # from end"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_START_FROM_START = "get substring from letter #"; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_TAIL = ""; +/** @export */ Blockly.Msg.TEXT_GET_SUBSTRING_TOOLTIP = "Returns a specified portion of the text."; +/** @export */ Blockly.Msg.TEXT_INDEXOF_HELPURL = "https://github.com/google/blockly/wiki/Text#finding-text"; +/** @export */ Blockly.Msg.TEXT_INDEXOF_OPERATOR_FIRST = "find first occurrence of text"; +/** @export */ Blockly.Msg.TEXT_INDEXOF_OPERATOR_LAST = "find last occurrence of text"; +/** @export */ Blockly.Msg.TEXT_INDEXOF_TITLE = "in text %1 %2 %3"; +/** @export */ Blockly.Msg.TEXT_INDEXOF_TOOLTIP = "Returns the index of the first/last occurrence of the first text in the second text. Returns %1 if text is not found."; +/** @export */ Blockly.Msg.TEXT_ISEMPTY_HELPURL = "https://github.com/google/blockly/wiki/Text#checking-for-empty-text"; +/** @export */ Blockly.Msg.TEXT_ISEMPTY_TITLE = "%1 is empty"; +/** @export */ Blockly.Msg.TEXT_ISEMPTY_TOOLTIP = "Returns true if the provided text is empty."; +/** @export */ Blockly.Msg.TEXT_JOIN_HELPURL = "https://github.com/google/blockly/wiki/Text#text-creation"; +/** @export */ Blockly.Msg.TEXT_JOIN_TITLE_CREATEWITH = "create text with"; +/** @export */ Blockly.Msg.TEXT_JOIN_TOOLTIP = "Create a piece of text by joining together any number of items."; +/** @export */ Blockly.Msg.TEXT_LENGTH_HELPURL = "https://github.com/google/blockly/wiki/Text#text-modification"; +/** @export */ Blockly.Msg.TEXT_LENGTH_TITLE = "length of %1"; +/** @export */ Blockly.Msg.TEXT_LENGTH_TOOLTIP = "Returns the number of letters (including spaces) in the provided text."; +/** @export */ Blockly.Msg.TEXT_PRINT_HELPURL = "https://github.com/google/blockly/wiki/Text#printing-text"; +/** @export */ Blockly.Msg.TEXT_PRINT_TITLE = "print %1"; +/** @export */ Blockly.Msg.TEXT_PRINT_TOOLTIP = "Print the specified text, number or other value."; +/** @export */ Blockly.Msg.TEXT_PROMPT_HELPURL = "https://github.com/google/blockly/wiki/Text#getting-input-from-the-user"; +/** @export */ Blockly.Msg.TEXT_PROMPT_TOOLTIP_NUMBER = "Prompt for user for a number."; +/** @export */ Blockly.Msg.TEXT_PROMPT_TOOLTIP_TEXT = "Prompt for user for some text."; +/** @export */ Blockly.Msg.TEXT_PROMPT_TYPE_NUMBER = "prompt for number with message"; +/** @export */ Blockly.Msg.TEXT_PROMPT_TYPE_TEXT = "prompt for text with message"; +/** @export */ Blockly.Msg.TEXT_REPLACE_HELPURL = "https://github.com/google/blockly/wiki/Text#replacing-substrings"; +/** @export */ Blockly.Msg.TEXT_REPLACE_MESSAGE0 = "replace %1 with %2 in %3"; +/** @export */ Blockly.Msg.TEXT_REPLACE_TOOLTIP = "Replace all occurances of some text within some other text."; +/** @export */ Blockly.Msg.TEXT_REVERSE_HELPURL = "https://github.com/google/blockly/wiki/Text#reversing-text"; +/** @export */ Blockly.Msg.TEXT_REVERSE_MESSAGE0 = "reverse %1"; +/** @export */ Blockly.Msg.TEXT_REVERSE_TOOLTIP = "Reverses the order of the characters in the text."; +/** @export */ Blockly.Msg.TEXT_TEXT_HELPURL = "https://en.wikipedia.org/wiki/String_(computer_science)"; +/** @export */ Blockly.Msg.TEXT_TEXT_TOOLTIP = "A letter, word, or line of text."; +/** @export */ Blockly.Msg.TEXT_TRIM_HELPURL = "https://github.com/google/blockly/wiki/Text#trimming-removing-spaces"; +/** @export */ Blockly.Msg.TEXT_TRIM_OPERATOR_BOTH = "trim spaces from both sides of"; +/** @export */ Blockly.Msg.TEXT_TRIM_OPERATOR_LEFT = "trim spaces from left side of"; +/** @export */ Blockly.Msg.TEXT_TRIM_OPERATOR_RIGHT = "trim spaces from right side of"; +/** @export */ Blockly.Msg.TEXT_TRIM_TOOLTIP = "Return a copy of the text with spaces removed from one or both ends."; +/** @export */ Blockly.Msg.TODAY = "Today"; +/** @export */ Blockly.Msg.UNDO = "Undo"; +/** @export */ Blockly.Msg.VARIABLES_DEFAULT_NAME = "item"; +/** @export */ Blockly.Msg.VARIABLES_GET_CREATE_SET = "Create 'set %1'"; +/** @export */ Blockly.Msg.VARIABLES_GET_HELPURL = "https://github.com/google/blockly/wiki/Variables#get"; +/** @export */ Blockly.Msg.VARIABLES_GET_TOOLTIP = "Returns the value of this variable."; +/** @export */ Blockly.Msg.VARIABLES_SET = "set %1 to %2"; +/** @export */ Blockly.Msg.VARIABLES_SET_CREATE_GET = "Create 'get %1'"; +/** @export */ Blockly.Msg.VARIABLES_SET_HELPURL = "https://github.com/google/blockly/wiki/Variables#set"; +/** @export */ Blockly.Msg.VARIABLES_SET_TOOLTIP = "Sets this variable to be equal to the input."; +/** @export */ Blockly.Msg.VARIABLE_ALREADY_EXISTS = "A variable named '%1' already exists."; +/** @export */ Blockly.Msg.VARIABLE_ALREADY_EXISTS_FOR_ANOTHER_TYPE = "A variable named '%1' already exists for another type: '%2'."; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_TITLE = Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE; +/** @export */ Blockly.Msg.CONTROLS_IF_IF_TITLE_IF = Blockly.Msg.CONTROLS_IF_MSG_IF; +/** @export */ Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +/** @export */ Blockly.Msg.CONTROLS_IF_MSG_THEN = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +/** @export */ Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE = Blockly.Msg.CONTROLS_IF_MSG_ELSE; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE = Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE; +/** @export */ Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +/** @export */ Blockly.Msg.MATH_CHANGE_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_DO = Blockly.Msg.PROCEDURES_DEFNORETURN_DO; +/** @export */ Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF = Blockly.Msg.CONTROLS_IF_MSG_ELSEIF; +/** @export */ Blockly.Msg.LISTS_GET_INDEX_HELPURL = Blockly.Msg.LISTS_INDEX_OF_HELPURL; +/** @export */ Blockly.Msg.CONTROLS_FOREACH_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +/** @export */ Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +/** @export */ Blockly.Msg.CONTROLS_FOR_INPUT_DO = Blockly.Msg.CONTROLS_REPEAT_INPUT_DO; +/** @export */ Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; +/** @export */ Blockly.Msg.TEXT_APPEND_VARIABLE = Blockly.Msg.VARIABLES_DEFAULT_NAME; +/** @export */ Blockly.Msg.TEXT_CREATE_JOIN_ITEM_TITLE_ITEM = Blockly.Msg.VARIABLES_DEFAULT_NAME; +/** @export */ Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST = Blockly.Msg.LISTS_INLIST; +/** @export */ Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT = Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT; + +/** @export */ Blockly.Msg.MATH_HUE = "230"; +/** @export */ Blockly.Msg.LOOPS_HUE = "120"; +/** @export */ Blockly.Msg.LISTS_HUE = "260"; +/** @export */ Blockly.Msg.LOGIC_HUE = "210"; +/** @export */ Blockly.Msg.VARIABLES_HUE = "330"; +/** @export */ Blockly.Msg.TEXTS_HUE = "160"; +/** @export */ Blockly.Msg.PROCEDURES_HUE = "290"; +/** @export */ Blockly.Msg.COLOUR_HUE = "20"; +/** @export */ Blockly.Msg.VARIABLES_DYNAMIC_HUE = "310"; \ No newline at end of file diff --git a/node_compressed_block.js b/node_compressed_block.js index 00d500c..0e3326a 100644 --- a/node_compressed_block.js +++ b/node_compressed_block.js @@ -115,7 +115,7 @@ return container},domToMutation:function(xmlElement){this.updateType_(xmlElement Blockly.Blocks["math_modulo"]={init:function(){this.jsonInit({"message0":Blockly.Msg.MATH_MODULO_TITLE,"args0":[{"type":"input_value","name":"DIVIDEND","check":Blockly.Types.NUMBER.checkList},{"type":"input_value","name":"DIVISOR","check":Blockly.Types.NUMBER.checkList}],"inputsInline":true,"output":Blockly.Types.NUMBER.output,"colour":Blockly.Blocks.math.HUE,"tooltip":Blockly.Msg.MATH_MODULO_TOOLTIP,"helpUrl":Blockly.Msg.MATH_MODULO_HELPURL})},getBlockType:function(){return Blockly.Types.NUMBER}}; Blockly.Blocks["math_constrain"]={init:function(){this.jsonInit({"message0":Blockly.Msg.MATH_CONSTRAIN_TITLE,"args0":[{"type":"input_value","name":"VALUE","check":Blockly.Types.NUMBER.checkList},{"type":"input_value","name":"LOW","check":Blockly.Types.NUMBER.checkList},{"type":"input_value","name":"HIGH","check":Blockly.Types.NUMBER.checkList}],"inputsInline":true,"output":Blockly.Types.NUMBER.output,"colour":Blockly.Blocks.math.HUE,"tooltip":Blockly.Msg.MATH_CONSTRAIN_TOOLTIP,"helpUrl":Blockly.Msg.MATH_CONSTRAIN_HELPURL})}}; Blockly.Blocks["math_random_int"]={init:function(){this.jsonInit({"message0":Blockly.Msg.MATH_RANDOM_INT_TITLE,"args0":[{"type":"input_value","name":"FROM","check":Blockly.Types.NUMBER.checkList},{"type":"input_value","name":"TO","check":Blockly.Types.NUMBER.checkList}],"inputsInline":true,"output":Blockly.Types.NUMBER.output,"colour":Blockly.Blocks.math.HUE,"tooltip":Blockly.Msg.MATH_RANDOM_INT_TOOLTIP,"helpUrl":Blockly.Msg.MATH_RANDOM_INT_HELPURL})},getBlockType:function(){return Blockly.Types.NUMBER}}; -Blockly.Blocks["math_random_float"]={init:function(){this.jsonInit({"message0":Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM,"output":Blockly.Types.DECIMAL.output,"colour":Blockly.Blocks.math.HUE,"tooltip":Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP,"helpUrl":Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL})},getBlockType:function(){return Blockly.Types.DECIMAL}};Blockly.Blocks.digitalOut={};Blockly.Blocks.digitalOut.HUE=230;Blockly.Blocks["mbed_digitalOut"]={init:function(){this.appendValueInput("STATE").appendField(Blockly.Msg.ARD_DIGITALWRITE).appendField(new Blockly.FieldDropdown([["LED1","LED1"],["LED2","LED2"]]),"digitalOut_enum");this.setPreviousStatement(true,null);this.setNextStatement(true,null);this.setColour(Blockly.Blocks.digitalOut.HUE);this.setTooltip(Blockly.Msg.ARD_DIGITALWRITE_TIP);this.setHelpUrl("")}};/* +Blockly.Blocks["math_random_float"]={init:function(){this.jsonInit({"message0":Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM,"output":Blockly.Types.DECIMAL.output,"colour":Blockly.Blocks.math.HUE,"tooltip":Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP,"helpUrl":Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL})},getBlockType:function(){return Blockly.Types.DECIMAL}};/* Licensed under the Apache License, Version 2.0 (the "License"): http://www.apache.org/licenses/LICENSE-2.0 */ @@ -143,21 +143,38 @@ this.contextMenuMsg_.replace("%1",name);var xmlField=goog.dom.createDom("field", input.fieldRow[j];j++)if(field instanceof Blockly.FieldMacro)vars.push(field.getValue());return vars},renameMacro:function(oldName,newName){for(var i=0,input;input=this.inputList[i];i++)for(var j=0,field;field=input.fieldRow[j];j++)if(field instanceof Blockly.FieldMacro&&Blockly.Names.equals(oldName,field.getValue()))field.setValue(newName)}}; Blockly.Blocks["macro_define"]={init:function(){this.appendValueInput("MACRO_DEFINE_AS").setCheck("Number").appendField("define").appendField(new Blockly.FieldMacro(Blockly.Msg.MACRO_DEFAULT_NAME),"MACRO_NAME").appendField("as");this.setPreviousStatement(true,null);this.setNextStatement(true,null);this.setColour(Blockly.Blocks.macro.HUE);this.setTooltip(Blockly.Msg.VARIABLES_SET_TOOLTIP);this.setHelpUrl(Blockly.Msg.VARIABLES_SET_HELPURL);this.contextMenuMsg_=Blockly.Msg.MACRO_DEFINE_CREATE_SET},contextMenuType_:"macro_get", customContextMenu:Blockly.Blocks["macro_get"].customContextMenu,renameMacro:function(oldName,newName){for(var i=0,input;input=this.inputList[i];i++)for(var j=0,field;field=input.fieldRow[j];j++)if(field instanceof Blockly.FieldMacro&&Blockly.Names.equals(oldName,field.getValue()))field.setValue(newName)},getMacro:function(){var vars=[];for(var i=0,input;input=this.inputList[i];i++)for(var j=0,field;field=input.fieldRow[j];j++)if(field instanceof Blockly.FieldMacro)vars.push(field.getValue());return vars}};Blockly.Blocks.map={};Blockly.Blocks.map.HUE=230; -Blockly.Blocks["base_map"]={init:function(){this.setHelpUrl("http://mbed.cc/en/Reference/map");this.setColour(Blockly.Blocks.map.HUE);this.appendValueInput("NUM").appendField(Blockly.Msg.ARD_MAP).setCheck(Blockly.Types.NUMBER.checkList);this.appendValueInput("DMAX").appendField(Blockly.Msg.ARD_MAP_VAL).setCheck(Blockly.Types.NUMBER.checkList);this.appendDummyInput().appendField("]");this.setInputsInline(true);this.setOutput(true);this.setTooltip(Blockly.Msg.ARD_MAP_TIP)},getBlockType:function(){return Blockly.Types.NUMBER}};Blockly.Blocks.procedures.HUE=290;Blockly.Blocks["mbed_functions"]={init:function(){this.appendDummyInput().appendField(Blockly.Msg.ARD_FUN_RUN_SETUP);this.appendStatementInput("SETUP_FUNC");this.appendDummyInput().appendField(Blockly.Msg.ARD_FUN_RUN_LOOP);this.appendStatementInput("LOOP_FUNC");this.setInputsInline(false);this.setColour(Blockly.Blocks.procedures.HUE);this.setTooltip(Blockly.Msg.ARD_FUN_RUN_TIP);this.setHelpUrl("https://mbed.cc/en/Reference/Loop");this.contextMenu=false},getmbedLoopsInstance:function(){return true}};Blockly.Blocks.serial={};Blockly.Blocks.serial.HUE=160; -Blockly.Blocks["serial_setup"]={init:function(){this.setHelpUrl("http://mbed.cc/en/Serial/Begin");this.setColour(Blockly.Blocks.serial.HUE);this.appendDummyInput().appendField("Serial Setup RX:","SERIAL_NAME").appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPinsRX),"SERIAL_ID").appendField("TX:").appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPinsTX),"SERIAL_ID_TX").appendField(Blockly.Msg.ARD_SERIAL_SPEED).appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialSpeed),"SPEED").appendField(Blockly.Msg.ARD_SERIAL_BPS); -this.setInputsInline(true);this.setPreviousStatement(false,null);this.setNextStatement(true,null);this.setTooltip(Blockly.Msg.ARD_SERIAL_SETUP_TIP)},getSerialSetupInstance:function(){return Blockly.mbed.Boards.selected.serialMapper[this.getFieldValue("SERIAL_ID")]},onchange:function(){if(!this.workspace)return;var serialId=this.getFieldValue("SERIAL_ID");var serialId_TX=this.getFieldValue("SERIAL_ID_TX");var serialRX=Blockly.mbed.Boards.selected.serialMapper[serialId];var serialTX=Blockly.mbed.Boards.selected.serialMapper[serialId_TX]; -if(serialRX==serialTX){this.setWarningText(null,"serial_rx_tx_mismatch");this.setFieldValue("%1 Setup RX:".replace("%1",serialRX),"SERIAL_NAME")}else{this.setWarningText(serialRX+" mismatches "+serialTX,"serial_rx_tx_mismatch");this.setFieldValue("Serial Setup RX","SERIAL_NAME")}},updateFields:function(){Blockly.mbed.Boards.refreshBlockFieldDropdown(this,"SERIAL_ID","digitalPins");Blockly.mbed.Boards.refreshBlockFieldDropdown(this,"SERIAL_ID_TX","digitalPins");Blockly.mbed.Boards.refreshBlockFieldDropdown(this, -"SPEED","serialSpeed")}};Blockly.Blocks["print_content"]={init:function(){this.appendValueInput("format_content").setCheck(null);this.setInputsInline(true);this.appendValueInput("join_content").setCheck(null).appendField("join");this.setInputsInline(false);this.setOutput(true,null);this.setColour(Blockly.Blocks.serial.HUE);this.setTooltip("print format extra content");this.setHelpUrl("")}}; -Blockly.Blocks["serial_attach"]={init:function(){this.appendDummyInput().appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPins),"SERIAL_Pins").appendField("attach");this.appendStatementInput("function_body").setCheck(null);this.setInputsInline(true);this.setPreviousStatement(true,null);this.setNextStatement(true,null);this.setColour(Blockly.Blocks.serial.HUE);this.setTooltip("attach an interrupt function to configured serial");this.setHelpUrl("");this.arguments_=[]}}; -Blockly.Blocks["serial_print"]={init:function(){this.setHelpUrl("http://www.mbed.cc/en/Serial/Print");this.setColour(Blockly.Blocks.serial.HUE);this.appendValueInput("CONTENT").setCheck(Blockly.Types.TEXT.checkList).appendField(new Blockly.FieldDropdown(Blockly.mbed.Boards.selected.serialPins),"SERIAL_Pins").appendField(Blockly.Msg.ARD_SERIAL_PRINT);this.appendValueInput("CONTENT_STR").setCheck(null).appendField(new Blockly.FieldCheckbox("TRUE"),"NEW_LINE").appendField(Blockly.Msg.ARD_SERIAL_PRINT_NEWLINE); -this.setInputsInline(false);this.setPreviousStatement(true,null);this.setNextStatement(true,null);this.setTooltip(Blockly.Msg.ARD_SERIAL_PRINT_TIP)},onchange:function(){if(!this.workspace)return;var thisInstanceName=this.getFieldValue("SERIAL_Pins");var setupInstancePresent=false;var blocks=Blockly.mainWorkspace.getTopBlocks();for(var x=0;xvar goog = undefined;'); + // Load fresh Closure Library. + document.write(''); + document.write(''); +} +""") + f.close() + print("SUCCESS: " + self.target_filename) + + + +if __name__ == "__main__": + try: + calcdeps = import_path(os.path.join( + os.path.pardir, "closure-library", "closure", "bin", "calcdeps.py")) + except ImportError: + if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")): + # Dir got renamed when Closure moved from Google Code to GitHub in 2014. + print("Error: Closure directory needs to be renamed from" + "'closure-library-read-only' to 'closure-library'.\n" + "Please rename this directory.") + elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")): + # When Closure is installed by npm, it is named "google-closure-library". + #calcdeps = import_path(os.path.join( + # os.path.pardir, "google-closure-library", "closure", "bin", "calcdeps.py")) + print("Error: Closure directory needs to be renamed from" + "'google-closure-library' to 'closure-library'.\n" + "Please rename this directory.") + else: + print("""Error: Closure not found. Read this: +developers.google.com/blockly/guides/modify/web/closure""") + sys.exit(1) + + core_search_paths = calcdeps.ExpandDirectories( + ["core","c_core",os.path.join(os.path.pardir, "closure-library")]) + core_search_paths.sort() # Deterministic build. + + Gen_uncompressed(core_search_paths,"blockly_uncompressed.js").start() + +