From 0f8d01209c3217cd6d66a8468606f0e8083c12f1 Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Tue, 23 Dec 2014 11:22:02 -0800 Subject: [PATCH] Headless workspaces and blocks. --- blockly_compressed.js | 505 ++++++++++---------- blockly_uncompressed.js | 20 +- core/block.js | 819 ++------------------------------ core/block_svg.js | 849 ++++++++++++++++++++++++++++++++-- core/blockly.js | 25 +- core/blocks.js | 20 + core/comment.js | 2 +- core/connection.js | 11 +- core/field.js | 46 +- core/field_checkbox.js | 2 +- core/field_colour.js | 21 +- core/field_dropdown.js | 2 +- core/field_image.js | 11 +- core/field_label.js | 5 +- core/field_textinput.js | 8 +- core/field_variable.js | 7 +- core/flyout.js | 33 +- core/generator.js | 9 +- core/icon.js | 4 + core/inject.js | 5 +- core/input.js | 4 +- core/mutator.js | 13 +- core/realtime-client-utils.js | 4 +- core/scrollbar.js | 7 +- core/tooltip.js | 2 +- core/utils.js | 3 +- core/variables.js | 23 +- core/warning.js | 2 +- core/workspace.js | 335 ++------------ core/workspace_svg.js | 388 ++++++++++++++++ core/xml.js | 72 +-- dart_compressed.js | 3 +- demos/headless/icon.png | Bin 0 -> 4359 bytes demos/headless/index.html | 120 +++++ demos/index.html | 12 + generators/dart.js | 6 +- generators/javascript.js | 6 +- generators/python.js | 6 +- javascript_compressed.js | 2 +- python_compressed.js | 2 +- 40 files changed, 1894 insertions(+), 1520 deletions(-) create mode 100644 core/workspace_svg.js create mode 100644 demos/headless/icon.png create mode 100644 demos/headless/index.html diff --git a/blockly_compressed.js b/blockly_compressed.js index 13dacded1..08d7214a8 100644 --- a/blockly_compressed.js +++ b/blockly_compressed.js @@ -93,7 +93,12 @@ goog.array.defaultCompareEquality=function(a,b){return a===b};goog.array.binaryI goog.array.toObject=function(a,b,c){var d={};goog.array.forEach(a,function(e,f){d[b.call(c,e,f,a)]=e});return d};goog.array.range=function(a,b,c){var d=[],e=0,f=a;c=c||1;void 0!==b&&(e=a,f=b);if(0>c*(f-e))return[];if(0f;a+=c)d.push(a);return d};goog.array.repeat=function(a,b){for(var c=[],d=0;db&&goog.array.ARRAY_PROTOTYPE_.push.apply(a,a.splice(0,-b)));return a}; goog.array.moveItem=function(a,b,c){goog.asserts.assert(0<=b&&b=f.length)return b;d.push(f[c])}b.push(d)}}; -goog.array.shuffle=function(a,b){for(var c=b||Math.random,d=a.length-1;0c*b?c+b:c};goog.math.lerp=function(a,b,c){return a+c*(b-a)};goog.math.nearlyEquals=function(a,b,c){return Math.abs(a-b)<=(c||1E-6)};goog.math.standardAngle=function(a){return goog.math.modulo(a,360)}; +goog.math.standardAngleInRadians=function(a){return goog.math.modulo(a,2*Math.PI)};goog.math.toRadians=function(a){return a*Math.PI/180};goog.math.toDegrees=function(a){return 180*a/Math.PI};goog.math.angleDx=function(a,b){return b*Math.cos(goog.math.toRadians(a))};goog.math.angleDy=function(a,b){return b*Math.sin(goog.math.toRadians(a))};goog.math.angle=function(a,b,c,d){return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(d-b,c-a)))}; +goog.math.angleDifference=function(a,b){var c=goog.math.standardAngle(b)-goog.math.standardAngle(a);180=c&&(c=360+c);return c};goog.math.sign=function(a){return 0==a?0:0>a?-1:1}; +goog.math.longestCommonSubsequence=function(a,b,c,d){c=c||function(a,b){return a==b};d=d||function(b,c){return a[b]};for(var e=a.length,f=b.length,g=[],h=0;hg[h][k-1]?h--:k--;return l}; +goog.math.sum=function(a){return goog.array.reduce(arguments,function(a,c){return a+c},0)};goog.math.average=function(a){return goog.math.sum.apply(null,arguments)/arguments.length};goog.math.sampleVariance=function(a){var b=arguments.length;if(2>b)return 0;var c=goog.math.average.apply(null,arguments);return goog.math.sum.apply(null,goog.array.map(arguments,function(a){return Math.pow(a-c,2)}))/(b-1)};goog.math.standardDeviation=function(a){return Math.sqrt(goog.math.sampleVariance.apply(null,arguments))}; +goog.math.isInt=function(a){return isFinite(a)&&0==a%1};goog.math.isFiniteNumber=function(a){return isFinite(a)&&!isNaN(a)};goog.math.log10Floor=function(a){if(0a)}return 0==a?-Infinity:NaN};goog.math.safeFloor=function(a,b){goog.asserts.assert(!goog.isDef(b)||0parseFloat(a))?String(b):a}; goog.userAgent.getDocumentMode_=function(){var a=goog.global.document;return a?a.documentMode:void 0};goog.userAgent.VERSION=goog.userAgent.determineVersion_();goog.userAgent.compare=function(a,b){return goog.string.compareVersions(a,b)};goog.userAgent.isVersionOrHigherCache_={}; goog.userAgent.isVersionOrHigher=function(a){return goog.userAgent.ASSUME_ANY_VERSION||goog.userAgent.isVersionOrHigherCache_[a]||(goog.userAgent.isVersionOrHigherCache_[a]=0<=goog.string.compareVersions(goog.userAgent.VERSION,a))};goog.userAgent.isVersion=goog.userAgent.isVersionOrHigher;goog.userAgent.isDocumentModeOrHigher=function(a){return goog.userAgent.IE&&goog.userAgent.DOCUMENT_MODE>=a};goog.userAgent.isDocumentMode=goog.userAgent.isDocumentModeOrHigher; -goog.userAgent.DOCUMENT_MODE=function(){var a=goog.global.document;return a&&goog.userAgent.IE?goog.userAgent.getDocumentMode_()||("CSS1Compat"==a.compatMode?parseInt(goog.userAgent.VERSION,10):5):void 0}();goog.math={};goog.math.randomInt=function(a){return Math.floor(Math.random()*a)};goog.math.uniformRandom=function(a,b){return a+Math.random()*(b-a)};goog.math.clamp=function(a,b,c){return Math.min(Math.max(a,b),c)};goog.math.modulo=function(a,b){var c=a%b;return 0>c*b?c+b:c};goog.math.lerp=function(a,b,c){return a+c*(b-a)};goog.math.nearlyEquals=function(a,b,c){return Math.abs(a-b)<=(c||1E-6)};goog.math.standardAngle=function(a){return goog.math.modulo(a,360)}; -goog.math.standardAngleInRadians=function(a){return goog.math.modulo(a,2*Math.PI)};goog.math.toRadians=function(a){return a*Math.PI/180};goog.math.toDegrees=function(a){return 180*a/Math.PI};goog.math.angleDx=function(a,b){return b*Math.cos(goog.math.toRadians(a))};goog.math.angleDy=function(a,b){return b*Math.sin(goog.math.toRadians(a))};goog.math.angle=function(a,b,c,d){return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(d-b,c-a)))}; -goog.math.angleDifference=function(a,b){var c=goog.math.standardAngle(b)-goog.math.standardAngle(a);180=c&&(c=360+c);return c};goog.math.sign=function(a){return 0==a?0:0>a?-1:1}; -goog.math.longestCommonSubsequence=function(a,b,c,d){c=c||function(a,b){return a==b};d=d||function(b,c){return a[b]};for(var e=a.length,f=b.length,g=[],h=0;hg[h][k-1]?h--:k--;return l}; -goog.math.sum=function(a){return goog.array.reduce(arguments,function(a,c){return a+c},0)};goog.math.average=function(a){return goog.math.sum.apply(null,arguments)/arguments.length};goog.math.sampleVariance=function(a){var b=arguments.length;if(2>b)return 0;var c=goog.math.average.apply(null,arguments);return goog.math.sum.apply(null,goog.array.map(arguments,function(a){return Math.pow(a-c,2)}))/(b-1)};goog.math.standardDeviation=function(a){return Math.sqrt(goog.math.sampleVariance.apply(null,arguments))}; -goog.math.isInt=function(a){return isFinite(a)&&0==a%1};goog.math.isFiniteNumber=function(a){return isFinite(a)&&!isNaN(a)};goog.math.log10Floor=function(a){if(0a)}return 0==a?-Infinity:NaN};goog.math.safeFloor=function(a,b){goog.asserts.assert(!goog.isDef(b)||0c||void 0==c)c=goog.cssom.getCssRulesFromStyleSheet(a).length;if(a.insertRule)a.insertRule(b,c);else if(b=/^([^\{]+)\{([^\{]+)\}/.exec(b),3==b.length)a.addRule(b[1],b[2],c);else throw Error("Your CSSRule appears to be ill-formatted.");};goog.cssom.removeCssRule=function(a,b){a.deleteRule?a.deleteRule(b):a.removeRule(b)}; goog.cssom.addCssText=function(a,b){var c=b?b.getDocument():goog.dom.getDocument(),d=c.createElement("style");d.type="text/css";c.getElementsByTagName("head")[0].appendChild(d);d.styleSheet?d.styleSheet.cssText=a:(c=c.createTextNode(a),d.appendChild(c));return d};goog.cssom.getFileNameFromStyleSheet=function(a){return(a=a.href)?/([^\/\?]+)[^\/]*$/.exec(a)[1]:null}; goog.cssom.getAllCss_=function(a,b){for(var c=[],d=goog.cssom.getAllCssStyleSheets(a),e=0;a=d[e];e++){var f=goog.cssom.getCssRulesFromStyleSheet(a);if(f&&f.length){if(!b)var g=0;for(var h=0,k=f.length,l;ha.colour,"details.colour must be a number from 0 to 360 (exclusive)");"undefined"!=a.output&&(goog.asserts.assert(!a.previousStatement,"When details.output is defined, details.previousStatement must not be true."),goog.asserts.assert(!a.nextStatement, "When details.output is defined, details.nextStatement must not be true."));var b={init:function(){var b=this;this.setColour(a.colour);this.setHelpUrl(a.helpUrl);"string"==typeof a.tooltip?this.setTooltip(a.tooltip):"function"==typeof a.tooltip&&this.setTooltip(function(){return a.tooltip(b)});"undefined"!=a.output?this.setOutput(!0,a.output):(this.setPreviousStatement("undefined"==typeof a.previousStatement?!0:a.previousStatement),this.setNextStatement("undefined"==typeof a.nextStatement?!0:a.nextStatement)); var d=[];d.push(a.text);a.args&&a.args.forEach(function(a){goog.asserts.assert(a.name);goog.asserts.assert("undefined"!=a.check);"undefined"==a.type||a.type==Blockly.INPUT_VALUE?d.push([a.name,a.check,"undefined"==typeof a.align?Blockly.ALIGN_RIGHT:a.align]):goog.asserts.fail("addTemplate() can only handle value inputs.")});d.push(Blockly.ALIGN_RIGHT);a.inline&&this.setInlineInputs(a.inline);Blockly.Block.prototype.interpolateMsg.apply(this,d)}};b.mutationToDom=a.switchable?function(){var b=a.mutationToDomFunc? a.mutatationToDomFunc():document.createElement("mutation");b.setAttribute("is_statement",this.isStatement||!1);return b}:a.mutationToDomFunc;Blockly.Blocks[a.blockName]=b}; +// Copyright 2012 Google Inc. Apache License 2.0 +Blockly.Workspace=function(){this.topBlocks_=[]};Blockly.Workspace.prototype.rendered=!1;Blockly.Workspace.prototype.maxBlocks=Infinity;Blockly.Workspace.prototype.dispose=function(){this.clear()};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a);this.fireChangeEvent()}; +Blockly.Workspace.prototype.removeTopBlock=function(a){for(var b=!1,c,d=0;c=this.topBlocks_[d];d++)if(c==a){this.topBlocks_.splice(d,1);b=!0;break}if(!b)throw"Block not present in workspace's list of top-most blocks.";this.fireChangeEvent()}; +Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1c.viewWidth&&(a=this.anchorX_-c.viewLeft-c.viewWidth):this.anchorX_+ae&&(g=2*Math.PI-g);var h=g+Math.PI/2;h>2*Math.PI&&(h-=2*Math.PI);var k=Math.sin(h),l=Math.cos(h),p=this.getBubbleSize(),h=(p.width+p.height)/Blockly.Bubble.ARROW_THICKNESS,h=Math.min(h,p.width,p.height)/2,p=1-Blockly.Bubble.ANCHOR_RADIUS/f,d=b+p*d,e=c+ +p*e,p=b+h*l,n=c+h*k,b=b-h*l,c=c-h*k,k=g+this.arrow_radians_;k>2*Math.PI&&(k-=2*Math.PI);g=Math.sin(k)*f/Blockly.Bubble.ARROW_BEND;f=Math.cos(k)*f/Blockly.Bubble.ARROW_BEND;a.push("M"+p+","+n);a.push("C"+(p+f)+","+(n+g)+" "+d+","+e+" "+d+","+e);a.push("C"+d+","+e+" "+(b+f)+","+(c+g)+" "+b+","+c)}a.push("z");this.bubbleArrow_.setAttribute("d",a.join(" "))};Blockly.Bubble.prototype.setColour=function(a){this.bubbleBack_.setAttribute("fill",a);this.bubbleArrow_.setAttribute("fill",a)}; +Blockly.Bubble.prototype.dispose=function(){Blockly.Bubble.unbindDragEvents_();goog.dom.removeNode(this.bubbleGroup_);this.shape_=this.content_=this.workspace_=this.bubbleGroup_=null}; +// Copyright 2013 Google Inc. Apache License 2.0 +Blockly.Icon=function(a){this.block_=a};Blockly.Icon.RADIUS=8;Blockly.Icon.prototype.bubble_=null;Blockly.Icon.prototype.iconX_=0;Blockly.Icon.prototype.iconY_=0;Blockly.Icon.prototype.createIcon_=function(){this.iconGroup_||(this.iconGroup_=Blockly.createSvgElement("g",{},null),this.block_.getSvgRoot().appendChild(this.iconGroup_),Blockly.bindEvent_(this.iconGroup_,"mouseup",this,this.iconClick_),this.updateEditable())}; +Blockly.Icon.prototype.dispose=function(){goog.dom.removeNode(this.iconGroup_);this.iconGroup_=null;this.setVisible(!1);this.block_=null};Blockly.Icon.prototype.updateEditable=function(){this.block_.isInFlyout?Blockly.removeClass_(this.iconGroup_,"blocklyIconGroup"):Blockly.addClass_(this.iconGroup_,"blocklyIconGroup")};Blockly.Icon.prototype.isVisible=function(){return!!this.bubble_};Blockly.Icon.prototype.iconClick_=function(a){this.block_.isInFlyout||this.setVisible(!this.isVisible())}; +Blockly.Icon.prototype.updateColour=function(){if(this.isVisible()){var a=Blockly.makeColour(this.block_.getColour());this.bubble_.setColour(a)}}; +Blockly.Icon.prototype.renderIcon=function(a){if(this.block_.isCollapsed())return this.iconGroup_.setAttribute("display","none"),a;this.iconGroup_.setAttribute("display","block");var b=2*Blockly.Icon.RADIUS;Blockly.RTL&&(a-=b);this.iconGroup_.setAttribute("transform","translate("+a+", 5)");this.computeIconLocation();return a=Blockly.RTL?a-Blockly.BlockSvg.SEP_SPACE_X:a+(b+Blockly.BlockSvg.SEP_SPACE_X)}; +Blockly.Icon.prototype.setIconLocation=function(a,b){this.iconX_=a;this.iconY_=b;this.isVisible()&&this.bubble_.setAnchorLocation(a,b)};Blockly.Icon.prototype.computeIconLocation=function(){var a=this.block_.getRelativeToSurfaceXY(),b=Blockly.getRelativeXY_(this.iconGroup_),c=a.x+b.x+Blockly.Icon.RADIUS,a=a.y+b.y+Blockly.Icon.RADIUS;c===this.iconX_&&a===this.iconY_||this.setIconLocation(c,a)};Blockly.Icon.prototype.getIconLocation=function(){return{x:this.iconX_,y:this.iconY_}}; +// Copyright 2011 Google Inc. Apache License 2.0 +Blockly.Comment=function(a){Blockly.Comment.superClass_.constructor.call(this,a);this.createIcon_()};goog.inherits(Blockly.Comment,Blockly.Icon);Blockly.Comment.prototype.text_="";Blockly.Comment.prototype.width_=160;Blockly.Comment.prototype.height_=80; +Blockly.Comment.prototype.createIcon_=function(){Blockly.Icon.prototype.createIcon_.call(this);Blockly.createSvgElement("circle",{"class":"blocklyIconShield",r:Blockly.Icon.RADIUS,cx:Blockly.Icon.RADIUS,cy:Blockly.Icon.RADIUS},this.iconGroup_);this.iconMark_=Blockly.createSvgElement("text",{"class":"blocklyIconMark",x:Blockly.Icon.RADIUS,y:2*Blockly.Icon.RADIUS-3},this.iconGroup_);this.iconMark_.appendChild(document.createTextNode("?"))}; +Blockly.Comment.prototype.createEditor_=function(){this.foreignObject_=Blockly.createSvgElement("foreignObject",{x:Blockly.Bubble.BORDER_WIDTH,y:Blockly.Bubble.BORDER_WIDTH},null);var a=document.createElementNS(Blockly.HTML_NS,"body");a.setAttribute("xmlns",Blockly.HTML_NS);a.className="blocklyMinimalBody";this.textarea_=document.createElementNS(Blockly.HTML_NS,"textarea");this.textarea_.className="blocklyCommentTextarea";this.textarea_.setAttribute("dir",Blockly.RTL?"RTL":"LTR");a.appendChild(this.textarea_); +this.foreignObject_.appendChild(a);Blockly.bindEvent_(this.textarea_,"mouseup",this,this.textareaFocus_);return this.foreignObject_};Blockly.Comment.prototype.updateEditable=function(){this.isVisible()&&(this.setVisible(!1),this.setVisible(!0));Blockly.Icon.prototype.updateEditable.call(this)}; +Blockly.Comment.prototype.resizeBubble_=function(){var a=this.bubble_.getBubbleSize(),b=2*Blockly.Bubble.BORDER_WIDTH;this.foreignObject_.setAttribute("width",a.width-b);this.foreignObject_.setAttribute("height",a.height-b);this.textarea_.style.width=a.width-b-4+"px";this.textarea_.style.height=a.height-b-4+"px"}; +Blockly.Comment.prototype.setVisible=function(a){if(a!=this.isVisible())if(!this.block_.isEditable()&&!this.textarea_||goog.userAgent.IE)Blockly.Warning.prototype.setVisible.call(this,a);else{var b=this.getText(),c=this.getBubbleSize();a?(this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svgPath_,this.iconX_,this.iconY_,this.width_,this.height_),this.bubble_.registerResizeEvent(this,this.resizeBubble_),this.updateColour(),this.text_=null):(this.bubble_.dispose(), +this.foreignObject_=this.textarea_=this.bubble_=null);this.setText(b);this.setBubbleSize(c.width,c.height)}};Blockly.Comment.prototype.textareaFocus_=function(a){this.bubble_.promote_();this.textarea_.focus()};Blockly.Comment.prototype.getBubbleSize=function(){return this.isVisible()?this.bubble_.getBubbleSize():{width:this.width_,height:this.height_}};Blockly.Comment.prototype.setBubbleSize=function(a,b){this.textarea_?this.bubble_.setBubbleSize(a,b):(this.width_=a,this.height_=b)}; +Blockly.Comment.prototype.getText=function(){return this.textarea_?this.textarea_.value:this.text_};Blockly.Comment.prototype.setText=function(a){this.textarea_?this.textarea_.value=a:this.text_=a};Blockly.Comment.prototype.dispose=function(){this.block_.comment=null;Blockly.Icon.prototype.dispose.call(this)}; +// Copyright 2011 Google Inc. Apache License 2.0 +Blockly.Connection=function(a,b){this.sourceBlock_=a;this.targetConnection=null;this.type=b;this.y_=this.x_=0;this.inDB_=!1;this.dbList_=this.sourceBlock_.workspace.connectionDBList}; +Blockly.Connection.prototype.dispose=function(){if(this.targetConnection)throw"Disconnect connection before disposing of it.";this.inDB_&&this.dbList_[this.type].removeConnection_(this);this.inDB_=!1;Blockly.highlightedConnection_==this&&(Blockly.highlightedConnection_=null);Blockly.localConnection_==this&&(Blockly.localConnection_=null)};Blockly.Connection.prototype.isSuperior=function(){return this.type==Blockly.INPUT_VALUE||this.type==Blockly.NEXT_STATEMENT}; +Blockly.Connection.prototype.connect=function(a){if(this.sourceBlock_==a.sourceBlock_)throw"Attempted to connect a block to itself.";if(this.sourceBlock_.workspace!==a.sourceBlock_.workspace)throw"Blocks are on different workspaces.";if(Blockly.OPPOSITE_TYPE[this.type]!=a.type)throw"Attempt to connect incompatible types.";if(this.type==Blockly.INPUT_VALUE||this.type==Blockly.OUTPUT_VALUE){if(this.targetConnection)throw"Source connection already connected (value).";if(a.targetConnection){var b=a.targetBlock(); +b.setParent(null);if(!b.outputConnection)throw"Orphan block does not have an output connection.";for(var c=this.sourceBlock_;c=Blockly.Connection.singleConnection_(c,b);)if(c.targetBlock())c=c.targetBlock();else{c.connect(b.outputConnection);b=null;break}b&&window.setTimeout(function(){b.outputConnection.bumpAwayFrom_(a)},Blockly.BUMP_DELAY)}}else{if(this.targetConnection)throw"Source connection already connected (block).";if(a.targetConnection){if(this.type!=Blockly.PREVIOUS_STATEMENT)throw"Can only do a mid-stack connection with the top of a block."; +b=a.targetBlock();b.setParent(null);if(!b.previousConnection)throw"Orphan block does not have a previous connection.";for(c=this.sourceBlock_;c.nextConnection;)if(c.nextConnection.targetConnection)c=c.getNextBlock();else{b.previousConnection.checkType_(c.nextConnection)&&(c.nextConnection.connect(b.previousConnection),b=null);break}b&&window.setTimeout(function(){b.previousConnection.bumpAwayFrom_(a)},Blockly.BUMP_DELAY)}}var d;this.isSuperior()?(c=this.sourceBlock_,d=a.sourceBlock_):(c=a.sourceBlock_, +d=this.sourceBlock_);this.targetConnection=a;a.targetConnection=this;d.setParent(c);c.rendered&&c.updateDisabled();d.rendered&&d.updateDisabled();c.rendered&&d.rendered&&(this.type==Blockly.NEXT_STATEMENT||this.type==Blockly.PREVIOUS_STATEMENT?d.render():c.render())};Blockly.Connection.singleConnection_=function(a,b){for(var c=!1,d=0;da.y_)c=d;else{b=d;break}}this.splice(b,0,a);a.inDB_=!0}; +Blockly.ConnectionDB.prototype.removeConnection_=function(a){if(!a.inDB_)throw"Connection not in database.";a.inDB_=!1;for(var b=0,c=this.length-2,d=c;bBlockly.Tooltip.RADIUS_OK&&Blockly.Tooltip.hide()}else Blockly.Tooltip.poisonedElement_!=Blockly.Tooltip.element_&&(window.clearTimeout(Blockly.Tooltip.showPid_),Blockly.Tooltip.lastXY_=Blockly.mouseToSvg(a), +Blockly.Tooltip.showPid_=window.setTimeout(Blockly.Tooltip.show_,Blockly.Tooltip.HOVER_MS))};Blockly.Tooltip.hide=function(){Blockly.Tooltip.visible&&(Blockly.Tooltip.visible=!1,Blockly.Tooltip.svgGroup_&&(Blockly.Tooltip.svgGroup_.style.display="none"));window.clearTimeout(Blockly.Tooltip.showPid_)}; +Blockly.Tooltip.show_=function(){Blockly.Tooltip.poisonedElement_=Blockly.Tooltip.element_;if(Blockly.Tooltip.svgGroup_){goog.dom.removeChildren(Blockly.Tooltip.svgText_);var a=Blockly.Tooltip.element_.tooltip;goog.isFunction(a)&&(a=a());for(var a=Blockly.Tooltip.wrap_(a,Blockly.Tooltip.LIMIT),a=a.split("\n"),b=0;bd.height&&(b-=a.height+2*Blockly.Tooltip.OFFSET_Y);Blockly.RTL?c=Math.max(Blockly.Tooltip.MARGINS,c):c+a.width>d.width-2*Blockly.Tooltip.MARGINS&&(c=d.width-a.width-2*Blockly.Tooltip.MARGINS);Blockly.Tooltip.svgGroup_.setAttribute("transform","translate("+c+","+b+")")}}; +Blockly.Tooltip.wrap_=function(a,b){if(a.length<=b)return a;for(var c=a.trim().split(/\s+/),d=0;db&&(b=c[d].length);var e,d=-Infinity,f,g=1;do{e=d;f=a;for(var h=[],k=c.length/g,l=1,d=0;de);return f}; +Blockly.Tooltip.wrapScore_=function(a,b,c){for(var d=[0],e=[],f=0;fd&&(d=h,e=g)}return e?Blockly.Tooltip.wrapMutate_(a,e,c):b};Blockly.Tooltip.wrapToText_=function(a,b){for(var c=[],d=0;dthis.lidOpen_)this.lidTask_=goog.Timer.callOnce(this.animateLid_,20,this)};Blockly.Trashcan.prototype.close=function(){this.setOpen_(!1)}; // Copyright 2012 Google Inc. Apache License 2.0 -Blockly.Xml={};Blockly.Xml.workspaceToDom=function(a){var b;Blockly.RTL&&(b=a.getMetrics().viewWidth);var c=goog.dom.createDom("xml");a=a.getTopBlocks(!0);for(var d=0,e;e=a[d];d++){var f=Blockly.Xml.blockToDom_(e);e=e.getRelativeToSurfaceXY();f.setAttribute("x",Blockly.RTL?b-e.x:e.x);f.setAttribute("y",e.y);c.appendChild(f)}return c}; -Blockly.Xml.blockToDom_=function(a){var b=goog.dom.createDom("block");b.setAttribute("type",a.type);b.setAttribute("id",a.id);if(a.mutationToDom){var c=a.mutationToDom();c&&b.appendChild(c)}for(var d=0;c=a.inputList[d];d++)for(var e=0,f;f=c.fieldRow[e];e++)if(f.name&&f.EDITABLE){var g=goog.dom.createDom("field",null,f.getValue());g.setAttribute("name",f.name);b.appendChild(g)}a.comment&&(c=goog.dom.createDom("comment",null,a.comment.getText()),c.setAttribute("pinned",a.comment.isVisible()),d=a.comment.getBubbleSize(), -c.setAttribute("h",d.height),c.setAttribute("w",d.width),b.appendChild(c));d=!1;for(e=0;c=a.inputList[e];e++){var h;f=!0;c.type!=Blockly.DUMMY_INPUT&&(g=c.connection.targetBlock(),c.type==Blockly.INPUT_VALUE?(h=goog.dom.createDom("value"),d=!0):c.type==Blockly.NEXT_STATEMENT&&(h=goog.dom.createDom("statement")),g&&(h.appendChild(Blockly.Xml.blockToDom_(g)),f=!1),h.setAttribute("name",c.name),f||b.appendChild(h))}d&&b.setAttribute("inline",a.inputsInline);a.isCollapsed()&&b.setAttribute("collapsed", -!0);a.disabled&&b.setAttribute("disabled",!0);a.isDeletable()||b.setAttribute("deletable",!1);a.isMovable()||b.setAttribute("movable",!1);a.isEditable()||b.setAttribute("editable",!1);if(a=a.getNextBlock())h=goog.dom.createDom("next",null,Blockly.Xml.blockToDom_(a)),b.appendChild(h);return b};Blockly.Xml.domToText=function(a){return(new XMLSerializer).serializeToString(a)}; +Blockly.Xml={};Blockly.Xml.workspaceToDom=function(a){var b;Blockly.RTL&&(b=workarea.getWidth());var c=goog.dom.createDom("xml");a=a.getTopBlocks(!0);for(var d=0,e;e=a[d];d++){var f=Blockly.Xml.blockToDom_(e);e=e.getRelativeToSurfaceXY();f.setAttribute("x",Blockly.RTL?b-e.x:e.x);f.setAttribute("y",e.y);c.appendChild(f)}return c}; +Blockly.Xml.blockToDom_=function(a){var b=goog.dom.createDom("block");b.setAttribute("type",a.type);b.setAttribute("id",a.id);if(a.mutationToDom){var c=a.mutationToDom();c&&b.appendChild(c)}for(var d=0;c=a.inputList[d];d++)for(var e=0,f;f=c.fieldRow[e];e++)if(f.name&&f.EDITABLE){var g=goog.dom.createDom("field",null,f.getValue());g.setAttribute("name",f.name);b.appendChild(g)}if(c=a.getCommentText())c=goog.dom.createDom("comment",null,c),"object"==typeof a.comment&&(c.setAttribute("pinned",a.comment.isVisible()), +d=a.comment.getBubbleSize(),c.setAttribute("h",d.height),c.setAttribute("w",d.width)),b.appendChild(c);d=!1;for(e=0;c=a.inputList[e];e++){var h;f=!0;c.type!=Blockly.DUMMY_INPUT&&(g=c.connection.targetBlock(),c.type==Blockly.INPUT_VALUE?(h=goog.dom.createDom("value"),d=!0):c.type==Blockly.NEXT_STATEMENT&&(h=goog.dom.createDom("statement")),g&&(h.appendChild(Blockly.Xml.blockToDom_(g)),f=!1),h.setAttribute("name",c.name),f||b.appendChild(h))}d&&b.setAttribute("inline",a.inputsInline);a.isCollapsed()&& +b.setAttribute("collapsed",!0);a.disabled&&b.setAttribute("disabled",!0);a.isDeletable()||b.setAttribute("deletable",!1);a.isMovable()||b.setAttribute("movable",!1);a.isEditable()||b.setAttribute("editable",!1);if(a=a.getNextBlock())h=goog.dom.createDom("next",null,Blockly.Xml.blockToDom_(a)),b.appendChild(h);return b};Blockly.Xml.domToText=function(a){return(new XMLSerializer).serializeToString(a)}; Blockly.Xml.domToPrettyText=function(a){a=Blockly.Xml.domToText(a).split("<");for(var b="",c=1;c"!=d.slice(-2)&&(b+=" ")}a=a.join("\n");a=a.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g,"$1");return a.replace(/^\n/,"")}; Blockly.Xml.textToDom=function(a){a=(new DOMParser).parseFromString(a,"text/xml");if(!a||!a.firstChild||"xml"!=a.firstChild.nodeName.toLowerCase()||a.firstChild!==a.lastChild)throw"Blockly.Xml.textToDom did not obtain a valid XML tree.";return a.firstChild}; -Blockly.Xml.domToWorkspace=function(a,b){if(Blockly.RTL)var c=a.getMetrics().viewWidth;for(var d=0,e;e=b.childNodes[d];d++)if("block"==e.nodeName.toLowerCase()){var f=Blockly.Xml.domToBlock(a,e),g=parseInt(e.getAttribute("x"),10);e=parseInt(e.getAttribute("y"),10);isNaN(g)||isNaN(e)||f.moveBy(Blockly.RTL?c-g:g,e)}}; -Blockly.Xml.domToBlock=function(a,b,c){var d=null,e=b.getAttribute("type");if(!e)throw"Block type unspecified: \n"+b.outerHTML;var f=b.getAttribute("id");if(c&&f){d=Blockly.Block.getById(f,a);if(!d)throw"Couldn't get Block with id: "+f;f=d.getParent();d.workspace&&d.dispose(!0,!1,!0);d.fill(a,e);d.parent_=f}else d=Blockly.Block.obtain(a,e);d.svg_||d.initSvg();(f=b.getAttribute("inline"))&&d.setInputsInline("true"==f);(f=b.getAttribute("disabled"))&&d.setDisabled("true"==f);(f=b.getAttribute("deletable"))&& -d.setDeletable("true"==f);(f=b.getAttribute("movable"))&&d.setMovable("true"==f);(f=b.getAttribute("editable"))&&d.setEditable("true"==f);for(var g=null,f=0,h;h=b.childNodes[f];f++)if(3!=h.nodeType||!h.data.match(/^\s*$/)){for(var g=null,k=0,l;l=h.childNodes[k];k++)3==l.nodeType&&l.data.match(/^\s*$/)||(g=l);k=h.getAttribute("name");switch(h.nodeName.toLowerCase()){case "mutation":d.domToMutation&&d.domToMutation(h);break;case "comment":d.setCommentText(h.textContent);var p=h.getAttribute("pinned"); -p&&setTimeout(function(){d.comment.setVisible("true"==p)},1);g=parseInt(h.getAttribute("w"),10);h=parseInt(h.getAttribute("h"),10);isNaN(g)||isNaN(h)||d.comment.setBubbleSize(g,h);break;case "title":case "field":d.setFieldValue(h.textContent,k);break;case "value":case "statement":h=d.getInput(k);if(!h)throw"Input "+k+" does not exist in block "+e;if(g&&"block"==g.nodeName.toLowerCase())if(g=Blockly.Xml.domToBlock(a,g,c),g.outputConnection)h.connection.connect(g.outputConnection);else if(g.previousConnection)h.connection.connect(g.previousConnection); -else throw"Child block does not have output or previous statement.";break;case "next":if(g&&"block"==g.nodeName.toLowerCase()){if(!d.nextConnection)throw"Next statement does not exist.";if(d.nextConnection.targetConnection)throw"Next statement is already connected.";g=Blockly.Xml.domToBlock(a,g,c);if(!g.previousConnection)throw"Next block does not have previous statement.";d.nextConnection.connect(g.previousConnection)}}}(a=b.getAttribute("collapsed"))&&d.setCollapsed("true"==a);(a=d.getNextBlock())? -a.render():d.render();return d};Blockly.Xml.deleteNext=function(a){for(var b=0,c;c=a.childNodes[b];b++)if("next"==c.nodeName.toLowerCase()){a.removeChild(c);break}};window.Blockly||(window.Blockly={});window.Blockly.Xml||(window.Blockly.Xml={});window.Blockly.Xml.domToText=Blockly.Xml.domToText;window.Blockly.Xml.domToWorkspace=Blockly.Xml.domToWorkspace;window.Blockly.Xml.textToDom=Blockly.Xml.textToDom;window.Blockly.Xml.workspaceToDom=Blockly.Xml.workspaceToDom; -// Copyright 2012 Google Inc. Apache License 2.0 -Blockly.Workspace=function(a,b){this.getMetrics=a;this.setMetrics=b;this.isFlyout=!1;this.topBlocks_=[];this.maxBlocks=Infinity;Blockly.ConnectionDB.init(this)};Blockly.Workspace.SCAN_ANGLE=3;Blockly.Workspace.prototype.dragMode=!1;Blockly.Workspace.prototype.scrollX=0;Blockly.Workspace.prototype.scrollY=0;Blockly.Workspace.prototype.trashcan=null;Blockly.Workspace.prototype.fireChangeEventPid_=null;Blockly.Workspace.prototype.scrollbar=null; -Blockly.Workspace.prototype.createDom=function(){this.svgGroup_=Blockly.createSvgElement("g",{},null);this.svgBlockCanvas_=Blockly.createSvgElement("g",{},this.svgGroup_);this.svgBubbleCanvas_=Blockly.createSvgElement("g",{},this.svgGroup_);this.fireChangeEvent();return this.svgGroup_}; -Blockly.Workspace.prototype.dispose=function(){this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.svgBubbleCanvas_=this.svgBlockCanvas_=null;this.flyout_&&(this.flyout_.dispose(),this.flyout_=null);this.trashcan&&(this.trashcan.dispose(),this.trashcan=null)}; -Blockly.Workspace.prototype.addTrashcan=function(){if(Blockly.hasTrashcan&&!Blockly.readOnly){this.trashcan=new Blockly.Trashcan(this);var a=this.trashcan.createDom();this.svgGroup_.insertBefore(a,this.svgBlockCanvas_);this.trashcan.init()}};Blockly.Workspace.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.Workspace.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_}; -Blockly.Workspace.prototype.addTopBlock=function(a){this.topBlocks_.push(a);Blockly.Realtime.isEnabled()&&this==Blockly.mainWorkspace&&Blockly.Realtime.addTopBlock(a);this.fireChangeEvent()}; -Blockly.Workspace.prototype.removeTopBlock=function(a){for(var b=!1,c,d=0;c=this.topBlocks_[d];d++)if(c==a){this.topBlocks_.splice(d,1);b=!0;break}if(!b)throw"Block not present in workspace's list of top-most blocks.";Blockly.Realtime.isEnabled()&&this==Blockly.mainWorkspace&&Blockly.Realtime.removeTopBlock(a);this.fireChangeEvent()}; -Blockly.Workspace.prototype.getTopBlocks=function(a){var b=[].concat(this.topBlocks_);if(a&&1=this.remainingCapacity())){var b=Blockly.Xml.domToBlock(this,a),c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(a)){Blockly.RTL&&(c=-c);do for(var d=!1,e=this.getAllBlocks(),f=0,g;g=e[f];f++)g=g.getRelativeToSurfaceXY(),1>=Math.abs(c-g.x)&&1>=Math.abs(a-g.y)&&(c=Blockly.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,a+=2*Blockly.SNAP_RADIUS,d=!0);while(d);b.moveBy(c, -a)}b.select()}};Blockly.Workspace.prototype.remainingCapacity=function(){return Infinity==this.maxBlocks?Infinity:this.maxBlocks-this.getAllBlocks().length};Blockly.Workspace.prototype.recordDeleteAreas=function(){this.deleteAreaTrash_=this.trashcan?this.trashcan.getRect():null;this.deleteAreaToolbox_=this.flyout_?this.flyout_.getRect():this.toolbox_?this.toolbox_.getRect():null}; -Blockly.Workspace.prototype.isDeleteArea=function(a){a=Blockly.mouseToSvg(a);a=new goog.math.Coordinate(a.x,a.y);if(this.deleteAreaTrash_){if(this.deleteAreaTrash_.contains(a))return this.trashcan.setOpen_(!0),Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;this.trashcan.setOpen_(!1)}if(this.deleteAreaToolbox_&&this.deleteAreaToolbox_.contains(a))return Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);return!1}; -Blockly.Workspace.prototype.clear=Blockly.Workspace.prototype.clear; -// Copyright 2012 Google Inc. Apache License 2.0 -Blockly.Bubble=function(a,b,c,d,e,f,g){var h=Blockly.Bubble.ARROW_ANGLE;Blockly.RTL&&(h=-h);this.arrow_radians_=goog.math.toRadians(h);this.workspace_=a;this.content_=b;this.shape_=c;a.getBubbleCanvas().appendChild(this.createDom_(b,!(!f||!g)));this.setAnchorLocation(d,e);f&&g||(a=this.content_.getBBox(),f=a.width+2*Blockly.Bubble.BORDER_WIDTH,g=a.height+2*Blockly.Bubble.BORDER_WIDTH);this.setBubbleSize(f,g);this.positionBubble_();this.renderArrow_();this.rendered_=!0;Blockly.readOnly||(Blockly.bindEvent_(this.bubbleBack_, -"mousedown",this,this.bubbleMouseDown_),this.resizeGroup_&&Blockly.bindEvent_(this.resizeGroup_,"mousedown",this,this.resizeMouseDown_))};Blockly.Bubble.BORDER_WIDTH=6;Blockly.Bubble.ARROW_THICKNESS=10;Blockly.Bubble.ARROW_ANGLE=20;Blockly.Bubble.ARROW_BEND=4;Blockly.Bubble.ANCHOR_RADIUS=8;Blockly.Bubble.onMouseUpWrapper_=null;Blockly.Bubble.onMouseMoveWrapper_=null; -Blockly.Bubble.unbindDragEvents_=function(){Blockly.Bubble.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_),Blockly.Bubble.onMouseUpWrapper_=null);Blockly.Bubble.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_),Blockly.Bubble.onMouseMoveWrapper_=null)};Blockly.Bubble.prototype.rendered_=!1;Blockly.Bubble.prototype.anchorX_=0;Blockly.Bubble.prototype.anchorY_=0;Blockly.Bubble.prototype.relativeLeft_=0;Blockly.Bubble.prototype.relativeTop_=0; -Blockly.Bubble.prototype.width_=0;Blockly.Bubble.prototype.height_=0;Blockly.Bubble.prototype.autoLayout_=!0; -Blockly.Bubble.prototype.createDom_=function(a,b){this.bubbleGroup_=Blockly.createSvgElement("g",{},null);var c=Blockly.createSvgElement("g",{filter:"url(#blocklyEmboss)"},this.bubbleGroup_);this.bubbleArrow_=Blockly.createSvgElement("path",{},c);this.bubbleBack_=Blockly.createSvgElement("rect",{"class":"blocklyDraggable",x:0,y:0,rx:Blockly.Bubble.BORDER_WIDTH,ry:Blockly.Bubble.BORDER_WIDTH},c);b?(this.resizeGroup_=Blockly.createSvgElement("g",{"class":Blockly.RTL?"blocklyResizeSW":"blocklyResizeSE"}, -this.bubbleGroup_),c=2*Blockly.Bubble.BORDER_WIDTH,Blockly.createSvgElement("polygon",{points:"0,x x,x x,0".replace(/x/g,c.toString())},this.resizeGroup_),Blockly.createSvgElement("line",{"class":"blocklyResizeLine",x1:c/3,y1:c-1,x2:c-1,y2:c/3},this.resizeGroup_),Blockly.createSvgElement("line",{"class":"blocklyResizeLine",x1:2*c/3,y1:c-1,x2:c-1,y2:2*c/3},this.resizeGroup_)):this.resizeGroup_=null;this.bubbleGroup_.appendChild(a);return this.bubbleGroup_}; -Blockly.Bubble.prototype.bubbleMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.isRightButton(a)||Blockly.isTargetInput_(a)||(Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED),this.dragDeltaX=Blockly.RTL?this.relativeLeft_+a.clientX:this.relativeLeft_-a.clientX,this.dragDeltaY=this.relativeTop_-a.clientY,Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,Blockly.Bubble.unbindDragEvents_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEvent_(document, -"mousemove",this,this.bubbleMouseMove_),Blockly.hideChaff(),a.stopPropagation())};Blockly.Bubble.prototype.bubbleMouseMove_=function(a){this.autoLayout_=!1;this.relativeLeft_=Blockly.RTL?this.dragDeltaX-a.clientX:this.dragDeltaX+a.clientX;this.relativeTop_=this.dragDeltaY+a.clientY;this.positionBubble_();this.renderArrow_()}; -Blockly.Bubble.prototype.resizeMouseDown_=function(a){this.promote_();Blockly.Bubble.unbindDragEvents_();Blockly.isRightButton(a)||(Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED),this.resizeDeltaWidth=Blockly.RTL?this.width_+a.clientX:this.width_-a.clientX,this.resizeDeltaHeight=this.height_-a.clientY,Blockly.Bubble.onMouseUpWrapper_=Blockly.bindEvent_(document,"mouseup",this,Blockly.Bubble.unbindDragEvents_),Blockly.Bubble.onMouseMoveWrapper_=Blockly.bindEvent_(document,"mousemove",this,this.resizeMouseMove_), -Blockly.hideChaff(),a.stopPropagation())};Blockly.Bubble.prototype.resizeMouseMove_=function(a){this.autoLayout_=!1;var b=this.resizeDeltaWidth,c=this.resizeDeltaHeight+a.clientY,b=Blockly.RTL?b-a.clientX:b+a.clientX;this.setBubbleSize(b,c);Blockly.RTL&&this.positionBubble_()};Blockly.Bubble.prototype.registerResizeEvent=function(a,b){Blockly.bindEvent_(this.bubbleGroup_,"resize",a,b)};Blockly.Bubble.prototype.promote_=function(){this.bubbleGroup_.parentNode.appendChild(this.bubbleGroup_)}; -Blockly.Bubble.prototype.setAnchorLocation=function(a,b){this.anchorX_=a;this.anchorY_=b;this.rendered_&&this.positionBubble_()}; -Blockly.Bubble.prototype.layoutBubble_=function(){var a=-this.width_/4,b=-this.height_-Blockly.BlockSvg.MIN_BLOCK_Y,c=this.workspace_.getMetrics();Blockly.RTL?this.anchorX_-c.viewLeft-a-this.width_c.viewWidth&&(a=this.anchorX_-c.viewLeft-c.viewWidth):this.anchorX_+ae&&(g=2*Math.PI-g);var h=g+Math.PI/2;h>2*Math.PI&&(h-=2*Math.PI);var k=Math.sin(h),l=Math.cos(h),p=this.getBubbleSize(),h=(p.width+p.height)/Blockly.Bubble.ARROW_THICKNESS,h=Math.min(h,p.width,p.height)/2,p=1-Blockly.Bubble.ANCHOR_RADIUS/f,d=b+p*d,e=c+ -p*e,p=b+h*l,n=c+h*k,b=b-h*l,c=c-h*k,k=g+this.arrow_radians_;k>2*Math.PI&&(k-=2*Math.PI);g=Math.sin(k)*f/Blockly.Bubble.ARROW_BEND;f=Math.cos(k)*f/Blockly.Bubble.ARROW_BEND;a.push("M"+p+","+n);a.push("C"+(p+f)+","+(n+g)+" "+d+","+e+" "+d+","+e);a.push("C"+d+","+e+" "+(b+f)+","+(c+g)+" "+b+","+c)}a.push("z");this.bubbleArrow_.setAttribute("d",a.join(" "))};Blockly.Bubble.prototype.setColour=function(a){this.bubbleBack_.setAttribute("fill",a);this.bubbleArrow_.setAttribute("fill",a)}; -Blockly.Bubble.prototype.dispose=function(){Blockly.Bubble.unbindDragEvents_();goog.dom.removeNode(this.bubbleGroup_);this.shape_=this.content_=this.workspace_=this.bubbleGroup_=null}; -// Copyright 2013 Google Inc. Apache License 2.0 -Blockly.Icon=function(a){this.block_=a};Blockly.Icon.RADIUS=8;Blockly.Icon.prototype.bubble_=null;Blockly.Icon.prototype.iconX_=0;Blockly.Icon.prototype.iconY_=0;Blockly.Icon.prototype.createIcon_=function(){this.iconGroup_=Blockly.createSvgElement("g",{},null);this.block_.getSvgRoot().appendChild(this.iconGroup_);Blockly.bindEvent_(this.iconGroup_,"mouseup",this,this.iconClick_);this.updateEditable()}; -Blockly.Icon.prototype.dispose=function(){goog.dom.removeNode(this.iconGroup_);this.iconGroup_=null;this.setVisible(!1);this.block_=null};Blockly.Icon.prototype.updateEditable=function(){this.block_.isInFlyout?Blockly.removeClass_(this.iconGroup_,"blocklyIconGroup"):Blockly.addClass_(this.iconGroup_,"blocklyIconGroup")};Blockly.Icon.prototype.isVisible=function(){return!!this.bubble_};Blockly.Icon.prototype.iconClick_=function(a){this.block_.isInFlyout||this.setVisible(!this.isVisible())}; -Blockly.Icon.prototype.updateColour=function(){if(this.isVisible()){var a=Blockly.makeColour(this.block_.getColour());this.bubble_.setColour(a)}}; -Blockly.Icon.prototype.renderIcon=function(a){if(this.block_.isCollapsed())return this.iconGroup_.setAttribute("display","none"),a;this.iconGroup_.setAttribute("display","block");var b=2*Blockly.Icon.RADIUS;Blockly.RTL&&(a-=b);this.iconGroup_.setAttribute("transform","translate("+a+", 5)");this.computeIconLocation();return a=Blockly.RTL?a-Blockly.BlockSvg.SEP_SPACE_X:a+(b+Blockly.BlockSvg.SEP_SPACE_X)}; -Blockly.Icon.prototype.setIconLocation=function(a,b){this.iconX_=a;this.iconY_=b;this.isVisible()&&this.bubble_.setAnchorLocation(a,b)};Blockly.Icon.prototype.computeIconLocation=function(){var a=this.block_.getRelativeToSurfaceXY(),b=Blockly.getRelativeXY_(this.iconGroup_),c=a.x+b.x+Blockly.Icon.RADIUS,a=a.y+b.y+Blockly.Icon.RADIUS;c===this.iconX_&&a===this.iconY_||this.setIconLocation(c,a)};Blockly.Icon.prototype.getIconLocation=function(){return{x:this.iconX_,y:this.iconY_}}; -// Copyright 2011 Google Inc. Apache License 2.0 -Blockly.Comment=function(a){Blockly.Comment.superClass_.constructor.call(this,a);this.createIcon_()};goog.inherits(Blockly.Comment,Blockly.Icon);Blockly.Comment.prototype.text_="";Blockly.Comment.prototype.width_=160;Blockly.Comment.prototype.height_=80; -Blockly.Comment.prototype.createIcon_=function(){Blockly.Icon.prototype.createIcon_.call(this);Blockly.createSvgElement("circle",{"class":"blocklyIconShield",r:Blockly.Icon.RADIUS,cx:Blockly.Icon.RADIUS,cy:Blockly.Icon.RADIUS},this.iconGroup_);this.iconMark_=Blockly.createSvgElement("text",{"class":"blocklyIconMark",x:Blockly.Icon.RADIUS,y:2*Blockly.Icon.RADIUS-3},this.iconGroup_);this.iconMark_.appendChild(document.createTextNode("?"))}; -Blockly.Comment.prototype.createEditor_=function(){this.foreignObject_=Blockly.createSvgElement("foreignObject",{x:Blockly.Bubble.BORDER_WIDTH,y:Blockly.Bubble.BORDER_WIDTH},null);var a=document.createElementNS(Blockly.HTML_NS,"body");a.setAttribute("xmlns",Blockly.HTML_NS);a.className="blocklyMinimalBody";this.textarea_=document.createElementNS(Blockly.HTML_NS,"textarea");this.textarea_.className="blocklyCommentTextarea";this.textarea_.setAttribute("dir",Blockly.RTL?"RTL":"LTR");a.appendChild(this.textarea_); -this.foreignObject_.appendChild(a);Blockly.bindEvent_(this.textarea_,"mouseup",this,this.textareaFocus_);return this.foreignObject_};Blockly.Comment.prototype.updateEditable=function(){this.isVisible()&&(this.setVisible(!1),this.setVisible(!0));Blockly.Icon.prototype.updateEditable.call(this)}; -Blockly.Comment.prototype.resizeBubble_=function(){var a=this.bubble_.getBubbleSize(),b=2*Blockly.Bubble.BORDER_WIDTH;this.foreignObject_.setAttribute("width",a.width-b);this.foreignObject_.setAttribute("height",a.height-b);this.textarea_.style.width=a.width-b-4+"px";this.textarea_.style.height=a.height-b-4+"px"}; -Blockly.Comment.prototype.setVisible=function(a){if(a!=this.isVisible())if(!this.block_.isEditable()&&!this.textarea_||goog.userAgent.IE)Blockly.Warning.prototype.setVisible.call(this,a);else{var b=this.getText(),c=this.getBubbleSize();a?(this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svg_.svgPath_,this.iconX_,this.iconY_,this.width_,this.height_),this.bubble_.registerResizeEvent(this,this.resizeBubble_),this.updateColour(),this.text_=null):(this.bubble_.dispose(), -this.foreignObject_=this.textarea_=this.bubble_=null);this.setText(b);this.setBubbleSize(c.width,c.height)}};Blockly.Comment.prototype.textareaFocus_=function(a){this.bubble_.promote_();this.textarea_.focus()};Blockly.Comment.prototype.getBubbleSize=function(){return this.isVisible()?this.bubble_.getBubbleSize():{width:this.width_,height:this.height_}};Blockly.Comment.prototype.setBubbleSize=function(a,b){this.textarea_?this.bubble_.setBubbleSize(a,b):(this.width_=a,this.height_=b)}; -Blockly.Comment.prototype.getText=function(){return this.textarea_?this.textarea_.value:this.text_};Blockly.Comment.prototype.setText=function(a){this.textarea_?this.textarea_.value=a:this.text_=a};Blockly.Comment.prototype.dispose=function(){this.block_.comment=null;Blockly.Icon.prototype.dispose.call(this)}; -// Copyright 2011 Google Inc. Apache License 2.0 -Blockly.Connection=function(a,b){this.sourceBlock_=a;this.targetConnection=null;this.type=b;this.y_=this.x_=0;this.inDB_=!1;this.dbList_=this.sourceBlock_.workspace.connectionDBList}; -Blockly.Connection.prototype.dispose=function(){if(this.targetConnection)throw"Disconnect connection before disposing of it.";this.inDB_&&this.dbList_[this.type].removeConnection_(this);this.inDB_=!1;Blockly.highlightedConnection_==this&&(Blockly.highlightedConnection_=null);Blockly.localConnection_==this&&(Blockly.localConnection_=null)};Blockly.Connection.prototype.isSuperior=function(){return this.type==Blockly.INPUT_VALUE||this.type==Blockly.NEXT_STATEMENT}; -Blockly.Connection.prototype.connect=function(a){if(this.sourceBlock_==a.sourceBlock_)throw"Attempted to connect a block to itself.";if(this.sourceBlock_.workspace!==a.sourceBlock_.workspace)throw"Blocks are on different workspaces.";if(Blockly.OPPOSITE_TYPE[this.type]!=a.type)throw"Attempt to connect incompatible types.";if(this.type==Blockly.INPUT_VALUE||this.type==Blockly.OUTPUT_VALUE){if(this.targetConnection)throw"Source connection already connected (value).";if(a.targetConnection){var b=a.targetBlock(); -b.setParent(null);if(!b.outputConnection)throw"Orphan block does not have an output connection.";for(var c=this.sourceBlock_;c=Blockly.Connection.singleConnection_(c,b);)if(c.targetBlock())c=c.targetBlock();else{c.connect(b.outputConnection);b=null;break}b&&window.setTimeout(function(){b.outputConnection.bumpAwayFrom_(a)},Blockly.BUMP_DELAY)}}else{if(this.targetConnection)throw"Source connection already connected (block).";if(a.targetConnection){if(this.type!=Blockly.PREVIOUS_STATEMENT)throw"Can only do a mid-stack connection with the top of a block."; -b=a.targetBlock();b.setParent(null);if(!b.previousConnection)throw"Orphan block does not have a previous connection.";for(c=this.sourceBlock_;c.nextConnection;)if(c.nextConnection.targetConnection)c=c.getNextBlock();else{b.previousConnection.checkType_(c.nextConnection)&&(c.nextConnection.connect(b.previousConnection),b=null);break}b&&window.setTimeout(function(){b.previousConnection.bumpAwayFrom_(a)},Blockly.BUMP_DELAY)}}var d;this.isSuperior()?(c=this.sourceBlock_,d=a.sourceBlock_):(c=a.sourceBlock_, -d=this.sourceBlock_);this.targetConnection=a;a.targetConnection=this;d.setParent(c);c.rendered&&c.svg_.updateDisabled();d.rendered&&d.svg_.updateDisabled();c.rendered&&d.rendered&&(this.type==Blockly.NEXT_STATEMENT||this.type==Blockly.PREVIOUS_STATEMENT?d.render():c.render())};Blockly.Connection.singleConnection_=function(a,b){for(var c=!1,d=0;da.y_)c=d;else{b=d;break}}this.splice(b,0,a);a.inDB_=!0}; -Blockly.ConnectionDB.prototype.removeConnection_=function(a){if(!a.inDB_)throw"Connection not in database.";a.inDB_=!1;for(var b=0,c=this.length-2,d=c;b=e.height&&(k-=h.height);Blockly.RTL?h.width>=a.clientX&&(d+=h.width):a.clientX+h.width>=e.width&&(d-=h.width);Blockly.WidgetDiv.position(d,k,e,f);c.setAllowAutoFocus(!0);setTimeout(function(){g.focus()},1);Blockly.ContextMenu.currentBlock=null}else Blockly.ContextMenu.hide()}; -Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null};Blockly.ContextMenu.callbackFactory=function(a,b){return function(){var c=Blockly.Xml.domToBlock(a.workspace,b),d=a.getRelativeToSurfaceXY();d.x=Blockly.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);c.select()}}; -// Copyright 2012 Google Inc. Apache License 2.0 -Blockly.Field=function(a){this.sourceBlock_=null;this.fieldGroup_=Blockly.createSvgElement("g",{},null);this.borderRect_=Blockly.createSvgElement("rect",{rx:4,ry:4,x:-Blockly.BlockSvg.SEP_SPACE_X/2,y:-12,height:16},this.fieldGroup_);this.textElement_=Blockly.createSvgElement("text",{"class":"blocklyText"},this.fieldGroup_);this.size_={height:25,width:0};this.setText(a);this.visible_=!0};Blockly.Field.prototype.clone=function(){goog.asserts.fail("There should never be an instance of Field, only its derived classes.")}; -Blockly.Field.NBSP="\u00a0";Blockly.Field.prototype.EDITABLE=!0;Blockly.Field.prototype.init=function(a){if(this.sourceBlock_)throw"Field has already been initialized once.";this.sourceBlock_=a;this.updateEditable();a.getSvgRoot().appendChild(this.fieldGroup_);this.mouseUpWrapper_=Blockly.bindEvent_(this.fieldGroup_,"mouseup",this,this.onMouseUp_);this.setText(null)}; -Blockly.Field.prototype.dispose=function(){this.mouseUpWrapper_&&(Blockly.unbindEvent_(this.mouseUpWrapper_),this.mouseUpWrapper_=null);this.sourceBlock_=null;goog.dom.removeNode(this.fieldGroup_);this.borderRect_=this.textElement_=this.fieldGroup_=null}; -Blockly.Field.prototype.updateEditable=function(){this.EDITABLE&&(this.sourceBlock_.isEditable()?(Blockly.addClass_(this.fieldGroup_,"blocklyEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyNoNEditableText"),this.fieldGroup_.style.cursor=this.CURSOR):(Blockly.addClass_(this.fieldGroup_,"blocklyNonEditableText"),Blockly.removeClass_(this.fieldGroup_,"blocklyEditableText"),this.fieldGroup_.style.cursor=""))};Blockly.Field.prototype.isVisible=function(){return this.visible_}; -Blockly.Field.prototype.setVisible=function(a){this.visible_=a;this.getRootElement().style.display=a?"block":"none";this.render_()};Blockly.Field.prototype.getRootElement=function(){return this.fieldGroup_};Blockly.Field.prototype.render_=function(){try{var a=this.textElement_.getComputedTextLength()}catch(b){a=8*this.textElement_.childNodes[0].length}this.borderRect_&&this.borderRect_.setAttribute("width",a+Blockly.BlockSvg.SEP_SPACE_X);this.size_.width=a}; -Blockly.Field.prototype.getSize=function(){this.size_.width||this.render_();return this.size_};Blockly.Field.prototype.getText=function(){return this.text_};Blockly.Field.prototype.setText=function(a){null!==a&&a!==this.text_&&(this.text_=a,this.updateTextNode_(),this.sourceBlock_&&this.sourceBlock_.rendered&&(this.sourceBlock_.render(),this.sourceBlock_.bumpNeighbours_(),this.sourceBlock_.workspace.fireChangeEvent()))}; -Blockly.Field.prototype.updateTextNode_=function(){var a=this.text_;goog.dom.removeChildren(this.textElement_);a=a.replace(/\s/g,Blockly.Field.NBSP);Blockly.RTL&&a&&(a+="\u200f");a||(a=Blockly.Field.NBSP);a=document.createTextNode(a);this.textElement_.appendChild(a);this.size_.width=0};Blockly.Field.prototype.getValue=function(){return this.getText()};Blockly.Field.prototype.setValue=function(a){this.setText(a)}; -Blockly.Field.prototype.onMouseUp_=function(a){if(!goog.userAgent.IPHONE&&!goog.userAgent.IPAD||0===a.layerX||0===a.layerY)Blockly.isRightButton(a)||2!=Blockly.Block.dragMode_&&this.sourceBlock_.isEditable()&&this.showEditor_()};Blockly.Field.prototype.setTooltip=function(a){}; -// Copyright 2011 Google Inc. Apache License 2.0 -Blockly.Tooltip={};Blockly.Tooltip.visible=!1;Blockly.Tooltip.LIMIT=50;Blockly.Tooltip.mouseOutPid_=0;Blockly.Tooltip.showPid_=0;Blockly.Tooltip.lastXY_={x:0,y:0};Blockly.Tooltip.element_=null;Blockly.Tooltip.poisonedElement_=null;Blockly.Tooltip.svgGroup_=null;Blockly.Tooltip.svgText_=null;Blockly.Tooltip.svgBackground_=null;Blockly.Tooltip.svgShadow_=null;Blockly.Tooltip.OFFSET_X=0;Blockly.Tooltip.OFFSET_Y=10;Blockly.Tooltip.RADIUS_OK=10;Blockly.Tooltip.HOVER_MS=1E3;Blockly.Tooltip.MARGINS=5; -Blockly.Tooltip.createDom=function(){var a=Blockly.createSvgElement("g",{"class":"blocklyHidden"},null);Blockly.Tooltip.svgGroup_=a;Blockly.Tooltip.svgShadow_=Blockly.createSvgElement("rect",{"class":"blocklyTooltipShadow",x:2,y:2},a);Blockly.Tooltip.svgBackground_=Blockly.createSvgElement("rect",{"class":"blocklyTooltipBackground"},a);Blockly.Tooltip.svgText_=Blockly.createSvgElement("text",{"class":"blocklyTooltipText"},a);return a}; -Blockly.Tooltip.bindMouseEvents=function(a){Blockly.bindEvent_(a,"mouseover",null,Blockly.Tooltip.onMouseOver_);Blockly.bindEvent_(a,"mouseout",null,Blockly.Tooltip.onMouseOut_);Blockly.bindEvent_(a,"mousemove",null,Blockly.Tooltip.onMouseMove_)};Blockly.Tooltip.onMouseOver_=function(a){for(a=a.target;!goog.isString(a.tooltip)&&!goog.isFunction(a.tooltip);)a=a.tooltip;Blockly.Tooltip.element_!=a&&(Blockly.Tooltip.hide(),Blockly.Tooltip.poisonedElement_=null,Blockly.Tooltip.element_=a);window.clearTimeout(Blockly.Tooltip.mouseOutPid_)}; -Blockly.Tooltip.onMouseOut_=function(a){Blockly.Tooltip.mouseOutPid_=window.setTimeout(function(){Blockly.Tooltip.element_=null;Blockly.Tooltip.poisonedElement_=null;Blockly.Tooltip.hide()},1);window.clearTimeout(Blockly.Tooltip.showPid_)}; -Blockly.Tooltip.onMouseMove_=function(a){if(Blockly.Tooltip.element_&&Blockly.Tooltip.element_.tooltip&&0==Blockly.Block.dragMode_&&!Blockly.WidgetDiv.isVisible())if(Blockly.Tooltip.visible){a=Blockly.mouseToSvg(a);var b=Blockly.Tooltip.lastXY_.y-a.y;Math.sqrt(Math.pow(Blockly.Tooltip.lastXY_.x-a.x,2)+Math.pow(b,2))>Blockly.Tooltip.RADIUS_OK&&Blockly.Tooltip.hide()}else Blockly.Tooltip.poisonedElement_!=Blockly.Tooltip.element_&&(window.clearTimeout(Blockly.Tooltip.showPid_),Blockly.Tooltip.lastXY_= -Blockly.mouseToSvg(a),Blockly.Tooltip.showPid_=window.setTimeout(Blockly.Tooltip.show_,Blockly.Tooltip.HOVER_MS))};Blockly.Tooltip.hide=function(){Blockly.Tooltip.visible&&(Blockly.Tooltip.visible=!1,Blockly.Tooltip.svgGroup_&&(Blockly.Tooltip.svgGroup_.style.display="none"));window.clearTimeout(Blockly.Tooltip.showPid_)}; -Blockly.Tooltip.show_=function(){Blockly.Tooltip.poisonedElement_=Blockly.Tooltip.element_;if(Blockly.Tooltip.svgGroup_){goog.dom.removeChildren(Blockly.Tooltip.svgText_);var a=Blockly.Tooltip.element_.tooltip;goog.isFunction(a)&&(a=a());for(var a=Blockly.Tooltip.wrap_(a,Blockly.Tooltip.LIMIT),a=a.split("\n"),b=0;bd.height&&(b-=a.height+2*Blockly.Tooltip.OFFSET_Y);Blockly.RTL?c=Math.max(Blockly.Tooltip.MARGINS,c):c+a.width>d.width-2*Blockly.Tooltip.MARGINS&&(c=d.width-a.width-2*Blockly.Tooltip.MARGINS);Blockly.Tooltip.svgGroup_.setAttribute("transform","translate("+c+","+b+")")}}; -Blockly.Tooltip.wrap_=function(a,b){if(a.length<=b)return a;for(var c=a.trim().split(/\s+/),d=0;db&&(b=c[d].length);var e,d=-Infinity,f,g=1;do{e=d;f=a;for(var h=[],k=c.length/g,l=1,d=0;de);return f}; -Blockly.Tooltip.wrapScore_=function(a,b,c){for(var d=[0],e=[],f=0;fd&&(d=h,e=g)}return e?Blockly.Tooltip.wrapMutate_(a,e,c):b};Blockly.Tooltip.wrapToText_=function(a,b){for(var c=[],d=0;d=this.remainingCapacity())){var b=Blockly.Xml.domToBlock(this,a),c=parseInt(a.getAttribute("x"),10);a=parseInt(a.getAttribute("y"),10);if(!isNaN(c)&&!isNaN(a)){Blockly.RTL&&(c=-c);do for(var d=!1,e=this.getAllBlocks(),f=0,g;g=e[f];f++)g=g.getRelativeToSurfaceXY(),1>=Math.abs(c-g.x)&&1>=Math.abs(a-g.y)&&(c=Blockly.RTL?c-Blockly.SNAP_RADIUS:c+Blockly.SNAP_RADIUS,a+=2*Blockly.SNAP_RADIUS,d=!0);while(d);b.moveBy(c, +a)}b.select()}};Blockly.WorkspaceSvg.prototype.recordDeleteAreas=function(){this.deleteAreaTrash_=this.trashcan?this.trashcan.getRect():null;this.deleteAreaToolbox_=this.flyout_?this.flyout_.getRect():this.toolbox_?this.toolbox_.getRect():null}; +Blockly.WorkspaceSvg.prototype.isDeleteArea=function(a){a=Blockly.mouseToSvg(a);a=new goog.math.Coordinate(a.x,a.y);if(this.deleteAreaTrash_){if(this.deleteAreaTrash_.contains(a))return this.trashcan.setOpen_(!0),Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;this.trashcan.setOpen_(!1)}if(this.deleteAreaToolbox_&&this.deleteAreaToolbox_.contains(a))return Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE),!0;Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);return!1}; +Blockly.WorkspaceSvg.prototype.clear=Blockly.WorkspaceSvg.prototype.clear; // Copyright 2012 Google Inc. Apache License 2.0 Blockly.Mutator=function(a){Blockly.Mutator.superClass_.constructor.call(this,null);this.quarkXml_=[];for(var b=0;ba||Math.abs(this.workspaceHeight_-b)>a)this.workspaceWidth_=d,this.workspaceHeight_=b,this.bubble_.setBubbleSize(d+a,b+a),this.svgDialog_.setAttribute("width",this.workspaceWidth_),this.svgDialog_.setAttribute("height", this.workspaceHeight_);Blockly.RTL&&(a="translate("+this.workspaceWidth_+",0)",this.workspace_.getCanvas().setAttribute("transform",a))}; -Blockly.Mutator.prototype.setVisible=function(a){if(a!=this.isVisible())if(a){this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svg_.svgPath_,this.iconX_,this.iconY_,null,null);var b=this;this.workspace_.flyout_.init(this.workspace_);this.workspace_.flyout_.show(this.quarkXml_);this.rootBlock_=this.block_.decompose(this.workspace_);a=this.rootBlock_.getDescendants();for(var c=0,d;d=a[c];c++)d.render();this.rootBlock_.setMovable(!1);this.rootBlock_.setDeletable(!1); +Blockly.Mutator.prototype.setVisible=function(a){if(a!=this.isVisible())if(a){this.bubble_=new Blockly.Bubble(this.block_.workspace,this.createEditor_(),this.block_.svgPath_,this.iconX_,this.iconY_,null,null);var b=this;this.workspace_.flyout_.init(this.workspace_);this.workspace_.flyout_.show(this.quarkXml_);this.rootBlock_=this.block_.decompose(this.workspace_);a=this.rootBlock_.getDescendants();for(var c=0,d;d=a[c];c++)d.render();this.rootBlock_.setMovable(!1);this.rootBlock_.setDeletable(!1); a=2*this.workspace_.flyout_.CORNER_RADIUS;c=this.workspace_.flyout_.width_+a;Blockly.RTL&&(c=-c);this.rootBlock_.moveBy(c,a);this.block_.saveConnections&&(this.block_.saveConnections(this.rootBlock_),this.sourceListener_=Blockly.bindEvent_(this.block_.workspace.getCanvas(),"blocklyWorkspaceChange",this.block_,function(){b.block_.saveConnections(b.rootBlock_)}));this.resizeBubble_();Blockly.bindEvent_(this.workspace_.getCanvas(),"blocklyWorkspaceChange",this.block_,function(){b.workspaceChanged_()}); this.updateColour()}else this.svgDialog_=null,this.workspace_.dispose(),this.rootBlock_=this.workspace_=null,this.bubble_.dispose(),this.bubble_=null,this.workspaceHeight_=this.workspaceWidth_=0,this.sourceListener_&&(Blockly.unbindEvent_(this.sourceListener_),this.sourceListener_=null)}; -Blockly.Mutator.prototype.workspaceChanged_=function(){if(0==Blockly.Block.dragMode_)for(var a=this.workspace_.getTopBlocks(!1),b=0,c;c=a[b];b++){var d=c.getRelativeToSurfaceXY(),e=c.getHeightWidth();20>d.y+e.height&&c.moveBy(0,20-e.height-d.y)}this.rootBlock_.workspace==this.workspace_&&(a=this.block_.rendered,this.block_.rendered=!1,this.block_.compose(this.rootBlock_),this.block_.rendered=a,this.block_.rendered&&this.block_.render(),this.resizeBubble_(),this.block_.workspace.fireChangeEvent())}; +Blockly.Mutator.prototype.workspaceChanged_=function(){if(0==Blockly.dragMode_)for(var a=this.workspace_.getTopBlocks(!1),b=0,c;c=a[b];b++){var d=c.getRelativeToSurfaceXY(),e=c.getHeightWidth();20>d.y+e.height&&c.moveBy(0,20-e.height-d.y)}this.rootBlock_.workspace==this.workspace_&&(a=this.block_.rendered,this.block_.rendered=!1,this.block_.compose(this.rootBlock_),this.block_.rendered=a,this.block_.initSvg(),this.block_.rendered&&this.block_.render(),this.resizeBubble_(),this.block_.workspace.fireChangeEvent())}; Blockly.Mutator.prototype.getFlyoutMetrics_=function(){var a=0;Blockly.RTL&&(a+=this.workspaceWidth_);return{viewHeight:this.workspaceHeight_,viewWidth:0,absoluteTop:0,absoluteLeft:a}};Blockly.Mutator.prototype.dispose=function(){this.block_.mutator=null;Blockly.Icon.prototype.dispose.call(this)}; // Copyright 2012 Google Inc. Apache License 2.0 Blockly.Warning=function(a){Blockly.Warning.superClass_.constructor.call(this,a);this.createIcon_()};goog.inherits(Blockly.Warning,Blockly.Icon);Blockly.Warning.textToDom_=function(a){var b=Blockly.createSvgElement("text",{"class":"blocklyText blocklyBubbleText",y:Blockly.Bubble.BORDER_WIDTH},null);a=a.split("\n");for(var c=0;cthis.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&Blockly.comments&&(d={enabled:!0},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT,d.callback=function(){b.setCommentText(null)}): -(d.text=Blockly.Msg.ADD_COMMENT,d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=0;d=a.clientX&&0==a.clientY&&0==a.button)){Blockly.removeAllRanges();var c=a.clientX-b.startDragMouseX,d=a.clientY-b.startDragMouseY;1==Blockly.Block.dragMode_&&Math.sqrt(Math.pow(c,2)+Math.pow(d,2))>Blockly.DRAG_RADIUS&&(Blockly.Block.dragMode_=2,b.setParent(null),b.setDragging_(!0),b.workspace.recordDeleteAreas());if(2==Blockly.Block.dragMode_){var e=b.startDragX+c,f=b.startDragY+d; -b.svg_.getRootElement().setAttribute("transform","translate("+e+", "+f+")");for(e=0;e=e.height&&(k-=h.height);Blockly.RTL?h.width>=a.clientX&&(d+=h.width):a.clientX+h.width>=e.width&&(d-=h.width);Blockly.WidgetDiv.position(d,k,e,f);c.setAllowAutoFocus(!0);setTimeout(function(){g.focus()},1);Blockly.ContextMenu.currentBlock=null}else Blockly.ContextMenu.hide()}; +Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null};Blockly.ContextMenu.callbackFactory=function(a,b){return function(){var c=Blockly.Xml.domToBlock(a.workspace,b),d=a.getRelativeToSurfaceXY();d.x=Blockly.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);c.select()}}; +// Copyright 2012 Google Inc. Apache License 2.0 +Blockly.BlockSvg=function(){this.svgGroup_=Blockly.createSvgElement("g",{},null);this.svgPathDark_=Blockly.createSvgElement("path",{"class":"blocklyPathDark",transform:"translate(1, 1)"},this.svgGroup_);this.svgPath_=Blockly.createSvgElement("path",{"class":"blocklyPath"},this.svgGroup_);this.svgPathLight_=Blockly.createSvgElement("path",{"class":"blocklyPathLight"},this.svgGroup_);this.svgPath_.tooltip=this;Blockly.Tooltip.bindMouseEvents(this.svgPath_);this.updateMovable()}; +goog.inherits(Blockly.BlockSvg,Blockly.Block);Blockly.BlockSvg.prototype.height=0;Blockly.BlockSvg.prototype.width=0;Blockly.BlockSvg.INLINE=-1; +Blockly.BlockSvg.prototype.initSvg=function(){goog.asserts.assert(this.workspace.rendered,"Workspace is headless.");this.updateColour();for(var a=0,b;b=this.inputList[a];a++)b.init();this.mutator&&this.mutator.createIcon();Blockly.readOnly||Blockly.bindEvent_(this.getSvgRoot(),"mousedown",this,this.onMouseDown_);this.workspace.getCanvas().appendChild(this.getSvgRoot());goog.isFunction(this.onchange)&&Blockly.bindEvent_(workspace.getCanvas(),"blocklyWorkspaceChange",this,this.onchange)}; +Blockly.BlockSvg.prototype.select=function(){Blockly.selected&&Blockly.selected.unselect();Blockly.selected=this;this.addSelect();Blockly.fireUiEvent(this.workspace.getCanvas(),"blocklySelectChange")};Blockly.BlockSvg.prototype.unselect=function(){Blockly.selected=null;this.removeSelect();Blockly.fireUiEvent(this.workspace.getCanvas(),"blocklySelectChange")};Blockly.BlockSvg.prototype.mutator=null;Blockly.BlockSvg.prototype.comment=null;Blockly.BlockSvg.prototype.warning=null; +Blockly.BlockSvg.prototype.getIcons=function(){var a=[];this.mutator&&a.push(this.mutator);this.comment&&a.push(this.comment);this.warning&&a.push(this.warning);return a};Blockly.BlockSvg.onMouseUpWrapper_=null;Blockly.BlockSvg.onMouseMoveWrapper_=null; +Blockly.BlockSvg.terminateDrag_=function(){Blockly.BlockSvg.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_),Blockly.BlockSvg.onMouseUpWrapper_=null);Blockly.BlockSvg.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_),Blockly.BlockSvg.onMouseMoveWrapper_=null);var a=Blockly.selected;if(2==Blockly.dragMode_&&a){var b=a.getRelativeToSurfaceXY();a.moveConnections_(b.x-a.startDragX,b.y-a.startDragY);delete a.draggedBubbles_;a.setDragging_(!1); +a.render();goog.Timer.callOnce(a.bumpNeighbours_,Blockly.BUMP_DELAY,a);Blockly.fireUiEvent(window,"resize");a.workspace.fireChangeEvent()}Blockly.dragMode_=0;Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN)}; +Blockly.BlockSvg.prototype.setParent=function(a){var b=this.getSvgRoot();if(this.parentBlock_&&b){var c=this.getRelativeToSurfaceXY();this.workspace.getCanvas().appendChild(b);b.setAttribute("transform","translate("+c.x+", "+c.y+")")}Blockly.BlockSvg.superClass_.setParent.call(this,a);a&&(c=this.getRelativeToSurfaceXY(),a.getSvgRoot().appendChild(b),a=this.getRelativeToSurfaceXY(),this.moveConnections_(a.x-c.x,a.y-c.y))}; +Blockly.BlockSvg.prototype.getRelativeToSurfaceXY=function(){var a=0,b=0,c=this.getSvgRoot();if(c){do var d=Blockly.getRelativeXY_(c),a=a+d.x,b=b+d.y,c=c.parentNode;while(c&&c!=this.workspace.getCanvas())}return new goog.math.Coordinate(a,b)};Blockly.BlockSvg.prototype.moveBy=function(a,b){var c=this.getRelativeToSurfaceXY();this.getSvgRoot().setAttribute("transform","translate("+(c.x+a)+", "+(c.y+b)+")");this.moveConnections_(a,b);Blockly.Realtime.blockChanged(this)}; +Blockly.BlockSvg.prototype.getHeightWidth=function(){var a=this.height,b=this.width,c=this.getNextBlock();c&&(c=c.getHeightWidth(),a+=c.height-4,b=Math.max(b,c.width));return{height:a,width:b}}; +Blockly.BlockSvg.prototype.setCollapsed=function(a){if(this.collapsed_!=a){Blockly.BlockSvg.superClass_.setCollapsed.call(this,a);for(var b=[],c=0,d;d=this.inputList[c];c++)b.push.apply(b,d.setVisible(!a));if(a){a=this.getIcons();for(c=0;cthis.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&Blockly.comments&&(d={enabled:!0},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT,d.callback=function(){b.setCommentText(null)}): +(d.text=Blockly.Msg.ADD_COMMENT,d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=0;d=a.clientX&&0==a.clientY&&0==a.button)){Blockly.removeAllRanges();var c=a.clientX-b.startDragMouseX,d=a.clientY-b.startDragMouseY;1==Blockly.dragMode_&&Math.sqrt(Math.pow(c,2)+Math.pow(d,2))>Blockly.DRAG_RADIUS&&(Blockly.dragMode_=2,b.setParent(null),b.setDragging_(!0),b.workspace.recordDeleteAreas());if(2==Blockly.dragMode_){var e=b.startDragX+c,f=b.startDragY+d;b.getSvgRoot().setAttribute("transform", +"translate("+e+", "+f+")");for(e=0;e=b.height+c.y?d.y-(f.height-1):d.y+(e.height-1);Blockly.RTL?(d.x+=e.width,d.x-=f.width,d.xb.width+c.x-f.width&&(d.x=b.width+c.x-f.width);Blockly.WidgetDiv.position(d.x,d.y,b,c);var g=this;Blockly.FieldColour.changeEventKey_=goog.events.listen(a,goog.ui.ColorPicker.EventType.CHANGE,function(a){a=a.target.getSelectedColor()||"#000000";Blockly.WidgetDiv.hide();if(g.changeHandler_){var b=g.changeHandler_(a);void 0!==b&&(a=b)}null!==a&&g.setValue(a)})}; +e.height>=b.height+c.y?d.y-(f.height-1):d.y+(e.height-1);Blockly.RTL?(d.x+=e.width,d.x-=f.width,d.xb.width+c.x-f.width&&(d.x=b.width+c.x-f.width);Blockly.WidgetDiv.position(d.x,d.y,b,c);var g=this;Blockly.FieldColour.changeEventKey_=goog.events.listen(a,goog.ui.ColorPicker.EventType.CHANGE,function(a){a=a.target.getSelectedColor()||"#000000";Blockly.WidgetDiv.hide();if(g.sourceBlock_&&g.changeHandler_){var b=g.changeHandler_(a);void 0!==b&&(a=b)}null!==a&&g.setValue(a)})}; Blockly.FieldColour.widgetDispose_=function(){Blockly.FieldColour.changeEventKey_&&goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_)}; // Copyright 2012 Google Inc. Apache License 2.0 Blockly.FieldDropdown=function(a,b){this.menuGenerator_=a;this.changeHandler_=b;this.trimOptions_();var c=this.getOptions_()[0];this.value_=c[1];this.arrow_=Blockly.createSvgElement("tspan",{},null);this.arrow_.appendChild(document.createTextNode(Blockly.RTL?Blockly.FieldDropdown.ARROW_CHAR+" ":" "+Blockly.FieldDropdown.ARROW_CHAR));Blockly.FieldDropdown.superClass_.constructor.call(this,c[0])};goog.inherits(Blockly.FieldDropdown,Blockly.Field);Blockly.FieldDropdown.CHECKMARK_OVERHANG=25; Blockly.FieldDropdown.ARROW_CHAR=goog.userAgent.ANDROID?"\u25bc":"\u25be";Blockly.FieldDropdown.prototype.clone=function(){return new Blockly.FieldDropdown(this.menuGenerator_,this.changeHandler_)};Blockly.FieldDropdown.prototype.CURSOR="default"; -Blockly.FieldDropdown.prototype.showEditor_=function(){Blockly.WidgetDiv.show(this,null);for(var a=this,b=new goog.ui.Menu,c=this.getOptions_(),d=0;d=c.height+d.y?e.y-h.height:e.y+f.height;Blockly.RTL?(e.x+=f.width,e.x+=Blockly.FieldDropdown.CHECKMARK_OVERHANG,e.xc.width+d.x-h.width&&(e.x=c.width+d.x-h.width));Blockly.WidgetDiv.position(e.x,e.y,c,d);b.setAllowAutoFocus(!0);g.focus()}; Blockly.FieldDropdown.prototype.trimOptions_=function(){this.suffixField=this.prefixField=null;var a=this.menuGenerator_;if(goog.isArray(a)&&!(2>a.length)){var b=a.map(function(a){return a[0]}),c=Blockly.shortestStringLength(b),d=Blockly.commonWordPrefix(b,c),e=Blockly.commonWordSuffix(b,c);if((d||e)&&!(c<=d+e)){d&&(this.prefixField=b[0].substring(0,d-1));e&&(this.suffixField=b[0].substr(1-e));b=[];for(c=0;ca.viewHeight+ -c||a.contentLeft<(Blockly.RTL?a.viewLeft:b)||a.contentLeft+a.contentWidth>(Blockly.RTL?a.viewWidth:a.viewWidth+b))for(var d=Blockly.mainWorkspace.getTopBlocks(!1),e=0,p;p=d[e];e++){var n=p.getRelativeToSurfaceXY(),q=p.getHeightWidth(),m=c+25-q.height-n.y;0m&&p.moveBy(0,m);m=25+b-n.x-(Blockly.RTL?0:q.width);0m&&p.moveBy(m,0)}}}));b.appendChild(Blockly.Tooltip.createDom());a.appendChild(b); -Blockly.svg=b;Blockly.svgResize();Blockly.WidgetDiv.DIV=goog.dom.createDom("div","blocklyWidgetDiv");Blockly.WidgetDiv.DIV.style.direction=Blockly.RTL?"rtl":"ltr";document.body.appendChild(Blockly.WidgetDiv.DIV)}; +d);c=Blockly.createSvgElement("pattern",{id:"blocklyDisabledPattern",patternUnits:"userSpaceOnUse",width:10,height:10},c);Blockly.createSvgElement("rect",{width:10,height:10,fill:"#aaa"},c);Blockly.createSvgElement("path",{d:"M 0 0 L 10 10 M 10 0 L 0 10",stroke:"#cc0"},c);Blockly.mainWorkspace=new Blockly.WorkspaceSvg(Blockly.getMainWorkspaceMetrics_,Blockly.setMainWorkspaceMetrics_);b.appendChild(Blockly.mainWorkspace.createDom());Blockly.mainWorkspace.maxBlocks=Blockly.maxBlocks;Blockly.readOnly|| +(Blockly.hasCategories?Blockly.mainWorkspace.toolbox_=new Blockly.Toolbox(b,a):(Blockly.mainWorkspace.flyout_=new Blockly.Flyout,c=Blockly.mainWorkspace.flyout_,d=c.createDom(),c.autoClose=!1,goog.dom.insertSiblingBefore(d,Blockly.mainWorkspace.svgGroup_)),Blockly.hasScrollbars||Blockly.addChangeListener(function(){if(0==Blockly.dragMode_){var a=Blockly.mainWorkspace.getMetrics(),b=a.viewLeft+a.absoluteLeft,c=a.viewTop+a.absoluteTop;if(a.contentTopa.viewHeight+c|| +a.contentLeft<(Blockly.RTL?a.viewLeft:b)||a.contentLeft+a.contentWidth>(Blockly.RTL?a.viewWidth:a.viewWidth+b))for(var d=Blockly.mainWorkspace.getTopBlocks(!1),e=0,p;p=d[e];e++){var n=p.getRelativeToSurfaceXY(),q=p.getHeightWidth(),m=c+25-q.height-n.y;0m&&p.moveBy(0,m);m=25+b-n.x-(Blockly.RTL?0:q.width);0m&&p.moveBy(m,0)}}}));b.appendChild(Blockly.Tooltip.createDom());a.appendChild(b);Blockly.svg= +b;Blockly.svgResize();Blockly.WidgetDiv.DIV=goog.dom.createDom("div","blocklyWidgetDiv");Blockly.WidgetDiv.DIV.style.direction=Blockly.RTL?"rtl":"ltr";document.body.appendChild(Blockly.WidgetDiv.DIV)}; Blockly.init_=function(){Blockly.bindEvent_(Blockly.svg,"mousedown",null,Blockly.onMouseDown_);Blockly.bindEvent_(Blockly.svg,"contextmenu",null,Blockly.onContextMenu_);Blockly.bindEvent_(Blockly.WidgetDiv.DIV,"contextmenu",null,Blockly.onContextMenu_);Blockly.documentEventsBound_||(Blockly.bindEvent_(window,"resize",document,Blockly.svgResize),Blockly.bindEvent_(document,"keydown",null,Blockly.onKeyDown_),document.addEventListener("mouseup",Blockly.onMouseUp_,!1),goog.userAgent.IPAD&&Blockly.bindEvent_(window, "orientationchange",document,function(){Blockly.fireUiEvent(window,"resize")}),Blockly.documentEventsBound_=!0);if(Blockly.languageTree)if(Blockly.mainWorkspace.toolbox_)Blockly.mainWorkspace.toolbox_.init();else if(Blockly.mainWorkspace.flyout_){Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace);Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes);Blockly.mainWorkspace.scrollX=Blockly.mainWorkspace.flyout_.width_;Blockly.RTL&&(Blockly.mainWorkspace.scrollX*=-1);var a="translate("+ Blockly.mainWorkspace.scrollX+", 0)";Blockly.mainWorkspace.getCanvas().setAttribute("transform",a);Blockly.mainWorkspace.getBubbleCanvas().setAttribute("transform",a)}Blockly.hasScrollbars&&(Blockly.mainWorkspace.scrollbar=new Blockly.ScrollbarPair(Blockly.mainWorkspace),Blockly.mainWorkspace.scrollbar.resize());Blockly.mainWorkspace.addTrashcan();if(Blockly.hasSounds){Blockly.loadAudio_([Blockly.pathToMedia+"click.mp3",Blockly.pathToMedia+"click.wav",Blockly.pathToMedia+"click.ogg"],"click");Blockly.loadAudio_([Blockly.pathToMedia+ @@ -1277,7 +1282,7 @@ Blockly.updateToolbox=function(a){if(a=Blockly.parseToolboxTree_(a)){if(!Blockly // Copyright 2012 Google Inc. Apache License 2.0 Blockly.utils={};Blockly.addClass_=function(a,b){var c=a.getAttribute("class")||"";-1==(" "+c+" ").indexOf(" "+b+" ")&&(c&&(c+=" "),a.setAttribute("class",c+b))};Blockly.removeClass_=function(a,b){var c=a.getAttribute("class");if(-1!=(" "+c+" ").indexOf(" "+b+" ")){for(var c=c.split(/\s+/),d=0;d} - * @private - */ -Blockly.Block.onMouseUpWrapper_ = null; - -/** - * Wrapper function called when a mouseMove occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.Block.onMouseMoveWrapper_ = null; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.Block.terminateDrag_ = function() { - if (Blockly.Block.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Block.onMouseUpWrapper_); - Blockly.Block.onMouseUpWrapper_ = null; - } - if (Blockly.Block.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Block.onMouseMoveWrapper_); - Blockly.Block.onMouseMoveWrapper_ = null; - } - var selected = Blockly.selected; - if (Blockly.Block.dragMode_ == 2) { - // Terminate a drag operation. - if (selected) { - // Update the connection locations. - var xy = selected.getRelativeToSurfaceXY(); - var dx = xy.x - selected.startDragX; - var dy = xy.y - selected.startDragY; - selected.moveConnections_(dx, dy); - delete selected.draggedBubbles_; - selected.setDragging_(false); - selected.render(); - goog.Timer.callOnce( - selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected); - // Fire an event to allow scrollbars to resize. - Blockly.fireUiEvent(window, 'resize'); - } - } - if (selected) { - selected.workspace.fireChangeEvent(); - } - Blockly.Block.dragMode_ = 0; - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); -}; - -/** - * Select this block. Highlight it visually. - */ -Blockly.Block.prototype.select = function() { - goog.asserts.assertObject(this.svg_, 'Block is not rendered.'); - if (Blockly.selected) { - // Unselect any previously selected block. - Blockly.selected.unselect(); - } - Blockly.selected = this; - this.svg_.addSelect(); - Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange'); -}; - -/** - * Unselect this block. Remove its highlighting. - */ -Blockly.Block.prototype.unselect = function() { - goog.asserts.assertObject(this.svg_, 'Block is not rendered.'); - Blockly.selected = null; - this.svg_.removeSelect(); - Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange'); -}; - /** * Dispose of this block. * @param {boolean} healStack If true, then try to heal any gap by connecting @@ -338,14 +154,8 @@ Blockly.Block.prototype.unselect = function() { */ Blockly.Block.prototype.dispose = function(healStack, animate, opt_dontRemoveFromWorkspace) { - // Switch off rerendering. - this.rendered = false; this.unplug(healStack, false); - if (animate && this.svg_) { - this.svg_.disposeUiEffect(); - } - // 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 && !opt_dontRemoveFromWorkspace) { @@ -359,13 +169,6 @@ Blockly.Block.prototype.dispose = function(healStack, animate, if (Blockly.selected == this) { Blockly.selected = null; - // If there's a drag in-progress, unlink the mouse events. - Blockly.terminateDrag_(); - } - - // If this block has a context menu open, close it. - if (Blockly.ContextMenu.currentBlock == this) { - Blockly.ContextMenu.hide(); } // First, dispose of all my children. @@ -373,15 +176,11 @@ Blockly.Block.prototype.dispose = function(healStack, animate, this.childBlocks_[x].dispose(false); } // Then dispose of myself. - var icons = this.getIcons(); - for (var x = 0; x < icons.length; x++) { - icons[x].dispose(); - } // Dispose of all inputs and their fields. for (var x = 0, input; input = this.inputList[x]; x++) { input.dispose(); } - this.inputList = []; + this.inputList.length = 0; // Dispose of any remaining connections (next/previous/output). var connections = this.getConnections_(true); for (var x = 0; x < connections.length; x++) { @@ -391,11 +190,6 @@ Blockly.Block.prototype.dispose = function(healStack, animate, } connections[x].dispose(); } - // Dispose of the SVG and break circular references. - if (this.svg_) { - this.svg_.dispose(); - this.svg_ = null; - } // Remove from Realtime set of blocks. if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) { Blockly.Realtime.removeBlock(this); @@ -442,171 +236,6 @@ Blockly.Block.prototype.unplug = function(healStack, bump) { } }; -/** - * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0). - * @return {!Object} Object with .x and .y properties. - */ -Blockly.Block.prototype.getRelativeToSurfaceXY = function() { - var x = 0; - var y = 0; - if (this.svg_) { - var element = this.svg_.getRootElement(); - do { - // Loop through this block and every parent. - var xy = Blockly.getRelativeXY_(element); - x += xy.x; - y += xy.y; - element = element.parentNode; - } while (element && element != this.workspace.getCanvas()); - } - return {x: x, y: y}; -}; - -/** - * Move a block by a relative offset. - * @param {number} dx Horizontal offset. - * @param {number} dy Vertical offset. - */ -Blockly.Block.prototype.moveBy = function(dx, dy) { - var xy = this.getRelativeToSurfaceXY(); - this.svg_.getRootElement().setAttribute('transform', - 'translate(' + (xy.x + dx) + ', ' + (xy.y + dy) + ')'); - this.moveConnections_(dx, dy); - Blockly.Realtime.blockChanged(this); -}; - -/** - * Returns a bounding box describing the dimensions of this block - * and any blocks stacked below it. - * @return {!Object} Object with height and width properties. - */ -Blockly.Block.prototype.getHeightWidth = function() { - var height = this.svg_.height; - var width = this.svg_.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); - } - return {height: height, width: width}; -}; - -/** - * Handle a mouse-down on an SVG block. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Block.prototype.onMouseDown_ = function(e) { - if (this.isInFlyout) { - return; - } - // Update Blockly's knowledge of its own location. - Blockly.svgResize(); - Blockly.terminateDrag_(); - this.select(); - Blockly.hideChaff(); - if (Blockly.isRightButton(e)) { - // Right-click. - this.showContextMenu_(e); - } else if (!this.isMovable()) { - // Allow unmovable 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 { - // Left-click (or middle click) - Blockly.removeAllRanges(); - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - // Look up the current translation and record it. - var xy = this.getRelativeToSurfaceXY(); - this.startDragX = xy.x; - this.startDragY = xy.y; - // Record the current mouse position. - this.startDragMouseX = e.clientX; - this.startDragMouseY = e.clientY; - Blockly.Block.dragMode_ = 1; - Blockly.Block.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, this.onMouseUp_); - Blockly.Block.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 x = 0, descendant; descendant = descendants[x]; x++) { - var icons = descendant.getIcons(); - for (var y = 0; y < icons.length; y++) { - var data = icons[y].getIconLocation(); - data.bubble = icons[y]; - this.draggedBubbles_.push(data); - } - } - } - // This event has been handled. No need to bubble up to the document. - 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.Block.prototype.onMouseUp_ = function(e) { - var this_ = this; - Blockly.doCommand(function() { - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { - // Connect two blocks together. - Blockly.localConnection_.connect(Blockly.highlightedConnection_); - if (this_.svg_) { - // Trigger a connection animation. - // Determine which connection is inferior (lower in the source stack). - var inferiorConnection; - if (Blockly.localConnection_.isSuperior()) { - inferiorConnection = Blockly.highlightedConnection_; - } else { - inferiorConnection = Blockly.localConnection_; - } - inferiorConnection.sourceBlock_.svg_.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_.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.fireUiEvent(window, 'resize'); - } - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); - }); -}; - -/** - * Load the block's help page in a new window. - * @private - */ -Blockly.Block.prototype.showHelp_ = function() { - var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; - if (url) { - window.open(url); - } -}; - /** * Duplicate this block and its children. * @return {!Blockly.Block} The duplicate. @@ -631,136 +260,6 @@ Blockly.Block.prototype.duplicate_ = function() { return newBlock; }; -/** - * Show the context menu for this block. - * @param {!Event} e Mouse event. - * @private - */ -Blockly.Block.prototype.showContextMenu_ = function(e) { - if (Blockly.readOnly || !this.contextMenu) { - return; - } - // Save the current block in a variable for use in closures. - var block = this; - var options = []; - - if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { - // Option to duplicate this block. - var duplicateOption = { - text: Blockly.Msg.DUPLICATE_BLOCK, - enabled: true, - callback: function() { - block.duplicate_(); - } - }; - if (this.getDescendants().length > this.workspace.remainingCapacity()) { - duplicateOption.enabled = false; - } - options.push(duplicateOption); - - if (this.isEditable() && !this.collapsed_ && Blockly.comments) { - // Option to add/remove a comment. - var commentOption = {enabled: true}; - 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(''); - }; - } - options.push(commentOption); - } - - // Option to make block inline. - if (!this.collapsed_) { - for (var i = 0; i < this.inputList.length; i++) { - if (this.inputList[i].type == Blockly.INPUT_VALUE) { - // Only display this option if there is a value input on the block. - var inlineOption = {enabled: true}; - inlineOption.text = this.inputsInline ? Blockly.Msg.EXTERNAL_INPUTS : - Blockly.Msg.INLINE_INPUTS; - inlineOption.callback = function() { - block.setInputsInline(!block.inputsInline); - }; - options.push(inlineOption); - break; - } - } - } - - if (Blockly.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); - }; - options.push(expandOption); - } else { - var collapseOption = {enabled: true}; - collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK; - collapseOption.callback = function() { - block.setCollapsed(true); - }; - options.push(collapseOption); - } - } - - if (Blockly.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); - } - }; - options.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() { - block.dispose(true, true); - } - }; - options.push(deleteOption); - } - - // 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_(); - }; - options.push(helpOption); - - // Allow the block to add or modify options. - if (this.customContextMenu && !block.isInFlyout) { - this.customContextMenu(options); - } - - Blockly.ContextMenu.show(e, options); - Blockly.ContextMenu.currentBlock = this; -}; - /** * Returns all connections originating from this block. * @param {boolean} all If true, return all connections even hidden ones. @@ -791,145 +290,13 @@ Blockly.Block.prototype.getConnections_ = function(all) { return myConnections; }; -/** - * 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. - * @private - */ -Blockly.Block.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 x = 0; x < myConnections.length; x++) { - myConnections[x].moveBy(dx, dy); - } - var icons = this.getIcons(); - for (var x = 0; x < icons.length; x++) { - icons[x].computeIconLocation(); - } - - // Recurse through all blocks attached under this one. - for (var x = 0; x < this.childBlocks_.length; x++) { - this.childBlocks_[x].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. - * @private - */ -Blockly.Block.prototype.setDragging_ = function(adding) { - if (adding) { - this.svg_.addDragging(); - } else { - this.svg_.removeDragging(); - } - // Recurse through all blocks attached under this one. - for (var x = 0; x < this.childBlocks_.length; x++) { - this.childBlocks_[x].setDragging_(adding); - } -}; - -/** - * Drag this block to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Block.prototype.onMouseMove_ = function(e) { - var this_ = this; - Blockly.doCommand(function() { - 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; - } - Blockly.removeAllRanges(); - var dx = e.clientX - this_.startDragMouseX; - var dy = e.clientY - this_.startDragMouseY; - if (Blockly.Block.dragMode_ == 1) { - // Still dragging within the sticky DRAG_RADIUS. - var dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); - if (dr > Blockly.DRAG_RADIUS) { - // Switch to unrestricted dragging. - Blockly.Block.dragMode_ = 2; - // Push this block to the very top of the stack. - this_.setParent(null); - this_.setDragging_(true); - this_.workspace.recordDeleteAreas(); - } - } - if (Blockly.Block.dragMode_ == 2) { - // Unrestricted dragging. - var x = this_.startDragX + dx; - var y = this_.startDragY + dy; - this_.svg_.getRootElement().setAttribute('transform', - 'translate(' + x + ', ' + y + ')'); - // Drag all the nested bubbles. - for (var i = 0; i < this_.draggedBubbles_.length; i++) { - var commentData = this_.draggedBubbles_[i]; - commentData.bubble.setIconLocation(commentData.x + dx, - commentData.y + dy); - } - - // Check to see if any of this block's connections are within range of - // another block's connection. - var myConnections = this_.getConnections_(false); - 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, dx, dy); - 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 event has been handled. No need to bubble up to the document. - e.stopPropagation(); - }); -}; - /** * 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() { - if (Blockly.Block.dragMode_ != 0) { + if (Blockly.dragMode_ != 0) { // Don't bump blocks during a drag. return; } @@ -1045,11 +412,6 @@ Blockly.Block.prototype.setParent = function(newParent) { break; } } - // Move this block up the DOM. Keep track of x/y translations. - var xy = this.getRelativeToSurfaceXY(); - this.workspace.getCanvas().appendChild(this.svg_.getRootElement()); - this.svg_.getRootElement().setAttribute('transform', - 'translate(' + xy.x + ', ' + xy.y + ')'); // Disconnect from superior blocks. this.parentBlock_ = null; @@ -1074,14 +436,6 @@ Blockly.Block.prototype.setParent = function(newParent) { if (newParent) { // Add this block to the new parent's child list. newParent.childBlocks_.push(this); - - var oldXY = this.getRelativeToSurfaceXY(); - if (newParent.svg_ && this.svg_) { - newParent.svg_.getRootElement().appendChild(this.svg_.getRootElement()); - } - var newXY = this.getRelativeToSurfaceXY(); - // Move the connections to match the child's new position. - this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y); } else { this.workspace.addTopBlock(this); } @@ -1183,21 +537,8 @@ Blockly.Block.prototype.getColour = function() { */ Blockly.Block.prototype.setColour = function(colourHue) { this.colourHue_ = colourHue; - if (this.svg_) { - this.svg_.updateColour(); - } - var icons = this.getIcons(); - for (var x = 0; x < icons.length; x++) { - icons[x].updateColour(); - } if (this.rendered) { - // Bump every dropdown to change its colour. - for (var x = 0, input; input = this.inputList[x]; x++) { - for (var y = 0, field; field = input.fieldRow[y]; y++) { - field.setText(null); - } - } - this.render(); + this.updateColour(); } }; @@ -1391,12 +732,7 @@ Blockly.Block.prototype.setInputsInline = function(newBoolean) { * @param {boolean} disabled True if disabled. */ Blockly.Block.prototype.setDisabled = function(disabled) { - if (this.disabled == disabled) { - return; - } this.disabled = disabled; - this.svg_.updateDisabled(); - this.workspace.fireChangeEvent(); }; /** @@ -1430,38 +766,7 @@ Blockly.Block.prototype.isCollapsed = function() { * @param {boolean} collapsed True if collapsed. */ Blockly.Block.prototype.setCollapsed = function(collapsed) { - if (this.collapsed_ == collapsed) { - return; - } this.collapsed_ = collapsed; - var renderList = []; - // Show/hide the inputs. - for (var x = 0, input; input = this.inputList[x]; x++) { - renderList.push.apply(renderList, input.setVisible(!collapsed)); - } - - var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; - if (collapsed) { - var icons = this.getIcons(); - for (var x = 0; x < icons.length; x++) { - icons[x].setVisible(false); - } - var text = this.toString(Blockly.COLLAPSE_CHARS); - this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text); - } else { - this.removeInput(COLLAPSED_INPUT_NAME); - } - - if (!renderList.length) { - // No child blocks, just render this block. - renderList[0] = this; - } - if (this.rendered) { - for (var x = 0, block; block = renderList[x]; x++) { - block.render(); - } - this.bumpNeighbours_(); - } }; /** @@ -1777,34 +1082,12 @@ Blockly.Block.prototype.getInputTargetBlock = function(name) { return input && input.connection && input.connection.targetBlock(); }; -/** - * Give this block a mutator dialog. - * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. - */ -Blockly.Block.prototype.setMutator = function(mutator) { - if (this.mutator && this.mutator !== mutator) { - this.mutator.dispose(); - } - if (mutator) { - mutator.block_ = this; - this.mutator = mutator; - if (this.svg_) { - mutator.createIcon(); - } - } -}; - /** * Returns the comment on this block (or '' if none). * @return {string} Block's comment. */ Blockly.Block.prototype.getCommentText = function() { - if (this.comment) { - var comment = this.comment.getText(); - // Trim off trailing whitespace. - return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n'); - } - return ''; + return this.comment || ''; }; /** @@ -1812,26 +1095,7 @@ Blockly.Block.prototype.getCommentText = function() { * @param {?string} text The text, or null to delete. */ Blockly.Block.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 (this.rendered) { - this.render(); - if (changedState) { - // Adding or removing a comment icon will cause the block to change shape. - this.bumpNeighbours_(); - } - } + this.comment = text; }; /** @@ -1839,36 +1103,31 @@ Blockly.Block.prototype.setCommentText = function(text) { * @param {?string} text The text, or null to delete. */ Blockly.Block.prototype.setWarningText = function(text) { - if (this.isInFlyout) { - text = null; - } - var changedState = false; - if (goog.isString(text)) { - if (!this.warning) { - this.warning = new Blockly.Warning(this); - changedState = true; - } - this.warning.setText(/** @type {string} */ (text)); - } else { - if (this.warning) { - this.warning.dispose(); - changedState = true; - } - } - if (changedState && this.rendered) { - this.render(); - // Adding or removing a warning icon will cause the block to change shape. - this.bumpNeighbours_(); - } + // NOP. }; /** - * Render the block. - * Lays out and reflows a block based on its contents and settings. + * Give this block a mutator dialog. + * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. */ -Blockly.Block.prototype.render = function() { - goog.asserts.assertObject(this.svg_, - 'Uninitialized block cannot be rendered. Call block.initSvg()'); - this.svg_.render(); - Blockly.Realtime.blockChanged(this); +Blockly.Block.prototype.setMutator = function(mutator) { + // NOP. +}; + +/** + * 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. + */ +Blockly.Block.prototype.getRelativeToSurfaceXY = function() { + return this.xy_; +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset. + * @param {number} dy Vertical offset. + */ +Blockly.Block.prototype.moveBy = function(dx, dy) { + this.xy_.translate(dx, dy); }; diff --git a/core/block_svg.js b/core/block_svg.js index a169fabec..385735822 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -26,16 +26,18 @@ goog.provide('Blockly.BlockSvg'); +goog.require('Blockly.Block'); +goog.require('Blockly.ContextMenu'); +goog.require('goog.asserts'); goog.require('goog.userAgent'); /** * Class for a block's SVG representation. - * @param {!Blockly.Block} block The underlying block object. + * @extends {Blockly.Block} * @constructor */ -Blockly.BlockSvg = function(block) { - this.block_ = block; +Blockly.BlockSvg = function() { // Create core elements for the block. this.svgGroup_ = Blockly.createSvgElement('g', {}, null); this.svgPathDark_ = Blockly.createSvgElement('path', @@ -45,10 +47,11 @@ Blockly.BlockSvg = function(block) { this.svgGroup_); this.svgPathLight_ = Blockly.createSvgElement('path', {'class': 'blocklyPathLight'}, this.svgGroup_); - this.svgPath_.tooltip = this.block_; + this.svgPath_.tooltip = this; Blockly.Tooltip.bindMouseEvents(this.svgPath_); this.updateMovable(); }; +goog.inherits(Blockly.BlockSvg, Blockly.Block); /** * Height of this block, not including any statement blocks above or below. @@ -67,25 +70,634 @@ Blockly.BlockSvg.prototype.width = 0; Blockly.BlockSvg.INLINE = -1; /** - * Initialize the SVG representation with any block attributes which have - * already been defined. + * Create and initialize the SVG representation of the block. */ -Blockly.BlockSvg.prototype.init = function() { - var block = this.block_; +Blockly.BlockSvg.prototype.initSvg = function() { + goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.'); this.updateColour(); - for (var x = 0, input; input = block.inputList[x]; x++) { + for (var x = 0, input; input = this.inputList[x]; x++) { input.init(); } - if (block.mutator) { - block.mutator.createIcon(); + if (this.mutator) { + this.mutator.createIcon(); } + if (!Blockly.readOnly) { + Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this, + this.onMouseDown_); + } + this.workspace.getCanvas().appendChild(this.getSvgRoot()); + + // Bind an onchange function, if it exists. + if (goog.isFunction(this.onchange)) { + Blockly.bindEvent_(workspace.getCanvas(), 'blocklyWorkspaceChange', this, + this.onchange); + } +}; + +/** + * Select this block. Highlight it visually. + */ +Blockly.BlockSvg.prototype.select = function() { + if (Blockly.selected) { + // Unselect any previously selected block. + Blockly.selected.unselect(); + } + Blockly.selected = this; + this.addSelect(); + Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange'); +}; + +/** + * Unselect this block. Remove its highlighting. + */ +Blockly.BlockSvg.prototype.unselect = function() { + Blockly.selected = null; + this.removeSelect(); + Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange'); +}; + +/** + * 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; +}; + +/** + * 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() { + 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_ == 2) { + // Terminate a drag operation. + if (selected) { + // Update the connection locations. + var xy = selected.getRelativeToSurfaceXY(); + var dx = xy.x - selected.startDragX; + var dy = xy.y - selected.startDragY; + selected.moveConnections_(dx, dy); + delete selected.draggedBubbles_; + selected.setDragging_(false); + selected.render(); + goog.Timer.callOnce( + selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected); + // Fire an event to allow scrollbars to resize. + Blockly.fireUiEvent(window, 'resize'); + selected.workspace.fireChangeEvent(); + } + } + Blockly.dragMode_ = 0; + 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. + */ +Blockly.BlockSvg.prototype.setParent = function(newParent) { + 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.BlockSvg.superClass_.setParent.call(this, newParent); + + 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). + * @return {!goog.math.Coordinate} Object with .x and .y properties. + */ +Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() { + var x = 0; + var y = 0; + var element = this.getSvgRoot(); + if (element) { + do { + // Loop through this block and every parent. + var xy = Blockly.getRelativeXY_(element); + x += xy.x; + y += xy.y; + element = element.parentNode; + } while (element && element != this.workspace.getCanvas()); + } + return new goog.math.Coordinate(x, y); +}; + +/** + * Move a block by a relative offset. + * @param {number} dx Horizontal offset. + * @param {number} dy Vertical offset. + */ +Blockly.BlockSvg.prototype.moveBy = function(dx, dy) { + var xy = this.getRelativeToSurfaceXY(); + this.getSvgRoot().setAttribute('transform', + 'translate(' + (xy.x + dx) + ', ' + (xy.y + dy) + ')'); + this.moveConnections_(dx, dy); + Blockly.Realtime.blockChanged(this); +}; + +/** + * Returns a bounding box describing the dimensions of this block + * and any blocks stacked below it. + * @return {!Object} 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); + } + return {height: height, width: width}; +}; + +/** + * 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; + } + Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed); + var renderList = []; + // Show/hide the inputs. + for (var x = 0, input; input = this.inputList[x]; x++) { + renderList.push.apply(renderList, input.setVisible(!collapsed)); + } + + var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; + if (collapsed) { + var icons = this.getIcons(); + for (var x = 0; x < icons.length; x++) { + icons[x].setVisible(false); + } + var text = this.toString(Blockly.COLLAPSE_CHARS); + this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init(); + } else { + this.removeInput(COLLAPSED_INPUT_NAME); + } + + if (!renderList.length) { + // No child blocks, just render this block. + renderList[0] = this; + } + if (this.rendered) { + for (var x = 0, block; block = renderList[x]; x++) { + block.render(); + } + this.bumpNeighbours_(); + } +}; + +/** + * Handle a mouse-down on an SVG block. + * @param {!Event} e Mouse down event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { + if (this.isInFlyout) { + return; + } + // Update Blockly's knowledge of its own location. + Blockly.svgResize(); + Blockly.terminateDrag_(); + this.select(); + Blockly.hideChaff(); + if (Blockly.isRightButton(e)) { + // Right-click. + this.showContextMenu_(e); + } else if (!this.isMovable()) { + // Allow unmovable 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 { + // Left-click (or middle click) + Blockly.removeAllRanges(); + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + // Look up the current translation and record it. + var xy = this.getRelativeToSurfaceXY(); + this.startDragX = xy.x; + this.startDragY = xy.y; + // Record the current mouse position. + this.startDragMouseX = e.clientX; + this.startDragMouseY = e.clientY; + Blockly.dragMode_ = 1; + 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 x = 0, descendant; descendant = descendants[x]; x++) { + var icons = descendant.getIcons(); + for (var y = 0; y < icons.length; y++) { + var data = icons[y].getIconLocation(); + data.bubble = icons[y]; + this.draggedBubbles_.push(data); + } + } + } + // This event has been handled. No need to bubble up to the document. + 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.BlockSvg.prototype.onMouseUp_ = function(e) { + var this_ = this; + Blockly.doCommand(function() { + 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; + if (Blockly.localConnection_.isSuperior()) { + inferiorConnection = Blockly.highlightedConnection_; + } else { + inferiorConnection = Blockly.localConnection_; + } + inferiorConnection.sourceBlock_.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_.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.fireUiEvent(window, 'resize'); + } + if (Blockly.highlightedConnection_) { + Blockly.highlightedConnection_.unhighlight(); + Blockly.highlightedConnection_ = null; + } + Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); + }); +}; + +/** + * 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 (Blockly.readOnly || !this.contextMenu) { + return; + } + // Save the current block in a variable for use in closures. + var block = this; + var options = []; + + if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { + // Option to duplicate this block. + var duplicateOption = { + text: Blockly.Msg.DUPLICATE_BLOCK, + enabled: true, + callback: function() { + block.duplicate_(); + } + }; + if (this.getDescendants().length > this.workspace.remainingCapacity()) { + duplicateOption.enabled = false; + } + options.push(duplicateOption); + + if (this.isEditable() && !this.collapsed_ && Blockly.comments) { + // Option to add/remove a comment. + var commentOption = {enabled: true}; + 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(''); + }; + } + options.push(commentOption); + } + + // Option to make block inline. + if (!this.collapsed_) { + for (var i = 0; i < this.inputList.length; i++) { + if (this.inputList[i].type == Blockly.INPUT_VALUE) { + // Only display this option if there is a value input on the block. + var inlineOption = {enabled: true}; + inlineOption.text = this.inputsInline ? Blockly.Msg.EXTERNAL_INPUTS : + Blockly.Msg.INLINE_INPUTS; + inlineOption.callback = function() { + block.setInputsInline(!block.inputsInline); + }; + options.push(inlineOption); + break; + } + } + } + + if (Blockly.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); + }; + options.push(expandOption); + } else { + var collapseOption = {enabled: true}; + collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK; + collapseOption.callback = function() { + block.setCollapsed(true); + }; + options.push(collapseOption); + } + } + + if (Blockly.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); + } + }; + options.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() { + block.dispose(true, true); + } + }; + options.push(deleteOption); + } + + // 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_(); + }; + options.push(helpOption); + + // Allow the block to add or modify options. + if (this.customContextMenu && !block.isInFlyout) { + this.customContextMenu(options); + } + + Blockly.ContextMenu.show(e, options); + 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. + * @param {number} dy Vertical offset from current location. + * @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 x = 0; x < myConnections.length; x++) { + myConnections[x].moveBy(dx, dy); + } + var icons = this.getIcons(); + for (var x = 0; x < icons.length; x++) { + icons[x].computeIconLocation(); + } + + // Recurse through all blocks attached under this one. + for (var x = 0; x < this.childBlocks_.length; x++) { + this.childBlocks_[x].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. + * @private + */ +Blockly.BlockSvg.prototype.setDragging_ = function(adding) { + if (adding) { + this.addDragging(); + } else { + this.removeDragging(); + } + // Recurse through all blocks attached under this one. + for (var x = 0; x < this.childBlocks_.length; x++) { + this.childBlocks_[x].setDragging_(adding); + } +}; + +/** + * Drag this block to follow the mouse. + * @param {!Event} e Mouse move event. + * @private + */ +Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { + var this_ = this; + Blockly.doCommand(function() { + 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; + } + Blockly.removeAllRanges(); + var dx = e.clientX - this_.startDragMouseX; + var dy = e.clientY - this_.startDragMouseY; + if (Blockly.dragMode_ == 1) { + // Still dragging within the sticky DRAG_RADIUS. + var dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); + if (dr > Blockly.DRAG_RADIUS) { + // Switch to unrestricted dragging. + Blockly.dragMode_ = 2; + // Push this block to the very top of the stack. + this_.setParent(null); + this_.setDragging_(true); + this_.workspace.recordDeleteAreas(); + } + } + if (Blockly.dragMode_ == 2) { + // Unrestricted dragging. + var x = this_.startDragX + dx; + var y = this_.startDragY + dy; + this_.getSvgRoot().setAttribute('transform', + 'translate(' + x + ', ' + y + ')'); + // Drag all the nested bubbles. + for (var i = 0; i < this_.draggedBubbles_.length; i++) { + var commentData = this_.draggedBubbles_[i]; + commentData.bubble.setIconLocation(commentData.x + dx, + commentData.y + dy); + } + + // Check to see if any of this block's connections are within range of + // another block's connection. + var myConnections = this_.getConnections_(false); + 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, dx, dy); + 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 event has been handled. No need to bubble up to the document. + e.stopPropagation(); + }); }; /** * Add or remove the UI indicating if this block is movable or not. */ Blockly.BlockSvg.prototype.updateMovable = function() { - if (this.block_.isMovable()) { + if (this.isMovable()) { Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable'); } else { @@ -95,10 +707,10 @@ Blockly.BlockSvg.prototype.updateMovable = function() { }; /** - * Get the root SVG element. - * @return {!Element} The root SVG element. + * Return the root node of the SVG or null if none exists. + * @return {Element} The root SVG node (probably a group). */ -Blockly.BlockSvg.prototype.getRootElement = function() { +Blockly.BlockSvg.prototype.getSvgRoot = function() { return this.svgGroup_; }; @@ -302,17 +914,44 @@ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR = (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 1); /** - * Dispose of this SVG block. + * 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. + * @param {boolean} opt_dontRemoveFromWorkspace If true, don't remove this + * block from the workspace's list of top blocks. */ -Blockly.BlockSvg.prototype.dispose = function() { +Blockly.BlockSvg.prototype.dispose = function(healStack, animate, + opt_dontRemoveFromWorkspace) { + // If there's a drag in-progress, unlink the mouse events. + Blockly.terminateDrag_(); + // If this block has a context menu open, close it. + if (Blockly.ContextMenu.currentBlock == this) { + Blockly.ContextMenu.hide(); + } + // Bring block to the top of the workspace. + this.unplug(healStack, false); + + if (animate && this.rendered) { + this.disposeUiEffect(); + } + // Stop rerendering. + this.rendered = false; + + var icons = this.getIcons(); + for (var x = 0; x < icons.length; x++) { + icons[x].dispose(); + } + + Blockly.BlockSvg.superClass_.dispose.call(this, healStack); + goog.dom.removeNode(this.svgGroup_); // Sever JavaScript to DOM connections. this.svgGroup_ = null; this.svgPath_ = null; this.svgPathLight_ = null; this.svgPathDark_ = null; - // Break circular references. - this.block_ = null; }; /** @@ -369,10 +1008,10 @@ Blockly.BlockSvg.prototype.connectionUiEffect = function() { // Determine the absolute coordinates of the inferior block. var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_)); // Offset the coordinates based on the two connection types. - if (this.block_.outputConnection) { + if (this.outputConnection) { xy.x += Blockly.RTL ? 3 : -3; xy.y += 13; - } else if (this.block_.previousConnection) { + } else if (this.previousConnection) { xy.x += Blockly.RTL ? -23 : 23; xy.y += 3; } @@ -409,24 +1048,39 @@ Blockly.BlockSvg.connectionUiStep_ = function(ripple) { * Change the colour of a block. */ Blockly.BlockSvg.prototype.updateColour = function() { - if (this.block_.disabled) { + if (this.disabled) { // Disabled blocks don't have colour. return; } - var hexColour = Blockly.makeColour(this.block_.getColour()); + var hexColour = Blockly.makeColour(this.getColour()); var rgb = goog.color.hexToRgb(hexColour); var rgbLight = goog.color.lighten(rgb, 0.3); var rgbDark = goog.color.darken(rgb, 0.4); this.svgPathLight_.setAttribute('stroke', goog.color.rgbArrayToHex(rgbLight)); this.svgPathDark_.setAttribute('fill', goog.color.rgbArrayToHex(rgbDark)); this.svgPath_.setAttribute('fill', hexColour); + + var icons = this.getIcons(); + for (var x = 0; x < icons.length; x++) { + icons[x].updateColour(); + } + + // Bump every dropdown to change its colour. + for (var x = 0, input; input = this.inputList[x]; x++) { + for (var y = 0, field; field = input.fieldRow[y]; y++) { + field.setText(null); + } + } + if (this.rendered) { + this.render(); + } }; /** * Enable or disable a block. */ Blockly.BlockSvg.prototype.updateDisabled = function() { - if (this.block_.disabled || this.block_.getInheritedDisabled()) { + if (this.disabled || this.getInheritedDisabled()) { Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), 'blocklyDisabled'); this.svgPath_.setAttribute('fill', 'url(#blocklyDisabledPattern)'); @@ -435,12 +1089,108 @@ Blockly.BlockSvg.prototype.updateDisabled = function() { 'blocklyDisabled'); this.updateColour(); } - var children = this.block_.getChildren(); + var children = this.getChildren(); for (var x = 0, child; child = children[x]; x++) { - child.svg_.updateDisabled(); + 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. + */ +Blockly.BlockSvg.prototype.setWarningText = function(text) { + if (this.isInFlyout) { + text = null; + } + var changedState = false; + if (goog.isString(text)) { + if (!this.warning) { + this.warning = new Blockly.Warning(this); + changedState = true; + } + this.warning.setText(/** @type {string} */ (text)); + } else { + if (this.warning) { + this.warning.dispose(); + changedState = true; + } + } + 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; + if (this.rendered) { + 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) { + return; + } + Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled); + this.updateDisabled(); + this.workspace.fireChangeEvent(); +}; + /** * Select this block. Highlight it visually. */ @@ -481,14 +1231,14 @@ Blockly.BlockSvg.prototype.removeDragging = function() { * Lays out and reflows a block based on its contents and settings. */ Blockly.BlockSvg.prototype.render = function() { - this.block_.rendered = true; + this.rendered = true; var cursorX = Blockly.BlockSvg.SEP_SPACE_X; if (Blockly.RTL) { cursorX = -cursorX; } // Move the icons into position. - var icons = this.block_.getIcons(); + var icons = this.getIcons(); for (var x = 0; x < icons.length; x++) { cursorX = icons[x].renderIcon(cursorX); } @@ -501,13 +1251,14 @@ Blockly.BlockSvg.prototype.render = function() { this.renderDraw_(cursorX, inputRows); // Render all blocks above this one (propagate a reflow). - var parentBlock = this.block_.getParent(); + var parentBlock = this.getParent(); if (parentBlock) { parentBlock.render(); } else { // Top-most block. Fire an event to allow scrollbars to resize. Blockly.fireUiEvent(window, 'resize'); } + Blockly.Realtime.blockChanged(this); }; /** @@ -526,13 +1277,13 @@ Blockly.BlockSvg.prototype.renderFields_ = for (var t = 0, field; field = fieldList[t]; t++) { if (Blockly.RTL) { cursorX -= field.renderSep + field.renderWidth; - field.getRootElement().setAttribute('transform', + field.getSvgRoot().setAttribute('transform', 'translate(' + cursorX + ', ' + cursorY + ')'); if (field.renderWidth) { cursorX -= Blockly.BlockSvg.SEP_SPACE_X; } } else { - field.getRootElement().setAttribute('transform', + field.getSvgRoot().setAttribute('transform', 'translate(' + (cursorX + field.renderSep) + ', ' + cursorY + ')'); if (field.renderWidth) { cursorX += field.renderSep + field.renderWidth + @@ -551,10 +1302,10 @@ Blockly.BlockSvg.prototype.renderFields_ = * @private */ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { - var inputList = this.block_.inputList; + var inputList = this.inputList; var inputRows = []; inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2; - if (this.block_.previousConnection || this.block_.nextConnection) { + if (this.previousConnection || this.nextConnection) { inputRows.rightEdge = Math.max(inputRows.rightEdge, Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X); } @@ -564,7 +1315,7 @@ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { var hasStatement = false; var hasDummy = false; var lastType = undefined; - var isInline = this.block_.inputsInline && !this.block_.isCollapsed(); + var isInline = this.inputsInline && !this.isCollapsed(); for (var i = 0, input; input = inputList[i]; i++) { if (!input.isVisible()) { continue; @@ -694,20 +1445,20 @@ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { */ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { // Should the top and bottom left corners be rounded or square? - if (this.block_.outputConnection) { + 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.block_.previousConnection) { - var prevBlock = this.block_.previousConnection.targetBlock(); - if (prevBlock && prevBlock.getNextBlock() == this.block_) { + if (this.previousConnection) { + var prevBlock = this.previousConnection.targetBlock(); + if (prevBlock && prevBlock.getNextBlock() == this) { this.squareTopLeftCorner_ = true; } } - var nextBlock = this.block_.getNextBlock(); + var nextBlock = this.getNextBlock(); if (nextBlock) { this.squareBottomLeftCorner_ = true; } @@ -715,7 +1466,7 @@ Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) { // Fetch the block's coordinates on the surface for use in anchoring // the connections. - var connectionsXY = this.block_.getRelativeToSurfaceXY(); + var connectionsXY = this.getRelativeToSurfaceXY(); // Assemble the block's path. var steps = []; @@ -771,7 +1522,7 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = } // Top edge. - if (this.block_.previousConnection) { + 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); @@ -780,7 +1531,7 @@ Blockly.BlockSvg.prototype.renderDrawTop_ = var connectionX = connectionsXY.x + (Blockly.RTL ? -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH); var connectionY = connectionsXY.y; - this.block_.previousConnection.moveTo(connectionX, connectionY); + this.previousConnection.moveTo(connectionX, connectionY); // This connection will be tightened when the parent renders. } steps.push('H', rightEdge); @@ -812,7 +1563,7 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, cursorX += Blockly.RTL ? -iconWidth : iconWidth; } highlightSteps.push('M', (inputRows.rightEdge - 1) + ',' + (cursorY + 1)); - if (this.block_.isCollapsed()) { + if (this.isCollapsed()) { // Jagged right edge. var input = row[0]; var fieldX = cursorX; @@ -1065,7 +1816,7 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, highlightSteps, connectionsXY, cursorY) { this.height = cursorY + 1; // Add one for the shadow. - if (this.block_.nextConnection) { + if (this.nextConnection) { steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH + ' ' + Blockly.BlockSvg.NOTCH_PATH_RIGHT); // Create next block connection. @@ -1076,9 +1827,9 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = connectionX = connectionsXY.x + Blockly.BlockSvg.NOTCH_WIDTH; } var connectionY = connectionsXY.y + cursorY + 1; - this.block_.nextConnection.moveTo(connectionX, connectionY); - if (this.block_.nextConnection.targetConnection) { - this.block_.nextConnection.tighten_(); + this.nextConnection.moveTo(connectionX, connectionY); + if (this.nextConnection.targetConnection) { + this.nextConnection.tighten_(); } this.height += 4; // Height of tab. } @@ -1115,9 +1866,9 @@ Blockly.BlockSvg.prototype.renderDrawBottom_ = */ Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, highlightSteps, connectionsXY, cursorY) { - if (this.block_.outputConnection) { + if (this.outputConnection) { // Create output connection. - this.block_.outputConnection.moveTo(connectionsXY.x, connectionsXY.y); + this.outputConnection.moveTo(connectionsXY.x, connectionsXY.y); // This connection will be tightened when the parent renders. steps.push('V', Blockly.BlockSvg.TAB_HEIGHT); steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' + diff --git a/core/blockly.js b/core/blockly.js index c55a44e84..f226e3b9b 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -28,7 +28,7 @@ goog.provide('Blockly'); // Blockly core dependencies. -goog.require('Blockly.Block'); +goog.require('Blockly.BlockSvg'); goog.require('Blockly.Connection'); goog.require('Blockly.FieldAngle'); goog.require('Blockly.FieldCheckbox'); @@ -43,7 +43,7 @@ goog.require('Blockly.Procedures'); goog.require('Blockly.Realtime'); goog.require('Blockly.Toolbox'); goog.require('Blockly.WidgetDiv'); -goog.require('Blockly.Workspace'); +goog.require('Blockly.WorkspaceSvg'); goog.require('Blockly.inject'); goog.require('Blockly.utils'); @@ -223,6 +223,15 @@ Blockly.mainWorkspace = null; */ Blockly.clipboard_ = null; +/** + * Is the mouse dragging a block? + * 0 - No drag operation. + * 1 - Still inside the sticky DRAG_RADIUS. + * 2 - Freely draggable. + * @private + */ +Blockly.dragMode_ = 0; + /** * Wrapper function called when a touch mouseUp occurs during a drag operation. * @type {Array.} @@ -408,7 +417,7 @@ Blockly.onKeyDown_ = function(e) { * @private */ Blockly.terminateDrag_ = function() { - Blockly.Block.terminateDrag_(); + Blockly.BlockSvg.terminateDrag_(); Blockly.Flyout.terminateDrag_(); }; @@ -758,9 +767,9 @@ Blockly.getMainWorkspace = function() { }; // Export symbols that would otherwise be renamed by Closure compiler. -if (!window['Blockly']) { - window['Blockly'] = {}; +if (!this['Blockly']) { + this['Blockly'] = {}; } -window['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; -window['Blockly']['addChangeListener'] = Blockly.addChangeListener; -window['Blockly']['removeChangeListener'] = Blockly.removeChangeListener; +this['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; +this['Blockly']['addChangeListener'] = Blockly.addChangeListener; +this['Blockly']['removeChangeListener'] = Blockly.removeChangeListener; diff --git a/core/blocks.js b/core/blocks.js index 4c9a1b1fb..5794b5606 100644 --- a/core/blocks.js +++ b/core/blocks.js @@ -32,6 +32,26 @@ goog.require('goog.asserts'); */ goog.provide('Blockly.Blocks'); +/** + * Unique ID counter for created blocks. + * @private + */ +Blockly.Blocks.uidCounter_ = 0; + +/** + * Generate a unique ID. This will be locally or globally unique, depending on + * whether we are in single user or realtime collaborative mode. + * @return {string} + */ +Blockly.Blocks.genUid = function() { + var uid = (++Blockly.Blocks.uidCounter_).toString(); + if (Blockly.Realtime.isEnabled()) { + return Blockly.Realtime.genUid(uid); + } else { + return uid; + } +}; + /** * Create a block template and add it as a field to Blockly.Blocks with the * name details.blockName. diff --git a/core/comment.js b/core/comment.js index 4cbdf9e88..c06edb129 100644 --- a/core/comment.js +++ b/core/comment.js @@ -166,7 +166,7 @@ Blockly.Comment.prototype.setVisible = function(visible) { // Create the bubble. this.bubble_ = new Blockly.Bubble( /** @type {!Blockly.Workspace} */ (this.block_.workspace), - this.createEditor_(), this.block_.svg_.svgPath_, + this.createEditor_(), this.block_.svgPath_, this.iconX_, this.iconY_, this.width_, this.height_); this.bubble_.registerResizeEvent(this, this.resizeBubble_); diff --git a/core/connection.js b/core/connection.js index 96d118df9..5e0349286 100644 --- a/core/connection.js +++ b/core/connection.js @@ -27,9 +27,6 @@ goog.provide('Blockly.Connection'); goog.provide('Blockly.ConnectionDB'); -goog.require('Blockly.Workspace'); -goog.require('Blockly.BlockSvg'); - /** * Class for a connection between blocks. @@ -181,10 +178,10 @@ Blockly.Connection.prototype.connect = function(otherConnection) { childBlock.setParent(parentBlock); if (parentBlock.rendered) { - parentBlock.svg_.updateDisabled(); + parentBlock.updateDisabled(); } if (childBlock.rendered) { - childBlock.svg_.updateDisabled(); + childBlock.updateDisabled(); } if (parentBlock.rendered && childBlock.rendered) { if (this.type == Blockly.NEXT_STATEMENT || @@ -252,7 +249,7 @@ Blockly.Connection.prototype.disconnect = function() { parentBlock.render(); } if (childBlock.rendered) { - childBlock.svg_.updateDisabled(); + childBlock.updateDisabled(); childBlock.render(); } }; @@ -276,7 +273,7 @@ Blockly.Connection.prototype.targetBlock = function() { * @private */ Blockly.Connection.prototype.bumpAwayFrom_ = function(staticConnection) { - if (Blockly.Block.dragMode_ != 0) { + if (Blockly.dragMode_ != 0) { // Don't move blocks around while the user is doing the same. return; } diff --git a/core/field.js b/core/field.js index 6d5006642..4a482435e 100644 --- a/core/field.js +++ b/core/field.js @@ -28,9 +28,6 @@ goog.provide('Blockly.Field'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); -goog.require('Blockly.BlockSvg'); goog.require('goog.asserts'); goog.require('goog.userAgent'); @@ -54,9 +51,14 @@ Blockly.Field = function(text) { {'class': 'blocklyText'}, this.fieldGroup_); this.size_ = {height: 25, width: 0}; this.setText(text); - this.visible_ = true; }; +/** + * Is the field visible, or hidden due to the block being collapsed? + * @private + */ +Blockly.Field.prototype.visible_ = true; + /** * Clone this Field. This must be implemented by all classes derived from * Field. Since this class should not be instantiated, calling this method @@ -84,7 +86,8 @@ Blockly.Field.prototype.EDITABLE = true; */ Blockly.Field.prototype.init = function(block) { if (this.sourceBlock_) { - throw 'Field has already been initialized once.'; + // Field has already been initialized once. + return; } this.sourceBlock_ = block; this.updateEditable(); @@ -145,8 +148,11 @@ Blockly.Field.prototype.isVisible = function() { * @param {boolean} visible True if visible. */ Blockly.Field.prototype.setVisible = function(visible) { + if (this.visible_ == visible) { + return; + } this.visible_ = visible; - this.getRootElement().style.display = visible ? 'block' : 'none'; + this.getSvgRoot().style.display = visible ? 'block' : 'none'; this.render_(); }; @@ -155,7 +161,7 @@ Blockly.Field.prototype.setVisible = function(visible) { * Used for measuring the size and for positioning. * @return {!Element} The group element. */ -Blockly.Field.prototype.getRootElement = function() { +Blockly.Field.prototype.getSvgRoot = function() { return /** @type {!Element} */ (this.fieldGroup_); }; @@ -165,16 +171,20 @@ Blockly.Field.prototype.getRootElement = function() { * @private */ Blockly.Field.prototype.render_ = function() { - 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_.childNodes[0].length * 8; - } - if (this.borderRect_) { - this.borderRect_.setAttribute('width', - width + Blockly.BlockSvg.SEP_SPACE_X); + if (this.visible_) { + 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 (this.borderRect_) { + this.borderRect_.setAttribute('width', + width + Blockly.BlockSvg.SEP_SPACE_X); + } + } else { + var width = 0; } this.size_.width = width; }; @@ -274,7 +284,7 @@ Blockly.Field.prototype.onMouseUp_ = function(e) { } else if (Blockly.isRightButton(e)) { // Right-click. return; - } else if (Blockly.Block.dragMode_ == 2) { + } else if (Blockly.dragMode_ == 2) { // Drag operation is concluding. Don't open the editor. return; } else if (this.sourceBlock_.isEditable()) { diff --git a/core/field_checkbox.js b/core/field_checkbox.js index a3141bca7..9669e9b0a 100644 --- a/core/field_checkbox.js +++ b/core/field_checkbox.js @@ -97,7 +97,7 @@ Blockly.FieldCheckbox.prototype.setValue = function(strBool) { */ Blockly.FieldCheckbox.prototype.showEditor_ = function() { var newState = !this.state_; - if (this.changeHandler_) { + if (this.sourceBlock_ && this.changeHandler_) { // Call any change handler, and allow it to override. var override = this.changeHandler_(newState); if (override !== undefined) { diff --git a/core/field_colour.js b/core/field_colour.js index f33d38e3d..9b24ca822 100644 --- a/core/field_colour.js +++ b/core/field_colour.js @@ -96,6 +96,19 @@ Blockly.FieldColour.prototype.setValue = function(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_; + 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: @@ -155,21 +168,21 @@ Blockly.FieldColour.prototype.showEditor_ = function() { Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset); // Configure event handler. - var thisObj = this; + 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 (thisObj.changeHandler_) { + if (thisField.sourceBlock_ && thisField.changeHandler_) { // Call any change handler, and allow it to override. - var override = thisObj.changeHandler_(colour); + var override = thisField.changeHandler_(colour); if (override !== undefined) { colour = override; } } if (colour !== null) { - thisObj.setValue(colour); + thisField.setValue(colour); } }); }; diff --git a/core/field_dropdown.js b/core/field_dropdown.js index 699537de0..ca44a9f16 100644 --- a/core/field_dropdown.js +++ b/core/field_dropdown.js @@ -102,7 +102,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() { var menuItem = e.target; if (menuItem) { var value = menuItem.getValue(); - if (thisField.changeHandler_) { + if (this.sourceBlock_ && thisField.changeHandler_) { // Call any change handler, and allow it to override. var override = thisField.changeHandler_(value); if (override !== undefined) { diff --git a/core/field_image.js b/core/field_image.js index eccb29fd6..8aa77d5fd 100644 --- a/core/field_image.js +++ b/core/field_image.js @@ -94,7 +94,8 @@ Blockly.FieldImage.prototype.EDITABLE = false; */ Blockly.FieldImage.prototype.init = function(block) { if (this.sourceBlock_) { - throw 'Image has already been initialized once.'; + // Image has already been initialized once. + return; } this.sourceBlock_ = block; block.getSvgRoot().appendChild(this.fieldGroup_); @@ -161,3 +162,11 @@ Blockly.FieldImage.prototype.setText = function(alt) { } this.text_ = alt; }; + +/** + * Images are fixed width, no need to render. + * @private + */ +Blockly.FieldImage.prototype.render_ = function() { + // NOP +}; diff --git a/core/field_label.js b/core/field_label.js index adf3acb7f..07776d91a 100644 --- a/core/field_label.js +++ b/core/field_label.js @@ -66,7 +66,8 @@ Blockly.FieldLabel.prototype.EDITABLE = false; */ Blockly.FieldLabel.prototype.init = function(block) { if (this.sourceBlock_) { - throw 'Text has already been initialized once.'; + // Text has already been initialized once. + return; } this.sourceBlock_ = block; block.getSvgRoot().appendChild(this.textElement_); @@ -89,7 +90,7 @@ Blockly.FieldLabel.prototype.dispose = function() { * Used for measuring the size and for positioning. * @return {!Element} The group element. */ -Blockly.FieldLabel.prototype.getRootElement = function() { +Blockly.FieldLabel.prototype.getSvgRoot = function() { return /** @type {!Element} */ (this.textElement_); }; diff --git a/core/field_textinput.js b/core/field_textinput.js index bebc737db..a5056919b 100644 --- a/core/field_textinput.js +++ b/core/field_textinput.js @@ -81,7 +81,7 @@ Blockly.FieldTextInput.prototype.setText = function(text) { // No change if null. return; } - if (this.changeHandler_) { + if (this.sourceBlock_ && this.changeHandler_) { var validated = this.changeHandler_(text); // If the new text is invalid, validation returns null. // In this case we still want to display the illegal result. @@ -104,7 +104,7 @@ Blockly.FieldTextInput.prototype.showEditor_ = function(opt_quietInput) { goog.userAgent.IPAD)) { // Mobile browsers have issues with in-line textareas (focus & keyboards). var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_); - if (this.changeHandler_) { + if (this.sourceBlock_ && this.changeHandler_) { var override = this.changeHandler_(newValue); if (override !== undefined) { newValue = override; @@ -182,7 +182,7 @@ Blockly.FieldTextInput.prototype.validate_ = function() { var valid = true; goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_); var htmlInput = /** @type {!Element} */ (Blockly.FieldTextInput.htmlInput_); - if (this.changeHandler_) { + if (this.sourceBlock_ && this.changeHandler_) { valid = this.changeHandler_(htmlInput.value); } if (valid === null) { @@ -229,7 +229,7 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() { var htmlInput = Blockly.FieldTextInput.htmlInput_; // Save the edit (if it validates). var text = htmlInput.value; - if (thisField.changeHandler_) { + if (thisField.sourceBlock_ && thisField.changeHandler_) { text = thisField.changeHandler_(text); if (text === null) { // Invalid edit. diff --git a/core/field_variable.js b/core/field_variable.js index ad1c90dd2..1ca8cd852 100644 --- a/core/field_variable.js +++ b/core/field_variable.js @@ -108,7 +108,12 @@ Blockly.FieldVariable.prototype.setValue = function(text) { * @this {!Blockly.FieldVariable} */ Blockly.FieldVariable.dropdownCreate = function() { - var variableList = Blockly.Variables.allVariables(); + if (this.sourceBlock_ && this.sourceBlock_.workspace) { + var variableList = + Blockly.Variables.allVariables(this.sourceBlock_.workspace); + } else { + var variableList = []; + } // Ensure that the currently selected variable is an option. var name = this.getText(); if (name && variableList.indexOf(name) == -1) { diff --git a/core/flyout.js b/core/flyout.js index e316e322d..dcdc710fc 100644 --- a/core/flyout.js +++ b/core/flyout.js @@ -28,6 +28,7 @@ goog.provide('Blockly.Flyout'); goog.require('Blockly.Block'); goog.require('Blockly.Comment'); +goog.require('Blockly.WorkspaceSvg'); goog.require('goog.userAgent'); goog.require('goog.math.Rect'); @@ -42,7 +43,7 @@ Blockly.Flyout = function() { * @type {!Blockly.Workspace} * @private */ - this.workspace_ = new Blockly.Workspace( + this.workspace_ = new Blockly.WorkspaceSvg( function() {return flyout.getMetrics_();}, function(ratio) {return flyout.setMetrics_(ratio);}); this.workspace_.isFlyout = true; @@ -404,16 +405,16 @@ Blockly.Flyout.prototype.show = function(xmlList) { this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, this.blockMouseDown_(block))); } - this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block.svg_, - block.svg_.addSelect)); - this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block.svg_, - block.svg_.removeSelect)); + 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, 'mousedown', null, this.createBlockFunc_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block.svg_, - block.svg_.addSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block.svg_, - block.svg_.removeSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, + block.addSelect)); + this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, + block.removeSelect)); } // IE 11 is an incompetant browser that fails to fire mouseout events. @@ -421,7 +422,7 @@ Blockly.Flyout.prototype.show = function(xmlList) { var deselectAll = function(e) { var blocks = this.workspace_.getTopBlocks(false); for (var i = 0, block; block = blocks[i]; i++) { - block.svg_.removeSelect(); + block.removeSelect(); } }; this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', @@ -479,18 +480,6 @@ Blockly.Flyout.prototype.reflow = function() { } }; -/** - * Move a block to a specific location on the drawing surface. - * @param {number} x Horizontal location. - * @param {number} y Vertical location. - */ -Blockly.Block.prototype.moveTo = function(x, y) { - var oldXY = this.getRelativeToSurfaceXY(); - this.svg_.getRootElement().setAttribute('transform', - 'translate(' + x + ', ' + y + ')'); - this.moveConnections_(x - oldXY.x, y - oldXY.y); -}; - /** * Handle a mouse-down on an SVG block in a non-closing flyout. * @param {!Blockly.Block} block The flyout block to copy. diff --git a/core/generator.js b/core/generator.js index 246e7ed41..5c77dd785 100644 --- a/core/generator.js +++ b/core/generator.js @@ -65,12 +65,15 @@ Blockly.Generator.prototype.STATEMENT_PREFIX = null; /** * Generate code for all blocks in the workspace to the specified language. + * @param {Blockly.Workspace=} opt_workspace Workspace to generate code from. + * Defaults to main workspace. * @return {string} Generated code. */ -Blockly.Generator.prototype.workspaceToCode = function() { +Blockly.Generator.prototype.workspaceToCode = function(opt_workspace) { + var workspace = opt_workspace || Blockly.mainWorkspace; var code = []; - this.init(); - var blocks = Blockly.mainWorkspace.getTopBlocks(true); + 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)) { diff --git a/core/icon.js b/core/icon.js index 138fd487d..512754a1c 100644 --- a/core/icon.js +++ b/core/icon.js @@ -64,6 +64,10 @@ Blockly.Icon.prototype.iconY_ = 0; * @private */ Blockly.Icon.prototype.createIcon_ = function() { + if (this.iconGroup_) { + // Icon already exists. + return; + } /* Here's the markup that will be generated: */ diff --git a/core/inject.js b/core/inject.js index fd9a11475..72e4c6e63 100644 --- a/core/inject.js +++ b/core/inject.js @@ -27,6 +27,7 @@ goog.provide('Blockly.inject'); goog.require('Blockly.Css'); +goog.require('Blockly.WorkspaceSvg'); goog.require('goog.dom'); @@ -260,7 +261,7 @@ Blockly.createDom_ = function(container) { {'width': 10, 'height': 10, 'fill': '#aaa'}, pattern); Blockly.createSvgElement('path', {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, pattern); - Blockly.mainWorkspace = new Blockly.Workspace( + Blockly.mainWorkspace = new Blockly.WorkspaceSvg( Blockly.getMainWorkspaceMetrics_, Blockly.setMainWorkspaceMetrics_); svg.appendChild(Blockly.mainWorkspace.createDom()); @@ -285,7 +286,7 @@ Blockly.createDom_ = function(container) { } if (!Blockly.hasScrollbars) { var workspaceChanged = function() { - if (Blockly.Block.dragMode_ == 0) { + if (Blockly.dragMode_ == 0) { var metrics = Blockly.mainWorkspace.getMetrics(); var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; var edgeTop = metrics.viewTop + metrics.absoluteTop; diff --git a/core/input.js b/core/input.js index 0716ef71e..1d04a9a48 100644 --- a/core/input.js +++ b/core/input.js @@ -69,7 +69,7 @@ Blockly.Input.prototype.appendField = function(field, opt_name) { if (goog.isString(field)) { field = new Blockly.FieldLabel(/** @type {string} */ (field)); } - if (this.sourceBlock_.svg_) { + if (this.sourceBlock_.rendered) { field.init(this.sourceBlock_); } field.name = opt_name; @@ -160,7 +160,7 @@ Blockly.Input.prototype.setVisible = function(visible) { } var child = this.connection.targetBlock(); if (child) { - child.svg_.getRootElement().style.display = display; + child.getSvgRoot().style.display = display; if (!visible) { child.rendered = false; } diff --git a/core/mutator.js b/core/mutator.js index 72be1c2e0..49cc6ce00 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -29,6 +29,7 @@ goog.provide('Blockly.Mutator'); goog.require('Blockly.Bubble'); goog.require('Blockly.Icon'); +goog.require('Blockly.WorkspaceSvg'); /** @@ -64,6 +65,10 @@ Blockly.Mutator.prototype.workspaceHeight_ = 0; * Create the icon on the block. */ Blockly.Mutator.prototype.createIcon = function() { + if (this.iconMark_) { + // Icon already exists. + return; + } Blockly.Icon.prototype.createIcon_.call(this); /* Here's the markup that will be generated: @@ -116,7 +121,7 @@ Blockly.Mutator.prototype.createEditor_ = function() { {'class': 'blocklyMutatorBackground', 'height': '100%', 'width': '100%'}, this.svgDialog_); var mutator = this; - this.workspace_ = new Blockly.Workspace( + this.workspace_ = new Blockly.WorkspaceSvg( function() {return mutator.getFlyoutMetrics_();}, null); this.workspace_.flyout_ = new Blockly.Flyout(); this.workspace_.flyout_.autoClose = false; @@ -190,7 +195,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) { if (visible) { // Create the bubble. this.bubble_ = new Blockly.Bubble(this.block_.workspace, - this.createEditor_(), this.block_.svg_.svgPath_, + this.createEditor_(), this.block_.svgPath_, this.iconX_, this.iconY_, null, null); var thisObj = this; this.workspace_.flyout_.init(this.workspace_); @@ -247,7 +252,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) { * @private */ Blockly.Mutator.prototype.workspaceChanged_ = function() { - if (Blockly.Block.dragMode_ == 0) { + if (Blockly.dragMode_ == 0) { var blocks = this.workspace_.getTopBlocks(false); var MARGIN = 20; for (var b = 0, block; block = blocks[b]; b++) { @@ -269,6 +274,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() { this.block_.compose(this.rootBlock_); // Restore rendering and show the changes. this.block_.rendered = savedRendered; + // Mutation may have added some elements that need initalizing. + this.block_.initSvg(); if (this.block_.rendered) { this.block_.render(); } diff --git a/core/realtime-client-utils.js b/core/realtime-client-utils.js index 3b1d18ab8..fd305f09a 100644 --- a/core/realtime-client-utils.js +++ b/core/realtime-client-utils.js @@ -88,12 +88,12 @@ rtclient.getParams = function() { params[decodeURIComponent(paramStr[0])] = decodeURIComponent(paramStr[1]); } } - var hashFragment = window.location.hash; + var hashFragment = this.location && this.location.hash; if (hashFragment) { parseParams(hashFragment); } // Opening from Drive will encode the state in a query search parameter. - var searchFragment = window.location.search; + var searchFragment = this.location && this.location.search; if (searchFragment) { parseParams(searchFragment); } diff --git a/core/scrollbar.js b/core/scrollbar.js index a0fe0fbdf..593935393 100644 --- a/core/scrollbar.js +++ b/core/scrollbar.js @@ -176,9 +176,12 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { /** * 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). */ -Blockly.Scrollbar.scrollbarThickness = - ('ontouchstart' in document.documentElement) ? 25 : 15; +if (this.document) { + Blockly.Scrollbar.scrollbarThickness = + ('ontouchstart' in this.document.documentElement) ? 25 : 15; +} /** * Dispose of this scrollbar. diff --git a/core/tooltip.js b/core/tooltip.js index 85661cfd6..6fb4666f0 100644 --- a/core/tooltip.js +++ b/core/tooltip.js @@ -213,7 +213,7 @@ Blockly.Tooltip.onMouseMove_ = function(e) { if (!Blockly.Tooltip.element_ || !Blockly.Tooltip.element_.tooltip) { // No tooltip here to show. return; - } else if (Blockly.Block.dragMode_ != 0) { + } else if (Blockly.dragMode_ != 0) { // Don't display a tooltip during a drag. return; } else if (Blockly.WidgetDiv.isVisible()) { diff --git a/core/utils.js b/core/utils.js index c54dfed94..31e547b0b 100644 --- a/core/utils.js +++ b/core/utils.js @@ -115,7 +115,8 @@ Blockly.bindEvent_ = function(node, name, thisObject, func) { * @type {Object} */ Blockly.bindEvent_.TOUCH_MAP = {}; -if ('ontouchstart' in document.documentElement) { +if (this.document && this.document.documentElement && + 'ontouchstart' in document.documentElement) { Blockly.bindEvent_.TOUCH_MAP = { 'mousedown': ['touchstart'], 'mousemove': ['touchmove'], diff --git a/core/variables.js b/core/variables.js index 00233e058..4cd8e95e0 100644 --- a/core/variables.js +++ b/core/variables.js @@ -38,15 +38,21 @@ Blockly.Variables.NAME_TYPE = 'VARIABLE'; /** * Find all user-created variables. - * @param {Blockly.Block=} opt_block Optional root block. + * @param {Blockly.Block|Blockly.Workspace|undefined} opt_root Optional root + * block or workspace. Defaults to main workspace. * @return {!Array.} Array of variable names. */ -Blockly.Variables.allVariables = function(opt_block) { +Blockly.Variables.allVariables = function(opt_root) { + var root = opt_root || Blockly.mainWorkspace; var blocks; - if (opt_block) { - blocks = opt_block.getDescendants(); + if (root.getDescendants) { + // Root is Block. + blocks = root.getDescendants(); + } else if (root.getAllBlocks) { + // Root is Workspace. + blocks = root.getAllBlocks(); } else { - blocks = Blockly.mainWorkspace.getAllBlocks(); + throw 'Not Block or Workspace: ' + root; } var variableHash = Object.create(null); // Iterate through every block and add each variable to the hash. @@ -75,9 +81,12 @@ Blockly.Variables.allVariables = function(opt_block) { * 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=} opt_workspace Workspace rename variables in. + * Defaults to main workspace. */ -Blockly.Variables.renameVariable = function(oldName, newName) { - var blocks = Blockly.mainWorkspace.getAllBlocks(); +Blockly.Variables.renameVariable = function(oldName, newName, opt_workspace) { + var workspace = opt_workspace || Blockly.mainWorkspace; + var blocks = workspace.getAllBlocks(); // Iterate through every block. for (var x = 0; x < blocks.length; x++) { var func = blocks[x].renameVar; diff --git a/core/warning.js b/core/warning.js index 750dcf2dd..00f4a151f 100644 --- a/core/warning.js +++ b/core/warning.js @@ -107,7 +107,7 @@ Blockly.Warning.prototype.setVisible = function(visible) { var paragraph = Blockly.Warning.textToDom_(this.text_); this.bubble_ = new Blockly.Bubble( /** @type {!Blockly.Workspace} */ (this.block_.workspace), - paragraph, this.block_.svg_.svgPath_, + paragraph, this.block_.svgPath_, this.iconX_, this.iconY_, null, null); if (Blockly.RTL) { // Right-align the paragraph. diff --git a/core/workspace.js b/core/workspace.js index a129bd9fc..d7a3a03d6 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -26,37 +26,38 @@ goog.provide('Blockly.Workspace'); -// TODO(scr): Fix circular dependencies -// goog.require('Blockly.Block'); -goog.require('Blockly.ScrollbarPair'); -goog.require('Blockly.Trashcan'); -goog.require('Blockly.Xml'); -goog.require('goog.math'); -goog.require('goog.math.Coordinate'); - /** - * Class for a workspace. - * @param {Function} getMetrics A function that returns size/scrolling metrics. - * @param {Function} setMetrics A function that sets size/scrolling metrics. + * Class for a workspace. This is a data structure that contains blocks. + * There is no UI, and can be created headlessly. * @constructor */ -Blockly.Workspace = function(getMetrics, setMetrics) { - this.getMetrics = getMetrics; - this.setMetrics = setMetrics; - - /** @type {boolean} */ - this.isFlyout = false; +Blockly.Workspace = function() { /** * @type {!Array.} * @private */ this.topBlocks_ = []; +}; - /** @type {number} */ - this.maxBlocks = Infinity; +/** + * Workspaces may be headless. + * @type {boolean} True if visible. False if headless. + */ +Blockly.Workspace.prototype.rendered = false; - Blockly.ConnectionDB.init(this); +/** + * Maximum number of blocks allowed in this workspace. + * @type number + */ +Blockly.Workspace.prototype.maxBlocks = Infinity; + +/** + * Dispose of this workspace. + * Unlink from all DOM elements to prevent memory leaks. + */ +Blockly.Workspace.prototype.dispose = function() { + this.clear(); }; /** @@ -67,121 +68,12 @@ Blockly.Workspace = function(getMetrics, setMetrics) { */ Blockly.Workspace.SCAN_ANGLE = 3; -/** - * Can this workspace be dragged around (true) or is it fixed (false)? - * @type {boolean} - */ -Blockly.Workspace.prototype.dragMode = false; - -/** - * Current horizontal scrolling offset. - * @type {number} - */ -Blockly.Workspace.prototype.scrollX = 0; - -/** - * Current vertical scrolling offset. - * @type {number} - */ -Blockly.Workspace.prototype.scrollY = 0; - -/** - * The workspace's trashcan (if any). - * @type {Blockly.Trashcan} - */ -Blockly.Workspace.prototype.trashcan = null; - -/** - * PID of upcoming firing of a change event. Used to fire only one event - * after multiple changes. - * @type {?number} - * @private - */ -Blockly.Workspace.prototype.fireChangeEventPid_ = null; - -/** - * This workspace's scrollbars, if they exist. - * @type {Blockly.ScrollbarPair} - */ -Blockly.Workspace.prototype.scrollbar = null; - -/** - * Create the trash can elements. - * @return {!Element} The workspace's SVG group. - */ -Blockly.Workspace.prototype.createDom = function() { - /* - - [Trashcan may go here] - - - - */ - this.svgGroup_ = Blockly.createSvgElement('g', {}, null); - this.svgBlockCanvas_ = Blockly.createSvgElement('g', {}, this.svgGroup_); - this.svgBubbleCanvas_ = Blockly.createSvgElement('g', {}, this.svgGroup_); - this.fireChangeEvent(); - return this.svgGroup_; -}; - -/** - * Dispose of this workspace. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Workspace.prototype.dispose = function() { - if (this.svgGroup_) { - goog.dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; - } - this.svgBlockCanvas_ = null; - this.svgBubbleCanvas_ = null; - if (this.flyout_) { - this.flyout_.dispose(); - this.flyout_ = null; - } - if (this.trashcan) { - this.trashcan.dispose(); - this.trashcan = null; - } -}; - -/** - * Add a trashcan. - */ -Blockly.Workspace.prototype.addTrashcan = function() { - if (Blockly.hasTrashcan && !Blockly.readOnly) { - this.trashcan = new Blockly.Trashcan(this); - var svgTrashcan = this.trashcan.createDom(); - this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_); - this.trashcan.init(); - } -}; - -/** - * Get the SVG element that forms the drawing surface. - * @return {!Element} SVG element. - */ -Blockly.Workspace.prototype.getCanvas = function() { - return this.svgBlockCanvas_; -}; - -/** - * Get the SVG element that forms the bubble surface. - * @return {!SVGGElement} SVG element. - */ -Blockly.Workspace.prototype.getBubbleCanvas = function() { - return this.svgBubbleCanvas_; -}; - /** * Add a block to the list of top blocks. * @param {!Blockly.Block} block Block to remove. */ Blockly.Workspace.prototype.addTopBlock = function(block) { this.topBlocks_.push(block); - if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) { - Blockly.Realtime.addTopBlock(block); - } this.fireChangeEvent(); }; @@ -191,9 +83,9 @@ Blockly.Workspace.prototype.addTopBlock = function(block) { */ Blockly.Workspace.prototype.removeTopBlock = function(block) { var found = false; - for (var child, x = 0; child = this.topBlocks_[x]; x++) { + for (var child, i = 0; child = this.topBlocks_[i]; i++) { if (child == block) { - this.topBlocks_.splice(x, 1); + this.topBlocks_.splice(i, 1); found = true; break; } @@ -201,9 +93,6 @@ Blockly.Workspace.prototype.removeTopBlock = function(block) { if (!found) { throw 'Block not present in workspace\'s list of top-most blocks.'; } - if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) { - Blockly.Realtime.removeTopBlock(block); - } this.fireChangeEvent(); }; @@ -236,8 +125,8 @@ Blockly.Workspace.prototype.getTopBlocks = function(ordered) { */ Blockly.Workspace.prototype.getAllBlocks = function() { var blocks = this.getTopBlocks(false); - for (var x = 0; x < blocks.length; x++) { - blocks.push.apply(blocks, blocks[x].getChildren()); + for (var i = 0; i < blocks.length; i++) { + blocks.push.apply(blocks, blocks[i].getChildren()); } return blocks; }; @@ -246,22 +135,19 @@ Blockly.Workspace.prototype.getAllBlocks = function() { * Dispose of all blocks in workspace. */ Blockly.Workspace.prototype.clear = function() { - Blockly.hideChaff(); while (this.topBlocks_.length) { this.topBlocks_[0].dispose(); } }; /** - * Render all blocks in workspace. + * 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.render = function() { - var renderList = this.getAllBlocks(); - for (var x = 0, block; block = renderList[x]; x++) { - if (!block.getChildren().length) { - block.render(); - } - } +Blockly.Workspace.prototype.getWidth = function() { + return 0; }; /** @@ -272,7 +158,7 @@ Blockly.Workspace.prototype.render = function() { Blockly.Workspace.prototype.getBlockById = function(id) { // If this O(n) function fails to scale well, maintain a hash table of IDs. var blocks = this.getAllBlocks(); - for (var x = 0, block; block = blocks[x]; x++) { + for (var i = 0, block; block = blocks[i]; i++) { if (block.id == id) { return block; } @@ -280,115 +166,6 @@ Blockly.Workspace.prototype.getBlockById = function(id) { return null; }; -/** - * Turn the visual trace functionality on or off. - * @param {boolean} armed True if the trace should be on. - */ -Blockly.Workspace.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}); - } -}; - -/** - * Highlight a block in the workspace. - * @param {?string} id ID of block to find. - */ -Blockly.Workspace.prototype.highlightBlock = function(id) { - if (this.traceOn_ && Blockly.Block.dragMode_ != 0) { - // 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; - } - } - // 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. - if (block) { - block.select(); - } else if (Blockly.selected) { - Blockly.selected.unselect(); - } - // Restore the monitor for user activity after the selection event has fired. - var thisWorkspace = this; - setTimeout(function() {thisWorkspace.traceOn(true);}, 1); -}; - -/** - * Fire a change event for this workspace. Changes include new block, dropdown - * edits, mutations, connections, etc. Groups of simultaneous changes (e.g. - * a tree of blocks being deleted) are merged into one event. - * Applications may hook workspace changes by listening for - * 'blocklyWorkspaceChange' on Blockly.mainWorkspace.getCanvas(). - */ -Blockly.Workspace.prototype.fireChangeEvent = function() { - if (this.fireChangeEventPid_) { - window.clearTimeout(this.fireChangeEventPid_); - } - var canvas = this.svgBlockCanvas_; - if (canvas) { - this.fireChangeEventPid_ = window.setTimeout(function() { - Blockly.fireUiEvent(canvas, 'blocklyWorkspaceChange'); - }, 0); - } -}; - -/** - * Paste the provided block onto the workspace. - * @param {!Element} xmlBlock XML block element. - */ -Blockly.Workspace.prototype.paste = function(xmlBlock) { - if (xmlBlock.getElementsByTagName('block').length >= - this.remainingCapacity()) { - return; - } - var block = Blockly.Xml.domToBlock(this, xmlBlock); - // 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 (Blockly.RTL) { - blockX = -blockX; - } - // Offset block until not clobbering another block. - do { - var collide = false; - var allBlocks = this.getAllBlocks(); - for (var x = 0, otherBlock; otherBlock = allBlocks[x]; x++) { - var otherXY = otherBlock.getRelativeToSurfaceXY(); - if (Math.abs(blockX - otherXY.x) <= 1 && - Math.abs(blockY - otherXY.y) <= 1) { - if (Blockly.RTL) { - blockX -= Blockly.SNAP_RADIUS; - } else { - blockX += Blockly.SNAP_RADIUS; - } - blockY += Blockly.SNAP_RADIUS * 2; - collide = true; - } - } - } while (collide); - block.moveBy(blockX, blockY); - } - block.select(); -}; - /** * The number of blocks that may be added to the workspace before reaching * the maxBlocks. @@ -402,50 +179,8 @@ Blockly.Workspace.prototype.remainingCapacity = function() { }; /** - * Make a list of all the delete areas for this workspace. + * Something on this workspace has changed. */ -Blockly.Workspace.prototype.recordDeleteAreas = function() { - if (this.trashcan) { - this.deleteAreaTrash_ = this.trashcan.getRect(); - } else { - this.deleteAreaTrash_ = null; - } - if (this.flyout_) { - this.deleteAreaToolbox_ = this.flyout_.getRect(); - } else if (this.toolbox_) { - this.deleteAreaToolbox_ = this.toolbox_.getRect(); - } else { - this.deleteAreaToolbox_ = null; - } +Blockly.Workspace.prototype.fireChangeEvent = function() { + // NOP. }; - -/** - * Is the mouse event over a delete area (toolbar 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. - */ -Blockly.Workspace.prototype.isDeleteArea = function(e) { - var isDelete = false; - var mouseXY = Blockly.mouseToSvg(e); - var xy = new goog.math.Coordinate(mouseXY.x, mouseXY.y); - 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.deleteAreaToolbox_) { - if (this.deleteAreaToolbox_.contains(xy)) { - Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); - return true; - } - } - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - return false; -}; - -// Export symbols that would otherwise be renamed by Closure compiler. -Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear; diff --git a/core/workspace_svg.js b/core/workspace_svg.js new file mode 100644 index 000000000..6ba4ffd21 --- /dev/null +++ b/core/workspace_svg.js @@ -0,0 +1,388 @@ +/** + * @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.Block'); +goog.require('Blockly.ScrollbarPair'); +goog.require('Blockly.Trashcan'); +goog.require('Blockly.Workspace'); +goog.require('Blockly.Xml'); +goog.require('goog.math'); +goog.require('goog.math.Coordinate'); + + +/** + * Class for a workspace. This is an onscreen area with optional trashcan, + * scrollbars, bubbles, and dragging. + * @param {Function} getMetrics A function that returns size/scrolling metrics. + * @param {Function} setMetrics A function that sets size/scrolling metrics. + * @extends {Blockly.Workspace} + * @constructor + */ +Blockly.WorkspaceSvg = function(getMetrics, setMetrics) { + Blockly.WorkspaceSvg.superClass_.constructor.call(this); + this.getMetrics = getMetrics; + this.setMetrics = setMetrics; + + Blockly.ConnectionDB.init(this); +}; +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. + */ +Blockly.WorkspaceSvg.prototype.rendered = true; + +/** + * Is this workspace the surface for a flyout? + * @type {boolean} + */ +Blockly.WorkspaceSvg.prototype.isFlyout = false; + +/** + * Can this workspace be dragged around (true) or is it fixed (false)? + * @type {boolean} + */ +Blockly.WorkspaceSvg.prototype.dragMode = false; + +/** + * Current horizontal scrolling offset. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollX = 0; + +/** + * Current vertical scrolling offset. + * @type {number} + */ +Blockly.WorkspaceSvg.prototype.scrollY = 0; + +/** + * The workspace's trashcan (if any). + * @type {Blockly.Trashcan} + */ +Blockly.WorkspaceSvg.prototype.trashcan = null; + +/** + * PID of upcoming firing of a change event. Used to fire only one event + * after multiple changes. + * @type {?number} + * @private + */ +Blockly.WorkspaceSvg.prototype.fireChangeEventPid_ = null; + +/** + * This workspace's scrollbars, if they exist. + * @type {Blockly.ScrollbarPair} + */ +Blockly.WorkspaceSvg.prototype.scrollbar = null; + +/** + * Create the trash can elements. + * @return {!Element} The workspace's SVG group. + */ +Blockly.WorkspaceSvg.prototype.createDom = function() { + /* + + [Trashcan may go here] + // Block canvas + // Bubble canvas + [Scrollbars may go here] + + */ + this.svgGroup_ = Blockly.createSvgElement('g', {}, null); + this.svgBlockCanvas_ = Blockly.createSvgElement('g', {}, this.svgGroup_); + this.svgBubbleCanvas_ = Blockly.createSvgElement('g', {}, this.svgGroup_); + this.fireChangeEvent(); + 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; + 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.flyout_) { + this.flyout_.dispose(); + this.flyout_ = null; + } + if (this.trashcan) { + this.trashcan.dispose(); + this.trashcan = null; + } +}; + +/** + * Add a trashcan. + */ +Blockly.WorkspaceSvg.prototype.addTrashcan = function() { + if (Blockly.hasTrashcan && !Blockly.readOnly) { + this.trashcan = new Blockly.Trashcan(this); + var svgTrashcan = this.trashcan.createDom(); + this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_); + this.trashcan.init(); + } +}; + +/** + * 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_; +}; + +/** + * Add a block to the list of top blocks. + * @param {!Blockly.Block} block Block to remove. + */ +Blockly.WorkspaceSvg.prototype.addTopBlock = function(block) { + Blockly.WorkspaceSvg.superClass_.addTopBlock.call(this, block); + if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) { + Blockly.Realtime.addTopBlock(block); + } +}; + +/** + * Remove a block from the list of top blocks. + * @param {!Blockly.Block} block Block to remove. + */ +Blockly.WorkspaceSvg.prototype.removeTopBlock = function(block) { + Blockly.WorkspaceSvg.superClass_.removeTopBlock.call(this, block); + if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) { + Blockly.Realtime.removeTopBlock(block); + } +}; + +/** + * Dispose of all blocks in workspace. + */ +Blockly.WorkspaceSvg.prototype.clear = function() { + Blockly.hideChaff(); + Blockly.WorkspaceSvg.superClass_.clear.call(this); +}; + +/** + * Returns the horizontal offset of the workspace. + * Intended for LTR/RTL compatibility in XML. + * @return {number} Width. + */ +Blockly.WorkspaceSvg.prototype.getWidth = function() { + return this.getMetrics().viewWidth; +}; + +/** + * Render all blocks in workspace. + */ +Blockly.WorkspaceSvg.prototype.render = function() { + var renderList = this.getAllBlocks(); + for (var x = 0, block; block = renderList[x]; x++) { + if (!block.getChildren().length) { + block.render(); + } + } +}; + +/** + * Turn the visual trace functionality on or off. + * @param {boolean} armed True if the trace should be on. + */ +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}); + } +}; + +/** + * Highlight a block in the workspace. + * @param {?string} id ID of block to find. + */ +Blockly.WorkspaceSvg.prototype.highlightBlock = function(id) { + if (this.traceOn_ && Blockly.dragMode_ != 0) { + // 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; + } + } + // 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. + if (block) { + block.select(); + } else if (Blockly.selected) { + Blockly.selected.unselect(); + } + // Restore the monitor for user activity after the selection event has fired. + var thisWorkspace = this; + setTimeout(function() {thisWorkspace.traceOn(true);}, 1); +}; + +/** + * Fire a change event for this workspace. Changes include new block, dropdown + * edits, mutations, connections, etc. Groups of simultaneous changes (e.g. + * a tree of blocks being deleted) are merged into one event. + * Applications may hook workspace changes by listening for + * 'blocklyWorkspaceChange' on Blockly.mainWorkspace.getCanvas(). + */ +Blockly.WorkspaceSvg.prototype.fireChangeEvent = function() { + if (!this.rendered) { + return; + } + if (this.fireChangeEventPid_) { + window.clearTimeout(this.fireChangeEventPid_); + } + var canvas = this.svgBlockCanvas_; + if (canvas) { + this.fireChangeEventPid_ = window.setTimeout(function() { + Blockly.fireUiEvent(canvas, 'blocklyWorkspaceChange'); + }, 0); + } +}; + +/** + * Paste the provided block onto the workspace. + * @param {!Element} xmlBlock XML block element. + */ +Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) { + if (xmlBlock.getElementsByTagName('block').length >= + this.remainingCapacity()) { + return; + } + var block = Blockly.Xml.domToBlock(this, xmlBlock); + // 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 (Blockly.RTL) { + blockX = -blockX; + } + // Offset block until not clobbering another block. + do { + var collide = false; + var allBlocks = this.getAllBlocks(); + for (var x = 0, otherBlock; otherBlock = allBlocks[x]; x++) { + var otherXY = otherBlock.getRelativeToSurfaceXY(); + if (Math.abs(blockX - otherXY.x) <= 1 && + Math.abs(blockY - otherXY.y) <= 1) { + if (Blockly.RTL) { + blockX -= Blockly.SNAP_RADIUS; + } else { + blockX += Blockly.SNAP_RADIUS; + } + blockY += Blockly.SNAP_RADIUS * 2; + collide = true; + } + } + } while (collide); + block.moveBy(blockX, blockY); + } + block.select(); +}; + +/** + * Make a list of all the delete areas for this workspace. + */ +Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { + if (this.trashcan) { + this.deleteAreaTrash_ = this.trashcan.getRect(); + } else { + this.deleteAreaTrash_ = null; + } + if (this.flyout_) { + this.deleteAreaToolbox_ = this.flyout_.getRect(); + } else if (this.toolbox_) { + this.deleteAreaToolbox_ = this.toolbox_.getRect(); + } else { + this.deleteAreaToolbox_ = null; + } +}; + +/** + * Is the mouse event over a delete area (toolbar 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. + */ +Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) { + var isDelete = false; + var mouseXY = Blockly.mouseToSvg(e); + var xy = new goog.math.Coordinate(mouseXY.x, mouseXY.y); + 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.deleteAreaToolbox_) { + if (this.deleteAreaToolbox_.contains(xy)) { + Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE); + return true; + } + } + Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); + return false; +}; + +// Export symbols that would otherwise be renamed by Closure compiler. +Blockly.WorkspaceSvg.prototype['clear'] = Blockly.WorkspaceSvg.prototype.clear; diff --git a/core/xml.js b/core/xml.js index dc2f96ff9..377ea622b 100644 --- a/core/xml.js +++ b/core/xml.js @@ -32,13 +32,13 @@ goog.provide('Blockly.Xml'); /** * Encode a block tree as XML. - * @param {!Object} workspace The SVG workspace. + * @param {!Blockly.Workspace} workspace The workspace containing blocks. * @return {!Element} XML document. */ Blockly.Xml.workspaceToDom = function(workspace) { var width; // Not used in LTR. if (Blockly.RTL) { - width = workspace.getMetrics().viewWidth; + width = workarea.getWidth(); } var xml = goog.dom.createDom('xml'); var blocks = workspace.getTopBlocks(true); @@ -82,13 +82,15 @@ Blockly.Xml.blockToDom_ = function(block) { } } - if (block.comment) { - var commentElement = goog.dom.createDom('comment', null, - block.comment.getText()); - commentElement.setAttribute('pinned', block.comment.isVisible()); - var hw = block.comment.getBubbleSize(); - commentElement.setAttribute('h', hw.height); - commentElement.setAttribute('w', hw.width); + 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); } @@ -208,12 +210,13 @@ Blockly.Xml.textToDom = function(text) { /** * Decode an XML DOM and create blocks on the workspace. - * @param {!Blockly.Workspace} workspace The SVG workspace. + * @param {!Blockly.Workspace} workspace The workspace. * @param {!Element} xml XML DOM. */ Blockly.Xml.domToWorkspace = function(workspace, xml) { + var width; // Not used in LTR. if (Blockly.RTL) { - var width = workspace.getMetrics().viewWidth; + width = workspace.getWidth(); } for (var x = 0, xmlChild; xmlChild = xml.childNodes[x]; x++) { if (xmlChild.nodeName.toLowerCase() == 'block') { @@ -260,7 +263,8 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { } else { block = Blockly.Block.obtain(workspace, prototypeName); } - if (!block.svg_) { + if (block.initSvg) { + // SVG blocks are rendered, headless blocks are not. block.initSvg(); } @@ -308,6 +312,10 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { // Custom data for an advanced block. if (block.domToMutation) { block.domToMutation(xmlChild); + if (block.initSvg) { + // Mutation may have added some elements that need initalizing. + block.initSvg(); + } } break; case 'comment': @@ -317,12 +325,15 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { // Give the renderer a millisecond to render and position the block // before positioning the comment bubble. setTimeout(function() { - block.comment.setVisible(visible == 'true'); + 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)) { + if (!isNaN(bubbleW) && !isNaN(bubbleH) && + block.comment && block.comment.setVisible) { block.comment.setBubbleSize(bubbleW, bubbleH); } break; @@ -370,6 +381,7 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { break; default: // Unknown tag; ignore. Same principle as HTML parsers. + console.log('Ignoring unknown tag: ' + xmlChild.nodeName); } } @@ -377,13 +389,15 @@ Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) { if (collapsed) { block.setCollapsed(collapsed == 'true'); } - var next = block.getNextBlock(); - if (next) { - // Next block in a stack needs to square off its corners. - // Rendering a child will render its parent. - next.render(); - } else { - block.render(); + if (workspace.rendered) { + var next = block.getNextBlock(); + if (next) { + // Next block in a stack needs to square off its corners. + // Rendering a child will render its parent. + next.render(); + } else { + block.render(); + } } return block; }; @@ -402,13 +416,13 @@ Blockly.Xml.deleteNext = function(xmlBlock) { }; // Export symbols that would otherwise be renamed by Closure compiler. -if (!window['Blockly']) { - window['Blockly'] = {}; +if (!this['Blockly']) { + this['Blockly'] = {}; } -if (!window['Blockly']['Xml']) { - window['Blockly']['Xml'] = {}; +if (!this['Blockly']['Xml']) { + this['Blockly']['Xml'] = {}; } -window['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText; -window['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace; -window['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom; -window['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom; +this['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText; +this['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace; +this['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom; +this['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom; diff --git a/dart_compressed.js b/dart_compressed.js index 6d618d28f..266ec880f 100644 --- a/dart_compressed.js +++ b/dart_compressed.js @@ -5,7 +5,8 @@ // Copyright 2014 Google Inc. Apache License 2.0 Blockly.Dart=new Blockly.Generator("Dart");Blockly.Dart.addReservedWords("assert,break,case,catch,class,const,continue,default,do,else,enum,extends,false,final,finally,for,if,in,is,new,null,rethrow,return,super,switch,this,throw,true,try,var,void,while,with,print,identityHashCode,identical,BidirectionalIterator,Comparable,double,Function,int,Invocation,Iterable,Iterator,List,Map,Match,num,Pattern,RegExp,Set,StackTrace,String,StringSink,Type,bool,DateTime,Deprecated,Duration,Expando,Null,Object,RuneIterator,Runes,Stopwatch,StringBuffer,Symbol,Uri,Comparator,AbstractClassInstantiationError,ArgumentError,AssertionError,CastError,ConcurrentModificationError,CyclicInitializationError,Error,Exception,FallThroughError,FormatException,IntegerDivisionByZeroException,NoSuchMethodError,NullThrownError,OutOfMemoryError,RangeError,StackOverflowError,StateError,TypeError,UnimplementedError,UnsupportedError"); Blockly.Dart.ORDER_ATOMIC=0;Blockly.Dart.ORDER_UNARY_POSTFIX=1;Blockly.Dart.ORDER_UNARY_PREFIX=2;Blockly.Dart.ORDER_MULTIPLICATIVE=3;Blockly.Dart.ORDER_ADDITIVE=4;Blockly.Dart.ORDER_SHIFT=5;Blockly.Dart.ORDER_BITWISE_AND=6;Blockly.Dart.ORDER_BITWISE_XOR=7;Blockly.Dart.ORDER_BITWISE_OR=8;Blockly.Dart.ORDER_RELATIONAL=9;Blockly.Dart.ORDER_EQUALITY=10;Blockly.Dart.ORDER_LOGICAL_AND=11;Blockly.Dart.ORDER_LOGICAL_OR=12;Blockly.Dart.ORDER_CONDITIONAL=13;Blockly.Dart.ORDER_CASCADE=14; -Blockly.Dart.ORDER_ASSIGNMENT=15;Blockly.Dart.ORDER_NONE=99;Blockly.Dart.init=function(){Blockly.Dart.definitions_=Object.create(null);Blockly.Dart.functionNames_=Object.create(null);Blockly.Dart.variableDB_?Blockly.Dart.variableDB_.reset():Blockly.Dart.variableDB_=new Blockly.Names(Blockly.Dart.RESERVED_WORDS_);for(var a=[],b=Blockly.Variables.allVariables(),c=0;c~;1G$<8Qy6Gx8D-Wicybh6jcP_m+ogd|(!i;zwE zz5O4L|Kszx``qJwpL>7apZDvzUYBTMq(e=?Mgc((wVtk~892LwmkEp%y#FR2{S8i} z&L|yC=;HDyef>NejF9{3+5|!nCBx;504gkE0fS^gdWKqLb5sZf{JH=&zcvKX<>+au zS%l4O-3~V~pJM(8hnv7)!cEns_elvmt_dTUOD(luc9nW0o4ATv(g)2t*0v=vYB9CO zxS5Q0`g+(s(xMlwSo*wVbS0R8kr>H#Evsmr+k1|Vfo}9)%juhfVb^Wt(8&3&wb{Ut z*XbIX#7w`bJ2v?o$(`Q8-^0fR2UY|2f6sHzN1urgeYm;sdZ@3;Oe85OX|&nh_%XSa zTO+1nT^I>ZB)(!*8&g#!usw^98y+^;-}p+o_PN+R%Re- znqHHYjeqk-g^-A7Y;EIsuDKN=_ae8_R?; z^75E-b8<%eNfR=L@~%qLjtveb8WqWx@pnX>g%GMP_oqMac|^+qu_=X!4Z2Dt(5aLd zPt`jJ@s^*SMlAWs6UT)7p8GW6;_GYtK&7Oxu+RdAym(=T!V;#YrXtwcQTAOS0vZ|`GjF2K`qc)&I}Eb1u^Fq-Nw;N; zK0lW9^z@{MY~MyJXV>wpd?mZ*=B8z3Wu@(qhHn)3B6NFfceb_Y}%VPNrO4Te~9J9&zABs7g;yZ((bz4=&=3jEsaQ z_xc7W$jHf!7s}q#wX=HyWz^SS{qZ{TXjytgQ`4*_$Jxb2&)7I!asi%*2?z{K1bfxh zrF223>G`^qjeaz&WJD({Ox|biRbosG zRCE)~mCmKC?&&EaCNAE1Z%Se(4E?)}n2d~1SQzH!=C6d+pES!b0~zk#Z!HtgNiAu5S10P~PI=;_et8S=7#0+UdVc+8O*` zg3pi6#_hv1RTyQ+)noxoFFJ*e_aP2f7fIypIEk#r{R4B1Lji8}3`pXa9KR~Xzw2! zI5|1R;se*p+uGWCBXBs};`Vl0fwX&@uyn}gEfyA_Lxu2BWo2qNxY8d)MTv-srGP64O7rtsT4P0c%S+Jz3gkIxzD-Y$zK^H$ zJly=oPX`+q7{H2(in8Ee+O@+WP}XPRe}0Z($QiKx%VVW#arxey(8|`9p{8bTZZ1DO zF=xit(>i?deLQi@=H{lblvLJat&K)9^Ho8p#cha5;ACOJkx&(n#}9WXaM0|YJSdd& zE}ZedvZT*J^XS#9SHg;lh3Xm_dMH$~b{dCX0KRC(Q1@PJ?a#~}pk{)9PyVd&BPjH& zt@FI@-Lr(%;BXan_FY*f!jQfyJLyGg(}5k#kBfKsaQ(Q?ljsJ z<>ftBA4p^nfGy!md5P;`BQLeZf2H0pFqtHgGfk7OrPYvetW)0 znU$60=;jvx<+*W10D3@87)`!N^V}>l|h+DH8mnYUV(7zPC1|n zpyAp4TUv`D0VfH*_wlDZS2{deb}08C=HMx!g{Ys78pihOe$)+M;ww3kuYQ*Q%^mHL8=y=D8 zvEq)sy&+Ioe!#a*ppZBhTz$R!l4?D#tZXin@#G0Rf#as-y=A84URQ5#!@j;g{!HEK z&d$Q1;9$@F^)Y?9%9fU#l+;ua5s^g2mXBT{8DC$y0Pi_JKNkjUyYKBy4tf9RZ0)tQ ziil9GIOF5x73Sy11O){ReE5LjQh6{i@OS@wr7ulVhbjAus0`nI;VuI}zSBPBPj zkr?dy(L7p6A`caHc4%oUQymu{PeM*i2yjsWe097xnYkc75ip;C$J*MuwXON2V{N3w zSWZrkIA(l&oaNifiYuY2MgmpYSDdvuP-9kh_H5w)KVF45yI_P(Y;5!h2nby7-qjA> zo<-ts8v*3C-2Wz}Z)hk0=rTSzDG28Mxn;?5;P2;Gip9dJPJs~&4hO+WQmAs}q@<+>dwSx4fDQt2 z5;T5laNTFtgnF^bgENkdRuH(aK}UcgS+4Fm#F+R#9knVGphi~oam zX+P4y0PAWt+xXsol}X&Tvh(7@syaVhjUWEnKk`28NOoXoNFyXf4uELqtF=RLnB7Gj za-}bo9Y#*BW@JPYxD=nt(gecU|HZS10vG`d2?~Z9_^u6hH6S2>Cwnkwyw_QB{L2@5 zM#h78@Vz?`wiVi`?`dzI#{j^)%y#%gF}1Z~_rEofXzm-082J&{Fbk{@msa6n#BfH5R3E295BxGY3j8371O z@}b)tK6$?_|cG%py1N_SOu_cHD_nurKP2uJzB0C*cUHEZ`s+ofY>|z7JZY0gM)`D zxzTrCMOy-_C^0ed@_KD;QZWBSL94=0kCnh(S z5ttXbudi=wR9Gu_(m7Hi!SQ%!Q5S{Mu(V_ZuEKQn&T!Gx>!>q1kgSOTxD zJ>B<&R836ifHIFxPY*mW08UD&-xLxO0@y_KFfPu2p_?FuLjevr{ZU#|FxzP`O8|ZK ztvrXOw)Trnp&y;Wl&h<&8TwTo_wQ@!>5)4-J1g$@iQ9J~z~g{nVG$8EkaN2}UUhiG z0ir^6O--zUpoYCYr-Xgy#1iv=wmraN0cPWYZ>WmqmUncp04y5(%jDC6AQcrA@wqaK zmMaK~-IYNGOniJ9_#T?La|4jl#iFY`x{7stVuF~Nnc1L7o*v*hEiFwHQ|s{d=?VJx zobvJ6+4AAmcYWWIrI(S#5rFtT%nlD?H#hI|^Ybr&reZ4si~e^a1TA#^eiH{63Q#{I z!+z(}(fMV@^qsMfUUGX3dM@QVqFJ8zz-*#(V&ZfAWKV2Y)T3Ss7dts zt`0FVacN_tsdqt{#v@*F@zffJt)I>w9@R7N4-O74f#l?b1U*4%P0ir&vouE#RzS@! z+aEX_Zo3(T%2x;!G&3`6aPFl5iLBJ&ZS?VG^T@*X_FUP&OH<;YA#>?VdH3_vlibsa zhQuU>&5cofpgUN% zGX`Dhl6ytDE5pAsSWnlbIiBwS@(T&k1~LQsJJ1G!Ty!Ia{W=ZEw_8V97vYT)QF%D= ze}N!Cn^;+qhK7cU*|kyK;G%W^Ttoo<6xBj`hYUzs7~s~|J%E@?wJlq=J{xUR3f;zN z@~XMH2>?)Jw~G_HCL`l|p7MBMY3coR@^j;o z^73-RnwlCY##rS<)0?%IL=ZekAriQ|wv7#|%Q0iKnix|L$UI4a>mY zuHj1Bmj0`MOZuqCwClEV#J(_y=n)0s-aMn#Y&-l8Ay!=|7C3$F(Km#Fb@uSKd5_ts)?~PQ;WUv?kBdr zot;w9_enB`D<%O%j#uhs5A^hC{A#+=_5C{y5D!fq9qF`YUH*;HH`Acu_S>3~g}R@Y zL?;QL9>gmL=d{xzTvEb@j1}N73rJ7PNV87e@!|gfs_P8M literal 0 HcmV?d00001 diff --git a/demos/headless/index.html b/demos/headless/index.html new file mode 100644 index 000000000..c3bad9d3b --- /dev/null +++ b/demos/headless/index.html @@ -0,0 +1,120 @@ + + + + + Blockly Demo: Headless + + + + + + + +

Blockly > + Demos > Headless

+ +

This is a simple demo of generating Python code from XML with no graphics. + This might be useful for server-side code generation.

+ + + + + + + +
+ + + + +
+ +
+ +
+ + + + + diff --git a/demos/index.html b/demos/index.html index 688ffbb6b..cbadcc15e 100644 --- a/demos/index.html +++ b/demos/index.html @@ -88,6 +88,18 @@ + + + + + + + + +
Generate code from XML without graphics.
+ + + diff --git a/generators/dart.js b/generators/dart.js index 2fa769890..01311efc9 100644 --- a/generators/dart.js +++ b/generators/dart.js @@ -73,8 +73,10 @@ Blockly.Dart.ORDER_NONE = 99; // (...) /** * Initialise the database of variable names. + * @param {Blockly.Workspace=} opt_workspace Workspace to generate code from. + * Defaults to main workspace. */ -Blockly.Dart.init = function() { +Blockly.Dart.init = function(opt_workspace) { // Create a dictionary of definitions to be printed before the code. Blockly.Dart.definitions_ = Object.create(null); // Create a dictionary mapping desired function names in definitions_ @@ -89,7 +91,7 @@ Blockly.Dart.init = function() { } var defvars = []; - var variables = Blockly.Variables.allVariables(); + var variables = Blockly.Variables.allVariables(opt_workspace); for (var x = 0; x < variables.length; x++) { defvars[x] = 'var ' + Blockly.Dart.variableDB_.getName(variables[x], diff --git a/generators/javascript.js b/generators/javascript.js index 401adf349..12b0c5926 100644 --- a/generators/javascript.js +++ b/generators/javascript.js @@ -105,8 +105,10 @@ Blockly.JavaScript.ORDER_NONE = 99; // (...) /** * Initialise the database of variable names. + * @param {Blockly.Workspace=} opt_workspace Workspace to generate code from. + * Defaults to main workspace. */ -Blockly.JavaScript.init = function() { +Blockly.JavaScript.init = function(opt_workspace) { // Create a dictionary of definitions to be printed before the code. Blockly.JavaScript.definitions_ = Object.create(null); // Create a dictionary mapping desired function names in definitions_ @@ -121,7 +123,7 @@ Blockly.JavaScript.init = function() { } var defvars = []; - var variables = Blockly.Variables.allVariables(); + var variables = Blockly.Variables.allVariables(opt_workspace); for (var x = 0; x < variables.length; x++) { defvars[x] = 'var ' + Blockly.JavaScript.variableDB_.getName(variables[x], diff --git a/generators/python.js b/generators/python.js index 7ef0c8f63..ba2169162 100644 --- a/generators/python.js +++ b/generators/python.js @@ -81,8 +81,10 @@ Blockly.Python.ORDER_NONE = 99; // (...) /** * Initialise the database of variable names. + * @param {Blockly.Workspace=} opt_workspace Workspace to generate code from. + * Defaults to main workspace. */ -Blockly.Python.init = function() { +Blockly.Python.init = function(opt_workspace) { // Create a dictionary of definitions to be printed before the code. Blockly.Python.definitions_ = Object.create(null); // Create a dictionary mapping desired function names in definitions_ @@ -97,7 +99,7 @@ Blockly.Python.init = function() { } var defvars = []; - var variables = Blockly.Variables.allVariables(); + var variables = Blockly.Variables.allVariables(opt_workspace); for (var x = 0; x < variables.length; x++) { defvars[x] = Blockly.Python.variableDB_.getName(variables[x], Blockly.Variables.NAME_TYPE) + ' = None'; diff --git a/javascript_compressed.js b/javascript_compressed.js index acf7789ba..f9cb7da77 100644 --- a/javascript_compressed.js +++ b/javascript_compressed.js @@ -7,7 +7,7 @@ Blockly.JavaScript=new Blockly.Generator("JavaScript");Blockly.JavaScript.addRes Blockly.JavaScript.ORDER_ATOMIC=0;Blockly.JavaScript.ORDER_MEMBER=1;Blockly.JavaScript.ORDER_NEW=1;Blockly.JavaScript.ORDER_FUNCTION_CALL=2;Blockly.JavaScript.ORDER_INCREMENT=3;Blockly.JavaScript.ORDER_DECREMENT=3;Blockly.JavaScript.ORDER_LOGICAL_NOT=4;Blockly.JavaScript.ORDER_BITWISE_NOT=4;Blockly.JavaScript.ORDER_UNARY_PLUS=4;Blockly.JavaScript.ORDER_UNARY_NEGATION=4;Blockly.JavaScript.ORDER_TYPEOF=4;Blockly.JavaScript.ORDER_VOID=4;Blockly.JavaScript.ORDER_DELETE=4; Blockly.JavaScript.ORDER_MULTIPLICATION=5;Blockly.JavaScript.ORDER_DIVISION=5;Blockly.JavaScript.ORDER_MODULUS=5;Blockly.JavaScript.ORDER_ADDITION=6;Blockly.JavaScript.ORDER_SUBTRACTION=6;Blockly.JavaScript.ORDER_BITWISE_SHIFT=7;Blockly.JavaScript.ORDER_RELATIONAL=8;Blockly.JavaScript.ORDER_IN=8;Blockly.JavaScript.ORDER_INSTANCEOF=8;Blockly.JavaScript.ORDER_EQUALITY=9;Blockly.JavaScript.ORDER_BITWISE_AND=10;Blockly.JavaScript.ORDER_BITWISE_XOR=11;Blockly.JavaScript.ORDER_BITWISE_OR=12; Blockly.JavaScript.ORDER_LOGICAL_AND=13;Blockly.JavaScript.ORDER_LOGICAL_OR=14;Blockly.JavaScript.ORDER_CONDITIONAL=15;Blockly.JavaScript.ORDER_ASSIGNMENT=16;Blockly.JavaScript.ORDER_COMMA=17;Blockly.JavaScript.ORDER_NONE=99; -Blockly.JavaScript.init=function(){Blockly.JavaScript.definitions_=Object.create(null);Blockly.JavaScript.functionNames_=Object.create(null);Blockly.JavaScript.variableDB_?Blockly.JavaScript.variableDB_.reset():Blockly.JavaScript.variableDB_=new Blockly.Names(Blockly.JavaScript.RESERVED_WORDS_);for(var a=[],b=Blockly.Variables.allVariables(),c=0;c