From eddade9edaa4adc4fad9fccc0a983d9b06a82dad Mon Sep 17 00:00:00 2001 From: "Evan W. Patton" Date: Wed, 28 Sep 2016 18:18:45 -0400 Subject: [PATCH] [WIP] Merged block, blocks, blockly, and block_svg sans instrumentation issue --- core/block.js | 3 +- core/block.js.orig | 1364 ------------------------------- core/block.js.rej | 510 ------------ core/block_render_svg.js | 101 ++- core/block_svg.js.orig | 1629 -------------------------------------- core/block_svg.js.rej | 231 ------ core/blockly.js | 26 +- core/blockly.js.rej | 439 +--------- core/blocks.js.rej | 19 - core/workspace.js | 2 +- core/workspace_svg.js | 137 ++++ 11 files changed, 253 insertions(+), 4208 deletions(-) delete mode 100644 core/block.js.orig delete mode 100644 core/block.js.rej delete mode 100644 core/block_svg.js.orig delete mode 100644 core/block_svg.js.rej delete mode 100644 core/blocks.js.rej diff --git a/core/block.js b/core/block.js index 872081246..e5b44d7dd 100644 --- a/core/block.js +++ b/core/block.js @@ -26,6 +26,7 @@ goog.provide('Blockly.Block'); +goog.require('Blockly.Instrument'); // lyn's instrumentation code goog.require('Blockly.Blocks'); goog.require('Blockly.Comment'); goog.require('Blockly.Connection'); @@ -1200,7 +1201,7 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { */ Blockly.Block.prototype.appendInput_ = function(type, name) { var connection = null; - if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) { + if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT || type == Blockly.INDENTED_VALUE) { connection = this.makeConnection_(type); } var input = new Blockly.Input(type, name, this, connection); diff --git a/core/block.js.orig b/core/block.js.orig deleted file mode 100644 index 2021d3f81..000000000 --- a/core/block.js.orig +++ /dev/null @@ -1,1364 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2011 Google Inc. - * https://developers.google.com/blockly/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview The class representing one block. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Block'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.Comment'); -goog.require('Blockly.Connection'); -goog.require('Blockly.Input'); -goog.require('Blockly.Mutator'); -goog.require('Blockly.Warning'); -goog.require('Blockly.Workspace'); -goog.require('Blockly.Xml'); -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.math.Coordinate'); -goog.require('goog.string'); - - -/** - * Class for one block. - * Not normally called directly, workspace.newBlock() is preferred. - * @param {!Blockly.Workspace} workspace The block's workspace. - * @param {?string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. - * @constructor - */ -Blockly.Block = function(workspace, prototypeName, opt_id) { - /** @type {string} */ - this.id = (opt_id && !workspace.getBlockById(opt_id)) ? - opt_id : Blockly.genUid(); - workspace.blockDB_[this.id] = this; - /** @type {Blockly.Connection} */ - this.outputConnection = null; - /** @type {Blockly.Connection} */ - this.nextConnection = null; - /** @type {Blockly.Connection} */ - this.previousConnection = null; - /** @type {!Array.} */ - this.inputList = []; - /** @type {boolean|undefined} */ - this.inputsInline = undefined; - /** @type {boolean} */ - this.disabled = false; - /** @type {string|!Function} */ - this.tooltip = ''; - /** @type {boolean} */ - this.contextMenu = true; - - /** - * @type {Blockly.Block} - * @private - */ - this.parentBlock_ = null; - - /** - * @type {!Array.} - * @private - */ - this.childBlocks_ = []; - - /** - * @type {boolean} - * @private - */ - this.deletable_ = true; - - /** - * @type {boolean} - * @private - */ - this.movable_ = true; - - /** - * @type {boolean} - * @private - */ - this.editable_ = true; - - /** - * @type {boolean} - * @private - */ - this.isShadow_ = false; - - /** - * @type {boolean} - * @private - */ - this.collapsed_ = false; - - /** @type {string|Blockly.Comment} */ - this.comment = null; - - /** - * @type {!goog.math.Coordinate} - * @private - */ - this.xy_ = new goog.math.Coordinate(0, 0); - - /** @type {!Blockly.Workspace} */ - this.workspace = workspace; - /** @type {boolean} */ - this.isInFlyout = workspace.isFlyout; - /** @type {boolean} */ - this.isInMutator = workspace.isMutator; - - /** @type {boolean} */ - this.RTL = workspace.RTL; - - // Copy the type-specific functions and data from the prototype. - if (prototypeName) { - /** @type {string} */ - this.type = prototypeName; - var prototype = Blockly.Blocks[prototypeName]; - goog.asserts.assertObject(prototype, - 'Error: "%s" is an unknown language block.', prototypeName); - goog.mixin(this, prototype); - } - - workspace.addTopBlock(this); - - // Call an initialization function, if it exists. - if (goog.isFunction(this.init)) { - this.init(); - } - // Record initial inline state. - /** @type {boolean|undefined} */ - this.inputsInlineDefault = this.inputsInline; - if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Create(this)); - } - // Bind an onchange function, if it exists. - if (goog.isFunction(this.onchange)) { - this.onchangeWrapper_ = this.onchange.bind(this); - this.workspace.addChangeListener(this.onchangeWrapper_); - } -}; - -/** - * Obtain a newly created block. - * @param {!Blockly.Workspace} workspace The block's workspace. - * @param {?string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @return {!Blockly.Block} The created block. - * @deprecated December 2015 - */ -Blockly.Block.obtain = function(workspace, prototypeName) { - console.warn('Deprecated call to Blockly.Block.obtain, ' + - 'use workspace.newBlock instead.'); - return workspace.newBlock(prototypeName); -}; - -/** - * Optional text data that round-trips beween blocks and XML. - * Has no effect. May be used by 3rd parties for meta information. - * @type {?string} - */ -Blockly.Block.prototype.data = null; - -/** - * Colour of the block in '#RRGGBB' format. - * @type {string} - * @private - */ -Blockly.Block.prototype.colour_ = '#000000'; - -/** - * Dispose of this block. - * @param {boolean} healStack If true, then try to heal any gap by connecting - * the next statement with the previous statement. Otherwise, dispose of - * all children of this block. - */ -Blockly.Block.prototype.dispose = function(healStack) { - if (!this.workspace) { - // Already deleted. - return; - } - // Terminate onchange event calls. - if (this.onchangeWrapper_) { - this.workspace.removeChangeListener(this.onchangeWrapper_); - } - this.unplug(healStack); - if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Delete(this)); - } - Blockly.Events.disable(); - - try { - // This block is now at the top of the workspace. - // Remove this block from the workspace's list of top-most blocks. - if (this.workspace) { - this.workspace.removeTopBlock(this); - // Remove from block database. - delete this.workspace.blockDB_[this.id]; - this.workspace = null; - } - - // Just deleting this block from the DOM would result in a memory leak as - // well as corruption of the connection database. Therefore we must - // methodically step through the blocks and carefully disassemble them. - - // First, dispose of all my children. - for (var i = this.childBlocks_.length - 1; i >= 0; i--) { - this.childBlocks_[i].dispose(false); - } - // Then dispose of myself. - // Dispose of all inputs and their fields. - for (var i = 0, input; input = this.inputList[i]; i++) { - input.dispose(); - } - this.inputList.length = 0; - // Dispose of any remaining connections (next/previous/output). - var connections = this.getConnections_(true); - for (var i = 0; i < connections.length; i++) { - var connection = connections[i]; - if (connection.isConnected()) { - connection.disconnect(); - } - connections[i].dispose(); - } - } finally { - Blockly.Events.enable(); - } -}; - -/** - * Unplug this block from its superior block. If this block is a statement, - * optionally reconnect the block underneath with the block on top. - * @param {boolean} opt_healStack Disconnect child statement and reconnect - * stack. Defaults to false. - */ -Blockly.Block.prototype.unplug = function(opt_healStack) { - if (this.outputConnection) { - if (this.outputConnection.isConnected()) { - // Disconnect from any superior block. - this.outputConnection.disconnect(); - } - } else if (this.previousConnection) { - var previousTarget = null; - if (this.previousConnection.isConnected()) { - // Remember the connection that any next statements need to connect to. - previousTarget = this.previousConnection.targetConnection; - // Detach this block from the parent's tree. - this.previousConnection.disconnect(); - } - var nextBlock = this.getNextBlock(); - if (opt_healStack && nextBlock) { - // Disconnect the next statement. - var nextTarget = this.nextConnection.targetConnection; - nextTarget.disconnect(); - if (previousTarget && previousTarget.checkType_(nextTarget)) { - // Attach the next statement to the previous statement. - previousTarget.connect(nextTarget); - } - } - } -}; - -/** - * Returns all connections originating from this block. - * @return {!Array.} Array of connections. - * @private - */ -Blockly.Block.prototype.getConnections_ = function() { - var myConnections = []; - if (this.outputConnection) { - myConnections.push(this.outputConnection); - } - if (this.previousConnection) { - myConnections.push(this.previousConnection); - } - if (this.nextConnection) { - myConnections.push(this.nextConnection); - } - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.connection) { - myConnections.push(input.connection); - } - } - return myConnections; -}; - -/** - * Walks down a stack of blocks and finds the last next connection on the stack. - * @return {Blockly.Connection} The last next connection on the stack, or null. - * @private - */ -Blockly.Block.prototype.lastConnectionInStack_ = function() { - var nextConnection = this.nextConnection; - while (nextConnection) { - var nextBlock = nextConnection.targetBlock(); - if (!nextBlock) { - // Found a next connection with nothing on the other side. - return nextConnection; - } - nextConnection = nextBlock.nextConnection; - } - // Ran out of next connections. - return null; -}; - -/** - * Bump unconnected blocks out of alignment. Two blocks which aren't actually - * connected should not coincidentally line up on screen. - * @private - */ -Blockly.Block.prototype.bumpNeighbours_ = function() { - if (!this.workspace) { - return; // Deleted block. - } - if (Blockly.dragMode_ != Blockly.DRAG_NONE) { - return; // Don't bump blocks during a drag. - } - var rootBlock = this.getRootBlock(); - if (rootBlock.isInFlyout) { - return; // Don't move blocks around in a flyout. - } - // Loop though every connection on this block. - var myConnections = this.getConnections_(false); - for (var i = 0, connection; connection = myConnections[i]; i++) { - // Spider down from this block bumping all sub-blocks. - if (connection.isConnected() && connection.isSuperior()) { - connection.targetBlock().bumpNeighbours_(); - } - - var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS); - for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) { - // If both connections are connected, that's probably fine. But if - // either one of them is unconnected, then there could be confusion. - if (!connection.isConnected() || !otherConnection.isConnected()) { - // Only bump blocks if they are from different tree structures. - if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) { - // Always bump the inferior block. - if (connection.isSuperior()) { - otherConnection.bumpAwayFrom_(connection); - } else { - connection.bumpAwayFrom_(otherConnection); - } - } - } - } - } -}; - -/** - * Return the parent block or null if this block is at the top level. - * @return {Blockly.Block} The block that holds the current block. - */ -Blockly.Block.prototype.getParent = function() { - // Look at the DOM to see if we are nested in another block. - return this.parentBlock_; -}; - -/** - * Return the input that connects to the specified block. - * @param {!Blockly.Block} block A block connected to an input on this block. - * @return {Blockly.Input} The input that connects to the specified block. - */ -Blockly.Block.prototype.getInputWithBlock = function(block) { - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.connection && input.connection.targetBlock() == block) { - return input; - } - } - return null; -}; - -/** - * Return the parent block that surrounds the current block, or null if this - * block has no surrounding block. A parent block might just be the previous - * statement, whereas the surrounding block is an if statement, while loop, etc. - * @return {Blockly.Block} The block that surrounds the current block. - */ -Blockly.Block.prototype.getSurroundParent = function() { - var block = this; - do { - var prevBlock = block; - block = block.getParent(); - if (!block) { - // Ran off the top. - return null; - } - } while (block.getNextBlock() == prevBlock); - // This block is an enclosing parent, not just a statement in a stack. - return block; -}; - -/** - * Return the next statement block directly connected to this block. - * @return {Blockly.Block} The next statement block or null. - */ -Blockly.Block.prototype.getNextBlock = function() { - return this.nextConnection && this.nextConnection.targetBlock(); -}; - -/** - * Return the top-most block in this block's tree. - * This will return itself if this block is at the top level. - * @return {!Blockly.Block} The root block. - */ -Blockly.Block.prototype.getRootBlock = function() { - var rootBlock; - var block = this; - do { - rootBlock = block; - block = rootBlock.parentBlock_; - } while (block); - return rootBlock; -}; - -/** - * Find all the blocks that are directly nested inside this one. - * Includes value and block inputs, as well as any following statement. - * Excludes any connection on an output tab or any preceding statement. - * @return {!Array.} Array of blocks. - */ -Blockly.Block.prototype.getChildren = function() { - return this.childBlocks_; -}; - -/** - * Set parent of this block to be a new block or null. - * @param {Blockly.Block} newParent New parent block. - */ -Blockly.Block.prototype.setParent = function(newParent) { - if (newParent == this.parentBlock_) { - return; - } - if (this.parentBlock_) { - // Remove this block from the old parent's child list. - var children = this.parentBlock_.childBlocks_; - for (var child, x = 0; child = children[x]; x++) { - if (child == this) { - children.splice(x, 1); - break; - } - } - - // Disconnect from superior blocks. - if (this.previousConnection && this.previousConnection.isConnected()) { - throw 'Still connected to previous block.'; - } - if (this.outputConnection && this.outputConnection.isConnected()) { - throw 'Still connected to parent block.'; - } - this.parentBlock_ = null; - // This block hasn't actually moved on-screen, so there's no need to update - // its connection locations. - } else { - // Remove this block from the workspace's list of top-most blocks. - this.workspace.removeTopBlock(this); - } - - this.parentBlock_ = newParent; - if (newParent) { - // Add this block to the new parent's child list. - newParent.childBlocks_.push(this); - } else { - this.workspace.addTopBlock(this); - } -}; - -/** - * Find all the blocks that are directly or indirectly nested inside this one. - * Includes this block in the list. - * Includes value and block inputs, as well as any following statements. - * Excludes any connection on an output tab or any preceding statements. - * @return {!Array.} Flattened array of blocks. - */ -Blockly.Block.prototype.getDescendants = function() { - var blocks = [this]; - for (var child, x = 0; child = this.childBlocks_[x]; x++) { - blocks.push.apply(blocks, child.getDescendants()); - } - return blocks; -}; - -/** - * Get whether this block is deletable or not. - * @return {boolean} True if deletable. - */ -Blockly.Block.prototype.isDeletable = function() { - return this.deletable_ && !this.isShadow_ && - !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is deletable or not. - * @param {boolean} deletable True if deletable. - */ -Blockly.Block.prototype.setDeletable = function(deletable) { - this.deletable_ = deletable; -}; - -/** - * Get whether this block is movable or not. - * @return {boolean} True if movable. - */ -Blockly.Block.prototype.isMovable = function() { - return this.movable_ && !this.isShadow_ && - !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is movable or not. - * @param {boolean} movable True if movable. - */ -Blockly.Block.prototype.setMovable = function(movable) { - this.movable_ = movable; -}; - -/** - * Get whether this block is a shadow block or not. - * @return {boolean} True if a shadow. - */ -Blockly.Block.prototype.isShadow = function() { - return this.isShadow_; -}; - -/** - * Set whether this block is a shadow block or not. - * @param {boolean} shadow True if a shadow. - */ -Blockly.Block.prototype.setShadow = function(shadow) { - this.isShadow_ = shadow; -}; - -/** - * Get whether this block is editable or not. - * @return {boolean} True if editable. - */ -Blockly.Block.prototype.isEditable = function() { - return this.editable_ && !(this.workspace && this.workspace.options.readOnly); -}; - -/** - * Set whether this block is editable or not. - * @param {boolean} editable True if editable. - */ -Blockly.Block.prototype.setEditable = function(editable) { - this.editable_ = editable; - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - field.updateEditable(); - } - } -}; - -/** - * Set whether the connections are hidden (not tracked in a database) or not. - * Recursively walk down all child blocks (except collapsed blocks). - * @param {boolean} hidden True if connections are hidden. - */ -Blockly.Block.prototype.setConnectionsHidden = function(hidden) { - if (!hidden && this.isCollapsed()) { - if (this.outputConnection) { - this.outputConnection.setHidden(hidden); - } - if (this.previousConnection) { - this.previousConnection.setHidden(hidden); - } - if (this.nextConnection) { - this.nextConnection.setHidden(hidden); - var child = this.nextConnection.targetBlock(); - if (child) { - child.setConnectionsHidden(hidden); - } - } - } else { - var myConnections = this.getConnections_(true); - for (var i = 0, connection; connection = myConnections[i]; i++) { - connection.setHidden(hidden); - if (connection.isSuperior()) { - var child = connection.targetBlock(); - if (child) { - child.setConnectionsHidden(hidden); - } - } - } - } -}; - -/** - * Set the URL of this block's help page. - * @param {string|Function} url URL string for block help, or function that - * returns a URL. Null for no help. - */ -Blockly.Block.prototype.setHelpUrl = function(url) { - this.helpUrl = url; -}; - -/** - * Change the tooltip text for a block. - * @param {string|!Function} newTip Text for tooltip or a parent element to - * link to for its tooltip. May be a function that returns a string. - */ -Blockly.Block.prototype.setTooltip = function(newTip) { - this.tooltip = newTip; -}; - -/** - * Get the colour of a block. - * @return {string} #RRGGBB string. - */ -Blockly.Block.prototype.getColour = function() { - return this.colour_; -}; - -/** - * Change the colour of a block. - * @param {number|string} colour HSV hue value, or #RRGGBB string. - */ -Blockly.Block.prototype.setColour = function(colour) { - var hue = parseFloat(colour); - if (!isNaN(hue)) { - this.colour_ = Blockly.hueToRgb(hue); - } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) { - this.colour_ = colour; - } else { - throw 'Invalid colour: ' + colour; - } -}; - -/** - * Returns the named field from a block. - * @param {string} name The name of the field. - * @return {Blockly.Field} Named field, or null if field does not exist. - */ -Blockly.Block.prototype.getField = function(name) { - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field.name === name) { - return field; - } - } - } - return null; -}; - -/** - * Return all variables referenced by this block. - * @return {!Array.} List of variable names. - */ -Blockly.Block.prototype.getVars = function() { - var vars = []; - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldVariable) { - vars.push(field.getValue()); - } - } - } - return vars; -}; - -/** - * Notification that a variable is renaming. - * If the name matches one of this block's variables, rename it. - * @param {string} oldName Previous name of variable. - * @param {string} newName Renamed variable. - */ -Blockly.Block.prototype.renameVar = function(oldName, newName) { - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldVariable && - Blockly.Names.equals(oldName, field.getValue())) { - field.setValue(newName); - } - } - } -}; - -/** - * Returns the language-neutral value from the field of a block. - * @param {string} name The name of the field. - * @return {?string} Value from the field or null if field does not exist. - */ -Blockly.Block.prototype.getFieldValue = function(name) { - var field = this.getField(name); - if (field) { - return field.getValue(); - } - return null; -}; - -/** - * Returns the language-neutral value from the field of a block. - * @param {string} name The name of the field. - * @return {?string} Value from the field or null if field does not exist. - * @deprecated December 2013 - */ -Blockly.Block.prototype.getTitleValue = function(name) { - console.warn('Deprecated call to getTitleValue, use getFieldValue instead.'); - return this.getFieldValue(name); -}; - -/** - * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). - * @param {string} newValue Value to be the new field. - * @param {string} name The name of the field. - */ -Blockly.Block.prototype.setFieldValue = function(newValue, name) { - var field = this.getField(name); - goog.asserts.assertObject(field, 'Field "%s" not found.', name); - field.setValue(newValue); -}; - -/** - * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE'). - * @param {string} newValue Value to be the new field. - * @param {string} name The name of the field. - * @deprecated December 2013 - */ -Blockly.Block.prototype.setTitleValue = function(newValue, name) { - console.warn('Deprecated call to setTitleValue, use setFieldValue instead.'); - this.setFieldValue(newValue, name); -}; - -/** - * Set whether this block can chain onto the bottom of another block. - * @param {boolean} newBoolean True if there can be a previous statement. - * @param {string|Array.|null|undefined} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.previousConnection) { - goog.asserts.assert(!this.outputConnection, - 'Remove output connection prior to adding previous connection.'); - this.previousConnection = - this.makeConnection_(Blockly.PREVIOUS_STATEMENT); - } - this.previousConnection.setCheck(opt_check); - } else { - if (this.previousConnection) { - goog.asserts.assert(!this.previousConnection.isConnected(), - 'Must disconnect previous statement before removing connection.'); - this.previousConnection.dispose(); - this.previousConnection = null; - } - } -}; - -/** - * Set whether another block can chain onto the bottom of this block. - * @param {boolean} newBoolean True if there can be a next statement. - * @param {string|Array.|null|undefined} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.nextConnection) { - this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT); - } - this.nextConnection.setCheck(opt_check); - } else { - if (this.nextConnection) { - goog.asserts.assert(!this.nextConnection.isConnected(), - 'Must disconnect next statement before removing connection.'); - this.nextConnection.dispose(); - this.nextConnection = null; - } - } -}; - -/** - * Set whether this block returns a value. - * @param {boolean} newBoolean True if there is an output. - * @param {string|Array.|null|undefined} opt_check Returned type or list - * of returned types. Null or undefined if any type could be returned - * (e.g. variable get). - */ -Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) { - if (newBoolean) { - if (opt_check === undefined) { - opt_check = null; - } - if (!this.outputConnection) { - goog.asserts.assert(!this.previousConnection, - 'Remove previous connection prior to adding output connection.'); - this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE); - } - this.outputConnection.setCheck(opt_check); - } else { - if (this.outputConnection) { - goog.asserts.assert(!this.outputConnection.isConnected(), - 'Must disconnect output value before removing connection.'); - this.outputConnection.dispose(); - this.outputConnection = null; - } - } -}; - -/** - * Set whether value inputs are arranged horizontally or vertically. - * @param {boolean} newBoolean True if inputs are horizontal. - */ -Blockly.Block.prototype.setInputsInline = function(newBoolean) { - if (this.inputsInline != newBoolean) { - Blockly.Events.fire(new Blockly.Events.Change( - this, 'inline', null, this.inputsInline, newBoolean)); - this.inputsInline = newBoolean; - } -}; - -/** - * Get whether value inputs are arranged horizontally or vertically. - * @return {boolean} True if inputs are horizontal. - */ -Blockly.Block.prototype.getInputsInline = function() { - if (this.inputsInline != undefined) { - // Set explicitly. - return this.inputsInline; - } - // Not defined explicitly. Figure out what would look best. - for (var i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT && - this.inputList[i].type == Blockly.DUMMY_INPUT) { - // Two dummy inputs in a row. Don't inline them. - return false; - } - } - for (var i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type == Blockly.INPUT_VALUE && - this.inputList[i].type == Blockly.DUMMY_INPUT) { - // Dummy input after a value input. Inline them. - return true; - } - } - return false; -}; - -/** - * Set whether the block is disabled or not. - * @param {boolean} disabled True if disabled. - */ -Blockly.Block.prototype.setDisabled = function(disabled) { - if (this.disabled != disabled) { - Blockly.Events.fire(new Blockly.Events.Change( - this, 'disabled', null, this.disabled, disabled)); - this.disabled = disabled; - } -}; - -/** - * Get whether the block is disabled or not due to parents. - * The block's own disabled property is not considered. - * @return {boolean} True if disabled. - */ -Blockly.Block.prototype.getInheritedDisabled = function() { - var block = this; - while (true) { - block = block.getSurroundParent(); - if (!block) { - // Ran off the top. - return false; - } else if (block.disabled) { - return true; - } - } -}; - -/** - * Get whether the block is collapsed or not. - * @return {boolean} True if collapsed. - */ -Blockly.Block.prototype.isCollapsed = function() { - return this.collapsed_; -}; - -/** - * Set whether the block is collapsed or not. - * @param {boolean} collapsed True if collapsed. - */ -Blockly.Block.prototype.setCollapsed = function(collapsed) { - if (this.collapsed_ != collapsed) { - Blockly.Events.fire(new Blockly.Events.Change( - this, 'collapsed', null, this.collapsed_, collapsed)); - this.collapsed_ = collapsed; - } -}; - -/** - * Create a human-readable text representation of this block and any children. - * @param {number=} opt_maxLength Truncate the string to this length. - * @param {string=} opt_emptyToken The placeholder string used to denote an - * empty field. If not specified, '?' is used. - * @return {string} Text of block. - */ -Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) { - var text = []; - var emptyFieldPlaceholder = opt_emptyToken || '?'; - if (this.collapsed_) { - text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_); - } else { - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - text.push(field.getText()); - } - if (input.connection) { - var child = input.connection.targetBlock(); - if (child) { - text.push(child.toString(undefined, opt_emptyToken)); - } else { - text.push(emptyFieldPlaceholder); - } - } - } - } - text = goog.string.trim(text.join(' ')) || '???'; - if (opt_maxLength) { - // TODO: Improve truncation so that text from this block is given priority. - // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...". - // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...". - text = goog.string.truncate(text, opt_maxLength); - } - return text; -}; - -/** - * Shortcut for appending a value input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Blockly.Input} The input object created. - */ -Blockly.Block.prototype.appendValueInput = function(name) { - return this.appendInput_(Blockly.INPUT_VALUE, name); -}; - -/** - * Shortcut for appending a statement input row. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Blockly.Input} The input object created. - */ -Blockly.Block.prototype.appendStatementInput = function(name) { - return this.appendInput_(Blockly.NEXT_STATEMENT, name); -}; - -/** - * Shortcut for appending a dummy input row. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this input again. Should be unique to this block. - * @return {!Blockly.Input} The input object created. - */ -Blockly.Block.prototype.appendDummyInput = function(opt_name) { - return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || ''); -}; - -/** - * Initialize this block using a cross-platform, internationalization-friendly - * JSON description. - * @param {!Object} json Structured data describing the block. - */ -Blockly.Block.prototype.jsonInit = function(json) { - // Validate inputs. - goog.asserts.assert(json['output'] == undefined || - json['previousStatement'] == undefined, - 'Must not have both an output and a previousStatement.'); - - // Set basic properties of block. - if (json['colour'] !== undefined) { - this.setColour(json['colour']); - } - - // Interpolate the message blocks. - var i = 0; - while (json['message' + i] !== undefined) { - this.interpolate_(json['message' + i], json['args' + i] || [], - json['lastDummyAlign' + i]); - i++; - } - - if (json['inputsInline'] !== undefined) { - this.setInputsInline(json['inputsInline']); - } - // Set output and previous/next connections. - if (json['output'] !== undefined) { - this.setOutput(true, json['output']); - } - if (json['previousStatement'] !== undefined) { - this.setPreviousStatement(true, json['previousStatement']); - } - if (json['nextStatement'] !== undefined) { - this.setNextStatement(true, json['nextStatement']); - } - if (json['tooltip'] !== undefined) { - this.setTooltip(json['tooltip']); - } - if (json['helpUrl'] !== undefined) { - this.setHelpUrl(json['helpUrl']); - } -}; - -/** - * Interpolate a message description onto the block. - * @param {string} message Text contains interpolation tokens (%1, %2, ...) - * that match with fields or inputs defined in the args array. - * @param {!Array} args Array of arguments to be interpolated. - * @param {=string} lastDummyAlign If a dummy input is added at the end, - * how should it be aligned? - * @private - */ -Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) { - var tokens = Blockly.utils.tokenizeInterpolation(message); - // Interpolate the arguments. Build a list of elements. - var indexDup = []; - var indexCount = 0; - var elements = []; - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - if (typeof token == 'number') { - goog.asserts.assert(token > 0 && token <= args.length, - 'Message index "%s" out of range.', token); - goog.asserts.assert(!indexDup[token], - 'Message index "%s" duplicated.', token); - indexDup[token] = true; - indexCount++; - elements.push(args[token - 1]); - } else { - token = token.trim(); - if (token) { - elements.push(token); - } - } - } - goog.asserts.assert(indexCount == args.length, - 'Message does not reference all %s arg(s).', args.length); - // Add last dummy input if needed. - if (elements.length && (typeof elements[elements.length - 1] == 'string' || - elements[elements.length - 1]['type'].indexOf('field_') == 0)) { - var dummyInput = {type: 'input_dummy'}; - if (lastDummyAlign) { - dummyInput['align'] = lastDummyAlign; - } - elements.push(dummyInput); - } - // Lookup of alignment constants. - var alignmentLookup = { - 'LEFT': Blockly.ALIGN_LEFT, - 'RIGHT': Blockly.ALIGN_RIGHT, - 'CENTRE': Blockly.ALIGN_CENTRE - }; - // Populate block with inputs and fields. - var fieldStack = []; - for (var i = 0; i < elements.length; i++) { - var element = elements[i]; - if (typeof element == 'string') { - fieldStack.push([element, undefined]); - } else { - var field = null; - var input = null; - do { - var altRepeat = false; - if (typeof element == 'string') { - field = new Blockly.FieldLabel(element); - } else { - switch (element['type']) { - case 'input_value': - input = this.appendValueInput(element['name']); - break; - case 'input_statement': - input = this.appendStatementInput(element['name']); - break; - case 'input_dummy': - input = this.appendDummyInput(element['name']); - break; - case 'field_label': - field = new Blockly.FieldLabel(element['text'], element['class']); - break; - case 'field_input': - field = new Blockly.FieldTextInput(element['text']); - if (typeof element['spellcheck'] == 'boolean') { - field.setSpellcheck(element['spellcheck']); - } - break; - case 'field_angle': - field = new Blockly.FieldAngle(element['angle']); - break; - case 'field_checkbox': - field = new Blockly.FieldCheckbox( - element['checked'] ? 'TRUE' : 'FALSE'); - break; - case 'field_colour': - field = new Blockly.FieldColour(element['colour']); - break; - case 'field_variable': - field = new Blockly.FieldVariable(element['variable']); - break; - case 'field_dropdown': - field = new Blockly.FieldDropdown(element['options']); - break; - case 'field_image': - field = new Blockly.FieldImage(element['src'], - element['width'], element['height'], element['alt']); - break; - case 'field_number': - field = new Blockly.FieldNumber(element['value'], - element['min'], element['max'], element['precision']); - break; - case 'field_date': - if (Blockly.FieldDate) { - field = new Blockly.FieldDate(element['date']); - break; - } - // Fall through if FieldDate is not compiled in. - default: - // Unknown field. - if (element['alt']) { - element = element['alt']; - altRepeat = true; - } - } - } - } while (altRepeat); - if (field) { - fieldStack.push([field, element['name']]); - } else if (input) { - if (element['check']) { - input.setCheck(element['check']); - } - if (element['align']) { - input.setAlign(alignmentLookup[element['align']]); - } - for (var j = 0; j < fieldStack.length; j++) { - input.appendField(fieldStack[j][0], fieldStack[j][1]); - } - fieldStack.length = 0; - } - } - } -}; - -/** - * Add a value input, statement input or local variable to this block. - * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or - * Blockly.DUMMY_INPUT. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Blockly.Input} The input object created. - * @private - */ -Blockly.Block.prototype.appendInput_ = function(type, name) { - var connection = null; - if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) { - connection = this.makeConnection_(type); - } - var input = new Blockly.Input(type, name, this, connection); - // Append input to list. - this.inputList.push(input); - return input; -}; - -/** - * Move a named input to a different location on this block. - * @param {string} name The name of the input to move. - * @param {?string} refName Name of input that should be after the moved input, - * or null to be the input at the end. - */ -Blockly.Block.prototype.moveInputBefore = function(name, refName) { - if (name == refName) { - return; - } - // Find both inputs. - var inputIndex = -1; - var refIndex = refName ? -1 : this.inputList.length; - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.name == name) { - inputIndex = i; - if (refIndex != -1) { - break; - } - } else if (refName && input.name == refName) { - refIndex = i; - if (inputIndex != -1) { - break; - } - } - } - goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name); - goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.', - refName); - this.moveNumberedInputBefore(inputIndex, refIndex); -}; - -/** - * Move a numbered input to a different location on this block. - * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. - */ -Blockly.Block.prototype.moveNumberedInputBefore = function( - inputIndex, refIndex) { - // Validate arguments. - goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.'); - goog.asserts.assert(inputIndex < this.inputList.length, - 'Input index ' + inputIndex + ' out of bounds.'); - goog.asserts.assert(refIndex <= this.inputList.length, - 'Reference input ' + refIndex + ' out of bounds.'); - // Remove input. - var input = this.inputList[inputIndex]; - this.inputList.splice(inputIndex, 1); - if (inputIndex < refIndex) { - refIndex--; - } - // Reinsert input. - this.inputList.splice(refIndex, 0, input); -}; - -/** - * Remove an input from this block. - * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent error if input is not present. - * @throws {goog.asserts.AssertionError} if the input is not present and - * opt_quiet is not true. - */ -Blockly.Block.prototype.removeInput = function(name, opt_quiet) { - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.name == name) { - if (input.connection && input.connection.isConnected()) { - input.connection.setShadowDom(null); - var block = input.connection.targetBlock(); - if (block.isShadow()) { - // Destroy any attached shadow block. - block.dispose(); - } else { - // Disconnect any attached normal block. - block.unplug(); - } - } - input.dispose(); - this.inputList.splice(i, 1); - return; - } - } - if (!opt_quiet) { - goog.asserts.fail('Input "%s" not found.', name); - } -}; - -/** - * Fetches the named input object. - * @param {string} name The name of the input. - * @return {Blockly.Input} The input object, or null if input does not exist. - */ -Blockly.Block.prototype.getInput = function(name) { - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.name == name) { - return input; - } - } - // This input does not exist. - return null; -}; - -/** - * Fetches the block attached to the named input. - * @param {string} name The name of the input. - * @return {Blockly.Block} The attached value block, or null if the input is - * either disconnected or if the input does not exist. - */ -Blockly.Block.prototype.getInputTargetBlock = function(name) { - var input = this.getInput(name); - return input && input.connection && input.connection.targetBlock(); -}; - -/** - * Returns the comment on this block (or '' if none). - * @return {string} Block's comment. - */ -Blockly.Block.prototype.getCommentText = function() { - return this.comment || ''; -}; - -/** - * Set this block's comment text. - * @param {?string} text The text, or null to delete. - */ -Blockly.Block.prototype.setCommentText = function(text) { - if (this.comment != text) { - Blockly.Events.fire(new Blockly.Events.Change( - this, 'comment', null, this.comment, text || '')); - this.comment = text; - } -}; - -/** - * Set this block's warning text. - * @param {?string} text The text, or null to delete. - */ -Blockly.Block.prototype.setWarningText = function(text) { - // NOP. -}; - -/** - * Give this block a mutator dialog. - * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. - */ -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) { - goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); - var event = new Blockly.Events.Move(this); - this.xy_.translate(dx, dy); - event.recordNew(); - Blockly.Events.fire(event); -}; - -/** - * Create a connection of the specified type. - * @param {number} type The type of the connection to create. - * @return {!Blockly.Connection} A new connection of the specified type. - * @private - */ -Blockly.Block.prototype.makeConnection_ = function(type) { - return new Blockly.Connection(this, type); -}; diff --git a/core/block.js.rej b/core/block.js.rej deleted file mode 100644 index 41e4c27df..000000000 --- a/core/block.js.rej +++ /dev/null @@ -1,510 +0,0 @@ -*************** -*** 26,40 **** - - goog.provide('Blockly.Block'); - - goog.require('Blockly.BlockSvg'); - goog.require('Blockly.Blocks'); - goog.require('Blockly.Comment'); - goog.require('Blockly.Connection'); - goog.require('Blockly.ContextMenu'); - goog.require('Blockly.Input'); - goog.require('Blockly.Msg'); - goog.require('Blockly.Mutator'); - goog.require('Blockly.Warning'); - goog.require('Blockly.Workspace'); - goog.require('Blockly.Xml'); - goog.require('goog.Timer'); ---- 26,43 ---- - - goog.provide('Blockly.Block'); - -+ goog.require('Blockly.Instrument'); // lyn's instrumentation code - goog.require('Blockly.BlockSvg'); - goog.require('Blockly.Blocks'); - goog.require('Blockly.Comment'); - goog.require('Blockly.Connection'); - goog.require('Blockly.ContextMenu'); -+ goog.require('Blockly.ErrorIcon'); - goog.require('Blockly.Input'); - goog.require('Blockly.Msg'); - goog.require('Blockly.Mutator'); - goog.require('Blockly.Warning'); -+ goog.require('Blockly.WarningHandler'); - goog.require('Blockly.Workspace'); - goog.require('Blockly.Xml'); - goog.require('goog.Timer'); -*************** -*** 149,154 **** - - this.workspace = workspace; - this.isInFlyout = workspace.isFlyout; - - // Copy the type-specific functions and data from the prototype. - if (prototypeName) { ---- 152,159 ---- - - this.workspace = workspace; - this.isInFlyout = workspace.isFlyout; -+ // This is missing from our latest version -+ //workspace.addTopBlock(this); - - // Copy the type-specific functions and data from the prototype. - if (prototypeName) { -*************** -*** 202,207 **** - Blockly.Block.prototype.warning = null; - - /** - * Returns a list of mutator, comment, and warning icons. - * @return {!Array} List of icons. - */ ---- 212,223 ---- - Blockly.Block.prototype.warning = null; - - /** -+ * Block's error icon (if any). -+ * @type {Blockly.ErrorIcon} -+ */ -+ Blockly.Block.prototype.errorIcon = null; -+ -+ /** - * Returns a list of mutator, comment, and warning icons. - * @return {!Array} List of icons. - */ -*************** -*** 216,221 **** - if (this.warning) { - icons.push(this.warning); - } - return icons; - }; - ---- 232,240 ---- - if (this.warning) { - icons.push(this.warning); - } -+ if (this.errorIcon) { -+ icons.push(this.errorIcon); -+ } - return icons; - }; - -*************** -*** 374,379 **** - 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(); ---- 393,402 ---- - for (var x = 0; x < icons.length; x++) { - icons[x].dispose(); - } -+ if (this.errorIcon) { -+ this.errorIcon.dispose(); -+ } -+ - // Dispose of all inputs and their fields. - for (var x = 0, input; input = this.inputList[x]; x++) { - input.dispose(); -*************** -*** 448,464 **** - * @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. ---- 473,534 ---- - * @return {!Object} Object with height and width properties. - */ - Blockly.Block.prototype.getHeightWidth = function() { -+ var start = new Date().getTime(); -+ var result; -+ if (Blockly.Instrument.useNeilGetHeightWidthFix) { -+ result = this.getHeightWidthNeil(); -+ } else { -+ // Old inexplicably quadratic version of getHeightWidth -+ // The quadratic nature has something to do with getBBox that Lyn -+ // and others never figured out. -+ try { -+ // var bBox = this.getSvgRoot().getBBox(); -+ var root = this.getSvgRoot(); //***lyn -+ var bBox = root.getBBox(); //***lyn -+ var height = bBox.height; -+ -+ } catch (e) { -+ // Firefox has trouble with hidden elements (Bug 528969). -+ return {height: 0, width: 0}; -+ } -+ if (Blockly.BROKEN_CONTROL_POINTS) { -+ /* HACK: -+ WebKit bug 67298 causes control points to be included in the reported -+ bounding box. The render functions (below) add two 5px spacer control -+ points that we need to subtract. -+ */ -+ height -= 10; -+ if (this.nextConnection) { -+ // Bottom control point partially masked by lower tab. -+ height += 4; -+ } -+ } -+ // Subtract one from the height due to the shadow. -+ height -= 1; -+ result = {height: height, width: bBox.width}; //Why is width handled differently here -+ } -+ var stop = new Date().getTime(); -+ var timeDiff = stop - start; -+ Blockly.Instrument.stats.getHeightWidthCalls++; -+ Blockly.Instrument.stats.getHeightWidthTime += timeDiff; -+ return result; -+ }; -+ -+ /** -+ * Neil's getHeightWidth -+ */ -+ Blockly.Block.prototype.getHeightWidthNeil = 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.getHeightWidthNeil(); - 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. -*************** -*** 474,480 **** - * @private - */ - Blockly.Block.prototype.onMouseUp_ = function(e) { - var this_ = this; - Blockly.doCommand(function() { - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { ---- 549,558 ---- - * @private - */ - Blockly.Block.prototype.onMouseUp_ = function(e) { -+ var start = new Date().getTime(); -+ Blockly.Instrument.initializeStats("onMouseUp"); - var this_ = this; -+ Blockly.resetWorkspaceArrangements(); - Blockly.doCommand(function() { - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { -*************** -*** 509,514 **** - Blockly.highlightedConnection_ = null; - } - }); - }; - - /** ---- 587,602 ---- - Blockly.highlightedConnection_ = null; - } - }); -+ if (! Blockly.Instrument.avoidRenderWorkspaceInMouseUp) { -+ // [lyn, 04/01/14] rendering a workspace takes a *long* time and is *not* necessary! -+ // This is the key source of the laggy drag problem. Remove it! -+ Blockly.mainWorkspace.render(); -+ } -+ Blockly.WarningHandler.checkAllBlocksForWarningsAndErrors(); -+ var stop = new Date().getTime(); -+ var timeDiff = stop - start; -+ Blockly.Instrument.stats.totalTime = timeDiff; -+ Blockly.Instrument.displayStats("onMouseUp"); - }; - - /** -*************** -*** 833,850 **** - - /** - * Get the colour of a block. -- * @return {number} HSV hue value. - */ - Blockly.Block.prototype.getColour = function() { -- return this.colourHue_; - }; - - /** - * Change the colour of a block. -- * @param {number} colourHue HSV hue value. - */ -- Blockly.Block.prototype.setColour = function(colourHue) { -- this.colourHue_ = colourHue; - if (this.svg_) { - this.svg_.updateColour(); - } ---- 924,954 ---- - - /** - * Get the colour of a block. -+ * @return {number|Array} HSV hue value or RGB Array. - */ - Blockly.Block.prototype.getColour = function() { -+ return (this.rgbArray_ == null ? this.colourHue_ : this.rgbArray_); - }; - - /** - * Change the colour of a block. -+ * @param {number|Array} hueOrRGBArray HSV hue value or array of RGB values. - */ -+ Blockly.Block.prototype.setColour = function(hueOrRGBArray) { -+ if(Array.isArray(hueOrRGBArray)) { -+ this.rgbArray_ = hueOrRGBArray; -+ this.colourHue_ = null; -+ } else { -+ this.colourHue_ = hueOrRGBArray; -+ this.rgbArray_ = null; -+ } -+ this.updateColour(); -+ }; -+ -+ /** -+ * Update the colour of a block. -+ */ -+ Blockly.Block.prototype.updateColour = function() { - if (this.svg_) { - this.svg_.updateColour(); - } -*************** -*** 852,857 **** - 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++) { ---- 956,964 ---- - for (var x = 0; x < icons.length; x++) { - icons[x].updateColour(); - } -+ if (this.errorIcon) { -+ this.errorIcon.updateColour(); -+ } - if (this.rendered) { - // Bump every dropdown to change its colour. - for (var x = 0, input; input = this.inputList[x]; x++) { -*************** -*** 1095,1105 **** - 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'; ---- 1202,1225 ---- - if (this.collapsed_ == collapsed) { - return; - } -+ var start = new Date().getTime(); - this.collapsed_ = collapsed; - var renderList = []; -+ // //Prepare the string for collapsing if needed -+ // if (collapsed){ -+ // if (this.prepareCollapsedText && goog.isFunction(this.prepareCollapsedText)) -+ // this.prepareCollapsedText(); -+ // } - // Show/hide the inputs. -+ if (Blockly.Instrument.useRenderDown) { -+ for (var x = 0, input; input = this.inputList[x]; x++) { -+ // No need to collect renderList if rendering down. -+ input.setVisible(!collapsed); -+ } -+ } else { -+ for (var x = 0, input; input = this.inputList[x]; x++) { - renderList.push.apply(renderList, input.setVisible(!collapsed)); -+ } - } - - var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; -*************** -*** 1108,1113 **** - 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 { ---- 1228,1236 ---- - for (var x = 0; x < icons.length; x++) { - icons[x].setVisible(false); - } -+ if (this.errorIcon) { -+ this.errorIcon.setVisible(false); -+ } - var text = this.toString(Blockly.COLLAPSE_CHARS); - this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text); - } else { -*************** -*** 1118,1129 **** - // 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_(); - } - }; - - /** ---- 1241,1264 ---- - // No child blocks, just render this block. - renderList[0] = this; - } -+ - if (this.rendered) { -+ if (Blockly.Instrument.useRenderDown) { -+ this.renderDown(); -+ } else { -+ for (var x = 0, block; block = renderList[x]; x++) { -+ block.render(); -+ } - } - this.bumpNeighbours_(); - } -+ -+ var stop = new Date().getTime(); -+ var timeDiff = stop - start; -+ if (! collapsed) { -+ Blockly.Instrument.stats.expandCollapsedCalls++; -+ Blockly.Instrument.stats.expandCollapsedTime += timeDiff; -+ } - }; - - /** -*************** -*** 1174,1180 **** - */ - Blockly.Block.prototype.appendInput_ = function(type, name) { - var connection = null; -- if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) { - connection = new Blockly.Connection(this, type); - } - var input = new Blockly.Input(type, name, this, connection); ---- 1320,1326 ---- - */ - Blockly.Block.prototype.appendInput_ = function(type, name) { - var connection = null; -+ if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT || type == Blockly.INDENTED_VALUE) { - connection = new Blockly.Connection(this, type); - } - var input = new Blockly.Input(type, name, this, connection); -*************** -*** 1389,1400 **** - }; - - /** - * Render the block. - * Lays out and reflows a block based on its contents and settings. - */ - 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); - }; ---- 1535,1609 ---- - }; - - /** -+ * Set this block's warning text. -+ * @param {?string} text The text, or null to delete. -+ */ -+ Blockly.Block.prototype.setErrorIconText = function(text) { -+ if (!Blockly.ErrorIcon) { -+ throw 'Warnings not supported.'; -+ } -+ var changedState = false; -+ if (goog.isString(text)) { -+ if (!this.errorIcon) { -+ this.errorIcon = new Blockly.ErrorIcon(this); -+ changedState = true; -+ } -+ this.errorIcon.setText(/** @type {string} */ (text)); -+ } else { -+ if (this.errorIcon) { -+ this.errorIcon.dispose(); -+ changedState = true; -+ } -+ } -+ if (this.rendered) { -+ this.render(); -+ if (changedState) { -+ // Adding or removing a warning icon will cause the block to change shape. -+ this.bumpNeighbours_(); -+ } -+ } -+ }; -+ -+ /** -+ * [lyn, 04/01/14] Global flag to control whether rendering is done. -+ * There is no need to render blocks in Blocky.SaveFile.load. -+ * We only need to render them when a Screen is loaded in the Blockly editor. -+ * This flag is used to turn off rendering for first case and turn it on for the second. -+ * @type {boolean} -+ */ -+ Blockly.Block.isRenderingOn = true; -+ -+ /** - * Render the block. - * Lays out and reflows a block based on its contents and settings. - */ - Blockly.Block.prototype.render = function() { -+ if (Blockly.Block.isRenderingOn) { -+ goog.asserts.assertObject(this.svg_, -+ 'Uninitialized block cannot be rendered. Call block.initSvg()'); -+ this.svg_.render(); -+ if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) { -+ Blockly.Realtime.blockChanged(this); -+ } -+ Blockly.Instrument.stats.renderCalls++; -+ // [lyn, 04/08/14] Because render is recursive, doesn't make sense to track its time here. -+ } -+ }; -+ -+ /** -+ * [lyn, 04/01/14] Render a tree of blocks from top down rather than bottom up. -+ * This is in contrast to render(), which renders a block and all its antecedents. -+ */ -+ Blockly.Block.prototype.renderDown = function() { -+ if (Blockly.Block.isRenderingOn) { -+ goog.asserts.assertObject(this.svg_, -+ ' Uninitialized block cannot be renderedDown. Call block.initSvg()'); -+ this.svg_.renderDown(); -+ Blockly.Instrument.stats.renderDownCalls++; //***lyn -+ if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) { -+ Blockly.Realtime.blockChanged(this); -+ } -+ } -+ // [lyn, 04/08/14] Because renderDown is recursive, doesn't make sense to track its time here. - }; -+ diff --git a/core/block_render_svg.js b/core/block_render_svg.js index 14c42b0af..3f3bb8f22 100644 --- a/core/block_render_svg.js +++ b/core/block_render_svg.js @@ -263,6 +263,49 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { Blockly.Field.startCache(); this.rendered = true; + this.renderHere(); + + // Render all blocks above this one (propagate a reflow). + var parentBlock = this.getParent(); + if (parentBlock) { + parentBlock.render(); + } + + Blockly.Field.stopCache(); +}; + +/** + * [lyn, 04/01/14] Render a tree of blocks. + * In general, this is more efficient than calling render() on all the leaves of the tree, + * because that will: + * (1) repeat the rendering of all internal nodes; and + * (2) will unnecessarily call Blockly.fireUiEvent(window, 'resize') in the + * case where the parentPointer hasn't been set yet (particularly for + * value, statement, and next connections in Xml.domToBlock). + * These two factors account for much of the slow project loading times in Blockly + * and previous versions of AI2. + */ +Blockly.BlockSvg.prototype.renderDown = function() { + this.rendered = true; + + // Recursively renderDown all my children (as long as I'm not collapsed) + if (! (Blockly.Instrument.avoidRenderDownOnCollapsedSubblocks && this.isCollapsed())) { + var childBlocks = this.childBlocks_; + for (var c = 0, childBlock; childBlock = childBlocks[c]; c++) { + childBlock.renderDown(); + } + } + + // Render me after all my children have been rendered. + this.renderHere(); +}; + +/** + * Render this block. Assumes descendants have already been rendered. + */ +Blockly.BlockSvg.prototype.renderHere = function() { + var start = new Date().getTime(); + // Now render me (even if I am collapsed, since still need to show collapsed block) var cursorX = Blockly.BlockSvg.SEP_SPACE_X; if (this.RTL) { cursorX = -cursorX; @@ -282,16 +325,16 @@ Blockly.BlockSvg.prototype.render = function(opt_bubble) { this.renderMoveConnections_(); if (opt_bubble !== false) { - // Render all blocks above this one (propagate a reflow). var parentBlock = this.getParent(); - if (parentBlock) { - parentBlock.render(true); - } else { + if (!parentBlock) { // Top-most block. Fire an event to allow scrollbars to resize. this.workspace.resizeContents(); } } - Blockly.Field.stopCache(); + var stop = new Date().getTime(); + var timeDiff = stop - start; + Blockly.Instrument.stats.renderHereCalls++; + Blockly.Instrument.stats.renderHereTime += timeDiff; }; /** @@ -368,6 +411,9 @@ Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) { row = []; if (isInline && input.type != Blockly.NEXT_STATEMENT) { row.type = Blockly.BlockSvg.INLINE; + } else if (input.subtype) { + row.type = input.type; + row.subtype = input.subtype; } else { row.type = input.type; } @@ -758,9 +804,37 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, } } this.renderFields_(input.fieldRow, fieldX, fieldY); - steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); - var v = row.height - Blockly.BlockSvg.TAB_HEIGHT; - steps.push('v', v); + if (row.subtype == Blockly.INDENTED_VALUE){ + cursorX = inputRows.statementEdge; + steps.push('H', cursorX+input.fieldWidth+8); + steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + steps.push('V',cursorY+row.height); + steps.push('H', inputRows.rightEdge); + steps.push('v 8'); + if (this.RTL) { + highlightSteps.push('M', + (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + + Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + + ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); + highlightSteps.push( + Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('v', + row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); + highlightSteps.push( + Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL); + highlightSteps.push('H', inputRows.rightEdge - 1); + } else { + highlightSteps.push('M', + (cursorX + 9 + input.fieldWidth) + ',' + + (cursorY + row.height)); + highlightSteps.push('H', inputRows.rightEdge); + } + cursorY+=8; + } else { + steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); + var v = row.height - Blockly.BlockSvg.TAB_HEIGHT + steps.push('v', v); + } if (this.RTL) { // Highlight around back of tab. highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); @@ -773,8 +847,15 @@ Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps, ',-2.1'); } // Create external input connection. - connectionX = this.RTL ? -inputRows.rightEdge - 1 : - inputRows.rightEdge + 1; + if (row.subtype == Blockly.INDENTED_VALUE) { + connectionX = connectionsXY.x + + (this.RTL ? -inputRows.statementEdge - 1: inputRows.statementEdge + 9 + input.fieldWidth); + connectionY = connectionsXY.y + cursorY-8; + } else { + connectionX = connectionsXY.x + + (this.RTL ? -inputRows.rightEdge - 1: inputRows.rightEdge + 1); + connectionY = connectionsXY.y + cursorY; + } input.connection.setOffsetInBlock(connectionX, cursorY); if (input.connection.isConnected()) { this.width = Math.max(this.width, inputRows.rightEdge + diff --git a/core/block_svg.js.orig b/core/block_svg.js.orig deleted file mode 100644 index 4d4a7299b..000000000 --- a/core/block_svg.js.orig +++ /dev/null @@ -1,1629 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2012 Google Inc. - * https://developers.google.com/blockly/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Methods for graphically rendering a block as SVG. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.BlockSvg'); - -goog.require('Blockly.Block'); -goog.require('Blockly.ContextMenu'); -goog.require('Blockly.RenderedConnection'); -goog.require('goog.Timer'); -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.math.Coordinate'); -goog.require('goog.userAgent'); - - -/** - * Class for a block's SVG representation. - * Not normally called directly, workspace.newBlock() is preferred. - * @param {!Blockly.Workspace} workspace The block's workspace. - * @param {?string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. - * @extends {Blockly.Block} - * @constructor - */ -Blockly.BlockSvg = function(workspace, prototypeName, opt_id) { - // Create core elements for the block. - /** - * @type {SVGElement} - * @private - */ - this.svgGroup_ = Blockly.createSvgElement('g', {}, null); - - /** - * @type {SVGElement} - * @private - */ - this.svgPathDark_ = Blockly.createSvgElement('path', - {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'}, - this.svgGroup_); - - /** - * @type {SVGElement} - * @private - */ - this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'}, - this.svgGroup_); - - /** - * @type {SVGElement} - * @private - */ - this.svgPathLight_ = Blockly.createSvgElement('path', - {'class': 'blocklyPathLight'}, this.svgGroup_); - this.svgPath_.tooltip = this; - - /** @type {boolean} */ - this.rendered = false; - - Blockly.Tooltip.bindMouseEvents(this.svgPath_); - Blockly.BlockSvg.superClass_.constructor.call(this, - workspace, prototypeName, opt_id); -}; -goog.inherits(Blockly.BlockSvg, Blockly.Block); - -/** - * Height of this block, not including any statement blocks above or below. - */ -Blockly.BlockSvg.prototype.height = 0; -/** - * Width of this block, including any connected value blocks. - */ -Blockly.BlockSvg.prototype.width = 0; - -/** - * Original location of block being dragged. - * @type {goog.math.Coordinate} - * @private - */ -Blockly.BlockSvg.prototype.dragStartXY_ = null; - -/** - * Constant for identifying rows that are to be rendered inline. - * Don't collide with Blockly.INPUT_VALUE and friends. - * @const - */ -Blockly.BlockSvg.INLINE = -1; - -/** - * Create and initialize the SVG representation of the block. - * May be called more than once. - */ -Blockly.BlockSvg.prototype.initSvg = function() { - goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.'); - for (var i = 0, input; input = this.inputList[i]; i++) { - input.init(); - } - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].createIcon(); - } - this.updateColour(); - this.updateMovable(); - if (!this.workspace.options.readOnly && !this.eventsInit_) { - Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this, - this.onMouseDown_); - var thisBlock = this; - Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null, - function(e) {Blockly.longStart_(e, thisBlock);}); - } - this.eventsInit_ = true; - - if (!this.getSvgRoot().parentNode) { - this.workspace.getCanvas().appendChild(this.getSvgRoot()); - } -}; - -/** - * Select this block. Highlight it visually. - */ -Blockly.BlockSvg.prototype.select = function() { - if (this.isShadow() && this.getParent()) { - // Shadow blocks should not be selected. - this.getParent().select(); - return; - } - if (Blockly.selected == this) { - return; - } - var oldId = null; - if (Blockly.selected) { - oldId = Blockly.selected.id; - // Unselect any previously selected block. - Blockly.Events.disable(); - try { - Blockly.selected.unselect(); - } finally { - Blockly.Events.enable(); - } - } - var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id); - event.workspaceId = this.workspace.id; - Blockly.Events.fire(event); - Blockly.selected = this; - this.addSelect(); -}; - -/** - * Unselect this block. Remove its highlighting. - */ -Blockly.BlockSvg.prototype.unselect = function() { - if (Blockly.selected != this) { - return; - } - var event = new Blockly.Events.Ui(null, 'selected', this.id, null); - event.workspaceId = this.workspace.id; - Blockly.Events.fire(event); - Blockly.selected = null; - this.removeSelect(); -}; - -/** - * Block's mutator icon (if any). - * @type {Blockly.Mutator} - */ -Blockly.BlockSvg.prototype.mutator = null; - -/** - * Block's comment icon (if any). - * @type {Blockly.Comment} - */ -Blockly.BlockSvg.prototype.comment = null; - -/** - * Block's warning icon (if any). - * @type {Blockly.Warning} - */ -Blockly.BlockSvg.prototype.warning = null; - -/** - * Returns a list of mutator, comment, and warning icons. - * @return {!Array} List of icons. - */ -Blockly.BlockSvg.prototype.getIcons = function() { - var icons = []; - if (this.mutator) { - icons.push(this.mutator); - } - if (this.comment) { - icons.push(this.comment); - } - if (this.warning) { - icons.push(this.warning); - } - return icons; -}; - -/** - * 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. - * @package - */ -Blockly.BlockSvg.terminateDrag = function() { - Blockly.BlockSvg.disconnectUiStop_(); - if (Blockly.BlockSvg.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_); - Blockly.BlockSvg.onMouseUpWrapper_ = null; - } - if (Blockly.BlockSvg.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_); - Blockly.BlockSvg.onMouseMoveWrapper_ = null; - } - var selected = Blockly.selected; - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Terminate a drag operation. - if (selected) { - // Update the connection locations. - var xy = selected.getRelativeToSurfaceXY(); - var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_); - var event = new Blockly.Events.Move(selected); - event.oldCoordinate = selected.dragStartXY_; - event.recordNew(); - Blockly.Events.fire(event); - - selected.moveConnections_(dxy.x, dxy.y); - delete selected.draggedBubbles_; - selected.setDragging_(false); - selected.render(); - // Ensure that any stap and bump are part of this move's event group. - var group = Blockly.Events.getGroup(); - setTimeout(function() { - Blockly.Events.setGroup(group); - selected.snapToGrid(); - Blockly.Events.setGroup(false); - }, Blockly.BUMP_DELAY / 2); - setTimeout(function() { - Blockly.Events.setGroup(group); - selected.bumpNeighbours_(); - Blockly.Events.setGroup(false); - }, Blockly.BUMP_DELAY); - // Fire an event to allow scrollbars to resize. - selected.workspace.resizeContents(); - } - } - Blockly.dragMode_ = Blockly.DRAG_NONE; - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); -}; - -/** - * Set parent of this block to be a new block or null. - * @param {Blockly.BlockSvg} newParent New parent block. - */ -Blockly.BlockSvg.prototype.setParent = function(newParent) { - if (newParent == this.parentBlock_) { - return; - } - var svgRoot = this.getSvgRoot(); - if (this.parentBlock_ && svgRoot) { - // Move this block up the DOM. Keep track of x/y translations. - var xy = this.getRelativeToSurfaceXY(); - this.workspace.getCanvas().appendChild(svgRoot); - svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')'); - } - - Blockly.Field.startCache(); - Blockly.BlockSvg.superClass_.setParent.call(this, newParent); - Blockly.Field.stopCache(); - - if (newParent) { - var oldXY = this.getRelativeToSurfaceXY(); - newParent.getSvgRoot().appendChild(svgRoot); - var newXY = this.getRelativeToSurfaceXY(); - // Move the connections to match the child's new position. - this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y); - } -}; - -/** - * Return the coordinates of the top-left corner of this block relative to the - * drawing surface's origin (0,0). - * @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) { - goog.asserts.assert(!this.parentBlock_, 'Block has parent.'); - var event = new Blockly.Events.Move(this); - var xy = this.getRelativeToSurfaceXY(); - this.getSvgRoot().setAttribute('transform', - 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')'); - this.moveConnections_(dx, dy); - event.recordNew(); - this.workspace.resizeContents(); - Blockly.Events.fire(event); -}; - -/** - * Snap this block to the nearest grid point. - */ -Blockly.BlockSvg.prototype.snapToGrid = function() { - if (!this.workspace) { - return; // Deleted block. - } - if (Blockly.dragMode_ != Blockly.DRAG_NONE) { - return; // Don't bump blocks during a drag. - } - if (this.getParent()) { - return; // Only snap top-level blocks. - } - if (this.isInFlyout) { - return; // Don't move blocks around in a flyout. - } - if (!this.workspace.options.gridOptions || - !this.workspace.options.gridOptions['snap']) { - return; // Config says no snapping. - } - var spacing = this.workspace.options.gridOptions['spacing']; - var half = spacing / 2; - var xy = this.getRelativeToSurfaceXY(); - var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x; - var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y; - dx = Math.round(dx); - dy = Math.round(dy); - if (dx != 0 || dy != 0) { - this.moveBy(dx, dy); - } -}; - -/** - * Returns a bounding box describing the dimensions of this block - * and any blocks stacked below it. - * @return {!{height: number, width: number}} Object with height and width - * properties. - */ -Blockly.BlockSvg.prototype.getHeightWidth = function() { - var height = this.height; - var width = this.width; - // Recursively add size of subsequent blocks. - var nextBlock = this.getNextBlock(); - if (nextBlock) { - var nextHeightWidth = nextBlock.getHeightWidth(); - height += nextHeightWidth.height - 4; // Height of tab. - width = Math.max(width, nextHeightWidth.width); - } else if (!this.nextConnection && !this.outputConnection) { - // Add a bit of margin under blocks with no bottom tab. - height += 2; - } - return {height: height, width: width}; -}; - -/** - * Returns the coordinates of a bounding box describing the dimensions of this - * block and any blocks stacked below it. - * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}} - * Object with top left and bottom right coordinates of the bounding box. - */ -Blockly.BlockSvg.prototype.getBoundingRectangle = function() { - var blockXY = this.getRelativeToSurfaceXY(this); - var tab = this.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - var blockBounds = this.getHeightWidth(); - var topLeft; - var bottomRight; - if (this.RTL) { - // Width has the tab built into it already so subtract it here. - topLeft = new goog.math.Coordinate(blockXY.x - (blockBounds.width - tab), - blockXY.y); - // Add the width of the tab/puzzle piece knob to the x coordinate - // since X is the corner of the rectangle, not the whole puzzle piece. - bottomRight = new goog.math.Coordinate(blockXY.x + tab, - blockXY.y + blockBounds.height); - } else { - // Subtract the width of the tab/puzzle piece knob to the x coordinate - // since X is the corner of the rectangle, not the whole puzzle piece. - topLeft = new goog.math.Coordinate(blockXY.x - tab, blockXY.y); - // Width has the tab built into it already so subtract it here. - bottomRight = new goog.math.Coordinate(blockXY.x + blockBounds.width - tab, - blockXY.y + blockBounds.height); - } - return {topLeft: topLeft, bottomRight: bottomRight}; -}; - -/** - * Set whether the block is collapsed or not. - * @param {boolean} collapsed True if collapsed. - */ -Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) { - if (this.collapsed_ == collapsed) { - return; - } - var renderList = []; - // Show/hide the inputs. - for (var i = 0, input; input = this.inputList[i]; i++) { - renderList.push.apply(renderList, input.setVisible(!collapsed)); - } - - var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT'; - if (collapsed) { - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].setVisible(false); - } - var text = this.toString(Blockly.COLLAPSE_CHARS); - this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init(); - } else { - this.removeInput(COLLAPSED_INPUT_NAME); - // Clear any warnings inherited from enclosed blocks. - this.setWarningText(null); - } - Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed); - - if (!renderList.length) { - // No child blocks, just render this block. - renderList[0] = this; - } - if (this.rendered) { - for (var i = 0, block; block = renderList[i]; i++) { - block.render(); - } - // Don't bump neighbours. - // Although bumping neighbours would make sense, users often collapse - // all their functions and store them next to each other. Expanding and - // bumping causes all their definitions to go out of alignment. - } -}; - -/** - * Open the next (or previous) FieldTextInput. - * @param {Blockly.Field|Blockly.Block} start Current location. - * @param {boolean} forward If true go forward, otherwise backward. - */ -Blockly.BlockSvg.prototype.tab = function(start, forward) { - // This function need not be efficient since it runs once on a keypress. - // Create an ordered list of all text fields and connected inputs. - var list = []; - for (var i = 0, input; input = this.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - if (field instanceof Blockly.FieldTextInput) { - // TODO: Also support dropdown fields. - list.push(field); - } - } - if (input.connection) { - var block = input.connection.targetBlock(); - if (block) { - list.push(block); - } - } - } - var i = list.indexOf(start); - if (i == -1) { - // No start location, start at the beginning or end. - i = forward ? -1 : list.length; - } - var target = list[forward ? i + 1 : i - 1]; - if (!target) { - // Ran off of list. - var parent = this.getParent(); - if (parent) { - parent.tab(this, forward); - } - } else if (target instanceof Blockly.Field) { - target.showEditor_(); - } else { - target.tab(null, forward); - } -}; - -/** - * Handle a mouse-down on an SVG block. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.BlockSvg.prototype.onMouseDown_ = function(e) { - if (this.workspace.options.readOnly) { - return; - } - if (this.isInFlyout) { - return; - } - if (this.isInMutator) { - // Mutator's coordinate system could be out of date because the bubble was - // dragged, the block was moved, the parent workspace zoomed, etc. - this.workspace.resize(); - } - - this.workspace.updateScreenCalculationsIfScrolled(); - this.workspace.markFocused(); - Blockly.terminateDrag_(); - this.select(); - Blockly.hideChaff(); - if (Blockly.isRightButton(e)) { - // Right-click. - this.showContextMenu_(e); - } else if (!this.isMovable()) { - // Allow immovable blocks to be selected and context menued, but not - // dragged. Let this event bubble up to document, so the workspace may be - // dragged instead. - return; - } else { - if (!Blockly.Events.getGroup()) { - Blockly.Events.setGroup(true); - } - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - - this.dragStartXY_ = this.getRelativeToSurfaceXY(); - this.workspace.startDrag(e, this.dragStartXY_); - - Blockly.dragMode_ = Blockly.DRAG_STICKY; - Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, this.onMouseUp_); - Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.onMouseMove_); - // Build a list of bubbles that need to be moved and where they started. - this.draggedBubbles_ = []; - var descendants = this.getDescendants(); - for (var i = 0, descendant; descendant = descendants[i]; i++) { - var icons = descendant.getIcons(); - for (var j = 0; j < icons.length; j++) { - var data = icons[j].getIconLocation(); - data.bubble = icons[j]; - this.draggedBubbles_.push(data); - } - } - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); -}; - -/** - * 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) { - if (Blockly.dragMode_ != Blockly.DRAG_FREE && - !Blockly.WidgetDiv.isVisible()) { - Blockly.Events.fire( - new Blockly.Events.Ui(this, 'click', undefined, undefined)); - } - Blockly.terminateDrag_(); - if (Blockly.selected && Blockly.highlightedConnection_) { - // Connect two blocks together. - Blockly.localConnection_.connect(Blockly.highlightedConnection_); - if (this.rendered) { - // Trigger a connection animation. - // Determine which connection is inferior (lower in the source stack). - var inferiorConnection = Blockly.localConnection_.isSuperior() ? - Blockly.highlightedConnection_ : Blockly.localConnection_; - inferiorConnection.getSourceBlock().connectionUiEffect(); - } - if (this.workspace.trashcan) { - // Don't throw an object in the trash can if it just got connected. - this.workspace.trashcan.close(); - } - } else if (!this.getParent() && Blockly.selected.isDeletable() && - this.workspace.isDeleteArea(e)) { - var trashcan = this.workspace.trashcan; - if (trashcan) { - goog.Timer.callOnce(trashcan.close, 100, trashcan); - } - Blockly.selected.dispose(false, true); - } - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); - if (!Blockly.WidgetDiv.isVisible()) { - Blockly.Events.setGroup(false); - } -}; - -/** - * Load the block's help page in a new window. - * @private - */ -Blockly.BlockSvg.prototype.showHelp_ = function() { - var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; - if (url) { - window.open(url); - } -}; - -/** - * Show the context menu for this block. - * @param {!Event} e Mouse event. - * @private - */ -Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { - if (this.workspace.options.readOnly || !this.contextMenu) { - return; - } - // Save the current block in a variable for use in closures. - var block = this; - var menuOptions = []; - - if (this.isDeletable() && this.isMovable() && !block.isInFlyout) { - // Option to duplicate this block. - var duplicateOption = { - text: Blockly.Msg.DUPLICATE_BLOCK, - enabled: true, - callback: function() { - Blockly.duplicate_(block); - } - }; - if (this.getDescendants().length > this.workspace.remainingCapacity()) { - duplicateOption.enabled = false; - } - menuOptions.push(duplicateOption); - - if (this.isEditable() && !this.collapsed_ && - this.workspace.options.comments) { - // Option to add/remove a comment. - var commentOption = {enabled: !goog.userAgent.IE}; - if (this.comment) { - commentOption.text = Blockly.Msg.REMOVE_COMMENT; - commentOption.callback = function() { - block.setCommentText(null); - }; - } else { - commentOption.text = Blockly.Msg.ADD_COMMENT; - commentOption.callback = function() { - block.setCommentText(''); - }; - } - menuOptions.push(commentOption); - } - - // Option to make block inline. - if (!this.collapsed_) { - for (var i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT && - this.inputList[i].type != Blockly.NEXT_STATEMENT) { - // Only display this option if there are two value or dummy inputs - // next to each other. - var inlineOption = {enabled: true}; - var isInline = this.getInputsInline(); - inlineOption.text = isInline ? - Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS; - inlineOption.callback = function() { - block.setInputsInline(!isInline); - }; - menuOptions.push(inlineOption); - break; - } - } - } - - if (this.workspace.options.collapse) { - // Option to collapse/expand block. - if (this.collapsed_) { - var expandOption = {enabled: true}; - expandOption.text = Blockly.Msg.EXPAND_BLOCK; - expandOption.callback = function() { - block.setCollapsed(false); - }; - menuOptions.push(expandOption); - } else { - var collapseOption = {enabled: true}; - collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK; - collapseOption.callback = function() { - block.setCollapsed(true); - }; - menuOptions.push(collapseOption); - } - } - - if (this.workspace.options.disable) { - // Option to disable/enable block. - var disableOption = { - text: this.disabled ? - Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK, - enabled: !this.getInheritedDisabled(), - callback: function() { - block.setDisabled(!block.disabled); - } - }; - menuOptions.push(disableOption); - } - - // Option to delete this block. - // Count the number of blocks that are nested in this block. - var descendantCount = this.getDescendants().length; - var nextBlock = this.getNextBlock(); - if (nextBlock) { - // Blocks in the current stack would survive this block's deletion. - descendantCount -= nextBlock.getDescendants().length; - } - var deleteOption = { - text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK : - Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)), - enabled: true, - callback: function() { - Blockly.Events.setGroup(true); - block.dispose(true, true); - Blockly.Events.setGroup(false); - } - }; - menuOptions.push(deleteOption); - } - - // Option to get help. - var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl; - var helpOption = {enabled: !!url}; - helpOption.text = Blockly.Msg.HELP; - helpOption.callback = function() { - block.showHelp_(); - }; - menuOptions.push(helpOption); - - // Allow the block to add or modify menuOptions. - if (this.customContextMenu && !block.isInFlyout) { - this.customContextMenu(menuOptions); - } - - Blockly.ContextMenu.show(e, menuOptions, this.RTL); - Blockly.ContextMenu.currentBlock = this; -}; - -/** - * Move the connections for this block and all blocks attached under it. - * Also update any attached bubbles. - * @param {number} dx Horizontal offset from current location. - * @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 i = 0; i < myConnections.length; i++) { - myConnections[i].moveBy(dx, dy); - } - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].computeIconLocation(); - } - - // Recurse through all blocks attached under this one. - for (var i = 0; i < this.childBlocks_.length; i++) { - this.childBlocks_[i].moveConnections_(dx, dy); - } -}; - -/** - * Recursively adds or removes the dragging class to this node and its children. - * @param {boolean} adding True if adding, false if removing. - * @private - */ -Blockly.BlockSvg.prototype.setDragging_ = function(adding) { - if (adding) { - var group = this.getSvgRoot(); - group.translate_ = ''; - group.skew_ = ''; - this.addDragging(); - Blockly.draggingConnections_ = - Blockly.draggingConnections_.concat(this.getConnections_(true)); - } else { - this.removeDragging(); - Blockly.draggingConnections_ = []; - } - // Recurse through all blocks attached under this one. - for (var i = 0; i < this.childBlocks_.length; i++) { - this.childBlocks_[i].setDragging_(adding); - } -}; - -/** - * Drag this block to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.BlockSvg.prototype.onMouseMove_ = function(e) { - if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && - e.button == 0) { - /* HACK: - Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove - events on certain touch actions. Ignore events with these signatures. - This may result in a one-pixel blind spot in other browsers, - but this shouldn't be noticeable. */ - e.stopPropagation(); - return; - } - - var oldXY = this.getRelativeToSurfaceXY(); - var newXY = this.workspace.moveDrag(e); - - if (Blockly.dragMode_ == Blockly.DRAG_STICKY) { - // Still dragging within the sticky DRAG_RADIUS. - var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale; - if (dr > Blockly.DRAG_RADIUS) { - // Switch to unrestricted dragging. - Blockly.dragMode_ = Blockly.DRAG_FREE; - Blockly.longStop_(); - if (this.parentBlock_) { - // Push this block to the very top of the stack. - this.unplug(); - var group = this.getSvgRoot(); - group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; - this.disconnectUiEffect(); - } - this.setDragging_(true); - } - } - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Unrestricted dragging. - var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_); - var group = this.getSvgRoot(); - group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')'; - group.setAttribute('transform', group.translate_ + group.skew_); - // Drag all the nested bubbles. - for (var i = 0; i < this.draggedBubbles_.length; i++) { - var commentData = this.draggedBubbles_[i]; - commentData.bubble.setIconLocation( - goog.math.Coordinate.sum(commentData, dxy)); - } - - // Check to see if any of this block's connections are within range of - // another block's connection. - var myConnections = this.getConnections_(false); - // Also check the last connection on this stack - var lastOnStack = this.lastConnectionInStack_(); - if (lastOnStack && lastOnStack != this.nextConnection) { - myConnections.push(lastOnStack); - } - var closestConnection = null; - var localConnection = null; - var radiusConnection = Blockly.SNAP_RADIUS; - for (var i = 0; i < myConnections.length; i++) { - var myConnection = myConnections[i]; - var neighbour = myConnection.closest(radiusConnection, dxy); - if (neighbour.connection) { - closestConnection = neighbour.connection; - localConnection = myConnection; - radiusConnection = neighbour.radius; - } - } - - // Remove connection highlighting if needed. - if (Blockly.highlightedConnection_ && - Blockly.highlightedConnection_ != closestConnection) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - Blockly.localConnection_ = null; - } - // Add connection highlighting if needed. - if (closestConnection && - closestConnection != Blockly.highlightedConnection_) { - closestConnection.highlight(); - Blockly.highlightedConnection_ = closestConnection; - Blockly.localConnection_ = localConnection; - } - // Provide visual indication of whether the block will be deleted if - // dropped here. - if (this.isDeletable()) { - this.workspace.isDeleteArea(e); - } - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); -}; - -/** - * Add or remove the UI indicating if this block is movable or not. - */ -Blockly.BlockSvg.prototype.updateMovable = function() { - if (this.isMovable()) { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); - } else { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDraggable'); - } -}; - -/** - * Set whether this block is movable or not. - * @param {boolean} movable True if movable. - */ -Blockly.BlockSvg.prototype.setMovable = function(movable) { - Blockly.BlockSvg.superClass_.setMovable.call(this, movable); - this.updateMovable(); -}; - -/** - * Set whether this block is editable or not. - * @param {boolean} editable True if editable. - */ -Blockly.BlockSvg.prototype.setEditable = function(editable) { - Blockly.BlockSvg.superClass_.setEditable.call(this, editable); - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].updateEditable(); - } -}; - -/** - * Set whether this block is a shadow block or not. - * @param {boolean} shadow True if a shadow. - */ -Blockly.BlockSvg.prototype.setShadow = function(shadow) { - Blockly.BlockSvg.superClass_.setShadow.call(this, shadow); - this.updateColour(); -}; - -/** - * Return the root node of the SVG or null if none exists. - * @return {Element} The root SVG node (probably a group). - */ -Blockly.BlockSvg.prototype.getSvgRoot = function() { - return this.svgGroup_; -}; - -/** - * Dispose of this block. - * @param {boolean} healStack If true, then try to heal any gap by connecting - * the next statement with the previous statement. Otherwise, dispose of - * all children of this block. - * @param {boolean} animate If true, show a disposal animation and sound. - */ -Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { - if (!this.workspace) { - // The block has already been deleted. - return; - } - Blockly.Tooltip.hide(); - Blockly.Field.startCache(); - // Save the block's workspace temporarily so we can resize the - // contents once the block is disposed. - var blockWorkspace = this.workspace; - // If this block is being dragged, unlink the mouse events. - if (Blockly.selected == this) { - this.unselect(); - Blockly.terminateDrag_(); - } - // If this block has a context menu open, close it. - if (Blockly.ContextMenu.currentBlock == this) { - Blockly.ContextMenu.hide(); - } - - if (animate && this.rendered) { - this.unplug(healStack); - this.disposeUiEffect(); - } - // Stop rerendering. - this.rendered = false; - - Blockly.Events.disable(); - try { - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].dispose(); - } - } finally { - Blockly.Events.enable(); - } - Blockly.BlockSvg.superClass_.dispose.call(this, healStack); - - goog.dom.removeNode(this.svgGroup_); - blockWorkspace.resizeContents(); - // Sever JavaScript to DOM connections. - this.svgGroup_ = null; - this.svgPath_ = null; - this.svgPathLight_ = null; - this.svgPathDark_ = null; - Blockly.Field.stopCache(); -}; - -/** - * Play some UI effects (sound, animation) when disposing of a block. - */ -Blockly.BlockSvg.prototype.disposeUiEffect = function() { - this.workspace.playAudio('delete'); - - var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), - this.workspace); - // Deeply clone the current block. - var clone = this.svgGroup_.cloneNode(true); - clone.translateX_ = xy.x; - clone.translateY_ = xy.y; - clone.setAttribute('transform', - 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')'); - this.workspace.getParentSvg().appendChild(clone); - clone.bBox_ = clone.getBBox(); - // Start the animation. - Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date, - this.workspace.scale); -}; - -/** - * Animate a cloned block and eventually dispose of it. - * This is a class method, not an instace method since the original block has - * been destroyed and is no longer accessible. - * @param {!Element} clone SVG element to animate and dispose of. - * @param {boolean} rtl True if RTL, false if LTR. - * @param {!Date} start Date of animation's start. - * @param {number} workspaceScale Scale of workspace. - * @private - */ -Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) { - var ms = new Date - start; - var percent = ms / 150; - if (percent > 1) { - goog.dom.removeNode(clone); - } else { - var x = clone.translateX_ + - (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent; - var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent; - var scale = (1 - percent) * workspaceScale; - clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' + - ' scale(' + scale + ')'); - var closure = function() { - Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale); - }; - setTimeout(closure, 10); - } -}; - -/** - * Play some UI effects (sound, ripple) after a connection has been established. - */ -Blockly.BlockSvg.prototype.connectionUiEffect = function() { - this.workspace.playAudio('click'); - if (this.workspace.scale < 1) { - return; // Too small to care about visual effects. - } - // Determine the absolute coordinates of the inferior block. - var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_), - this.workspace); - // Offset the coordinates based on the two connection types, fix scale. - if (this.outputConnection) { - xy.x += (this.RTL ? 3 : -3) * this.workspace.scale; - xy.y += 13 * this.workspace.scale; - } else if (this.previousConnection) { - xy.x += (this.RTL ? -23 : 23) * this.workspace.scale; - xy.y += 3 * this.workspace.scale; - } - var ripple = Blockly.createSvgElement('circle', - {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none', - 'stroke': '#888', 'stroke-width': 10}, - this.workspace.getParentSvg()); - // Start the animation. - Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale); -}; - -/** - * Expand a ripple around a connection. - * @param {!Element} ripple Element to animate. - * @param {!Date} start Date of animation's start. - * @param {number} workspaceScale Scale of workspace. - * @private - */ -Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) { - var ms = new Date - start; - var percent = ms / 150; - if (percent > 1) { - goog.dom.removeNode(ripple); - } else { - ripple.setAttribute('r', percent * 25 * workspaceScale); - ripple.style.opacity = 1 - percent; - var closure = function() { - Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale); - }; - Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10); - } -}; - -/** - * Play some UI effects (sound, animation) when disconnecting a block. - */ -Blockly.BlockSvg.prototype.disconnectUiEffect = function() { - this.workspace.playAudio('disconnect'); - if (this.workspace.scale < 1) { - return; // Too small to care about visual effects. - } - // Horizontal distance for bottom of block to wiggle. - var DISPLACEMENT = 10; - // Scale magnitude of skew to height of block. - var height = this.getHeightWidth().height; - var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180; - if (!this.RTL) { - magnitude *= -1; - } - // Start the animation. - Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date); -}; - -/** - * Animate a brief wiggle of a disconnected block. - * @param {!Element} group SVG element to animate. - * @param {number} magnitude Maximum degrees skew (reversed for RTL). - * @param {!Date} start Date of animation's start. - * @private - */ -Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) { - var DURATION = 200; // Milliseconds. - var WIGGLES = 3; // Half oscillations. - - var ms = new Date - start; - var percent = ms / DURATION; - - if (percent > 1) { - group.skew_ = ''; - } else { - var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) * - (1 - percent) * magnitude); - group.skew_ = 'skewX(' + skew + ')'; - var closure = function() { - Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start); - }; - Blockly.BlockSvg.disconnectUiStop_.group = group; - Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10); - } - group.setAttribute('transform', group.translate_ + group.skew_); -}; - -/** - * Stop the disconnect UI animation immediately. - * @private - */ -Blockly.BlockSvg.disconnectUiStop_ = function() { - if (Blockly.BlockSvg.disconnectUiStop_.group) { - clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid); - var group = Blockly.BlockSvg.disconnectUiStop_.group; - group.skew_ = ''; - group.setAttribute('transform', group.translate_); - Blockly.BlockSvg.disconnectUiStop_.group = null; - } -}; - -/** - * PID of disconnect UI animation. There can only be one at a time. - * @type {number} - */ -Blockly.BlockSvg.disconnectUiStop_.pid = 0; - -/** - * SVG group of wobbling block. There can only be one at a time. - * @type {Element} - */ -Blockly.BlockSvg.disconnectUiStop_.group = null; - -/** - * Change the colour of a block. - */ -Blockly.BlockSvg.prototype.updateColour = function() { - if (this.disabled) { - // Disabled blocks don't have colour. - return; - } - var hexColour = this.getColour(); - var rgb = goog.color.hexToRgb(hexColour); - if (this.isShadow()) { - rgb = goog.color.lighten(rgb, 0.6); - hexColour = goog.color.rgbArrayToHex(rgb); - this.svgPathLight_.style.display = 'none'; - this.svgPathDark_.setAttribute('fill', hexColour); - } else { - this.svgPathLight_.style.display = ''; - var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3)); - var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2)); - this.svgPathLight_.setAttribute('stroke', hexLight); - this.svgPathDark_.setAttribute('fill', hexDark); - } - this.svgPath_.setAttribute('fill', hexColour); - - var icons = this.getIcons(); - for (var i = 0; i < icons.length; i++) { - icons[i].updateColour(); - } - - // Bump every dropdown to change its colour. - for (var x = 0, input; input = this.inputList[x]; x++) { - for (var y = 0, field; field = input.fieldRow[y]; y++) { - field.setText(null); - } - } -}; - -/** - * Enable or disable a block. - */ -Blockly.BlockSvg.prototype.updateDisabled = function() { - var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); - if (this.disabled || this.getInheritedDisabled()) { - if (!hasClass) { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); - this.svgPath_.setAttribute('fill', - 'url(#' + this.workspace.options.disabledPatternId + ')'); - } - } else { - if (hasClass) { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDisabled'); - this.updateColour(); - } - } - var children = this.getChildren(); - for (var i = 0, child; child = children[i]; i++) { - child.updateDisabled(); - } -}; - -/** - * Returns the comment on this block (or '' if none). - * @return {string} Block's comment. - */ -Blockly.BlockSvg.prototype.getCommentText = function() { - if (this.comment) { - var comment = this.comment.getText(); - // Trim off trailing whitespace. - return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n'); - } - return ''; -}; - -/** - * Set this block's comment text. - * @param {?string} text The text, or null to delete. - */ -Blockly.BlockSvg.prototype.setCommentText = function(text) { - var changedState = false; - if (goog.isString(text)) { - if (!this.comment) { - this.comment = new Blockly.Comment(this); - changedState = true; - } - this.comment.setText(/** @type {string} */ (text)); - } else { - if (this.comment) { - this.comment.dispose(); - changedState = true; - } - } - if (changedState && this.rendered) { - this.render(); - // Adding or removing a comment icon will cause the block to change shape. - this.bumpNeighbours_(); - } -}; - -/** - * Set this block's warning text. - * @param {?string} text The text, or null to delete. - * @param {string=} opt_id An optional ID for the warning text to be able to - * maintain multiple warnings. - */ -Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) { - if (!this.setWarningText.pid_) { - // Create a database of warning PIDs. - // Only runs once per block (and only those with warnings). - this.setWarningText.pid_ = Object.create(null); - } - var id = opt_id || ''; - if (!id) { - // Kill all previous pending processes, this edit supercedes them all. - for (var n in this.setWarningText.pid_) { - clearTimeout(this.setWarningText.pid_[n]); - delete this.setWarningText.pid_[n]; - } - } else if (this.setWarningText.pid_[id]) { - // Only queue up the latest change. Kill any earlier pending process. - clearTimeout(this.setWarningText.pid_[id]); - delete this.setWarningText.pid_[id]; - } - if (Blockly.dragMode_ == Blockly.DRAG_FREE) { - // Don't change the warning text during a drag. - // Wait until the drag finishes. - var thisBlock = this; - this.setWarningText.pid_[id] = setTimeout(function() { - if (thisBlock.workspace) { // Check block wasn't deleted. - delete thisBlock.setWarningText.pid_[id]; - thisBlock.setWarningText(text, id); - } - }, 100); - return; - } - if (this.isInFlyout) { - text = null; - } - - // Bubble up to add a warning on top-most collapsed block. - var parent = this.getSurroundParent(); - var collapsedParent = null; - while (parent) { - if (parent.isCollapsed()) { - collapsedParent = parent; - } - parent = parent.getSurroundParent(); - } - if (collapsedParent) { - collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id); - } - - var changedState = false; - if (goog.isString(text)) { - if (!this.warning) { - this.warning = new Blockly.Warning(this); - changedState = true; - } - this.warning.setText(/** @type {string} */ (text), id); - } else { - // Dispose all warnings if no id is given. - if (this.warning && !id) { - this.warning.dispose(); - changedState = true; - } else if (this.warning) { - var oldText = this.warning.getText(); - this.warning.setText('', id); - var newText = this.warning.getText(); - if (!newText) { - this.warning.dispose(); - } - changedState = oldText == newText; - } - } - if (changedState && this.rendered) { - this.render(); - // Adding or removing a warning icon will cause the block to change shape. - this.bumpNeighbours_(); - } -}; - -/** - * Give this block a mutator dialog. - * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove. - */ -Blockly.BlockSvg.prototype.setMutator = function(mutator) { - if (this.mutator && this.mutator !== mutator) { - this.mutator.dispose(); - } - if (mutator) { - mutator.block_ = this; - this.mutator = mutator; - mutator.createIcon(); - } -}; - -/** - * Set whether the block is disabled or not. - * @param {boolean} disabled True if disabled. - */ -Blockly.BlockSvg.prototype.setDisabled = function(disabled) { - if (this.disabled != disabled) { - Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled); - if (this.rendered) { - this.updateDisabled(); - } - } -}; - -/** - * Select this block. Highlight it visually. - */ -Blockly.BlockSvg.prototype.addSelect = function() { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); - // Move the selected block to the top of the stack. - var block = this; - do { - var root = block.getSvgRoot(); - root.parentNode.appendChild(root); - block = block.getParent(); - } while (block); -}; - -/** - * Unselect this block. Remove its highlighting. - */ -Blockly.BlockSvg.prototype.removeSelect = function() { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklySelected'); -}; - -/** - * Adds the dragging class to this block. - * Also disables the highlights/shadows to improve performance. - */ -Blockly.BlockSvg.prototype.addDragging = function() { - Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); -}; - -/** - * Removes the dragging class from this block. - */ -Blockly.BlockSvg.prototype.removeDragging = function() { - Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_), - 'blocklyDragging'); -}; - -// Overrides of functions on Blockly.Block that take into account whether the -// block has been rendered. - -/** - * Change the colour of a block. - * @param {number|string} colour HSV hue value, or #RRGGBB string. - */ -Blockly.BlockSvg.prototype.setColour = function(colour) { - Blockly.BlockSvg.superClass_.setColour.call(this, colour); - - if (this.rendered) { - this.updateColour(); - } -}; - -/** - * Set whether this block can chain onto the bottom of another block. - * @param {boolean} newBoolean True if there can be a previous statement. - * @param {string|Array.|null|undefined} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Blockly.BlockSvg.prototype.setPreviousStatement = - function(newBoolean, opt_check) { - /* eslint-disable indent */ - Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, - opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours_(); - } -}; /* eslint-enable indent */ - -/** - * Set whether another block can chain onto the bottom of this block. - * @param {boolean} newBoolean True if there can be a next statement. - * @param {string|Array.|null|undefined} opt_check Statement type or - * list of statement types. Null/undefined if any type could be connected. - */ -Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) { - Blockly.BlockSvg.superClass_.setNextStatement.call(this, newBoolean, - opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours_(); - } -}; - -/** - * Set whether this block returns a value. - * @param {boolean} newBoolean True if there is an output. - * @param {string|Array.|null|undefined} opt_check Returned type or list - * of returned types. Null or undefined if any type could be returned - * (e.g. variable get). - */ -Blockly.BlockSvg.prototype.setOutput = function(newBoolean, opt_check) { - Blockly.BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check); - - if (this.rendered) { - this.render(); - this.bumpNeighbours_(); - } -}; - -/** - * Set whether value inputs are arranged horizontally or vertically. - * @param {boolean} newBoolean True if inputs are horizontal. - */ -Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) { - Blockly.BlockSvg.superClass_.setInputsInline.call(this, newBoolean); - - if (this.rendered) { - this.render(); - this.bumpNeighbours_(); - } -}; - -/** - * Remove an input from this block. - * @param {string} name The name of the input. - * @param {boolean=} opt_quiet True to prevent error if input is not present. - * @throws {goog.asserts.AssertionError} if the input is not present and - * opt_quiet is not true. - */ -Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) { - Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet); - - if (this.rendered) { - this.render(); - // Removing an input will cause the block to change shape. - this.bumpNeighbours_(); - } -}; - -/** - * Move a numbered input to a different location on this block. - * @param {number} inputIndex Index of the input to move. - * @param {number} refIndex Index of input that should be after the moved input. - */ -Blockly.BlockSvg.prototype.moveNumberedInputBefore = function( - inputIndex, refIndex) { - Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex, - refIndex); - - if (this.rendered) { - this.render(); - // Moving an input will cause the block to change shape. - this.bumpNeighbours_(); - } -}; - -/** - * Add a value input, statement input or local variable to this block. - * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or - * Blockly.DUMMY_INPUT. - * @param {string} name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. - * @return {!Blockly.Input} The input object created. - * @private - */ -Blockly.BlockSvg.prototype.appendInput_ = function(type, name) { - var input = Blockly.BlockSvg.superClass_.appendInput_.call(this, type, name); - - if (this.rendered) { - this.render(); - // Adding an input will cause the block to change shape. - this.bumpNeighbours_(); - } - return input; -}; - -/** - * Returns connections originating from this block. - * @param {boolean} all If true, return all connections even hidden ones. - * Otherwise, for a non-rendered block return an empty list, and for a - * collapsed block don't return inputs connections. - * @return {!Array.} Array of connections. - * @private - */ -Blockly.BlockSvg.prototype.getConnections_ = function(all) { - var myConnections = []; - if (all || this.rendered) { - if (this.outputConnection) { - myConnections.push(this.outputConnection); - } - if (this.previousConnection) { - myConnections.push(this.previousConnection); - } - if (this.nextConnection) { - myConnections.push(this.nextConnection); - } - if (all || !this.collapsed_) { - for (var i = 0, input; input = this.inputList[i]; i++) { - if (input.connection) { - myConnections.push(input.connection); - } - } - } - } - return myConnections; -}; - -/** - * Create a connection of the specified type. - * @param {number} type The type of the connection to create. - * @return {!Blockly.RenderedConnection} A new connection of the specified type. - * @private - */ -Blockly.BlockSvg.prototype.makeConnection_ = function(type) { - return new Blockly.RenderedConnection(this, type); -}; diff --git a/core/block_svg.js.rej b/core/block_svg.js.rej deleted file mode 100644 index 59aca1875..000000000 --- a/core/block_svg.js.rej +++ /dev/null @@ -1,231 +0,0 @@ -*************** -*** 26,31 **** - - goog.provide('Blockly.BlockSvg'); - - goog.require('goog.userAgent'); - - ---- 26,32 ---- - - goog.provide('Blockly.BlockSvg'); - -+ goog.require('Blockly.Instrument'); // lyn's instrumentation code - goog.require('goog.userAgent'); - - -*************** -*** 154,167 **** - * @const - */ - Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) * -- (Blockly.BlockSvg.CORNER_RADIUS - 1) + 1; - /** - * Distance from shape edge to intersect with a curved corner at 45 degrees. - * Applies to highlighting on around the outside of a curve. - * @const - */ - Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) * -- (Blockly.BlockSvg.CORNER_RADIUS + 1) - 1; - /** - * SVG path for drawing next/previous notch from left to right. - * @const ---- 155,168 ---- - * @const - */ - Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) * -+ (Blockly.BlockSvg.CORNER_RADIUS - 1) + 1; - /** - * Distance from shape edge to intersect with a curved corner at 45 degrees. - * Applies to highlighting on around the outside of a curve. - * @const - */ - Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) * -+ (Blockly.BlockSvg.CORNER_RADIUS + 1) - 1; - /** - * SVG path for drawing next/previous notch from left to right. - * @const -*************** -*** 483,488 **** - Blockly.BlockSvg.prototype.render = function() { - this.block_.rendered = true; - - var cursorX = Blockly.BlockSvg.SEP_SPACE_X; - if (Blockly.RTL) { - cursorX = -cursorX; ---- 484,530 ---- - Blockly.BlockSvg.prototype.render = function() { - this.block_.rendered = true; - -+ this.renderHere(); -+ -+ // Render all blocks above this one (propagate a reflow). -+ var parentBlock = this.block_.getParent(); -+ if (parentBlock) { -+ parentBlock.render(); -+ } -+ }; -+ -+ /** -+ * [lyn, 04/01/14] Render a tree of blocks. -+ * In general, this is more efficient than calling render() on all the leaves of the tree, -+ * because that will: -+ * (1) repeat the rendering of all internal nodes; and -+ * (2) will unnecessarily call Blockly.fireUiEvent(window, 'resize') in the -+ * case where the parentPointer hasn't been set yet (particularly for -+ * value, statement, and next connections in Xml.domToBlock). -+ * These two factors account for much of the slow project loading times in Blockly -+ * and previous versions of AI2. -+ */ -+ Blockly.BlockSvg.prototype.renderDown = function() { -+ this.block_.rendered = true; -+ -+ // Recursively renderDown all my children (as long as I'm not collapsed) -+ if (! (Blockly.Instrument.avoidRenderDownOnCollapsedSubblocks && this.block_.isCollapsed())) { -+ var childBlocks = this.block_.childBlocks_; -+ for (var c = 0, childBlock; childBlock = childBlocks[c]; c++) { -+ childBlock.renderDown(); -+ } -+ } -+ -+ // Render me after all my children have been rendered. -+ this.renderHere(); -+ }; -+ -+ /** -+ * Render this block. Assumes descendants have already been rendered. -+ */ -+ Blockly.BlockSvg.prototype.renderHere = function() { -+ var start = new Date().getTime(); -+ // Now render me (even if I am collapsed, since still need to show collapsed block) - var cursorX = Blockly.BlockSvg.SEP_SPACE_X; - if (Blockly.RTL) { - cursorX = -cursorX; -*************** -*** 500,514 **** - var inputRows = this.renderCompute_(cursorX); - this.renderDraw_(cursorX, inputRows); - -- // Render all blocks above this one (propagate a reflow). - var parentBlock = this.block_.getParent(); -- if (parentBlock) { -- parentBlock.render(); -- } else { - // Top-most block. Fire an event to allow scrollbars to resize. - Blockly.fireUiEvent(window, 'resize'); - } -- }; - - /** - * Render a list of fields starting at the specified location. ---- 542,557 ---- - var inputRows = this.renderCompute_(cursorX); - this.renderDraw_(cursorX, inputRows); - - var parentBlock = this.block_.getParent(); -+ if (!parentBlock) { - // Top-most block. Fire an event to allow scrollbars to resize. - Blockly.fireUiEvent(window, 'resize'); - } -+ var stop = new Date().getTime(); -+ var timeDiff = stop-start; -+ Blockly.Instrument.stats.renderHereCalls++; -+ Blockly.Instrument.stats.renderHereTime += timeDiff; -+ } - - /** - * Render a list of fields starting at the specified location. -*************** -*** 578,583 **** - row = []; - if (isInline && input.type != Blockly.NEXT_STATEMENT) { - row.type = Blockly.BlockSvg.INLINE; - } else { - row.type = input.type; - } ---- 621,629 ---- - row = []; - if (isInline && input.type != Blockly.NEXT_STATEMENT) { - row.type = Blockly.BlockSvg.INLINE; -+ } else if (input.subtype) { -+ row.type = input.type; -+ row.subtype = input.subtype; - } else { - row.type = input.type; - } -*************** -*** 924,932 **** - } - } - this.renderFields_(input.fieldRow, fieldX, fieldY); -- steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); -- var v = row.height - Blockly.BlockSvg.TAB_HEIGHT -- steps.push('v', v); - if (Blockly.RTL) { - // Highlight around back of tab. - highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); ---- 970,1006 ---- - } - } - this.renderFields_(input.fieldRow, fieldX, fieldY); -+ if (row.subtype == Blockly.INDENTED_VALUE){ -+ cursorX = inputRows.statementEdge; -+ steps.push('H', cursorX+input.fieldWidth+8); -+ steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); -+ steps.push('V',cursorY+row.height); -+ steps.push('H', inputRows.rightEdge); -+ steps.push('v 8'); -+ if (Blockly.RTL) { -+ highlightSteps.push('M', -+ (cursorX - Blockly.BlockSvg.NOTCH_WIDTH + -+ Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + -+ ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE)); -+ highlightSteps.push( -+ Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL); -+ highlightSteps.push('v', -+ row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS); -+ highlightSteps.push( -+ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL); -+ highlightSteps.push('H', inputRows.rightEdge - 1); -+ } else { -+ highlightSteps.push('M', -+ (cursorX + 9 + input.fieldWidth) + ',' + -+ (cursorY + row.height)); -+ highlightSteps.push('H', inputRows.rightEdge); -+ } -+ cursorY+=8; -+ } else { -+ steps.push(Blockly.BlockSvg.TAB_PATH_DOWN); -+ var v = row.height - Blockly.BlockSvg.TAB_HEIGHT -+ steps.push('v', v); -+ } - if (Blockly.RTL) { - // Highlight around back of tab. - highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL); -*************** -*** 939,947 **** - ',-1.8'); - } - // Create external input connection. -- connectionX = connectionsXY.x + -- (Blockly.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1); -- connectionY = connectionsXY.y + cursorY; - input.connection.moveTo(connectionX, connectionY); - if (input.connection.targetConnection) { - input.connection.tighten_(); ---- 1013,1027 ---- - ',-1.8'); - } - // Create external input connection. -+ if (row.subtype == Blockly.INDENTED_VALUE){ -+ connectionX = connectionsXY.x + -+ (Blockly.RTL ? -inputRows.statementEdge - 1: inputRows.statementEdge + 9 + input.fieldWidth); -+ connectionY = connectionsXY.y + cursorY-8; -+ } else { -+ connectionX = connectionsXY.x + -+ (Blockly.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1); -+ connectionY = connectionsXY.y + cursorY; -+ } - input.connection.moveTo(connectionX, connectionY); - if (input.connection.targetConnection) { - input.connection.tighten_(); diff --git a/core/blockly.js b/core/blockly.js index 2d0715b68..1f2f06e5e 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -269,12 +269,26 @@ Blockly.onKeyDown_ = function(e) { Blockly.hideChaff(); } else if (e.keyCode == 8 || e.keyCode == 46) { // Delete or backspace. - // Stop the browser from going back to the previous page. - // Do this first to prevent an error in the delete code from resulting in - // data loss. - e.preventDefault(); - if (Blockly.selected && Blockly.selected.isDeletable()) { - deleteBlock = true; + try { + // Stop the browser from going back to the previous page. + // Do this first to prevent an error in the delete code from resulting in + // data loss. + e.preventDefault(); + if (Blockly.selected && Blockly.selected.isDeletable()) { + var descendantCount = Blockly.selected.getDescendants().length; + if (Blockly.selected.nextConnection && Blockly.selected.nextConnection.targetConnection) { + descendantCount -= Blockly.selected.nextConnection.targetBlock(). + getDescendants().length; + } + // Ask for confirmation before deleting 3 or more blocks + if (descendantCount >= 3) { + if (confirm("Are you sure you want to delete all " + descendantCount + " of these blocks?")) { + deleteBlock = true; + } + } else { + deleteBlock = true; + } + } } } else if (e.altKey || e.ctrlKey || e.metaKey) { if (Blockly.selected && diff --git a/core/blockly.js.rej b/core/blockly.js.rej index 351f98928..7644eedfe 100644 --- a/core/blockly.js.rej +++ b/core/blockly.js.rej @@ -1,228 +1,5 @@ -*************** -*** 24,32 **** - */ - 'use strict'; - - // Top level object for Blockly. - goog.provide('Blockly'); - - // Blockly core dependencies. - goog.require('Blockly.Block'); - goog.require('Blockly.Connection'); ---- 24,38 ---- - */ - 'use strict'; - -+ /** -+ * [lyn, 10/10/13] Modified Blockly.hideChaff() method to hide single instance of Blockly.FieldFlydown. -+ */ -+ - // Top level object for Blockly. - goog.provide('Blockly'); - -+ goog.require('Blockly.Instrument'); // lyn's instrumentation code -+ - // Blockly core dependencies. - goog.require('Blockly.Block'); - goog.require('Blockly.Connection'); -*************** -*** 41,47 **** - goog.require('Blockly.Msg'); - goog.require('Blockly.Procedures'); - goog.require('Blockly.Realtime'); -- goog.require('Blockly.Toolbox'); - goog.require('Blockly.WidgetDiv'); - goog.require('Blockly.Workspace'); - goog.require('Blockly.inject'); ---- 47,54 ---- - goog.require('Blockly.Msg'); - goog.require('Blockly.Procedures'); - goog.require('Blockly.Realtime'); -+ //goog.require('Blockly.Toolbox'); -+ goog.require('Blockly.TypeBlock'); - goog.require('Blockly.WidgetDiv'); - goog.require('Blockly.Workspace'); - goog.require('Blockly.inject'); -*************** -*** 99,107 **** - * @param {number} hue Hue on a colour wheel (0-360). - * @return {string} RGB code, e.g. '#5ba65b'. - */ -- Blockly.makeColour = function(hue) { -- return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION, - Blockly.HSV_VALUE * 256); - }; - - /** ---- 106,118 ---- - * @param {number} hue Hue on a colour wheel (0-360). - * @return {string} RGB code, e.g. '#5ba65b'. - */ -+ Blockly.makeColour = function(hueOrRGBArray) { -+ if(Array.isArray(hueOrRGBArray)){ -+ return goog.color.rgbArrayToHex(hueOrRGBArray); -+ } else { -+ return goog.color.hsvToHex(hueOrRGBArray, Blockly.HSV_SATURATION, - Blockly.HSV_VALUE * 256); -+ } - }; - - /** -*************** -*** 131,136 **** - Blockly.DUMMY_INPUT = 5; - - /** - * ENUM for left alignment. - * @const - */ ---- 142,154 ---- - Blockly.DUMMY_INPUT = 5; - - /** -+ * ENUM for an indented value input. Similar to next_statement but with value -+ * input shape. -+ * @const -+ */ -+ Blockly.INDENTED_VALUE = 6; -+ -+ /** - * ENUM for left alignment. - * @const - */ -*************** -*** 163,176 **** - }; - - /** - * Handle a mouse-down on SVG drawing surface. - * @param {!Event} e Mouse down event. - * @private - */ - Blockly.onMouseDown_ = function(e) { - Blockly.svgResize(); - Blockly.terminateDrag_(); // In case mouse-up event was lost. - Blockly.hideChaff(); - var isTargetSvg = e.target && e.target.nodeName && - e.target.nodeName.toLowerCase() == 'svg'; - if (!Blockly.readOnly && Blockly.selected && isTargetSvg) { ---- 196,237 ---- - }; - - /** -+ * latest clicked position is used to open the type blocking suggestions window -+ * Initial position is 0,0 -+ * @type {{x: number, y:number}} -+ */ -+ Blockly.latestClick = { x: 0, y: 0 }; -+ -+ /** - * Handle a mouse-down on SVG drawing surface. - * @param {!Event} e Mouse down event. - * @private - */ - Blockly.onMouseDown_ = function(e) { -+ Blockly.latestClick = { x: e.clientX, y: e.clientY }; // Might be needed? - Blockly.svgResize(); - Blockly.terminateDrag_(); // In case mouse-up event was lost. - Blockly.hideChaff(); -+ //if drawer exists and supposed to close -+ if(Blockly.Drawer && Blockly.Drawer.flyout_.autoClose) { -+ Blockly.Drawer.hide(); -+ } -+ -+ //Closes mutators -+ var blocks = Blockly.mainWorkspace.getAllBlocks(); -+ var numBlocks = blocks.length; -+ var temp_block = null; -+ for(var i =0; i= 3) { -+ if (confirm("Are you sure you want to delete all " + descendantCount + " of these blocks?")) { -+ Blockly.hideChaff(); -+ Blockly.selected.dispose(true, true); -+ } -+ } -+ else { -+ Blockly.hideChaff(); -+ Blockly.selected.dispose(true, true); -+ } - } - } finally { - // Stop the browser from going back to the previous page. -*************** -*** 363,368 **** - ms += COLLAPSE_DELAY; - } - } - }; - options.push(collapseOption); - ---- 438,444 ---- - ms += COLLAPSE_DELAY; - } - } -+ Blockly.resetWorkspaceArrangements(); - }; - options.push(collapseOption); - -*************** -*** 370,390 **** - var expandOption = {enabled: hasCollapsedBlocks}; - expandOption.text = Blockly.Msg.EXPAND_ALL; - expandOption.callback = function() { -- var ms = 0; -- for (var i = 0; i < topBlocks.length; i++) { -- var block = topBlocks[i]; -- while (block) { -- setTimeout(block.setCollapsed.bind(block, false), ms); -- block = block.getNextBlock(); -- ms += COLLAPSE_DELAY; -- } -- } - }; - options.push(expandOption); - } - - Blockly.ContextMenu.show(e, options); - }; - - /** - * Cancel the native context menu, unless the focus is on an HTML input widget. ---- 446,621 ---- - var expandOption = {enabled: hasCollapsedBlocks}; +*** 943,968 **** + var expandOption = {enabled: hasCollapsedBlocks}; expandOption.text = Blockly.Msg.EXPAND_ALL; expandOption.callback = function() { + Blockly.Instrument.initializeStats("expandAllCollapsedBlocks"); @@ -247,215 +24,3 @@ }; options.push(expandOption); } - -+ // Arrange blocks in row order. -+ var arrangeOptionH = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_HORIZONTAL)}; -+ arrangeOptionH.text = Blockly.Msg.ARRANGE_H; -+ arrangeOptionH.callback = function() { -+ Blockly.workspace_arranged_position = Blockly.BLKS_HORIZONTAL; -+ Blockly.workspace_arranged_latest_position= Blockly.BLKS_HORIZONTAL; -+ arrangeBlocks(Blockly.BLKS_HORIZONTAL); -+ }; -+ options.push(arrangeOptionH); -+ -+ // Arrange blocks in column order. -+ var arrangeOptionV = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_VERTICAL)}; -+ arrangeOptionV.text = Blockly.Msg.ARRANGE_V; -+ arrangeOptionV.callback = function() { -+ Blockly.workspace_arranged_position = Blockly.BLKS_VERTICAL; -+ Blockly.workspace_arranged_latest_position = Blockly.BLKS_VERTICAL; -+ arrangeBlocks(Blockly.BLKS_VERTICAL); -+ }; -+ options.push(arrangeOptionV); -+ -+ /** -+ * Function that returns a name to be used to sort blocks. -+ * The general comparator is the block.category attribute. -+ * In the case of 'Components' the comparator is the instanceName of the component if it exists -+ * (it does not exist for generic components). -+ * In the case of Procedures the comparator is the NAME(for definitions) or PROCNAME (for calls) -+ * @param {!Blockly.Block} the block that will be compared in the sortByCategory function -+ * @returns {string} text to be used in the comparison -+ */ -+ function comparisonName(block){ -+ if (block.category === 'Component' && block.instanceName) -+ return block.instanceName; -+ if (block.category === 'Procedures') -+ return (block.getFieldValue('NAME') || block.getFieldValue('PROCNAME')); -+ return block.category; -+ } -+ -+ /** -+ * Function used to sort blocks by Category. -+ * @param {!Blockly.Block} a first block to be compared -+ * @param {!Blockly.Block} b second block to be compared -+ * @returns {number} returns 0 if the blocks are equal, and -1 or 1 if they are not -+ */ -+ function sortByCategory(a,b) { -+ var comparatorA = comparisonName(a).toLowerCase(); -+ var comparatorB = comparisonName(b).toLowerCase(); -+ -+ if (comparatorA < comparatorB) return -1; -+ else if (comparatorA > comparatorB) return +1; -+ else return 0; -+ } -+ -+ // Arranges block in layout (Horizontal or Vertical). -+ function arrangeBlocks(layout) { -+ var SPACER = 25; -+ var topblocks = Blockly.mainWorkspace.getTopBlocks(false); -+ // If the blocks are arranged by Category, sort the array -+ if (Blockly.workspace_arranged_type === Blockly.BLKS_CATEGORY){ -+ topblocks.sort(sortByCategory); -+ } -+ var metrics = Blockly.mainWorkspace.getMetrics(); -+ var viewLeft = metrics.viewLeft + 5; -+ var viewTop = metrics.viewTop + 5; -+ var x = viewLeft; -+ var y = viewTop; -+ var wsRight = viewLeft + metrics.viewWidth; -+ var wsBottom = viewTop + metrics.viewHeight; -+ var maxHgt = 0; -+ var maxWidth = 0; -+ for (var i = 0, len = topblocks.length; i < len; i++) { -+ var blk = topblocks[i]; -+ var blkXY = blk.getRelativeToSurfaceXY(); -+ var blockHW = blk.getHeightWidth(); -+ var blkHgt = blockHW.height; -+ var blkWidth = blockHW.width; -+ switch (layout) { -+ case Blockly.BLKS_HORIZONTAL: -+ if (x < wsRight) { -+ blk.moveBy(x - blkXY.x, y - blkXY.y); -+ blk.select(); -+ x += blkWidth + SPACER; -+ if (blkHgt > maxHgt) // Remember highest block -+ maxHgt = blkHgt; -+ } else { -+ y += maxHgt + SPACER; -+ maxHgt = blkHgt; -+ x = viewLeft; -+ blk.moveBy(x - blkXY.x, y - blkXY.y); -+ blk.select(); -+ x += blkWidth + SPACER; -+ } -+ break; -+ case Blockly.BLKS_VERTICAL: -+ if (y < wsBottom) { -+ blk.moveBy(x - blkXY.x, y - blkXY.y); -+ blk.select(); -+ y += blkHgt + SPACER; -+ if (blkWidth > maxWidth) // Remember widest block -+ maxWidth = blkWidth; -+ } else { -+ x += maxWidth + SPACER; -+ maxWidth = blkWidth; -+ y = viewTop; -+ blk.moveBy(x - blkXY.x, y - blkXY.y); -+ blk.select(); -+ y += blkHgt + SPACER; -+ } -+ break; -+ } -+ } -+ } -+ -+ // Sort by Category. -+ var sortOptionCat = {enabled: (Blockly.workspace_arranged_type !== Blockly.BLKS_CATEGORY)}; -+ sortOptionCat.text = Blockly.Msg.SORT_C; -+ sortOptionCat.callback = function() { -+ Blockly.workspace_arranged_type = Blockly.BLKS_CATEGORY; -+ rearrangeWorkspace(); -+ }; -+ options.push(sortOptionCat); -+ -+ // Called after a sort or collapse/expand to redisplay blocks. -+ function rearrangeWorkspace() { -+ //default arrangement position set to Horizontal if it hasn't been set yet (is null) -+ if (Blockly.workspace_arranged_latest_position === null || Blockly.workspace_arranged_latest_position === Blockly.BLKS_HORIZONTAL) -+ arrangeOptionH.callback(); -+ else if (Blockly.workspace_arranged_latest_position === Blockly.BLKS_VERTICAL) -+ arrangeOptionV.callback(); -+ } -+ -+ // Option to get help. -+ var helpOption = {enabled: false}; -+ helpOption.text = Blockly.Msg.HELP; -+ helpOption.callback = function() {}; -+ options.push(helpOption); -+ - Blockly.ContextMenu.show(e, options); - }; -+ /** -+ * reset arrangement state; to be called when blocks in the workspace change -+ */ -+ Blockly.resetWorkspaceArrangements = function(){ -+ // reset the variables used for menus, but keep the latest position, so the current horizontal or -+ // vertical state can be kept -+ Blockly.workspace_arranged_type = null; -+ Blockly.workspace_arranged_position = null; -+ }; - - /** - * Cancel the native context menu, unless the focus is on an HTML input widget. -*************** -*** 404,414 **** - */ - Blockly.hideChaff = function(opt_allowToolbox) { - Blockly.Tooltip.hide(); - Blockly.WidgetDiv.hide(); -- if (!opt_allowToolbox && -- Blockly.Toolbox.flyout_ && Blockly.Toolbox.flyout_.autoClose) { -- Blockly.Toolbox.clearSelection(); -- } - }; - - /** ---- 635,643 ---- - */ - Blockly.hideChaff = function(opt_allowToolbox) { - Blockly.Tooltip.hide(); -+ Blockly.FieldFlydown && Blockly.FieldFlydown.hide(); // [lyn, 10/06/13] for handling parameter & procedure flydowns - Blockly.WidgetDiv.hide(); -+ Blockly.TypeBlock && Blockly.TypeBlock.hide(); - }; - - /** -*************** -*** 556,562 **** - */ - Blockly.getMainWorkspaceMetrics_ = function() { - var svgSize = Blockly.svgSize(); -- svgSize.width -= Blockly.Toolbox.width; // Zero if no Toolbox. - var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness; - var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness; - try { ---- 785,793 ---- - */ - Blockly.getMainWorkspaceMetrics_ = function() { - var svgSize = Blockly.svgSize(); -+ //We don't use Blockly.Toolbox in our version of Blockly instead we use drawer.js -+ //svgSize.width -= Blockly.Toolbox.width; // Zero if no Toolbox. -+ svgSize.width -= 0; // Zero if no Toolbox. - var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness; - var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness; - try { -*************** -*** 582,588 **** - var topEdge = blockBox.y; - var bottomEdge = topEdge + blockBox.height; - } -- var absoluteLeft = Blockly.RTL ? 0 : Blockly.Toolbox.width; - var metrics = { - viewHeight: svgSize.height, - viewWidth: svgSize.width, ---- 813,821 ---- - var topEdge = blockBox.y; - var bottomEdge = topEdge + blockBox.height; - } -+ //We don't use Blockly.Toolbox in our version of Blockly instead we use drawer.js -+ //var absoluteLeft = Blockly.RTL ? 0 : Blockly.Toolbox.width; -+ var absoluteLeft = Blockly.RTL ? 0 : 0; - var metrics = { - viewHeight: svgSize.height, - viewWidth: svgSize.width, diff --git a/core/blocks.js.rej b/core/blocks.js.rej deleted file mode 100644 index cc620ee74..000000000 --- a/core/blocks.js.rej +++ /dev/null @@ -1,19 +0,0 @@ -*************** -*** 168,175 **** - * @this Blockly.Block - */ - block.mutationToDom = function() { -- var container = details.mutationToDomFunc ? -- details.mutatationToDomFunc() : document.createElement('mutation'); - container.setAttribute('is_statement', this['isStatement'] || false); - return container; - }; ---- 168,175 ---- - * @this Blockly.Block - */ - block.mutationToDom = function() { -+ var container = details.mutationToDomFunc ? details.mutatationToDomFunc() -+ : document.createElement('mutation'); - container.setAttribute('is_statement', this['isStatement'] || false); - return container; - }; diff --git a/core/workspace.js b/core/workspace.js index f54b8b8ac..94c4daf4a 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -141,7 +141,7 @@ Blockly.Workspace.prototype.addTopBlock = function(block) { */ Blockly.Workspace.prototype.removeTopBlock = function(block) { if (block.workspace == Blockly.mainWorkspace) //Do not reset arrangements for the flyout - Blockly.resetWorkspaceArrangements(); + this.resetArrangements(); var found = false; for (var child, i = 0; child = this.topBlocks_[i]; i++) { if (child == block) { diff --git a/core/workspace_svg.js b/core/workspace_svg.js index a3c0b5344..0deb2cf0f 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -925,6 +925,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { ms += DELAY; } } + this.resetArrangements(); }; // Option to collapse top blocks. @@ -989,6 +990,142 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) { }; menuOptions.push(deleteOption); + // Arrange blocks in row order. + var arrangeOptionH = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_HORIZONTAL)}; + arrangeOptionH.text = Blockly.Msg.ARRANGE_H; + arrangeOptionH.callback = function() { + Blockly.workspace_arranged_position = Blockly.BLKS_HORIZONTAL; + Blockly.workspace_arranged_latest_position= Blockly.BLKS_HORIZONTAL; + arrangeBlocks(Blockly.BLKS_HORIZONTAL); + }; + options.push(arrangeOptionH); + + // Arrange blocks in column order. + var arrangeOptionV = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_VERTICAL)}; + arrangeOptionV.text = Blockly.Msg.ARRANGE_V; + arrangeOptionV.callback = function() { + Blockly.workspace_arranged_position = Blockly.BLKS_VERTICAL; + Blockly.workspace_arranged_latest_position = Blockly.BLKS_VERTICAL; + arrangeBlocks(Blockly.BLKS_VERTICAL); + }; + options.push(arrangeOptionV); + + /** + * Function that returns a name to be used to sort blocks. + * The general comparator is the block.category attribute. + * In the case of 'Components' the comparator is the instanceName of the component if it exists + * (it does not exist for generic components). + * In the case of Procedures the comparator is the NAME(for definitions) or PROCNAME (for calls) + * @param {!Blockly.Block} the block that will be compared in the sortByCategory function + * @returns {string} text to be used in the comparison + */ + function comparisonName(block){ + if (block.category === 'Component' && block.instanceName) + return block.instanceName; + if (block.category === 'Procedures') + return (block.getFieldValue('NAME') || block.getFieldValue('PROCNAME')); + return block.category; + } + + /** + * Function used to sort blocks by Category. + * @param {!Blockly.Block} a first block to be compared + * @param {!Blockly.Block} b second block to be compared + * @returns {number} returns 0 if the blocks are equal, and -1 or 1 if they are not + */ + function sortByCategory(a,b) { + var comparatorA = comparisonName(a).toLowerCase(); + var comparatorB = comparisonName(b).toLowerCase(); + + if (comparatorA < comparatorB) return -1; + else if (comparatorA > comparatorB) return +1; + else return 0; + } + + // Arranges block in layout (Horizontal or Vertical). + function arrangeBlocks(layout) { + var SPACER = 25; + var topblocks = Blockly.mainWorkspace.getTopBlocks(false); + // If the blocks are arranged by Category, sort the array + if (Blockly.workspace_arranged_type === Blockly.BLKS_CATEGORY){ + topblocks.sort(sortByCategory); + } + var metrics = Blockly.mainWorkspace.getMetrics(); + var viewLeft = metrics.viewLeft + 5; + var viewTop = metrics.viewTop + 5; + var x = viewLeft; + var y = viewTop; + var wsRight = viewLeft + metrics.viewWidth; + var wsBottom = viewTop + metrics.viewHeight; + var maxHgt = 0; + var maxWidth = 0; + for (var i = 0, len = topblocks.length; i < len; i++) { + var blk = topblocks[i]; + var blkXY = blk.getRelativeToSurfaceXY(); + var blockHW = blk.getHeightWidth(); + var blkHgt = blockHW.height; + var blkWidth = blockHW.width; + switch (layout) { + case Blockly.BLKS_HORIZONTAL: + if (x < wsRight) { + blk.moveBy(x - blkXY.x, y - blkXY.y); + blk.select(); + x += blkWidth + SPACER; + if (blkHgt > maxHgt) // Remember highest block + maxHgt = blkHgt; + } else { + y += maxHgt + SPACER; + maxHgt = blkHgt; + x = viewLeft; + blk.moveBy(x - blkXY.x, y - blkXY.y); + blk.select(); + x += blkWidth + SPACER; + } + break; + case Blockly.BLKS_VERTICAL: + if (y < wsBottom) { + blk.moveBy(x - blkXY.x, y - blkXY.y); + blk.select(); + y += blkHgt + SPACER; + if (blkWidth > maxWidth) // Remember widest block + maxWidth = blkWidth; + } else { + x += maxWidth + SPACER; + maxWidth = blkWidth; + y = viewTop; + blk.moveBy(x - blkXY.x, y - blkXY.y); + blk.select(); + y += blkHgt + SPACER; + } + break; + } + } + } + + // Sort by Category. + var sortOptionCat = {enabled: (Blockly.workspace_arranged_type !== Blockly.BLKS_CATEGORY)}; + sortOptionCat.text = Blockly.Msg.SORT_C; + sortOptionCat.callback = function() { + Blockly.workspace_arranged_type = Blockly.BLKS_CATEGORY; + rearrangeWorkspace(); + }; + options.push(sortOptionCat); + + // Called after a sort or collapse/expand to redisplay blocks. + function rearrangeWorkspace() { + //default arrangement position set to Horizontal if it hasn't been set yet (is null) + if (Blockly.workspace_arranged_latest_position === null || Blockly.workspace_arranged_latest_position === Blockly.BLKS_HORIZONTAL) + arrangeOptionH.callback(); + else if (Blockly.workspace_arranged_latest_position === Blockly.BLKS_VERTICAL) + arrangeOptionV.callback(); + } + + // Option to get help. + var helpOption = {enabled: false}; + helpOption.text = Blockly.Msg.HELP; + helpOption.callback = function() {}; + options.push(helpOption); + Blockly.ContextMenu.show(e, menuOptions, this.RTL); };