diff --git a/core/blockly.js.orig b/core/blockly.js.orig deleted file mode 100644 index fb6562ed2..000000000 --- a/core/blockly.js.orig +++ /dev/null @@ -1,453 +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 Core JavaScript library for Blockly. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -// Top level object for Blockly. -goog.provide('Blockly'); - -goog.require('Blockly.BlockSvg.render'); -goog.require('Blockly.Events'); -goog.require('Blockly.FieldAngle'); -goog.require('Blockly.FieldCheckbox'); -goog.require('Blockly.FieldColour'); -// Date picker commented out since it increases footprint by 60%. -// Add it only if you need it. -//goog.require('Blockly.FieldDate'); -goog.require('Blockly.FieldDropdown'); -goog.require('Blockly.FieldImage'); -goog.require('Blockly.FieldTextInput'); -goog.require('Blockly.FieldNumber'); -goog.require('Blockly.FieldVariable'); -goog.require('Blockly.Generator'); -goog.require('Blockly.Msg'); -goog.require('Blockly.Procedures'); -goog.require('Blockly.Toolbox'); -goog.require('Blockly.WidgetDiv'); -goog.require('Blockly.WorkspaceSvg'); -goog.require('Blockly.constants'); -goog.require('Blockly.inject'); -goog.require('Blockly.utils'); -goog.require('goog.color'); -goog.require('goog.userAgent'); - - -// Turn off debugging when compiled. -var CLOSURE_DEFINES = {'goog.DEBUG': false}; - -/** - * The main workspace most recently used. - * Set by Blockly.WorkspaceSvg.prototype.markFocused - * @type {Blockly.Workspace} - */ -Blockly.mainWorkspace = null; - -/** - * Currently selected block. - * @type {Blockly.Block} - */ -Blockly.selected = null; - -/** - * Currently highlighted connection (during a drag). - * @type {Blockly.Connection} - * @private - */ -Blockly.highlightedConnection_ = null; - -/** - * Connection on dragged block that matches the highlighted connection. - * @type {Blockly.Connection} - * @private - */ -Blockly.localConnection_ = null; - -/** - * All of the connections on blocks that are currently being dragged. - * @type {!Array.} - * @private - */ -Blockly.draggingConnections_ = []; - -/** - * Contents of the local clipboard. - * @type {Element} - * @private - */ -Blockly.clipboardXml_ = null; - -/** - * Source of the local clipboard. - * @type {Blockly.WorkspaceSvg} - * @private - */ -Blockly.clipboardSource_ = null; - -/** - * Is the mouse dragging a block? - * DRAG_NONE - No drag operation. - * DRAG_STICKY - Still inside the sticky DRAG_RADIUS. - * DRAG_FREE - Freely draggable. - * @private - */ -Blockly.dragMode_ = Blockly.DRAG_NONE; - -/** - * Wrapper function called when a touch mouseUp occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.onTouchUpWrapper_ = null; - -/** - * Convert a hue (HSV model) into an RGB hex triplet. - * @param {number} hue Hue on a colour wheel (0-360). - * @return {string} RGB code, e.g. '#5ba65b'. - */ -Blockly.hueToRgb = function(hue) { - return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION, - Blockly.HSV_VALUE * 255); -}; - -/** - * Returns the dimensions of the specified SVG image. - * @param {!Element} svg SVG image. - * @return {!Object} Contains width and height properties. - */ -Blockly.svgSize = function(svg) { - return {width: svg.cachedWidth_, - height: svg.cachedHeight_}; -}; - -/** - * Size the workspace when the contents change. This also updates - * scrollbars accordingly. - * @param {!Blockly.WorkspaceSvg} workspace The workspace to resize. - */ -Blockly.resizeSvgContents = function(workspace) { - workspace.resizeContents(); -}; - -/** - * Size the SVG image to completely fill its container. Call this when the view - * actually changes sizes (e.g. on a window resize/device orientation change). - * See Blockly.resizeSvgContents to resize the workspace when the contents - * change (e.g. when a block is added or removed). - * Record the height/width of the SVG image. - * @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG. - */ -Blockly.svgResize = function(workspace) { - var mainWorkspace = workspace; - while (mainWorkspace.options.parentWorkspace) { - mainWorkspace = mainWorkspace.options.parentWorkspace; - } - var svg = mainWorkspace.getParentSvg(); - var div = svg.parentNode; - if (!div) { - // Workspace deleted, or something. - return; - } - var width = div.offsetWidth; - var height = div.offsetHeight; - if (svg.cachedWidth_ != width) { - svg.setAttribute('width', width + 'px'); - svg.cachedWidth_ = width; - } - if (svg.cachedHeight_ != height) { - svg.setAttribute('height', height + 'px'); - svg.cachedHeight_ = height; - } - mainWorkspace.resize(); -}; - -/** - * Handle a mouse-up anywhere on the page. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.onMouseUp_ = function(e) { - var workspace = Blockly.getMainWorkspace(); - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); - workspace.dragMode_ = Blockly.DRAG_NONE; - // Unbind the touch event if it exists. - if (Blockly.onTouchUpWrapper_) { - Blockly.unbindEvent_(Blockly.onTouchUpWrapper_); - Blockly.onTouchUpWrapper_ = null; - } - if (Blockly.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_); - Blockly.onMouseMoveWrapper_ = null; - } -}; - -/** - * Handle a mouse-move on SVG drawing surface. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.onMouseMove_ = function(e) { - if (e.touches && e.touches.length >= 2) { - return; // Multi-touch gestures won't have e.clientX. - } - var workspace = Blockly.getMainWorkspace(); - if (workspace.dragMode_ != Blockly.DRAG_NONE) { - var dx = e.clientX - workspace.startDragMouseX; - var dy = e.clientY - workspace.startDragMouseY; - var metrics = workspace.startDragMetrics; - var x = workspace.startScrollX + dx; - var y = workspace.startScrollY + dy; - x = Math.min(x, -metrics.contentLeft); - y = Math.min(y, -metrics.contentTop); - x = Math.max(x, metrics.viewWidth - metrics.contentLeft - - metrics.contentWidth); - y = Math.max(y, metrics.viewHeight - metrics.contentTop - - metrics.contentHeight); - - // Move the scrollbars and the page will scroll automatically. - workspace.scrollbar.set(-x - metrics.contentLeft, - -y - metrics.contentTop); - // Cancel the long-press if the drag has moved too far. - if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { - Blockly.longStop_(); - workspace.dragMode_ = Blockly.DRAG_FREE; - } - e.stopPropagation(); - e.preventDefault(); - } -}; - -/** - * Handle a key-down on SVG drawing surface. - * @param {!Event} e Key down event. - * @private - */ -Blockly.onKeyDown_ = function(e) { - if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) { - // No key actions on readonly workspaces. - // When focused on an HTML text input widget, don't trap any keys. - return; - } - var deleteBlock = false; - if (e.keyCode == 27) { - // Pressing esc closes the context menu. - Blockly.hideChaff(); - } else if (e.keyCode == 8 || e.keyCode == 46) { - // Delete or backspace. - // Stop the browser from going back to the previous page. - // Do this first to prevent an error in the delete code from resulting in - // data loss. - e.preventDefault(); - if (Blockly.selected && Blockly.selected.isDeletable()) { - deleteBlock = true; - } - } else if (e.altKey || e.ctrlKey || e.metaKey) { - if (Blockly.selected && - Blockly.selected.isDeletable() && Blockly.selected.isMovable()) { - if (e.keyCode == 67) { - // 'c' for copy. - Blockly.hideChaff(); - Blockly.copy_(Blockly.selected); - } else if (e.keyCode == 88) { - // 'x' for cut. - Blockly.copy_(Blockly.selected); - deleteBlock = true; - } - } - if (e.keyCode == 86) { - // 'v' for paste. - if (Blockly.clipboardXml_) { - Blockly.Events.setGroup(true); - Blockly.clipboardSource_.paste(Blockly.clipboardXml_); - Blockly.Events.setGroup(false); - } - } else if (e.keyCode == 90) { - // 'z' for undo 'Z' is for redo. - Blockly.hideChaff(); - Blockly.mainWorkspace.undo(e.shiftKey); - } - } - if (deleteBlock) { - // Common code for delete and cut. - Blockly.Events.setGroup(true); - Blockly.hideChaff(); - var heal = Blockly.dragMode_ != Blockly.DRAG_FREE; - Blockly.selected.dispose(heal, true); - if (Blockly.highlightedConnection_) { - Blockly.highlightedConnection_.unhighlight(); - Blockly.highlightedConnection_ = null; - } - Blockly.Events.setGroup(false); - } -}; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.terminateDrag_ = function() { - Blockly.BlockSvg.terminateDrag(); - Blockly.Flyout.terminateDrag_(); -}; - -/** - * PID of queued long-press task. - * @private - */ -Blockly.longPid_ = 0; - -/** - * Context menus on touch devices are activated using a long-press. - * Unfortunately the contextmenu touch event is currently (2015) only suported - * by Chrome. This function is fired on any touchstart event, queues a task, - * which after about a second opens the context menu. The tasks is killed - * if the touch event terminates early. - * @param {!Event} e Touch start event. - * @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace - * under the touchstart event. - * @private - */ -Blockly.longStart_ = function(e, uiObject) { - Blockly.longStop_(); - Blockly.longPid_ = setTimeout(function() { - e.button = 2; // Simulate a right button click. - uiObject.onMouseDown_(e); - }, Blockly.LONGPRESS); -}; - -/** - * Nope, that's not a long-press. Either touchend or touchcancel was fired, - * or a drag hath begun. Kill the queued long-press task. - * @private - */ -Blockly.longStop_ = function() { - if (Blockly.longPid_) { - clearTimeout(Blockly.longPid_); - Blockly.longPid_ = 0; - } -}; - -/** - * Copy a block onto the local clipboard. - * @param {!Blockly.Block} block Block to be copied. - * @private - */ -Blockly.copy_ = function(block) { - var xmlBlock = Blockly.Xml.blockToDom(block); - if (Blockly.dragMode_ != Blockly.DRAG_FREE) { - Blockly.Xml.deleteNext(xmlBlock); - } - // Encode start position in XML. - var xy = block.getRelativeToSurfaceXY(); - xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x); - xmlBlock.setAttribute('y', xy.y); - Blockly.clipboardXml_ = xmlBlock; - Blockly.clipboardSource_ = block.workspace; -}; - -/** - * Duplicate this block and its children. - * @param {!Blockly.Block} block Block to be copied. - * @private - */ -Blockly.duplicate_ = function(block) { - // Save the clipboard. - var clipboardXml = Blockly.clipboardXml_; - var clipboardSource = Blockly.clipboardSource_; - - // Create a duplicate via a copy/paste operation. - Blockly.copy_(block); - block.workspace.paste(Blockly.clipboardXml_); - - // Restore the clipboard. - Blockly.clipboardXml_ = clipboardXml; - Blockly.clipboardSource_ = clipboardSource; -}; - -/** - * Cancel the native context menu, unless the focus is on an HTML input widget. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.onContextMenu_ = function(e) { - if (!Blockly.isTargetInput_(e)) { - // When focused on an HTML text input widget, don't cancel the context menu. - e.preventDefault(); - } -}; - -/** - * Close tooltips, context menus, dropdown selections, etc. - * @param {boolean=} opt_allowToolbox If true, don't close the toolbox. - */ -Blockly.hideChaff = function(opt_allowToolbox) { - Blockly.Tooltip.hide(); - Blockly.WidgetDiv.hide(); - if (!opt_allowToolbox) { - var workspace = Blockly.getMainWorkspace(); - if (workspace.toolbox_ && - workspace.toolbox_.flyout_ && - workspace.toolbox_.flyout_.autoClose) { - workspace.toolbox_.clearSelection(); - } - } -}; - -/** - * When something in Blockly's workspace changes, call a function. - * @param {!Function} func Function to call. - * @return {!Array.} Opaque data that can be passed to - * removeChangeListener. - * @deprecated April 2015 - */ -Blockly.addChangeListener = function(func) { - // Backwards compatability from before there could be multiple workspaces. - console.warn('Deprecated call to Blockly.addChangeListener, ' + - 'use workspace.addChangeListener instead.'); - return Blockly.getMainWorkspace().addChangeListener(func); -}; - -/** - * Returns the main workspace. Returns the last used main workspace (based on - * focus). Try not to use this function, particularly if there are multiple - * Blockly instances on a page. - * @return {!Blockly.Workspace} The main workspace. - */ -Blockly.getMainWorkspace = function() { - return Blockly.mainWorkspace; -}; - -// IE9 does not have a console. Create a stub to stop errors. -if (!goog.global['console']) { - goog.global['console'] = { - 'log': function() {}, - 'warn': function() {} - }; -} - -// Export symbols that would otherwise be renamed by Closure compiler. -if (!goog.global['Blockly']) { - goog.global['Blockly'] = {}; -} -goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace; -goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener; diff --git a/core/blocks.js.orig b/core/blocks.js.orig deleted file mode 100644 index d6932ceb9..000000000 --- a/core/blocks.js.orig +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2013 Google Inc. - * https://developers.google.com/blockly/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Empty name space for the Blocks singleton. - * @author spertus@google.com (Ellen Spertus) - */ -'use strict'; - -goog.provide('Blockly.Blocks'); - -/** - * Allow for switching between one and zero based indexing for lists and text, - * one based by default. - */ -Blockly.Blocks.ONE_BASED_INDEXING = true; diff --git a/core/bubble.js.orig b/core/bubble.js.orig deleted file mode 100644 index d4c1e2719..000000000 --- a/core/bubble.js.orig +++ /dev/null @@ -1,579 +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 Object representing a UI bubble. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Bubble'); - -goog.require('Blockly.Workspace'); -goog.require('goog.dom'); -goog.require('goog.math'); -goog.require('goog.math.Coordinate'); -goog.require('goog.userAgent'); - - -/** - * Class for UI bubble. - * @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the - * bubble. - * @param {!Element} content SVG content for the bubble. - * @param {Element} shape SVG element to avoid eclipsing. - * @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor - * point. - * @param {?number} bubbleWidth Width of bubble, or null if not resizable. - * @param {?number} bubbleHeight Height of bubble, or null if not resizable. - * @constructor - */ -Blockly.Bubble = function(workspace, content, shape, anchorXY, - bubbleWidth, bubbleHeight) { - this.workspace_ = workspace; - this.content_ = content; - this.shape_ = shape; - - var angle = Blockly.Bubble.ARROW_ANGLE; - if (this.workspace_.RTL) { - angle = -angle; - } - this.arrow_radians_ = goog.math.toRadians(angle); - - var canvas = workspace.getBubbleCanvas(); - canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight))); - - this.setAnchorLocation(anchorXY); - if (!bubbleWidth || !bubbleHeight) { - var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox(); - bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH; - bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH; - } - this.setBubbleSize(bubbleWidth, bubbleHeight); - - // Render the bubble. - this.positionBubble_(); - this.renderArrow_(); - this.rendered_ = true; - - if (!workspace.options.readOnly) { - Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this, - this.bubbleMouseDown_); - if (this.resizeGroup_) { - Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this, - this.resizeMouseDown_); - } - } -}; - -/** - * Width of the border around the bubble. - */ -Blockly.Bubble.BORDER_WIDTH = 6; - -/** - * Determines the thickness of the base of the arrow in relation to the size - * of the bubble. Higher numbers result in thinner arrows. - */ -Blockly.Bubble.ARROW_THICKNESS = 10; - -/** - * The number of degrees that the arrow bends counter-clockwise. - */ -Blockly.Bubble.ARROW_ANGLE = 20; - -/** - * The sharpness of the arrow's bend. Higher numbers result in smoother arrows. - */ -Blockly.Bubble.ARROW_BEND = 4; - -/** - * Distance between arrow point and anchor point. - */ -Blockly.Bubble.ANCHOR_RADIUS = 8; - -/** - * Wrapper function called when a mouseUp occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.Bubble.onMouseUpWrapper_ = null; - -/** - * Wrapper function called when a mouseMove occurs during a drag operation. - * @type {Array.} - * @private - */ -Blockly.Bubble.onMouseMoveWrapper_ = null; - -/** - * Function to call on resize of bubble. - * @type {Function} - */ -Blockly.Bubble.prototype.resizeCallback_ = null; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.Bubble.unbindDragEvents_ = function() { - if (Blockly.Bubble.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_); - Blockly.Bubble.onMouseUpWrapper_ = null; - } - if (Blockly.Bubble.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_); - Blockly.Bubble.onMouseMoveWrapper_ = null; - } -}; - -/** - * Flag to stop incremental rendering during construction. - * @private - */ -Blockly.Bubble.prototype.rendered_ = false; - -/** - * Absolute coordinate of anchor point. - * @type {goog.math.Coordinate} - * @private - */ -Blockly.Bubble.prototype.anchorXY_ = null; - -/** - * Relative X coordinate of bubble with respect to the anchor's centre. - * In RTL mode the initial value is negated. - * @private - */ -Blockly.Bubble.prototype.relativeLeft_ = 0; - -/** - * Relative Y coordinate of bubble with respect to the anchor's centre. - * @private - */ -Blockly.Bubble.prototype.relativeTop_ = 0; - -/** - * Width of bubble. - * @private - */ -Blockly.Bubble.prototype.width_ = 0; - -/** - * Height of bubble. - * @private - */ -Blockly.Bubble.prototype.height_ = 0; - -/** - * Automatically position and reposition the bubble. - * @private - */ -Blockly.Bubble.prototype.autoLayout_ = true; - -/** - * Create the bubble's DOM. - * @param {!Element} content SVG content for the bubble. - * @param {boolean} hasResize Add diagonal resize gripper if true. - * @return {!Element} The bubble's SVG group. - * @private - */ -Blockly.Bubble.prototype.createDom_ = function(content, hasResize) { - /* Create the bubble. Here's the markup that will be generated: - - - - - - - - - - - [...content goes here...] - - */ - this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null); - var filter = - {'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'}; - if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) { - // Multiple reports that JavaFX can't handle filters. UserAgent: - // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44 - // (KHTML, like Gecko) JavaFX/8.0 Safari/537.44 - // https://github.com/google/blockly/issues/99 - filter = {}; - } - var bubbleEmboss = Blockly.createSvgElement('g', - filter, this.bubbleGroup_); - this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss); - this.bubbleBack_ = Blockly.createSvgElement('rect', - {'class': 'blocklyDraggable', 'x': 0, 'y': 0, - 'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH}, - bubbleEmboss); - if (hasResize) { - this.resizeGroup_ = Blockly.createSvgElement('g', - {'class': this.workspace_.RTL ? - 'blocklyResizeSW' : 'blocklyResizeSE'}, - this.bubbleGroup_); - var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; - Blockly.createSvgElement('polygon', - {'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())}, - this.resizeGroup_); - Blockly.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_); - Blockly.createSvgElement('line', - {'class': 'blocklyResizeLine', - 'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1, - 'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_); - } else { - this.resizeGroup_ = null; - } - this.bubbleGroup_.appendChild(content); - return this.bubbleGroup_; -}; - -/** - * Handle a mouse-down on bubble's border. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) { - this.promote_(); - Blockly.Bubble.unbindDragEvents_(); - if (Blockly.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } else if (Blockly.isTargetInput_(e)) { - // When focused on an HTML text input widget, don't trap any events. - return; - } - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - - this.workspace_.startDrag(e, new goog.math.Coordinate( - this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_, - this.relativeTop_)); - - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, Blockly.Bubble.unbindDragEvents_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.bubbleMouseMove_); - Blockly.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); -}; - -/** - * Drag this bubble to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) { - this.autoLayout_ = false; - var newXY = this.workspace_.moveDrag(e); - this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x; - this.relativeTop_ = newXY.y; - this.positionBubble_(); - this.renderArrow_(); -}; - -/** - * Handle a mouse-down on bubble's resize corner. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Bubble.prototype.resizeMouseDown_ = function(e) { - this.promote_(); - Blockly.Bubble.unbindDragEvents_(); - if (Blockly.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - - this.workspace_.startDrag(e, new goog.math.Coordinate( - this.workspace_.RTL ? -this.width_ : this.width_, this.height_)); - - Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, Blockly.Bubble.unbindDragEvents_); - Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.resizeMouseMove_); - Blockly.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); -}; - -/** - * Resize this bubble to follow the mouse. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Bubble.prototype.resizeMouseMove_ = function(e) { - this.autoLayout_ = false; - var newXY = this.workspace_.moveDrag(e); - this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y); - if (this.workspace_.RTL) { - // RTL requires the bubble to move its left edge. - this.positionBubble_(); - } -}; - -/** - * Register a function as a callback event for when the bubble is resized. - * @param {!Function} callback The function to call on resize. - */ -Blockly.Bubble.prototype.registerResizeEvent = function(callback) { - this.resizeCallback_ = callback; -}; - -/** - * Move this bubble to the top of the stack. - * @private - */ -Blockly.Bubble.prototype.promote_ = function() { - var svgGroup = this.bubbleGroup_.parentNode; - svgGroup.appendChild(this.bubbleGroup_); -}; - -/** - * Notification that the anchor has moved. - * Update the arrow and bubble accordingly. - * @param {!goog.math.Coordinate} xy Absolute location. - */ -Blockly.Bubble.prototype.setAnchorLocation = function(xy) { - this.anchorXY_ = xy; - if (this.rendered_) { - this.positionBubble_(); - } -}; - -/** - * Position the bubble so that it does not fall off-screen. - * @private - */ -Blockly.Bubble.prototype.layoutBubble_ = function() { - // Compute the preferred bubble location. - var relativeLeft = -this.width_ / 4; - var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y; - // Prevent the bubble from being off-screen. - var metrics = this.workspace_.getMetrics(); - metrics.viewWidth /= this.workspace_.scale; - metrics.viewLeft /= this.workspace_.scale; - var anchorX = this.anchorXY_.x; - if (this.workspace_.RTL) { - if (anchorX - metrics.viewLeft - relativeLeft - this.width_ < - Blockly.Scrollbar.scrollbarThickness) { - // Slide the bubble right until it is onscreen. - relativeLeft = anchorX - metrics.viewLeft - this.width_ - - Blockly.Scrollbar.scrollbarThickness; - } else if (anchorX - metrics.viewLeft - relativeLeft > - metrics.viewWidth) { - // Slide the bubble left until it is onscreen. - relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth; - } - } else { - if (anchorX + relativeLeft < metrics.viewLeft) { - // Slide the bubble right until it is onscreen. - relativeLeft = metrics.viewLeft - anchorX; - } else if (metrics.viewLeft + metrics.viewWidth < - anchorX + relativeLeft + this.width_ + - Blockly.BlockSvg.SEP_SPACE_X + - Blockly.Scrollbar.scrollbarThickness) { - // Slide the bubble left until it is onscreen. - relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX - - this.width_ - Blockly.Scrollbar.scrollbarThickness; - } - } - if (this.anchorXY_.y + relativeTop < metrics.viewTop) { - // Slide the bubble below the block. - var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox(); - relativeTop = bBox.height; - } - this.relativeLeft_ = relativeLeft; - this.relativeTop_ = relativeTop; -}; - -/** - * Move the bubble to a location relative to the anchor's centre. - * @private - */ -Blockly.Bubble.prototype.positionBubble_ = function() { - var left = this.anchorXY_.x; - if (this.workspace_.RTL) { - left -= this.relativeLeft_ + this.width_; - } else { - left += this.relativeLeft_; - } - var top = this.relativeTop_ + this.anchorXY_.y; - this.bubbleGroup_.setAttribute('transform', - 'translate(' + left + ',' + top + ')'); -}; - -/** - * Get the dimensions of this bubble. - * @return {!Object} Object with width and height properties. - */ -Blockly.Bubble.prototype.getBubbleSize = function() { - return {width: this.width_, height: this.height_}; -}; - -/** - * Size this bubble. - * @param {number} width Width of the bubble. - * @param {number} height Height of the bubble. - */ -Blockly.Bubble.prototype.setBubbleSize = function(width, height) { - var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; - // Minimum size of a bubble. - width = Math.max(width, doubleBorderWidth + 45); - height = Math.max(height, doubleBorderWidth + 20); - this.width_ = width; - this.height_ = height; - this.bubbleBack_.setAttribute('width', width); - this.bubbleBack_.setAttribute('height', height); - if (this.resizeGroup_) { - if (this.workspace_.RTL) { - // Mirror the resize group. - var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH; - this.resizeGroup_.setAttribute('transform', 'translate(' + - resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)'); - } else { - this.resizeGroup_.setAttribute('transform', 'translate(' + - (width - doubleBorderWidth) + ',' + - (height - doubleBorderWidth) + ')'); - } - } - if (this.rendered_) { - if (this.autoLayout_) { - this.layoutBubble_(); - } - this.positionBubble_(); - this.renderArrow_(); - } - // Allow the contents to resize. - if (this.resizeCallback_) { - this.resizeCallback_(); - } -}; - -/** - * Draw the arrow between the bubble and the origin. - * @private - */ -Blockly.Bubble.prototype.renderArrow_ = function() { - var steps = []; - // Find the relative coordinates of the center of the bubble. - var relBubbleX = this.width_ / 2; - var relBubbleY = this.height_ / 2; - // Find the relative coordinates of the center of the anchor. - var relAnchorX = -this.relativeLeft_; - var relAnchorY = -this.relativeTop_; - if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) { - // Null case. Bubble is directly on top of the anchor. - // Short circuit this rather than wade through divide by zeros. - steps.push('M ' + relBubbleX + ',' + relBubbleY); - } else { - // Compute the angle of the arrow's line. - var rise = relAnchorY - relBubbleY; - var run = relAnchorX - relBubbleX; - if (this.workspace_.RTL) { - run *= -1; - } - var hypotenuse = Math.sqrt(rise * rise + run * run); - var angle = Math.acos(run / hypotenuse); - if (rise < 0) { - angle = 2 * Math.PI - angle; - } - // Compute a line perpendicular to the arrow. - var rightAngle = angle + Math.PI / 2; - if (rightAngle > Math.PI * 2) { - rightAngle -= Math.PI * 2; - } - var rightRise = Math.sin(rightAngle); - var rightRun = Math.cos(rightAngle); - - // Calculate the thickness of the base of the arrow. - var bubbleSize = this.getBubbleSize(); - var thickness = (bubbleSize.width + bubbleSize.height) / - Blockly.Bubble.ARROW_THICKNESS; - thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2; - - // Back the tip of the arrow off of the anchor. - var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse; - relAnchorX = relBubbleX + backoffRatio * run; - relAnchorY = relBubbleY + backoffRatio * rise; - - // Coordinates for the base of the arrow. - var baseX1 = relBubbleX + thickness * rightRun; - var baseY1 = relBubbleY + thickness * rightRise; - var baseX2 = relBubbleX - thickness * rightRun; - var baseY2 = relBubbleY - thickness * rightRise; - - // Distortion to curve the arrow. - var swirlAngle = angle + this.arrow_radians_; - if (swirlAngle > Math.PI * 2) { - swirlAngle -= Math.PI * 2; - } - var swirlRise = Math.sin(swirlAngle) * - hypotenuse / Blockly.Bubble.ARROW_BEND; - var swirlRun = Math.cos(swirlAngle) * - hypotenuse / Blockly.Bubble.ARROW_BEND; - - steps.push('M' + baseX1 + ',' + baseY1); - steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + - ' ' + relAnchorX + ',' + relAnchorY + - ' ' + relAnchorX + ',' + relAnchorY); - steps.push('C' + relAnchorX + ',' + relAnchorY + - ' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) + - ' ' + baseX2 + ',' + baseY2); - } - steps.push('z'); - this.bubbleArrow_.setAttribute('d', steps.join(' ')); -}; - -/** - * Change the colour of a bubble. - * @param {string} hexColour Hex code of colour. - */ -Blockly.Bubble.prototype.setColour = function(hexColour) { - this.bubbleBack_.setAttribute('fill', hexColour); - this.bubbleArrow_.setAttribute('fill', hexColour); -}; - -/** - * Dispose of this bubble. - */ -Blockly.Bubble.prototype.dispose = function() { - Blockly.Bubble.unbindDragEvents_(); - // Dispose of and unlink the bubble. - goog.dom.removeNode(this.bubbleGroup_); - this.bubbleGroup_ = null; - this.bubbleArrow_ = null; - this.bubbleBack_ = null; - this.resizeGroup_ = null; - this.workspace_ = null; - this.content_ = null; - this.shape_ = null; -}; diff --git a/core/connection.js.orig b/core/connection.js.orig deleted file mode 100644 index 3b8c82af8..000000000 --- a/core/connection.js.orig +++ /dev/null @@ -1,615 +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 Components for creating connections between blocks. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Connection'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); - - -/** - * Class for a connection between blocks. - * @param {!Blockly.Block} source The block establishing this connection. - * @param {number} type The type of the connection. - * @constructor - */ -Blockly.Connection = function(source, type) { - /** - * @type {!Blockly.Block} - * @private - */ - this.sourceBlock_ = source; - /** @type {number} */ - this.type = type; - // Shortcut for the databases for this connection's workspace. - if (source.workspace.connectionDBList) { - this.db_ = source.workspace.connectionDBList[type]; - this.dbOpposite_ = - source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]]; - this.hidden_ = !this.db_; - } -}; - -/** - * Constants for checking whether two connections are compatible. - */ -Blockly.Connection.CAN_CONNECT = 0; -Blockly.Connection.REASON_SELF_CONNECTION = 1; -Blockly.Connection.REASON_WRONG_TYPE = 2; -Blockly.Connection.REASON_TARGET_NULL = 3; -Blockly.Connection.REASON_CHECKS_FAILED = 4; -Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5; -Blockly.Connection.REASON_SHADOW_PARENT = 6; - -/** - * Connection this connection connects to. Null if not connected. - * @type {Blockly.Connection} - */ -Blockly.Connection.prototype.targetConnection = null; - -/** - * List of compatible value types. Null if all types are compatible. - * @type {Array} - * @private - */ -Blockly.Connection.prototype.check_ = null; - -/** - * DOM representation of a shadow block, or null if none. - * @type {Element} - * @private - */ -Blockly.Connection.prototype.shadowDom_ = null; - -/** - * Horizontal location of this connection. - * @type {number} - * @private - */ -Blockly.Connection.prototype.x_ = 0; - -/** - * Vertical location of this connection. - * @type {number} - * @private - */ -Blockly.Connection.prototype.y_ = 0; - -/** - * Has this connection been added to the connection database? - * @type {boolean} - * @private - */ -Blockly.Connection.prototype.inDB_ = false; - -/** - * Connection database for connections of this type on the current workspace. - * @type {Blockly.ConnectionDB} - * @private - */ -Blockly.Connection.prototype.db_ = null; - -/** - * Connection database for connections compatible with this type on the - * current workspace. - * @type {Blockly.ConnectionDB} - * @private - */ -Blockly.Connection.prototype.dbOpposite_ = null; - -/** - * Whether this connections is hidden (not tracked in a database) or not. - * @type {boolean} - * @private - */ -Blockly.Connection.prototype.hidden_ = null; - -/** - * Connect two connections together. This is the connection on the superior - * block. - * @param {!Blockly.Connection} childConnection Connection on inferior block. - * @private - */ -Blockly.Connection.prototype.connect_ = function(childConnection) { - var parentConnection = this; - var parentBlock = parentConnection.getSourceBlock(); - var childBlock = childConnection.getSourceBlock(); - // Disconnect any existing parent on the child connection. - if (childConnection.isConnected()) { - childConnection.disconnect(); - } - if (parentConnection.isConnected()) { - // Other connection is already connected to something. - // Disconnect it and reattach it or bump it as needed. - var orphanBlock = parentConnection.targetBlock(); - var shadowDom = parentConnection.getShadowDom(); - // Temporarily set the shadow DOM to null so it does not respawn. - parentConnection.setShadowDom(null); - // Displaced shadow blocks dissolve rather than reattaching or bumping. - if (orphanBlock.isShadow()) { - // Save the shadow block so that field values are preserved. - shadowDom = Blockly.Xml.blockToDom(orphanBlock); - orphanBlock.dispose(); - orphanBlock = null; - } else if (parentConnection.type == Blockly.INPUT_VALUE) { - // Value connections. - // If female block is already connected, disconnect and bump the male. - if (!orphanBlock.outputConnection) { - throw 'Orphan block does not have an output connection.'; - } - // Attempt to reattach the orphan at the end of the newly inserted - // block. Since this block may be a row, walk down to the end - // or to the first (and only) shadow block. - var connection = Blockly.Connection.lastConnectionInRow_( - childBlock, orphanBlock); - if (connection) { - orphanBlock.outputConnection.connect(connection); - orphanBlock = null; - } - } else if (parentConnection.type == Blockly.NEXT_STATEMENT) { - // Statement connections. - // Statement blocks may be inserted into the middle of a stack. - // Split the stack. - if (!orphanBlock.previousConnection) { - throw 'Orphan block does not have a previous connection.'; - } - // Attempt to reattach the orphan at the bottom of the newly inserted - // block. Since this block may be a stack, walk down to the end. - var newBlock = childBlock; - while (newBlock.nextConnection) { - var nextBlock = newBlock.getNextBlock(); - if (nextBlock && !nextBlock.isShadow()) { - newBlock = nextBlock; - } else { - if (orphanBlock.previousConnection.checkType_( - newBlock.nextConnection)) { - newBlock.nextConnection.connect(orphanBlock.previousConnection); - orphanBlock = null; - } - break; - } - } - } - if (orphanBlock) { - // Unable to reattach orphan. - parentConnection.disconnect(); - if (Blockly.Events.recordUndo) { - // Bump it off to the side after a moment. - var group = Blockly.Events.getGroup(); - setTimeout(function() { - // Verify orphan hasn't been deleted or reconnected (user on meth). - if (orphanBlock.workspace && !orphanBlock.getParent()) { - Blockly.Events.setGroup(group); - if (orphanBlock.outputConnection) { - orphanBlock.outputConnection.bumpAwayFrom_(parentConnection); - } else if (orphanBlock.previousConnection) { - orphanBlock.previousConnection.bumpAwayFrom_(parentConnection); - } - Blockly.Events.setGroup(false); - } - }, Blockly.BUMP_DELAY); - } - } - // Restore the shadow DOM. - parentConnection.setShadowDom(shadowDom); - } - - var event; - if (Blockly.Events.isEnabled()) { - event = new Blockly.Events.Move(childBlock); - } - // Establish the connections. - Blockly.Connection.connectReciprocally_(parentConnection, childConnection); - // Demote the inferior block so that one is a child of the superior one. - childBlock.setParent(parentBlock); - if (event) { - event.recordNew(); - Blockly.Events.fire(event); - } -}; - -/** - * Sever all links to this connection (not including from the source object). - */ -Blockly.Connection.prototype.dispose = function() { - if (this.isConnected()) { - throw 'Disconnect connection before disposing of it.'; - } - if (this.inDB_) { - this.db_.removeConnection_(this); - } - if (Blockly.highlightedConnection_ == this) { - Blockly.highlightedConnection_ = null; - } - if (Blockly.localConnection_ == this) { - Blockly.localConnection_ = null; - } - this.db_ = null; - this.dbOpposite_ = null; -}; - -/** - * Get the source block for this connection. - * @return {Blockly.Block} The source block, or null if there is none. - */ -Blockly.Connection.prototype.getSourceBlock = function() { - return this.sourceBlock_; -}; - -/** - * Does the connection belong to a superior block (higher in the source stack)? - * @return {boolean} True if connection faces down or right. - */ -Blockly.Connection.prototype.isSuperior = function() { - return this.type == Blockly.INPUT_VALUE || - this.type == Blockly.NEXT_STATEMENT; -}; - -/** - * Is the connection connected? - * @return {boolean} True if connection is connected to another connection. - */ -Blockly.Connection.prototype.isConnected = function() { - return !!this.targetConnection; -}; - -/** - * Checks whether the current connection can connect with the target - * connection. - * @param {Blockly.Connection} target Connection to check compatibility with. - * @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal, - * an error code otherwise. - * @private - */ -Blockly.Connection.prototype.canConnectWithReason_ = function(target) { - if (!target) { - return Blockly.Connection.REASON_TARGET_NULL; - } - if (this.isSuperior()) { - var blockA = this.sourceBlock_; - var blockB = target.getSourceBlock(); - } else { - var blockB = this.sourceBlock_; - var blockA = target.getSourceBlock(); - } - if (blockA && blockA == blockB) { - return Blockly.Connection.REASON_SELF_CONNECTION; - } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { - return Blockly.Connection.REASON_WRONG_TYPE; - } else if (blockA && blockB && blockA.workspace !== blockB.workspace) { - return Blockly.Connection.REASON_DIFFERENT_WORKSPACES; - } else if (!this.checkType_(target)) { - return Blockly.Connection.REASON_CHECKS_FAILED; - } else if (blockA.isShadow() && !blockB.isShadow()) { - return Blockly.Connection.REASON_SHADOW_PARENT; - } - return Blockly.Connection.CAN_CONNECT; -}; - -/** - * Checks whether the current connection and target connection are compatible - * and throws an exception if they are not. - * @param {Blockly.Connection} target The connection to check compatibility - * with. - * @private - */ -Blockly.Connection.prototype.checkConnection_ = function(target) { - switch (this.canConnectWithReason_(target)) { - case Blockly.Connection.CAN_CONNECT: - break; - case Blockly.Connection.REASON_SELF_CONNECTION: - throw 'Attempted to connect a block to itself.'; - case Blockly.Connection.REASON_DIFFERENT_WORKSPACES: - // Usually this means one block has been deleted. - throw 'Blocks not on same workspace.'; - case Blockly.Connection.REASON_WRONG_TYPE: - throw 'Attempt to connect incompatible types.'; - case Blockly.Connection.REASON_TARGET_NULL: - throw 'Target connection is null.'; - case Blockly.Connection.REASON_CHECKS_FAILED: - throw 'Connection checks failed.'; - case Blockly.Connection.REASON_SHADOW_PARENT: - throw 'Connecting non-shadow to shadow block.'; - default: - throw 'Unknown connection failure: this should never happen!'; - } -}; - -/** - * Check if the two connections can be dragged to connect to each other. - * @param {!Blockly.Connection} candidate A nearby connection to check. - * @return {boolean} True if the connection is allowed, false otherwise. - */ -Blockly.Connection.prototype.isConnectionAllowed = function(candidate) { - // Type checking. - var canConnect = this.canConnectWithReason_(candidate); - if (canConnect != Blockly.Connection.CAN_CONNECT) { - return false; - } - - // Don't offer to connect an already connected left (male) value plug to - // an available right (female) value plug. Don't offer to connect the - // bottom of a statement block to one that's already connected. - if (candidate.type == Blockly.OUTPUT_VALUE || - candidate.type == Blockly.PREVIOUS_STATEMENT) { - if (candidate.isConnected() || this.isConnected()) { - return false; - } - } - - // Offering to connect the left (male) of a value block to an already - // connected value pair is ok, we'll splice it in. - // However, don't offer to splice into an immovable block. - if (candidate.type == Blockly.INPUT_VALUE && candidate.isConnected() && - !candidate.targetBlock().isMovable() && - !candidate.targetBlock().isShadow()) { - return false; - } - - // Don't let a block with no next connection bump other blocks out of the - // stack. But covering up a shadow block or stack of shadow blocks is fine. - // Similarly, replacing a terminal statement with another terminal statement - // is allowed. - if (this.type == Blockly.PREVIOUS_STATEMENT && - candidate.isConnected() && - !this.sourceBlock_.nextConnection && - !candidate.targetBlock().isShadow() && - candidate.targetBlock().nextConnection) { - return false; - } - - // Don't let blocks try to connect to themselves or ones they nest. - if (Blockly.draggingConnections_.indexOf(candidate) != -1) { - return false; - } - - return true; -}; - -/** - * Connect this connection to another connection. - * @param {!Blockly.Connection} otherConnection Connection to connect to. - */ -Blockly.Connection.prototype.connect = function(otherConnection) { - if (this.targetConnection == otherConnection) { - // Already connected together. NOP. - return; - } - this.checkConnection_(otherConnection); - // Determine which block is superior (higher in the source stack). - if (this.isSuperior()) { - // Superior block. - this.connect_(otherConnection); - } else { - // Inferior block. - otherConnection.connect_(this); - } -}; - -/** - * Update two connections to target each other. - * @param {Blockly.Connection} first The first connection to update. - * @param {Blockly.Connection} second The second conneciton to update. - * @private - */ -Blockly.Connection.connectReciprocally_ = function(first, second) { - goog.asserts.assert(first && second, 'Cannot connect null connections.'); - first.targetConnection = second; - second.targetConnection = first; -}; - -/** - * Does the given block have one and only one connection point that will accept - * an orphaned block? - * @param {!Blockly.Block} block The superior block. - * @param {!Blockly.Block} orphanBlock The inferior block. - * @return {Blockly.Connection} The suitable connection point on 'block', - * or null. - * @private - */ -Blockly.Connection.singleConnection_ = function(block, orphanBlock) { - var connection = false; - for (var i = 0; i < block.inputList.length; i++) { - var thisConnection = block.inputList[i].connection; - if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE && - orphanBlock.outputConnection.checkType_(thisConnection)) { - if (connection) { - return null; // More than one connection. - } - connection = thisConnection; - } - } - return connection; -}; - -/** - * Walks down a row a blocks, at each stage checking if there are any - * connections that will accept the orphaned block. If at any point there - * are zero or multiple eligible connections, returns null. Otherwise - * returns the only input on the last block in the chain. - * Terminates early for shadow blocks. - * @param {!Blockly.Block} startBlock The block on which to start the search. - * @param {!Blockly.Block} orphanBlock The block that is looking for a home. - * @return {Blockly.Connection} The suitable connection point on the chain - * of blocks, or null. - * @private - */ -Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) { - var newBlock = startBlock; - var connection; - while (connection = Blockly.Connection.singleConnection_( - /** @type {!Blockly.Block} */ (newBlock), orphanBlock)) { - // '=' is intentional in line above. - newBlock = connection.targetBlock(); - if (!newBlock || newBlock.isShadow()) { - return connection; - } - } - return null; -}; - -/** - * Disconnect this connection. - */ -Blockly.Connection.prototype.disconnect = function() { - var otherConnection = this.targetConnection; - goog.asserts.assert(otherConnection, 'Source connection not connected.'); - goog.asserts.assert(otherConnection.targetConnection == this, - 'Target connection not connected to source connection.'); - - var parentBlock, childBlock, parentConnection; - if (this.isSuperior()) { - // Superior block. - parentBlock = this.sourceBlock_; - childBlock = otherConnection.getSourceBlock(); - parentConnection = this; - } else { - // Inferior block. - parentBlock = otherConnection.getSourceBlock(); - childBlock = this.sourceBlock_; - parentConnection = otherConnection; - } - this.disconnectInternal_(parentBlock, childBlock); - parentConnection.respawnShadow_(); -}; - -/** - * Disconnect two blocks that are connected by this connection. - * @param {!Blockly.Block} parentBlock The superior block. - * @param {!Blockly.Block} childBlock The inferior block. - * @private - */ -Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock, - childBlock) { - var event; - if (Blockly.Events.isEnabled()) { - event = new Blockly.Events.Move(childBlock); - } - var otherConnection = this.targetConnection; - otherConnection.targetConnection = null; - this.targetConnection = null; - childBlock.setParent(null); - if (event) { - event.recordNew(); - Blockly.Events.fire(event); - } -}; - -/** - * Respawn the shadow block if there was one connected to the this connection. - * @private - */ -Blockly.Connection.prototype.respawnShadow_ = function() { - var parentBlock = this.getSourceBlock(); - var shadow = this.getShadowDom(); - if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) { - var blockShadow = - Blockly.Xml.domToBlock(shadow, parentBlock.workspace); - if (blockShadow.outputConnection) { - this.connect(blockShadow.outputConnection); - } else if (blockShadow.previousConnection) { - this.connect(blockShadow.previousConnection); - } else { - throw 'Child block does not have output or previous statement.'; - } - } -}; - -/** - * Returns the block that this connection connects to. - * @return {Blockly.Block} The connected block or null if none is connected. - */ -Blockly.Connection.prototype.targetBlock = function() { - if (this.isConnected()) { - return this.targetConnection.getSourceBlock(); - } - return null; -}; - -/** - * Is this connection compatible with another connection with respect to the - * value type system. E.g. square_root("Hello") is not compatible. - * @param {!Blockly.Connection} otherConnection Connection to compare against. - * @return {boolean} True if the connections share a type. - * @private - */ -Blockly.Connection.prototype.checkType_ = function(otherConnection) { - if (!this.check_ || !otherConnection.check_) { - // One or both sides are promiscuous enough that anything will fit. - return true; - } - // Find any intersection in the check lists. - for (var i = 0; i < this.check_.length; i++) { - if (otherConnection.check_.indexOf(this.check_[i]) != -1) { - return true; - } - } - // No intersection. - return false; -}; - -/** - * Change a connection's compatibility. - * @param {*} check Compatible value type or list of value types. - * Null if all types are compatible. - * @return {!Blockly.Connection} The connection being modified - * (to allow chaining). - */ -Blockly.Connection.prototype.setCheck = function(check) { - if (check) { - // Ensure that check is in an array. - if (!goog.isArray(check)) { - check = [check]; - } - this.check_ = check; - // The new value type may not be compatible with the existing connection. - if (this.isConnected() && !this.checkType_(this.targetConnection)) { - var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_; - child.unplug(); - // Bump away. - this.sourceBlock_.bumpNeighbours_(); - } - } else { - this.check_ = null; - } - return this; -}; - -/** - * Change a connection's shadow block. - * @param {Element} shadow DOM representation of a block or null. - */ -Blockly.Connection.prototype.setShadowDom = function(shadow) { - this.shadowDom_ = shadow; -}; - -/** - * Return a connection's shadow block. - * @return {Element} shadow DOM representation of a block or null. - */ -Blockly.Connection.prototype.getShadowDom = function() { - return this.shadowDom_; -}; diff --git a/core/css.js.orig b/core/css.js.orig deleted file mode 100644 index 0871a4fae..000000000 --- a/core/css.js.orig +++ /dev/null @@ -1,786 +0,0 @@ -/** - * @license - * Visual Blocks Editor - * - * Copyright 2013 Google Inc. - * https://developers.google.com/blockly/ - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @fileoverview Inject Blockly's CSS synchronously. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Css'); - - -/** - * List of cursors. - * @enum {string} - */ -Blockly.Css.Cursor = { - OPEN: 'handopen', - CLOSED: 'handclosed', - DELETE: 'handdelete' -}; - -/** - * Current cursor (cached value). - * @type {string} - * @private - */ -Blockly.Css.currentCursor_ = ''; - -/** - * Large stylesheet added by Blockly.Css.inject. - * @type {Element} - * @private - */ -Blockly.Css.styleSheet_ = null; - -/** - * Path to media directory, with any trailing slash removed. - * @type {string} - * @private - */ -Blockly.Css.mediaPath_ = ''; - -/** - * Inject the CSS into the DOM. This is preferable over using a regular CSS - * file since: - * a) It loads synchronously and doesn't force a redraw later. - * b) It speeds up loading by not blocking on a separate HTTP transfer. - * c) The CSS content may be made dynamic depending on init options. - * @param {boolean} hasCss If false, don't inject CSS - * (providing CSS becomes the document's responsibility). - * @param {string} pathToMedia Path from page to the Blockly media directory. - */ -Blockly.Css.inject = function(hasCss, pathToMedia) { - // Only inject the CSS once. - if (Blockly.Css.styleSheet_) { - return; - } - // Placeholder for cursor rule. Must be first rule (index 0). - var text = '.blocklyDraggable {}\n'; - if (hasCss) { - text += Blockly.Css.CONTENT.join('\n'); - if (Blockly.FieldDate) { - text += Blockly.FieldDate.CSS.join('\n'); - } - } - // Strip off any trailing slash (either Unix or Windows). - Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); - text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); - // Inject CSS tag. - var cssNode = document.createElement('style'); - document.head.appendChild(cssNode); - var cssTextNode = document.createTextNode(text); - cssNode.appendChild(cssTextNode); - Blockly.Css.styleSheet_ = cssNode.sheet; - Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); -}; - -/** - * Set the cursor to be displayed when over something draggable. - * @param {Blockly.Css.Cursor} cursor Enum. - */ -Blockly.Css.setCursor = function(cursor) { - if (Blockly.Css.currentCursor_ == cursor) { - return; - } - Blockly.Css.currentCursor_ = cursor; - var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto'; - // There are potentially hundreds of draggable objects. Changing their style - // properties individually is too slow, so change the CSS rule instead. - var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n'; - Blockly.Css.styleSheet_.deleteRule(0); - Blockly.Css.styleSheet_.insertRule(rule, 0); - // There is probably only one toolbox, so just change its style property. - var toolboxen = document.getElementsByClassName('blocklyToolboxDiv'); - for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) { - if (cursor == Blockly.Css.Cursor.DELETE) { - toolbox.style.cursor = url; - } else { - toolbox.style.cursor = ''; - } - } - // Set cursor on the whole document, so that rapid movements - // don't result in cursor changing to an arrow momentarily. - var html = document.body.parentNode; - if (cursor == Blockly.Css.Cursor.OPEN) { - html.style.cursor = ''; - } else { - html.style.cursor = url; - } -}; - -/** - * Array making up the CSS content for Blockly. - */ -Blockly.Css.CONTENT = [ - '.blocklySvg {', - 'background-color: #fff;', - 'outline: none;', - 'overflow: hidden;', /* IE overflows by default. */ - 'display: block;', - '}', - - '.blocklyWidgetDiv {', - 'display: none;', - 'position: absolute;', - 'z-index: 99999;', /* big value for bootstrap3 compatibility */ - '}', - - '.injectionDiv {', - 'height: 100%;', - 'position: relative;', - '}', - - '.blocklyNonSelectable {', - 'user-select: none;', - '-moz-user-select: none;', - '-webkit-user-select: none;', - '-ms-user-select: none;', - '}', - - '.blocklyTooltipDiv {', - 'background-color: #ffffc7;', - 'border: 1px solid #ddc;', - 'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);', - 'color: #000;', - 'display: none;', - 'font-family: sans-serif;', - 'font-size: 9pt;', - 'opacity: 0.9;', - 'padding: 2px;', - 'position: absolute;', - 'z-index: 100000;', /* big value for bootstrap3 compatibility */ - '}', - - '.blocklyResizeSE {', - 'cursor: se-resize;', - 'fill: #aaa;', - '}', - - '.blocklyResizeSW {', - 'cursor: sw-resize;', - 'fill: #aaa;', - '}', - - '.blocklyResizeLine {', - 'stroke: #888;', - 'stroke-width: 1;', - '}', - - '.blocklyHighlightedConnectionPath {', - 'fill: none;', - 'stroke: #fc3;', - 'stroke-width: 4px;', - '}', - - '.blocklyPathLight {', - 'fill: none;', - 'stroke-linecap: round;', - 'stroke-width: 1;', - '}', - - '.blocklySelected>.blocklyPath {', - 'stroke: #fc3;', - 'stroke-width: 3px;', - '}', - - '.blocklySelected>.blocklyPathLight {', - 'display: none;', - '}', - - '.blocklyDragging>.blocklyPath,', - '.blocklyDragging>.blocklyPathLight {', - 'fill-opacity: .8;', - 'stroke-opacity: .8;', - '}', - - '.blocklyDragging>.blocklyPathDark {', - 'display: none;', - '}', - - '.blocklyDisabled>.blocklyPath {', - 'fill-opacity: .5;', - 'stroke-opacity: .5;', - '}', - - '.blocklyDisabled>.blocklyPathLight,', - '.blocklyDisabled>.blocklyPathDark {', - 'display: none;', - '}', - - '.blocklyText {', - 'cursor: default;', - 'fill: #fff;', - 'font-family: sans-serif;', - 'font-size: 11pt;', - '}', - - '.blocklyNonEditableText>text {', - 'pointer-events: none;', - '}', - - '.blocklyNonEditableText>rect,', - '.blocklyEditableText>rect {', - 'fill: #fff;', - 'fill-opacity: .6;', - '}', - - '.blocklyNonEditableText>text,', - '.blocklyEditableText>text {', - 'fill: #000;', - '}', - - '.blocklyEditableText:hover>rect {', - 'stroke: #fff;', - 'stroke-width: 2;', - '}', - - '.blocklyBubbleText {', - 'fill: #000;', - '}', - - '.blocklyFlyoutButton {', - 'fill: #888;', - 'cursor: default;', - '}', - - '.blocklyFlyoutButtonShadow {', - 'fill: #444;', - '}', - - '.blocklyFlyoutButton:hover {', - 'fill: #aaa;', - '}', - - /* - Don't allow users to select text. It gets annoying when trying to - drag a block and selected text moves instead. - */ - '.blocklySvg text {', - 'user-select: none;', - '-moz-user-select: none;', - '-webkit-user-select: none;', - 'cursor: inherit;', - '}', - - '.blocklyHidden {', - 'display: none;', - '}', - - '.blocklyFieldDropdown:not(.blocklyHidden) {', - 'display: block;', - '}', - - '.blocklyIconGroup {', - 'cursor: default;', - '}', - - '.blocklyIconGroup:not(:hover),', - '.blocklyIconGroupReadonly {', - 'opacity: .6;', - '}', - - '.blocklyIconShape {', - 'fill: #00f;', - 'stroke: #fff;', - 'stroke-width: 1px;', - '}', - - '.blocklyIconSymbol {', - 'fill: #fff;', - '}', - - '.blocklyMinimalBody {', - 'margin: 0;', - 'padding: 0;', - '}', - - '.blocklyCommentTextarea {', - 'background-color: #ffc;', - 'border: 0;', - 'margin: 0;', - 'padding: 2px;', - 'resize: none;', - '}', - - '.blocklyHtmlInput {', - 'border: none;', - 'border-radius: 4px;', - 'font-family: sans-serif;', - 'height: 100%;', - 'margin: 0;', - 'outline: none;', - 'padding: 0 1px;', - 'width: 100%', - '}', - - '.blocklyMainBackground {', - 'stroke-width: 1;', - 'stroke: #c6c6c6;', /* Equates to #ddd due to border being off-pixel. */ - '}', - - '.blocklyMutatorBackground {', - 'fill: #fff;', - 'stroke: #ddd;', - 'stroke-width: 1;', - '}', - - '.blocklyFlyoutBackground {', - 'fill: #ddd;', - 'fill-opacity: .8;', - '}', - - '.blocklyScrollbarBackground {', - 'opacity: 0;', - '}', - - '.blocklyScrollbarHandle {', - 'fill: #ccc;', - '}', - - '.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', - '.blocklyScrollbarHandle:hover {', - 'fill: #bbb;', - '}', - - '.blocklyZoom>image {', - 'opacity: .4;', - '}', - - '.blocklyZoom>image:hover {', - 'opacity: .6;', - '}', - - '.blocklyZoom>image:active {', - 'opacity: .8;', - '}', - - /* Darken flyout scrollbars due to being on a grey background. */ - /* By contrast, workspace scrollbars are on a white background. */ - '.blocklyFlyout .blocklyScrollbarHandle {', - 'fill: #bbb;', - '}', - - '.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,', - '.blocklyFlyout .blocklyScrollbarHandle:hover {', - 'fill: #aaa;', - '}', - - '.blocklyInvalidInput {', - 'background: #faa;', - '}', - - '.blocklyAngleCircle {', - 'stroke: #444;', - 'stroke-width: 1;', - 'fill: #ddd;', - 'fill-opacity: .8;', - '}', - - '.blocklyAngleMarks {', - 'stroke: #444;', - 'stroke-width: 1;', - '}', - - '.blocklyAngleGauge {', - 'fill: #f88;', - 'fill-opacity: .8;', - '}', - - '.blocklyAngleLine {', - 'stroke: #f00;', - 'stroke-width: 2;', - 'stroke-linecap: round;', - '}', - - '.blocklyContextMenu {', - 'border-radius: 4px;', - '}', - - '.blocklyDropdownMenu {', - 'padding: 0 !important;', - '}', - - /* Override the default Closure URL. */ - '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', - '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', - 'background: url(<<>>/sprites.png) no-repeat -48px -16px !important;', - '}', - - /* Category tree in Toolbox. */ - '.blocklyToolboxDiv {', - 'background-color: #ddd;', - 'overflow-x: visible;', - 'overflow-y: auto;', - 'position: absolute;', - '}', - - '.blocklyTreeRoot {', - 'padding: 4px 0;', - '}', - - '.blocklyTreeRoot:focus {', - 'outline: none;', - '}', - - '.blocklyTreeRow {', - 'height: 22px;', - 'line-height: 22px;', - 'margin-bottom: 3px;', - 'padding-right: 8px;', - 'white-space: nowrap;', - '}', - - '.blocklyHorizontalTree {', - 'float: left;', - 'margin: 1px 5px 8px 0;', - '}', - - '.blocklyHorizontalTreeRtl {', - 'float: right;', - 'margin: 1px 0 8px 5px;', - '}', - - '.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {', - 'margin-left: 8px;', - '}', - - '.blocklyTreeRow:not(.blocklyTreeSelected):hover {', - 'background-color: #e4e4e4;', - '}', - - '.blocklyTreeSeparator {', - 'border-bottom: solid #e5e5e5 1px;', - 'height: 0;', - 'margin: 5px 0;', - '}', - - '.blocklyTreeSeparatorHorizontal {', - 'border-right: solid #e5e5e5 1px;', - 'width: 0;', - 'padding: 5px 0;', - 'margin: 0 5px;', - '}', - - - '.blocklyTreeIcon {', - 'background-image: url(<<>>/sprites.png);', - 'height: 16px;', - 'vertical-align: middle;', - 'width: 16px;', - '}', - - '.blocklyTreeIconClosedLtr {', - 'background-position: -32px -1px;', - '}', - - '.blocklyTreeIconClosedRtl {', - 'background-position: 0px -1px;', - '}', - - '.blocklyTreeIconOpen {', - 'background-position: -16px -1px;', - '}', - - '.blocklyTreeSelected>.blocklyTreeIconClosedLtr {', - 'background-position: -32px -17px;', - '}', - - '.blocklyTreeSelected>.blocklyTreeIconClosedRtl {', - 'background-position: 0px -17px;', - '}', - - '.blocklyTreeSelected>.blocklyTreeIconOpen {', - 'background-position: -16px -17px;', - '}', - - '.blocklyTreeIconNone,', - '.blocklyTreeSelected>.blocklyTreeIconNone {', - 'background-position: -48px -1px;', - '}', - - '.blocklyTreeLabel {', - 'cursor: default;', - 'font-family: sans-serif;', - 'font-size: 16px;', - 'padding: 0 3px;', - 'vertical-align: middle;', - '}', - - '.blocklyTreeSelected .blocklyTreeLabel {', - 'color: #fff;', - '}', - - /* Copied from: goog/css/colorpicker-simplegrid.css */ - /* - * Copyright 2007 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by the Apache License, Version 2.0. - * See the COPYING file for details. - */ - - /* Author: pupius@google.com (Daniel Pupius) */ - - /* - Styles to make the colorpicker look like the old gmail color picker - NOTE: without CSS scoping this will override styles defined in palette.css - */ - '.blocklyWidgetDiv .goog-palette {', - 'outline: none;', - 'cursor: default;', - '}', - - '.blocklyWidgetDiv .goog-palette-table {', - 'border: 1px solid #666;', - 'border-collapse: collapse;', - '}', - - '.blocklyWidgetDiv .goog-palette-cell {', - 'height: 13px;', - 'width: 15px;', - 'margin: 0;', - 'border: 0;', - 'text-align: center;', - 'vertical-align: middle;', - 'border-right: 1px solid #666;', - 'font-size: 1px;', - '}', - - '.blocklyWidgetDiv .goog-palette-colorswatch {', - 'position: relative;', - 'height: 13px;', - 'width: 15px;', - 'border: 1px solid #666;', - '}', - - '.blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {', - 'border: 1px solid #FFF;', - '}', - - '.blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {', - 'border: 1px solid #000;', - 'color: #fff;', - '}', - - /* Copied from: goog/css/menu.css */ - /* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by the Apache License, Version 2.0. - * See the COPYING file for details. - */ - - /** - * Standard styling for menus created by goog.ui.MenuRenderer. - * - * @author attila@google.com (Attila Bodis) - */ - - '.blocklyWidgetDiv .goog-menu {', - 'background: #fff;', - 'border-color: #ccc #666 #666 #ccc;', - 'border-style: solid;', - 'border-width: 1px;', - 'cursor: default;', - 'font: normal 13px Arial, sans-serif;', - 'margin: 0;', - 'outline: none;', - 'padding: 4px 0;', - 'position: absolute;', - 'overflow-y: auto;', - 'overflow-x: hidden;', - 'max-height: 100%;', - 'z-index: 20000;', /* Arbitrary, but some apps depend on it... */ - '}', - - /* Copied from: goog/css/menuitem.css */ - /* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by the Apache License, Version 2.0. - * See the COPYING file for details. - */ - - /** - * Standard styling for menus created by goog.ui.MenuItemRenderer. - * - * @author attila@google.com (Attila Bodis) - */ - - /** - * State: resting. - * - * NOTE(mleibman,chrishenry): - * The RTL support in Closure is provided via two mechanisms -- "rtl" CSS - * classes and BiDi flipping done by the CSS compiler. Closure supports RTL - * with or without the use of the CSS compiler. In order for them not - * to conflict with each other, the "rtl" CSS classes need to have the #noflip - * annotation. The non-rtl counterparts should ideally have them as well, but, - * since .goog-menuitem existed without .goog-menuitem-rtl for so long before - * being added, there is a risk of people having templates where they are not - * rendering the .goog-menuitem-rtl class when in RTL and instead rely solely - * on the BiDi flipping by the CSS compiler. That's why we're not adding the - * #noflip to .goog-menuitem. - */ - '.blocklyWidgetDiv .goog-menuitem {', - 'color: #000;', - 'font: normal 13px Arial, sans-serif;', - 'list-style: none;', - 'margin: 0;', - /* 28px on the left for icon or checkbox; 7em on the right for shortcut. */ - 'padding: 4px 7em 4px 28px;', - 'white-space: nowrap;', - '}', - - /* BiDi override for the resting state. */ - /* #noflip */ - '.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {', - /* Flip left/right padding for BiDi. */ - 'padding-left: 7em;', - 'padding-right: 28px;', - '}', - - /* If a menu doesn't have checkable items or items with icons, remove padding. */ - '.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,', - '.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {', - 'padding-left: 12px;', - '}', - - /* - * If a menu doesn't have items with shortcuts, leave just enough room for - * submenu arrows, if they are rendered. - */ - '.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {', - 'padding-right: 20px;', - '}', - - '.blocklyWidgetDiv .goog-menuitem-content {', - 'color: #000;', - 'font: normal 13px Arial, sans-serif;', - '}', - - /* State: disabled. */ - '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,', - '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {', - 'color: #ccc !important;', - '}', - - '.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {', - 'opacity: 0.3;', - '-moz-opacity: 0.3;', - 'filter: alpha(opacity=30);', - '}', - - /* State: hover. */ - '.blocklyWidgetDiv .goog-menuitem-highlight,', - '.blocklyWidgetDiv .goog-menuitem-hover {', - 'background-color: #d6e9f8;', - /* Use an explicit top and bottom border so that the selection is visible', - * in high contrast mode. */ - 'border-color: #d6e9f8;', - 'border-style: dotted;', - 'border-width: 1px 0;', - 'padding-bottom: 3px;', - 'padding-top: 3px;', - '}', - - /* State: selected/checked. */ - '.blocklyWidgetDiv .goog-menuitem-checkbox,', - '.blocklyWidgetDiv .goog-menuitem-icon {', - 'background-repeat: no-repeat;', - 'height: 16px;', - 'left: 6px;', - 'position: absolute;', - 'right: auto;', - 'vertical-align: middle;', - 'width: 16px;', - '}', - - /* BiDi override for the selected/checked state. */ - /* #noflip */ - '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,', - '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {', - /* Flip left/right positioning. */ - 'left: auto;', - 'right: 6px;', - '}', - - '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,', - '.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {', - /* Client apps may override the URL at which they serve the sprite. */ - 'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;', - '}', - - /* Keyboard shortcut ("accelerator") style. */ - '.blocklyWidgetDiv .goog-menuitem-accel {', - 'color: #999;', - /* Keyboard shortcuts are untranslated; always left-to-right. */ - /* #noflip */ - 'direction: ltr;', - 'left: auto;', - 'padding: 0 6px;', - 'position: absolute;', - 'right: 0;', - 'text-align: right;', - '}', - - /* BiDi override for shortcut style. */ - /* #noflip */ - '.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {', - /* Flip left/right positioning and text alignment. */ - 'left: 0;', - 'right: auto;', - 'text-align: left;', - '}', - - /* Mnemonic styles. */ - '.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {', - 'text-decoration: underline;', - '}', - - '.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {', - 'color: #999;', - 'font-size: 12px;', - 'padding-left: 4px;', - '}', - - /* Copied from: goog/css/menuseparator.css */ - /* - * Copyright 2009 The Closure Library Authors. All Rights Reserved. - * - * Use of this source code is governed by the Apache License, Version 2.0. - * See the COPYING file for details. - */ - - /** - * Standard styling for menus created by goog.ui.MenuSeparatorRenderer. - * - * @author attila@google.com (Attila Bodis) - */ - - '.blocklyWidgetDiv .goog-menuseparator {', - 'border-top: 1px solid #ccc;', - 'margin: 4px 0;', - 'padding: 0;', - '}', - - '' -]; diff --git a/core/field.js.orig b/core/field.js.orig deleted file mode 100644 index 131a62679..000000000 --- a/core/field.js.orig +++ /dev/null @@ -1,495 +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 Field. Used for editable titles, variables, etc. - * This is an abstract class that defines the UI on the block. Actual - * instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Field'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); -goog.require('goog.math.Size'); -goog.require('goog.style'); -goog.require('goog.userAgent'); - - -/** - * Abstract class for an editable field. - * @param {string} text The initial content of the field. - * @param {Function=} opt_validator An optional function that is called - * to validate any constraints on what the user entered. Takes the new - * text as an argument and returns either the accepted text, a replacement - * text, or null to abort the change. - * @constructor - */ -Blockly.Field = function(text, opt_validator) { - this.size_ = new goog.math.Size(0, 25); - this.setValue(text); - this.setValidator(opt_validator); -}; - -/** - * Temporary cache of text widths. - * @type {Object} - * @private - */ -Blockly.Field.cacheWidths_ = null; - -/** - * Number of current references to cache. - * @type {number} - * @private - */ -Blockly.Field.cacheReference_ = 0; - - -/** - * Name of field. Unique within each block. - * Static labels are usually unnamed. - * @type {string=} - */ -Blockly.Field.prototype.name = undefined; - -/** - * Maximum characters of text to display before adding an ellipsis. - * @type {number} - */ -Blockly.Field.prototype.maxDisplayLength = 50; - -/** - * Visible text to display. - * @type {string} - * @private - */ -Blockly.Field.prototype.text_ = ''; - -/** - * Block this field is attached to. Starts as null, then in set in init. - * @type {Blockly.Block} - * @private - */ -Blockly.Field.prototype.sourceBlock_ = null; - -/** - * Is the field visible, or hidden due to the block being collapsed? - * @type {boolean} - * @private - */ -Blockly.Field.prototype.visible_ = true; - -/** - * Validation function called when user edits an editable field. - * @type {Function} - * @private - */ -Blockly.Field.prototype.validator_ = null; - -/** - * Non-breaking space. - * @const - */ -Blockly.Field.NBSP = '\u00A0'; - -/** - * Editable fields are saved by the XML renderer, non-editable fields are not. - */ -Blockly.Field.prototype.EDITABLE = true; - -/** - * Attach this field to a block. - * @param {!Blockly.Block} block The block containing this field. - */ -Blockly.Field.prototype.setSourceBlock = function(block) { - goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.'); - this.sourceBlock_ = block; -}; - -/** - * Install this field on a block. - */ -Blockly.Field.prototype.init = function() { - if (this.fieldGroup_) { - // Field has already been initialized once. - return; - } - // Build the DOM. - this.fieldGroup_ = Blockly.createSvgElement('g', {}, null); - if (!this.visible_) { - this.fieldGroup_.style.display = 'none'; - } - this.borderRect_ = Blockly.createSvgElement('rect', - {'rx': 4, - 'ry': 4, - 'x': -Blockly.BlockSvg.SEP_SPACE_X / 2, - 'y': 0, - 'height': 16}, this.fieldGroup_, this.sourceBlock_.workspace); - /** @type {!Element} */ - this.textElement_ = Blockly.createSvgElement('text', - {'class': 'blocklyText', 'y': this.size_.height - 12.5}, - this.fieldGroup_); - - this.updateEditable(); - this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); - this.mouseUpWrapper_ = - Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_); - // Force a render. - this.updateTextNode_(); -}; - -/** - * Dispose of all DOM objects belonging to this editable field. - */ -Blockly.Field.prototype.dispose = function() { - if (this.mouseUpWrapper_) { - Blockly.unbindEvent_(this.mouseUpWrapper_); - this.mouseUpWrapper_ = null; - } - this.sourceBlock_ = null; - goog.dom.removeNode(this.fieldGroup_); - this.fieldGroup_ = null; - this.textElement_ = null; - this.borderRect_ = null; - this.validator_ = null; -}; - -/** - * Add or remove the UI indicating if this field is editable or not. - */ -Blockly.Field.prototype.updateEditable = function() { - var group = this.fieldGroup_; - if (!this.EDITABLE || !group) { - return; - } - if (this.sourceBlock_.isEditable()) { - Blockly.addClass_(group, 'blocklyEditableText'); - Blockly.removeClass_(group, 'blocklyNonEditableText'); - this.fieldGroup_.style.cursor = this.CURSOR; - } else { - Blockly.addClass_(group, 'blocklyNonEditableText'); - Blockly.removeClass_(group, 'blocklyEditableText'); - this.fieldGroup_.style.cursor = ''; - } -}; - -/** - * Gets whether this editable field is visible or not. - * @return {boolean} True if visible. - */ -Blockly.Field.prototype.isVisible = function() { - return this.visible_; -}; - -/** - * Sets whether this editable field is visible or not. - * @param {boolean} visible True if visible. - */ -Blockly.Field.prototype.setVisible = function(visible) { - if (this.visible_ == visible) { - return; - } - this.visible_ = visible; - var root = this.getSvgRoot(); - if (root) { - root.style.display = visible ? 'block' : 'none'; - this.render_(); - } -}; - -/** - * Sets a new validation function for editable fields. - * @param {Function} handler New validation function, or null. - */ -Blockly.Field.prototype.setValidator = function(handler) { - this.validator_ = handler; -}; - -/** - * Gets the validation function for editable fields. - * @return {Function} Validation function, or null. - */ -Blockly.Field.prototype.getValidator = function() { - return this.validator_; -}; - -/** - * Validates a change. Does nothing. Subclasses may override this. - * @param {string} text The user's text. - * @return {string} No change needed. - */ -Blockly.Field.prototype.classValidator = function(text) { - return text; -}; - -/** - * Calls the validation function for this field, as well as all the validation - * function for the field's class and its parents. - * @param {string} text Proposed text. - * @return {?string} Revised text, or null if invalid. - */ -Blockly.Field.prototype.callValidator = function(text) { - var classResult = this.classValidator(text); - if (classResult === null) { - // Class validator rejects value. Game over. - return null; - } else if (classResult !== undefined) { - text = classResult; - } - var userValidator = this.getValidator(); - if (userValidator) { - var userResult = userValidator.call(this, text); - if (userResult === null) { - // User validator rejects value. Game over. - return null; - } else if (userResult !== undefined) { - text = userResult; - } - } - return text; -}; - -/** - * Gets the group element for this editable field. - * Used for measuring the size and for positioning. - * @return {!Element} The group element. - */ -Blockly.Field.prototype.getSvgRoot = function() { - return /** @type {!Element} */ (this.fieldGroup_); -}; - -/** - * Draws the border with the correct width. - * Saves the computed width in a property. - * @private - */ -Blockly.Field.prototype.render_ = function() { - if (this.visible_ && this.textElement_) { - var key = this.textElement_.textContent + '\n' + - this.textElement_.className.baseVal; - if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) { - var width = Blockly.Field.cacheWidths_[key]; - } else { - try { - var width = this.textElement_.getComputedTextLength(); - } catch (e) { - // MSIE 11 is known to throw "Unexpected call to method or property - // access." if Blockly is hidden. - var width = this.textElement_.textContent.length * 8; - } - if (Blockly.Field.cacheWidths_) { - Blockly.Field.cacheWidths_[key] = width; - } - } - if (this.borderRect_) { - this.borderRect_.setAttribute('width', - width + Blockly.BlockSvg.SEP_SPACE_X); - } - } else { - var width = 0; - } - this.size_.width = width; -}; - -/** - * Start caching field widths. Every call to this function MUST also call - * stopCache. Caches must not survive between execution threads. - */ -Blockly.Field.startCache = function() { - Blockly.Field.cacheReference_++; - if (!Blockly.Field.cacheWidths_) { - Blockly.Field.cacheWidths_ = {}; - } -}; - -/** - * Stop caching field widths. Unless caching was already on when the - * corresponding call to startCache was made. - */ -Blockly.Field.stopCache = function() { - Blockly.Field.cacheReference_--; - if (!Blockly.Field.cacheReference_) { - Blockly.Field.cacheWidths_ = null; - } -}; - -/** - * Returns the height and width of the field. - * @return {!goog.math.Size} Height and width. - */ -Blockly.Field.prototype.getSize = function() { - if (!this.size_.width) { - this.render_(); - } - return this.size_; -}; - -/** - * Returns the height and width of the field, - * accounting for the workspace scaling. - * @return {!goog.math.Size} Height and width. - * @private - */ -Blockly.Field.prototype.getScaledBBox_ = function() { - var bBox = this.borderRect_.getBBox(); - // Create new object, as getBBox can return an uneditable SVGRect in IE. - return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale, - bBox.height * this.sourceBlock_.workspace.scale); -}; - -/** - * Get the text from this field. - * @return {string} Current text. - */ -Blockly.Field.prototype.getText = function() { - return this.text_; -}; - -/** - * Set the text in this field. Trigger a rerender of the source block. - * @param {*} text New text. - */ -Blockly.Field.prototype.setText = function(text) { - if (text === null) { - // No change if null. - return; - } - text = String(text); - if (text === this.text_) { - // No change. - return; - } - this.text_ = text; - this.updateTextNode_(); - - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours_(); - } -}; - -/** - * Update the text node of this field to display the current text. - * @private - */ -Blockly.Field.prototype.updateTextNode_ = function() { - if (!this.textElement_) { - // Not rendered yet. - return; - } - var text = this.text_; - if (text.length > this.maxDisplayLength) { - // Truncate displayed string and add an ellipsis ('...'). - text = text.substring(0, this.maxDisplayLength - 2) + '\u2026'; - } - // Empty the text element. - goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_)); - // Replace whitespace with non-breaking spaces so the text doesn't collapse. - text = text.replace(/\s/g, Blockly.Field.NBSP); - if (this.sourceBlock_.RTL && text) { - // The SVG is LTR, force text to be RTL. - text += '\u200F'; - } - if (!text) { - // Prevent the field from disappearing if empty. - text = Blockly.Field.NBSP; - } - var textNode = document.createTextNode(text); - this.textElement_.appendChild(textNode); - - // Cached width is obsolete. Clear it. - this.size_.width = 0; -}; - -/** - * By default there is no difference between the human-readable text and - * the language-neutral values. Subclasses (such as dropdown) may define this. - * @return {string} Current text. - */ -Blockly.Field.prototype.getValue = function() { - return this.getText(); -}; - -/** - * By default there is no difference between the human-readable text and - * the language-neutral values. Subclasses (such as dropdown) may define this. - * @param {string} newText New text. - */ -Blockly.Field.prototype.setValue = function(newText) { - if (newText === null) { - // No change if null. - return; - } - var oldText = this.getValue(); - if (oldText == newText) { - return; - } - if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, oldText, newText)); - } - this.setText(newText); -}; - -/** - * Handle a mouse up event on an editable field. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.Field.prototype.onMouseUp_ = function(e) { - if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) && - !goog.userAgent.isVersionOrHigher('537.51.2') && - e.layerX !== 0 && e.layerY !== 0) { - // Old iOS spawns a bogus event on the next touch after a 'prompt()' edit. - // Unlike the real events, these have a layerX and layerY set. - return; - } else if (Blockly.isRightButton(e)) { - // Right-click. - return; - } else if (this.sourceBlock_.workspace.isDragging()) { - // Drag operation is concluding. Don't open the editor. - return; - } else if (this.sourceBlock_.isEditable()) { - // Non-abstract sub-classes must define a showEditor_ method. - this.showEditor_(); - } -}; - -/** - * Change the tooltip text for this field. - * @param {string|!Element} newTip Text for tooltip or a parent element to - * link to for its tooltip. - */ -Blockly.Field.prototype.setTooltip = function(newTip) { - // Non-abstract sub-classes may wish to implement this. See FieldLabel. -}; - -/** - * Return the absolute coordinates of the top-left corner of this field. - * The origin (0,0) is the top-left corner of the page body. - * @return {!goog.math.Coordinate} Object with .x and .y properties. - * @private - */ -Blockly.Field.prototype.getAbsoluteXY_ = function() { - return goog.style.getPageOffset(this.borderRect_); -}; diff --git a/core/field_dropdown.js.orig b/core/field_dropdown.js.orig deleted file mode 100644 index ec3dd4f5a..000000000 --- a/core/field_dropdown.js.orig +++ /dev/null @@ -1,320 +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 Dropdown input field. Used for editable titles and variables. - * In the interests of a consistent UI, the toolbox shares some functions and - * properties with the context menu. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.FieldDropdown'); - -goog.require('Blockly.Field'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.style'); -goog.require('goog.ui.Menu'); -goog.require('goog.ui.MenuItem'); -goog.require('goog.userAgent'); - - -/** - * Class for an editable dropdown field. - * @param {(!Array.>|!Function)} menuGenerator An array of - * options for a dropdown list, or a function which generates these options. - * @param {Function=} opt_validator A function that is executed when a new - * option is selected, with the newly selected value as its sole argument. - * If it returns a value, that value (which must be one of the options) will - * become selected in place of the newly selected option, unless the return - * value is null, in which case the change is aborted. - * @extends {Blockly.Field} - * @constructor - */ -Blockly.FieldDropdown = function(menuGenerator, opt_validator) { - this.menuGenerator_ = menuGenerator; - this.trimOptions_(); - var firstTuple = this.getOptions_()[0]; - - // Call parent's constructor. - Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1], - opt_validator); -}; -goog.inherits(Blockly.FieldDropdown, Blockly.Field); - -/** - * Horizontal distance that a checkmark ovehangs the dropdown. - */ -Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25; - -/** - * Android can't (in 2014) display "▾", so use "▼" instead. - */ -Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE'; - -/** - * Mouse cursor style when over the hotspot that initiates the editor. - */ -Blockly.FieldDropdown.prototype.CURSOR = 'default'; - -/** - * Install this dropdown on a block. - */ -Blockly.FieldDropdown.prototype.init = function() { - if (this.fieldGroup_) { - // Dropdown has already been initialized once. - return; - } - // Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL) - this.arrow_ = Blockly.createSvgElement('tspan', {}, null); - this.arrow_.appendChild(document.createTextNode( - this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' : - ' ' + Blockly.FieldDropdown.ARROW_CHAR)); - - Blockly.FieldDropdown.superClass_.init.call(this); - // Force a reset of the text to add the arrow. - var text = this.text_; - this.text_ = null; - this.setText(text); -}; - -/** - * Create a dropdown menu under the text. - * @private - */ -Blockly.FieldDropdown.prototype.showEditor_ = function() { - Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null); - var thisField = this; - - function callback(e) { - var menuItem = e.target; - if (menuItem) { - var value = menuItem.getValue(); - if (thisField.sourceBlock_) { - // Call any validation function, and allow it to override. - value = thisField.callValidator(value); - } - if (value !== null) { - thisField.setValue(value); - } - } - Blockly.WidgetDiv.hideIfOwner(thisField); - } - - var menu = new goog.ui.Menu(); - menu.setRightToLeft(this.sourceBlock_.RTL); - var options = this.getOptions_(); - for (var i = 0; i < options.length; i++) { - var text = options[i][0]; // Human-readable text. - var value = options[i][1]; // Language-neutral value. - var menuItem = new goog.ui.MenuItem(text); - menuItem.setRightToLeft(this.sourceBlock_.RTL); - menuItem.setValue(value); - menuItem.setCheckable(true); - menu.addChild(menuItem, true); - menuItem.setChecked(value == this.value_); - } - // Listen for mouse/keyboard events. - goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback); - // Listen for touch events (why doesn't Closure handle this already?). - function callbackTouchStart(e) { - var control = this.getOwnerControl(/** @type {Node} */ (e.target)); - // Highlight the menu item. - control.handleMouseDown(e); - } - function callbackTouchEnd(e) { - var control = this.getOwnerControl(/** @type {Node} */ (e.target)); - // Activate the menu item. - control.performActionInternal(e); - } - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART, - callbackTouchStart); - menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND, - callbackTouchEnd); - - // Record windowSize and scrollOffset before adding menu. - var windowSize = goog.dom.getViewportSize(); - var scrollOffset = goog.style.getViewportPageOffset(document); - var xy = this.getAbsoluteXY_(); - var borderBBox = this.getScaledBBox_(); - var div = Blockly.WidgetDiv.DIV; - menu.render(div); - var menuDom = menu.getElement(); - Blockly.addClass_(menuDom, 'blocklyDropdownMenu'); - // Record menuSize after adding menu. - var menuSize = goog.style.getSize(menuDom); - // Recalculate height for the total content, not only box height. - menuSize.height = menuDom.scrollHeight; - - // Position the menu. - // Flip menu vertically if off the bottom. - if (xy.y + menuSize.height + borderBBox.height >= - windowSize.height + scrollOffset.y) { - xy.y -= menuSize.height + 2; - } else { - xy.y += borderBBox.height; - } - if (this.sourceBlock_.RTL) { - xy.x += borderBBox.width; - xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG; - // Don't go offscreen left. - if (xy.x < scrollOffset.x + menuSize.width) { - xy.x = scrollOffset.x + menuSize.width; - } - } else { - xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG; - // Don't go offscreen right. - if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) { - xy.x = windowSize.width + scrollOffset.x - menuSize.width; - } - } - Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset, - this.sourceBlock_.RTL); - menu.setAllowAutoFocus(true); - menuDom.focus(); -}; - -/** - * Factor out common words in statically defined options. - * Create prefix and/or suffix labels. - * @private - */ -Blockly.FieldDropdown.prototype.trimOptions_ = function() { - this.prefixField = null; - this.suffixField = null; - var options = this.menuGenerator_; - if (!goog.isArray(options) || options.length < 2) { - return; - } - var strings = options.map(function(t) {return t[0];}); - var shortest = Blockly.shortestStringLength(strings); - var prefixLength = Blockly.commonWordPrefix(strings, shortest); - var suffixLength = Blockly.commonWordSuffix(strings, shortest); - if (!prefixLength && !suffixLength) { - return; - } - if (shortest <= prefixLength + suffixLength) { - // One or more strings will entirely vanish if we proceed. Abort. - return; - } - if (prefixLength) { - this.prefixField = strings[0].substring(0, prefixLength - 1); - } - if (suffixLength) { - this.suffixField = strings[0].substr(1 - suffixLength); - } - // Remove the prefix and suffix from the options. - var newOptions = []; - for (var i = 0; i < options.length; i++) { - var text = options[i][0]; - var value = options[i][1]; - text = text.substring(prefixLength, text.length - suffixLength); - newOptions[i] = [text, value]; - } - this.menuGenerator_ = newOptions; -}; - -/** - * Return a list of the options for this dropdown. - * @return {!Array.>} Array of option tuples: - * (human-readable text, language-neutral name). - * @private - */ -Blockly.FieldDropdown.prototype.getOptions_ = function() { - if (goog.isFunction(this.menuGenerator_)) { - return this.menuGenerator_.call(this); - } - return /** @type {!Array.>} */ (this.menuGenerator_); -}; - -/** - * Get the language-neutral value from this dropdown menu. - * @return {string} Current text. - */ -Blockly.FieldDropdown.prototype.getValue = function() { - return this.value_; -}; - -/** - * Set the language-neutral value for this dropdown menu. - * @param {string} newValue New value to set. - */ -Blockly.FieldDropdown.prototype.setValue = function(newValue) { - if (newValue === null || newValue === this.value_) { - return; // No change if null. - } - if (this.sourceBlock_ && Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Change( - this.sourceBlock_, 'field', this.name, this.value_, newValue)); - } - this.value_ = newValue; - // Look up and display the human-readable text. - var options = this.getOptions_(); - for (var i = 0; i < options.length; i++) { - // Options are tuples of human-readable text and language-neutral values. - if (options[i][1] == newValue) { - this.setText(options[i][0]); - return; - } - } - // Value not found. Add it, maybe it will become valid once set - // (like variable names). - this.setText(newValue); -}; - -/** - * Set the text in this field. Trigger a rerender of the source block. - * @param {?string} text New text. - */ -Blockly.FieldDropdown.prototype.setText = function(text) { - if (this.sourceBlock_ && this.arrow_) { - // Update arrow's colour. - this.arrow_.style.fill = this.sourceBlock_.getColour(); - } - if (text === null || text === this.text_) { - // No change if null. - return; - } - this.text_ = text; - this.updateTextNode_(); - - if (this.textElement_) { - // Insert dropdown arrow. - if (this.sourceBlock_.RTL) { - this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild); - } else { - this.textElement_.appendChild(this.arrow_); - } - } - - if (this.sourceBlock_ && this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - this.sourceBlock_.bumpNeighbours_(); - } -}; - -/** - * Close the dropdown menu if this input is being deleted. - */ -Blockly.FieldDropdown.prototype.dispose = function() { - Blockly.WidgetDiv.hideIfOwner(this); - Blockly.FieldDropdown.superClass_.dispose.call(this); -}; diff --git a/core/flyout.js.orig b/core/flyout.js.orig deleted file mode 100644 index 03ea15059..000000000 --- a/core/flyout.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 Flyout tray containing blocks which may be created. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Flyout'); - -goog.require('Blockly.Block'); -goog.require('Blockly.Comment'); -goog.require('Blockly.Events'); -goog.require('Blockly.FlyoutButton'); -goog.require('Blockly.WorkspaceSvg'); -goog.require('goog.dom'); -goog.require('goog.events'); -goog.require('goog.math.Rect'); -goog.require('goog.userAgent'); - - -/** - * Class for a flyout. - * @param {!Object} workspaceOptions Dictionary of options for the workspace. - * @constructor - */ -Blockly.Flyout = function(workspaceOptions) { - workspaceOptions.getMetrics = this.getMetrics_.bind(this); - workspaceOptions.setMetrics = this.setMetrics_.bind(this); - /** - * @type {!Blockly.Workspace} - * @private - */ - this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); - this.workspace_.isFlyout = true; - - /** - * Is RTL vs LTR. - * @type {boolean} - */ - this.RTL = !!workspaceOptions.RTL; - - /** - * Flyout should be laid out horizontally vs vertically. - * @type {boolean} - * @private - */ - this.horizontalLayout_ = workspaceOptions.horizontalLayout; - - /** - * Position of the toolbox and flyout relative to the workspace. - * @type {number} - * @private - */ - this.toolboxPosition_ = workspaceOptions.toolboxPosition; - - /** - * Opaque data that can be passed to Blockly.unbindEvent_. - * @type {!Array.} - * @private - */ - this.eventWrappers_ = []; - - /** - * List of background buttons that lurk behind each block to catch clicks - * landing in the blocks' lakes and bays. - * @type {!Array.} - * @private - */ - this.backgroundButtons_ = []; - - /** - * List of visible buttons. - * @type {!Array.} - * @private - */ - this.buttons_ = []; - - /** - * List of event listeners. - * @type {!Array.} - * @private - */ - this.listeners_ = []; - - /** - * List of blocks that should always be disabled. - * @type {!Array.} - * @private - */ - this.permanentlyDisabled_ = []; - - /** - * y coordinate of mousedown - used to calculate scroll distances. - * @private {number} - */ - this.startDragMouseY_ = 0; - - /** - * x coordinate of mousedown - used to calculate scroll distances. - * @private {number} - */ - this.startDragMouseX_ = 0; -}; - -/** - * When a flyout drag is in progress, this is a reference to the flyout being - * dragged. This is used by Flyout.terminateDrag_ to reset dragMode_. - * @private {Blockly.Flyout} - */ -Blockly.Flyout.startFlyout_ = null; - -/** - * Event that started a drag. Used to determine the drag distance/direction and - * also passed to BlockSvg.onMouseDown_() after creating a new block. - * @private {Event} - */ -Blockly.Flyout.startDownEvent_ = null; - -/** - * Flyout block where the drag/click was initiated. Used to fire click events or - * create a new block. - * @private {Event} - */ -Blockly.Flyout.startBlock_ = null; - -/** - * Wrapper function called when a mouseup occurs during a background or block - * drag operation. - * @private {Array.} - */ -Blockly.Flyout.onMouseUpWrapper_ = null; - -/** - * Wrapper function called when a mousemove occurs during a background drag. - * @private {Array.} - */ -Blockly.Flyout.onMouseMoveWrapper_ = null; - -/** - * Wrapper function called when a mousemove occurs during a block drag. - * @private {Array.} - */ -Blockly.Flyout.onMouseMoveBlockWrapper_ = null; - -/** - * Does the flyout automatically close when a block is created? - * @type {boolean} - */ -Blockly.Flyout.prototype.autoClose = true; - -/** - * Corner radius of the flyout background. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.CORNER_RADIUS = 8; - -/** - * Number of pixels the mouse must move before a drag/scroll starts. Because the - * drag-intention is determined when this is reached, it is larger than - * Blockly.DRAG_RADIUS so that the drag-direction is clearer. - */ -Blockly.Flyout.prototype.DRAG_RADIUS = 10; - -/** - * Margin around the edges of the blocks in the flyout. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS; - -/** - * Gap between items in horizontal flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ -Blockly.Flyout.prototype.GAP_X = Blockly.Flyout.prototype.MARGIN * 3; - -/** - * Gap between items in vertical flyouts. Can be overridden with the "sep" - * element. - * @const {number} - */ -Blockly.Flyout.prototype.GAP_Y = Blockly.Flyout.prototype.MARGIN * 3; - -/** - * Top/bottom padding between scrollbar and edge of flyout background. - * @type {number} - * @const - */ -Blockly.Flyout.prototype.SCROLLBAR_PADDING = 2; - -/** - * Width of flyout. - * @type {number} - * @private - */ -Blockly.Flyout.prototype.width_ = 0; - -/** - * Height of flyout. - * @type {number} - * @private - */ -Blockly.Flyout.prototype.height_ = 0; - -/** - * Is the flyout dragging (scrolling)? - * DRAG_NONE - no drag is ongoing or state is undetermined. - * DRAG_STICKY - still within the sticky drag radius. - * DRAG_FREE - in scroll mode (never create a new block). - * @private - */ -Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE; - -/** - * Range of a drag angle from a flyout considered "dragging toward workspace". - * Drags that are within the bounds of this many degrees from the orthogonal - * line to the flyout edge are considered to be "drags toward the workspace". - * Example: - * Flyout Edge Workspace - * [block] / <-within this angle, drags "toward workspace" | - * [block] ---- orthogonal to flyout boundary ---- | - * [block] \ | - * The angle is given in degrees from the orthogonal. - * - * This is used to know when to create a new block and when to scroll the - * flyout. Setting it to 360 means that all drags create a new block. - * @type {number} - * @private -*/ -Blockly.Flyout.prototype.dragAngleRange_ = 70; - -/** - * Creates the flyout's DOM. Only needs to be called once. - * @return {!Element} The flyout's SVG group. - */ -Blockly.Flyout.prototype.createDom = function() { - /* - - - - - */ - this.svgGroup_ = Blockly.createSvgElement('g', - {'class': 'blocklyFlyout'}, null); - this.svgBackground_ = Blockly.createSvgElement('path', - {'class': 'blocklyFlyoutBackground'}, this.svgGroup_); - this.svgGroup_.appendChild(this.workspace_.createDom()); - return this.svgGroup_; -}; - -/** - * Initializes the flyout. - * @param {!Blockly.Workspace} targetWorkspace The workspace in which to create - * new blocks. - */ -Blockly.Flyout.prototype.init = function(targetWorkspace) { - this.targetWorkspace_ = targetWorkspace; - this.workspace_.targetWorkspace = targetWorkspace; - // Add scrollbar. - this.scrollbar_ = new Blockly.Scrollbar(this.workspace_, - this.horizontalLayout_, false); - - this.hide(); - - Array.prototype.push.apply(this.eventWrappers_, - Blockly.bindEvent_(this.svgGroup_, 'wheel', this, this.wheel_)); - if (!this.autoClose) { - this.filterWrapper_ = this.filterForCapacity_.bind(this); - this.targetWorkspace_.addChangeListener(this.filterWrapper_); - } - // Dragging the flyout up and down. - Array.prototype.push.apply(this.eventWrappers_, - Blockly.bindEvent_(this.svgGroup_, 'mousedown', this, this.onMouseDown_)); -}; - -/** - * Dispose of this flyout. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Flyout.prototype.dispose = function() { - this.hide(); - Blockly.unbindEvent_(this.eventWrappers_); - if (this.filterWrapper_) { - this.targetWorkspace_.removeChangeListener(this.filterWrapper_); - this.filterWrapper_ = null; - } - if (this.scrollbar_) { - this.scrollbar_.dispose(); - this.scrollbar_ = null; - } - if (this.workspace_) { - this.workspace_.targetWorkspace = null; - this.workspace_.dispose(); - this.workspace_ = null; - } - if (this.svgGroup_) { - goog.dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; - } - this.svgBackground_ = null; - this.targetWorkspace_ = null; -}; - -/** - * Get the width of the flyout. - * @return {number} The width of the flyout. - */ -Blockly.Flyout.prototype.getWidth = function() { - return this.width_; -}; - -/** - * Get the height of the flyout. - * @return {number} The width of the flyout. - */ -Blockly.Flyout.prototype.getHeight = function() { - return this.height_; -}; - -/** - * Return an object with all the metrics required to size scrollbars for the - * flyout. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the contents, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .absoluteTop: Top-edge of view. - * .viewLeft: Offset of the left edge of visible rectangle from parent, - * .contentLeft: Offset of the left-most content from the x=0 coordinate, - * .absoluteLeft: Left-edge of view. - * @return {Object} Contains size and position metrics of the flyout. - * @private - */ -Blockly.Flyout.prototype.getMetrics_ = function() { - if (!this.isVisible()) { - // Flyout is hidden. - return null; - } - - try { - var optionBox = this.workspace_.getCanvas().getBBox(); - } catch (e) { - // Firefox has trouble with hidden elements (Bug 528969). - var optionBox = {height: 0, y: 0, width: 0, x: 0}; - } - - var absoluteTop = this.SCROLLBAR_PADDING; - var absoluteLeft = this.SCROLLBAR_PADDING; - if (this.horizontalLayout_) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - absoluteTop = 0; - } - var viewHeight = this.height_; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - viewHeight += this.MARGIN - this.SCROLLBAR_PADDING; - } - var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING; - } else { - absoluteLeft = 0; - var viewHeight = this.height_ - 2 * this.SCROLLBAR_PADDING; - var viewWidth = this.width_; - if (!this.RTL) { - viewWidth -= this.SCROLLBAR_PADDING; - } - } - - var metrics = { - viewHeight: viewHeight, - viewWidth: viewWidth, - contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale, - contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale, - viewTop: -this.workspace_.scrollY, - viewLeft: -this.workspace_.scrollX, - contentTop: optionBox.y, - contentLeft: optionBox.x, - absoluteTop: absoluteTop, - absoluteLeft: absoluteLeft - }; - return metrics; -}; - -/** - * Sets the translation of the flyout to match the scrollbars. - * @param {!Object} xyRatio Contains a y property which is a float - * between 0 and 1 specifying the degree of scrolling and a - * similar x property. - * @private - */ -Blockly.Flyout.prototype.setMetrics_ = function(xyRatio) { - var metrics = this.getMetrics_(); - // This is a fix to an apparent race condition. - if (!metrics) { - return; - } - if (!this.horizontalLayout_ && goog.isNumber(xyRatio.y)) { - this.workspace_.scrollY = -metrics.contentHeight * xyRatio.y; - } else if (this.horizontalLayout_ && goog.isNumber(xyRatio.x)) { - this.workspace_.scrollX = -metrics.contentWidth * xyRatio.x; - } - - this.workspace_.translate(this.workspace_.scrollX + metrics.absoluteLeft, - this.workspace_.scrollY + metrics.absoluteTop); -}; - -/** - * Move the flyout to the edge of the workspace. - */ -Blockly.Flyout.prototype.position = function() { - if (!this.isVisible()) { - return; - } - var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics(); - if (!targetWorkspaceMetrics) { - // Hidden components will return null. - return; - } - var edgeWidth = this.horizontalLayout_ ? - targetWorkspaceMetrics.viewWidth : this.width_; - edgeWidth -= this.CORNER_RADIUS; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - edgeWidth *= -1; - } - - this.setBackgroundPath_(edgeWidth, - this.horizontalLayout_ ? this.height_ : - targetWorkspaceMetrics.viewHeight); - - var x = targetWorkspaceMetrics.absoluteLeft; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - x += targetWorkspaceMetrics.viewWidth; - x -= this.width_; - } - - var y = targetWorkspaceMetrics.absoluteTop; - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - y += targetWorkspaceMetrics.viewHeight; - y -= this.height_; - } - - this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')'); - - // Record the height for Blockly.Flyout.getMetrics_, or width if the layout is - // horizontal. - if (this.horizontalLayout_) { - this.width_ = targetWorkspaceMetrics.viewWidth; - } else { - this.height_ = targetWorkspaceMetrics.viewHeight; - } - - // Update the scrollbar (if one exists). - if (this.scrollbar_) { - this.scrollbar_.resize(); - } -}; - -/** - * Create and set the path for the visible boundaries of the flyout. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPath_ = function(width, height) { - if (this.horizontalLayout_) { - this.setBackgroundPathHorizontal_(width, height); - } else { - this.setBackgroundPathVertical_(width, height); - } -}; - -/** - * Create and set the path for the visible boundaries of the flyout in vertical - * mode. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) { - var atRight = this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT; - // Decide whether to start on the left or right. - var path = ['M ' + (atRight ? this.width_ : 0) + ',0']; - // Top. - path.push('h', width); - // Rounded corner. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - atRight ? 0 : 1, - atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, - this.CORNER_RADIUS); - // Side closest to workspace. - path.push('v', Math.max(0, height - this.CORNER_RADIUS * 2)); - // Rounded corner. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, - atRight ? 0 : 1, - atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, - this.CORNER_RADIUS); - // Bottom. - path.push('h', -width); - path.push('z'); - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Create and set the path for the visible boundaries of the flyout in - * horizontal mode. - * @param {number} width The width of the flyout, not including the - * rounded corners. - * @param {number} height The height of the flyout, not including - * rounded corners. - * @private - */ -Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width, - height) { - var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP; - // Start at top left. - var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)]; - - if (atTop) { - // Top. - path.push('h', width + this.CORNER_RADIUS); - // Right. - path.push('v', height); - // Bottom. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('h', -1 * (width - this.CORNER_RADIUS)); - // Left. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - -this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('z'); - } else { - // Top. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, -this.CORNER_RADIUS); - path.push('h', width - this.CORNER_RADIUS); - // Right. - path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1, - this.CORNER_RADIUS, this.CORNER_RADIUS); - path.push('v', height - this.CORNER_RADIUS); - // Bottom. - path.push('h', -width - this.CORNER_RADIUS); - // Left. - path.push('z'); - } - this.svgBackground_.setAttribute('d', path.join(' ')); -}; - -/** - * Scroll the flyout to the top. - */ -Blockly.Flyout.prototype.scrollToStart = function() { - this.scrollbar_.set((this.horizontalLayout_ && this.RTL) ? Infinity : 0); -}; - -/** - * Scroll the flyout. - * @param {!Event} e Mouse wheel scroll event. - * @private - */ -Blockly.Flyout.prototype.wheel_ = function(e) { - var delta = this.horizontalLayout_ ? e.deltaX : e.deltaY; - - if (delta) { - if (goog.userAgent.GECKO) { - // Firefox's deltas are a tenth that of Chrome/Safari. - delta *= 10; - } - var metrics = this.getMetrics_(); - var pos = this.horizontalLayout_ ? metrics.viewLeft + delta : - metrics.viewTop + delta; - var limit = this.horizontalLayout_ ? - metrics.contentWidth - metrics.viewWidth : - metrics.contentHeight - metrics.viewHeight; - pos = Math.min(pos, limit); - pos = Math.max(pos, 0); - this.scrollbar_.set(pos); - } - - // Don't scroll the page. - e.preventDefault(); - // Don't propagate mousewheel event (zooming). - e.stopPropagation(); -}; - -/** - * Is the flyout visible? - * @return {boolean} True if visible. - */ -Blockly.Flyout.prototype.isVisible = function() { - return this.svgGroup_ && this.svgGroup_.style.display == 'block'; -}; - -/** - * Hide and empty the flyout. - */ -Blockly.Flyout.prototype.hide = function() { - if (!this.isVisible()) { - return; - } - this.svgGroup_.style.display = 'none'; - // Delete all the event listeners. - for (var x = 0, listen; listen = this.listeners_[x]; x++) { - Blockly.unbindEvent_(listen); - } - this.listeners_.length = 0; - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); - this.reflowWrapper_ = null; - } - // Do NOT delete the blocks here. Wait until Flyout.show. - // https://neil.fraser.name/news/2014/08/09/ -}; - -/** - * Show and populate the flyout. - * @param {!Array|string} xmlList List of blocks to show. - * Variables and procedures have a custom set of blocks. - */ -Blockly.Flyout.prototype.show = function(xmlList) { - this.hide(); - this.clearOldBlocks_(); - - if (xmlList == Blockly.Variables.NAME_TYPE) { - // Special category for variables. - xmlList = - Blockly.Variables.flyoutCategory(this.workspace_.targetWorkspace); - } else if (xmlList == Blockly.Procedures.NAME_TYPE) { - // Special category for procedures. - xmlList = - Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace); - } - - this.svgGroup_.style.display = 'block'; - // Create the blocks to be shown in this flyout. - var contents = []; - var gaps = []; - this.permanentlyDisabled_.length = 0; - for (var i = 0, xml; xml = xmlList[i]; i++) { - if (xml.tagName) { - var tagName = xml.tagName.toUpperCase(); - var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y; - if (tagName == 'BLOCK') { - var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_); - if (curBlock.disabled) { - // Record blocks that were initially disabled. - // Do not enable these blocks as a result of capacity filtering. - this.permanentlyDisabled_.push(curBlock); - } - contents.push({type: 'block', block: curBlock}); - var gap = parseInt(xml.getAttribute('gap'), 10); - gaps.push(isNaN(gap) ? default_gap : gap); - } else if (xml.tagName.toUpperCase() == 'SEP') { - // Change the gap between two blocks. - // - // The default gap is 24, can be set larger or smaller. - // This overwrites the gap attribute on the previous block. - // Note that a deprecated method is to add a gap to a block. - // - var newGap = parseInt(xml.getAttribute('gap'), 10); - // Ignore gaps before the first block. - if (!isNaN(newGap) && gaps.length > 0) { - gaps[gaps.length - 1] = newGap; - } else { - gaps.push(default_gap); - } - } else if (tagName == 'BUTTON') { - var label = xml.getAttribute('text'); - var curButton = new Blockly.FlyoutButton(this.workspace_, - this.targetWorkspace_, label); - contents.push({type: 'button', button: curButton}); - gaps.push(default_gap); - } - } - } - - this.layout_(contents, gaps); - - // IE 11 is an incompetent browser that fails to fire mouseout events. - // When the mouse is over the background, deselect all blocks. - var deselectAll = function() { - var topBlocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = topBlocks[i]; i++) { - block.removeSelect(); - } - }; - - this.listeners_.push(Blockly.bindEvent_(this.svgBackground_, 'mouseover', - this, deselectAll)); - - if (this.horizontalLayout_) { - this.height_ = 0; - } else { - this.width_ = 0; - } - this.reflow(); - - this.filterForCapacity_(); - - // Correctly position the flyout's scrollbar when it opens. - this.position(); - - this.reflowWrapper_ = this.reflow.bind(this); - this.workspace_.addChangeListener(this.reflowWrapper_); -}; - -/** - * Lay out the blocks in the flyout. - * @param {!Array.} contents The blocks and buttons to lay out. - * @param {!Array.} gaps The visible gaps between blocks. - * @private - */ -Blockly.Flyout.prototype.layout_ = function(contents, gaps) { - this.workspace_.scale = this.targetWorkspace_.scale; - var margin = this.MARGIN; - var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH; - var cursorY = margin; - if (this.horizontalLayout_ && this.RTL) { - contents = contents.reverse(); - } - - for (var i = 0, item; item = contents[i]; i++) { - if (item.type == 'block') { - var block = item.block; - var allBlocks = block.getDescendants(); - for (var j = 0, child; child = allBlocks[j]; j++) { - // Mark blocks as being inside a flyout. This is used to detect and - // prevent the closure of the flyout if the user right-clicks on such a - // block. - child.isInFlyout = true; - } - block.render(); - var root = block.getSvgRoot(); - var blockHW = block.getHeightWidth(); - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - if (this.horizontalLayout_) { - cursorX += tab; - } - block.moveBy((this.horizontalLayout_ && this.RTL) ? - cursorX + blockHW.width - tab : cursorX, - cursorY); - if (this.horizontalLayout_) { - cursorX += (blockHW.width + gaps[i] - tab); - } else { - cursorY += blockHW.height + gaps[i]; - } - - // Create an invisible rectangle under the block to act as a button. Just - // using the block as a button is poor, since blocks have holes in them. - var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null); - rect.tooltip = block; - Blockly.Tooltip.bindMouseEvents(rect); - // Add the rectangles under the blocks, so that the blocks' tooltips work. - this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot()); - block.flyoutRect_ = rect; - this.backgroundButtons_[i] = rect; - - this.addBlockListeners_(root, block, rect); - } else if (item.type == 'button') { - var button = item.button; - var buttonSvg = button.createDom(); - button.moveTo(cursorX, cursorY); - button.show(); - Blockly.bindEvent_(buttonSvg, 'mouseup', button, button.onMouseUp); - - this.buttons_.push(button); - if (this.horizontalLayout_) { - cursorX += (button.width + gaps[i]); - } else { - cursorY += button.height + gaps[i]; - } - } - } -}; - -/** - * Delete blocks and background buttons from a previous showing of the flyout. - * @private - */ -Blockly.Flyout.prototype.clearOldBlocks_ = function() { - // Delete any blocks from a previous showing. - var oldBlocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = oldBlocks[i]; i++) { - if (block.workspace == this.workspace_) { - block.dispose(false, false); - } - } - // Delete any background buttons from a previous showing. - for (var j = 0, rect; rect = this.backgroundButtons_[j]; j++) { - goog.dom.removeNode(rect); - } - this.backgroundButtons_.length = 0; - - for (var i = 0, button; button = this.buttons_[i]; i++) { - button.dispose(); - } - this.buttons_.length = 0; -}; - -/** - * Add listeners to a block that has been added to the flyout. - * @param {!Element} root The root node of the SVG group the block is in. - * @param {!Blockly.Block} block The block to add listeners for. - * @param {!Element} rect The invisible rectangle under the block that acts as - * a button for that block. - * @private - */ -Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) { - this.listeners_.push(Blockly.bindEvent_(root, 'mousedown', null, - this.blockMouseDown_(block))); - this.listeners_.push(Blockly.bindEvent_(rect, 'mousedown', null, - this.blockMouseDown_(block))); - this.listeners_.push(Blockly.bindEvent_(root, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(root, 'mouseout', block, - block.removeSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseover', block, - block.addSelect)); - this.listeners_.push(Blockly.bindEvent_(rect, 'mouseout', block, - block.removeSelect)); -}; - -/** - * Handle a mouse-down on an SVG block in a non-closing flyout. - * @param {!Blockly.Block} block The flyout block to copy. - * @return {!Function} Function to call when block is clicked. - * @private - */ -Blockly.Flyout.prototype.blockMouseDown_ = function(block) { - var flyout = this; - return function(e) { - Blockly.terminateDrag_(); - Blockly.hideChaff(true); - if (Blockly.isRightButton(e)) { - // Right-click. - block.showContextMenu_(e); - } else { - // Left-click (or middle click) - Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED); - // Record the current mouse position. - flyout.startDragMouseY_ = e.clientY; - flyout.startDragMouseX_ = e.clientX; - Blockly.Flyout.startDownEvent_ = e; - Blockly.Flyout.startBlock_ = block; - Blockly.Flyout.startFlyout_ = flyout; - Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', flyout, flyout.onMouseUp_); - Blockly.Flyout.onMouseMoveBlockWrapper_ = Blockly.bindEvent_(document, - 'mousemove', flyout, flyout.onMouseMoveBlock_); - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - e.preventDefault(); - }; -}; - -/** - * Mouse down on the flyout background. Start a vertical scroll drag. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Flyout.prototype.onMouseDown_ = function(e) { - if (Blockly.isRightButton(e)) { - return; - } - Blockly.hideChaff(true); - this.dragMode_ = Blockly.DRAG_FREE; - this.startDragMouseY_ = e.clientY; - this.startDragMouseX_ = e.clientX; - Blockly.Flyout.startFlyout_ = this; - Blockly.Flyout.onMouseMoveWrapper_ = Blockly.bindEvent_(document, 'mousemove', - this, this.onMouseMove_); - Blockly.Flyout.onMouseUpWrapper_ = Blockly.bindEvent_(document, 'mouseup', - this, Blockly.Flyout.terminateDrag_); - // This event has been handled. No need to bubble up to the document. - e.preventDefault(); - e.stopPropagation(); -}; - -/** - * Handle a mouse-up anywhere in the SVG pane. Is only registered when a - * block is clicked. We can't use mouseUp on the block since a fast-moving - * cursor can briefly escape the block before it catches up. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.Flyout.prototype.onMouseUp_ = function(e) { - if (!this.workspace_.isDragging()) { - if (this.autoClose) { - this.createBlockFunc_(Blockly.Flyout.startBlock_)( - Blockly.Flyout.startDownEvent_); - } else if (!Blockly.WidgetDiv.isVisible()) { - Blockly.Events.fire( - new Blockly.Events.Ui(Blockly.Flyout.startBlock_, 'click', - undefined, undefined)); - } - } - Blockly.terminateDrag_(); -}; - -/** - * Handle a mouse-move to vertically drag the flyout. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Flyout.prototype.onMouseMove_ = function(e) { - var metrics = this.getMetrics_(); - if (this.horizontalLayout_) { - if (metrics.contentWidth - metrics.viewWidth < 0) { - return; - } - var dx = e.clientX - this.startDragMouseX_; - this.startDragMouseX_ = e.clientX; - var x = metrics.viewLeft - dx; - x = goog.math.clamp(x, 0, metrics.contentWidth - metrics.viewWidth); - this.scrollbar_.set(x); - } else { - if (metrics.contentHeight - metrics.viewHeight < 0) { - return; - } - var dy = e.clientY - this.startDragMouseY_; - this.startDragMouseY_ = e.clientY; - var y = metrics.viewTop - dy; - y = goog.math.clamp(y, 0, metrics.contentHeight - metrics.viewHeight); - this.scrollbar_.set(y); - } -}; - -/** - * Mouse button is down on a block in a non-closing flyout. Create the block - * if the mouse moves beyond a small radius. This allows one to play with - * fields without instantiating blocks that instantly self-destruct. - * @param {!Event} e Mouse move event. - * @private - */ -Blockly.Flyout.prototype.onMouseMoveBlock_ = function(e) { - if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 && - e.button == 0) { - /* HACK: - Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove events - on certain touch actions. Ignore events with these signatures. - This may result in a one-pixel blind spot in other browsers, - but this shouldn't be noticeable. */ - e.stopPropagation(); - return; - } - var dx = e.clientX - Blockly.Flyout.startDownEvent_.clientX; - var dy = e.clientY - Blockly.Flyout.startDownEvent_.clientY; - - var createBlock = this.determineDragIntention_(dx, dy); - if (createBlock) { - this.createBlockFunc_(Blockly.Flyout.startBlock_)( - Blockly.Flyout.startDownEvent_); - } else if (this.dragMode_ == Blockly.DRAG_FREE) { - // Do a scroll. - this.onMouseMove_(e); - } - e.stopPropagation(); -}; - -/** - * Determine the intention of a drag. - * Updates dragMode_ based on a drag delta and the current mode, - * and returns true if we should create a new block. - * @param {number} dx X delta of the drag. - * @param {number} dy Y delta of the drag. - * @return {boolean} True if a new block should be created. - * @private - */ -Blockly.Flyout.prototype.determineDragIntention_ = function(dx, dy) { - if (this.dragMode_ == Blockly.DRAG_FREE) { - // Once in free mode, always stay in free mode and never create a block. - return false; - } - var dragDistance = Math.sqrt(dx * dx + dy * dy); - if (dragDistance < this.DRAG_RADIUS) { - // Still within the sticky drag radius. - this.dragMode_ = Blockly.DRAG_STICKY; - return false; - } else { - if (this.isDragTowardWorkspace_(dx, dy) || !this.scrollbar_.isVisible()) { - // Immediately create a block. - return true; - } else { - // Immediately move to free mode - the drag is away from the workspace. - this.dragMode_ = Blockly.DRAG_FREE; - return false; - } - } -}; - -/** - * Determine if a drag delta is toward the workspace, based on the position - * and orientation of the flyout. This is used in determineDragIntention_ to - * determine if a new block should be created or if the flyout should scroll. - * @param {number} dx X delta of the drag. - * @param {number} dy Y delta of the drag. - * @return {boolean} true if the drag is toward the workspace. - * @private - */ -Blockly.Flyout.prototype.isDragTowardWorkspace_ = function(dx, dy) { - // Direction goes from -180 to 180, with 0 toward the right and 90 on top. - var dragDirection = Math.atan2(dy, dx) / Math.PI * 180; - - var draggingTowardWorkspace = false; - var range = this.dragAngleRange_; - if (this.horizontalLayout_) { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - // Horizontal at top. - if (dragDirection < 90 + range && dragDirection > 90 - range) { - draggingTowardWorkspace = true; - } - } else { - // Horizontal at bottom. - if (dragDirection > -90 - range && dragDirection < -90 + range) { - draggingTowardWorkspace = true; - } - } - } else { - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - // Vertical at left. - if (dragDirection < range && dragDirection > -range) { - draggingTowardWorkspace = true; - } - } else { - // Vertical at right. - if (dragDirection < -180 + range || dragDirection > 180 - range) { - draggingTowardWorkspace = true; - } - } - } - return draggingTowardWorkspace; -}; - -/** - * Create a copy of this block on the workspace. - * @param {!Blockly.Block} originBlock The flyout block to copy. - * @return {!Function} Function to call when block is clicked. - * @private - */ -Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) { - var flyout = this; - return function(e) { - if (Blockly.isRightButton(e)) { - // Right-click. Don't create a block, let the context menu show. - return; - } - if (originBlock.disabled) { - // Beyond capacity. - return; - } - Blockly.Events.disable(); - try { - var block = flyout.placeNewBlock_(originBlock); - } finally { - Blockly.Events.enable(); - } - if (Blockly.Events.isEnabled()) { - Blockly.Events.setGroup(true); - Blockly.Events.fire(new Blockly.Events.Create(block)); - } - if (flyout.autoClose) { - flyout.hide(); - } else { - flyout.filterForCapacity_(); - } - // Start a dragging operation on the new block. - block.onMouseDown_(e); - Blockly.dragMode_ = Blockly.DRAG_FREE; - block.setDragging_(true); - }; -}; - -/** - * Copy a block from the flyout to the workspace and position it correctly. - * @param {!Blockly.Block} originBlock The flyout block to copy.. - * @return {!Blockly.Block} The new block in the main workspace. - * @private - */ -Blockly.Flyout.prototype.placeNewBlock_ = function(originBlock) { - var targetWorkspace = this.targetWorkspace_; - var svgRootOld = originBlock.getSvgRoot(); - if (!svgRootOld) { - throw 'originBlock is not rendered.'; - } - // Figure out where the original block is on the screen, relative to the upper - // left corner of the main workspace. - var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace); - // Take into account that the flyout might have been scrolled horizontally - // (separately from the main workspace). - // Generally a no-op in vertical mode but likely to happen in horizontal - // mode. - var scrollX = this.workspace_.scrollX; - var scale = this.workspace_.scale; - xyOld.x += scrollX / scale - scrollX; - // If the flyout is on the right side, (0, 0) in the flyout is offset to - // the right of (0, 0) in the main workspace. Add an offset to take that - // into account. - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) { - scrollX = targetWorkspace.getMetrics().viewWidth - this.width_; - scale = targetWorkspace.scale; - // Scale the scroll (getSvgXY_ did not do this). - xyOld.x += scrollX / scale - scrollX; - } - - // Take into account that the flyout might have been scrolled vertically - // (separately from the main workspace). - // Generally a no-op in horizontal mode but likely to happen in vertical - // mode. - var scrollY = this.workspace_.scrollY; - scale = this.workspace_.scale; - xyOld.y += scrollY / scale - scrollY; - // If the flyout is on the bottom, (0, 0) in the flyout is offset to be below - // (0, 0) in the main workspace. Add an offset to take that into account. - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - scrollY = targetWorkspace.getMetrics().viewHeight - this.height_; - scale = targetWorkspace.scale; - xyOld.y += scrollY / scale - scrollY; - } - - // Create the new block by cloning the block in the flyout (via XML). - var xml = Blockly.Xml.blockToDom(originBlock); - var block = Blockly.Xml.domToBlock(xml, targetWorkspace); - var svgRootNew = block.getSvgRoot(); - if (!svgRootNew) { - throw 'block is not rendered.'; - } - // Figure out where the new block got placed on the screen, relative to the - // upper left corner of the workspace. This may not be the same as the - // original block because the flyout's origin may not be the same as the - // main workspace's origin. - var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace); - // Scale the scroll (getSvgXY_ did not do this). - xyNew.x += - targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX; - xyNew.y += - targetWorkspace.scrollY / targetWorkspace.scale - targetWorkspace.scrollY; - // If the flyout is collapsible and the workspace can't be scrolled. - if (targetWorkspace.toolbox_ && !targetWorkspace.scrollbar) { - xyNew.x += targetWorkspace.toolbox_.getWidth() / targetWorkspace.scale; - xyNew.y += targetWorkspace.toolbox_.getHeight() / targetWorkspace.scale; - } - - // Move the new block to where the old block is. - block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y); - return block; -}; - -/** - * Filter the blocks on the flyout to disable the ones that are above the - * capacity limit. - * @private - */ -Blockly.Flyout.prototype.filterForCapacity_ = function() { - var remainingCapacity = this.targetWorkspace_.remainingCapacity(); - var blocks = this.workspace_.getTopBlocks(false); - for (var i = 0, block; block = blocks[i]; i++) { - if (this.permanentlyDisabled_.indexOf(block) == -1) { - var allBlocks = block.getDescendants(); - block.setDisabled(allBlocks.length > remainingCapacity); - } - } -}; - -/** - * Return the deletion rectangle for this flyout. - * @return {goog.math.Rect} Rectangle in which to delete. - */ -Blockly.Flyout.prototype.getClientRect = function() { - if (!this.svgGroup_) { - return null; - } - - var flyoutRect = this.svgGroup_.getBoundingClientRect(); - // BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout - // area are still deleted. Must be larger than the largest screen size, - // but be smaller than half Number.MAX_SAFE_INTEGER (not available on IE). - var BIG_NUM = 1000000000; - var x = flyoutRect.left; - var y = flyoutRect.top; - var width = flyoutRect.width; - var height = flyoutRect.height; - - if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) { - return new goog.math.Rect(-BIG_NUM, y - BIG_NUM, BIG_NUM * 2, - BIG_NUM + height); - } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_BOTTOM) { - return new goog.math.Rect(-BIG_NUM, y, BIG_NUM * 2, - BIG_NUM + height); - } else if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) { - return new goog.math.Rect(x - BIG_NUM, -BIG_NUM, BIG_NUM + width, - BIG_NUM * 2); - } else { // Right - return new goog.math.Rect(x, -BIG_NUM, BIG_NUM + width, BIG_NUM * 2); - } -}; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.Flyout.terminateDrag_ = function() { - if (Blockly.Flyout.startFlyout_) { - Blockly.Flyout.startFlyout_.dragMode_ = Blockly.DRAG_NONE; - } - if (Blockly.Flyout.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_); - Blockly.Flyout.onMouseUpWrapper_ = null; - } - if (Blockly.Flyout.onMouseMoveBlockWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_); - Blockly.Flyout.onMouseMoveBlockWrapper_ = null; - } - if (Blockly.Flyout.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_); - Blockly.Flyout.onMouseMoveWrapper_ = null; - } - Blockly.Flyout.startDownEvent_ = null; - Blockly.Flyout.startBlock_ = null; - Blockly.Flyout.startFlyout_ = null; -}; - -/** - * Compute height of flyout. Position button under each block. - * For RTL: Lay out the blocks right-aligned. - * @param {!Array} blocks The blocks to reflow. - */ -Blockly.Flyout.prototype.reflowHorizontal = function(blocks) { - this.workspace_.scale = this.targetWorkspace_.scale; - var flyoutHeight = 0; - for (var i = 0, block; block = blocks[i]; i++) { - flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height); - } - flyoutHeight += this.MARGIN * 1.5; - flyoutHeight *= this.workspace_.scale; - flyoutHeight += Blockly.Scrollbar.scrollbarThickness; - if (this.height_ != flyoutHeight) { - for (var i = 0, block; block = blocks[i]; i++) { - var blockHW = block.getHeightWidth(); - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('width', blockHW.width); - block.flyoutRect_.setAttribute('height', blockHW.height); - // Rectangles behind blocks with output tabs are shifted a bit. - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - var blockXY = block.getRelativeToSurfaceXY(); - block.flyoutRect_.setAttribute('y', blockXY.y); - block.flyoutRect_.setAttribute('x', - this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); - // For hat blocks we want to shift them down by the hat height - // since the y coordinate is the corner, not the top of the hat. - var hatOffset = - block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; - if (hatOffset) { - block.moveBy(0, hatOffset); - } - block.flyoutRect_.setAttribute('y', blockXY.y); - } - } - // Record the height for .getMetrics_ and .position. - this.height_ = flyoutHeight; - // Call this since it is possible the trash and zoom buttons need - // to move. e.g. on a bottom positioned flyout when zoom is clicked. - this.targetWorkspace_.resize(); - } -}; - -/** - * Compute width of flyout. Position button under each block. - * For RTL: Lay out the blocks right-aligned. - * @param {!Array} blocks The blocks to reflow. - */ -Blockly.Flyout.prototype.reflowVertical = function(blocks) { - this.workspace_.scale = this.targetWorkspace_.scale; - var flyoutWidth = 0; - for (var i = 0, block; block = blocks[i]; i++) { - var width = block.getHeightWidth().width; - if (block.outputConnection) { - width -= Blockly.BlockSvg.TAB_WIDTH; - } - flyoutWidth = Math.max(flyoutWidth, width); - } - for (var i = 0, button; button = this.buttons_[i]; i++) { - flyoutWidth = Math.max(flyoutWidth, button.width); - } - flyoutWidth += this.MARGIN * 1.5 + Blockly.BlockSvg.TAB_WIDTH; - flyoutWidth *= this.workspace_.scale; - flyoutWidth += Blockly.Scrollbar.scrollbarThickness; - if (this.width_ != flyoutWidth) { - for (var i = 0, block; block = blocks[i]; i++) { - var blockHW = block.getHeightWidth(); - if (this.RTL) { - // With the flyoutWidth known, right-align the blocks. - var oldX = block.getRelativeToSurfaceXY().x; - var newX = flyoutWidth / this.workspace_.scale - this.MARGIN; - newX -= Blockly.BlockSvg.TAB_WIDTH; - block.moveBy(newX - oldX, 0); - } - if (block.flyoutRect_) { - block.flyoutRect_.setAttribute('width', blockHW.width); - block.flyoutRect_.setAttribute('height', blockHW.height); - // Blocks with output tabs are shifted a bit. - var tab = block.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0; - var blockXY = block.getRelativeToSurfaceXY(); - block.flyoutRect_.setAttribute('x', - this.RTL ? blockXY.x - blockHW.width + tab : blockXY.x - tab); - // For hat blocks we want to shift them down by the hat height - // since the y coordinate is the corner, not the top of the hat. - var hatOffset = - block.startHat_ ? Blockly.BlockSvg.START_HAT_HEIGHT : 0; - if (hatOffset) { - block.moveBy(0, hatOffset); - } - block.flyoutRect_.setAttribute('y', blockXY.y); - } - } - // Record the width for .getMetrics_ and .position. - this.width_ = flyoutWidth; - // Call this since it is possible the trash and zoom buttons need - // to move. e.g. on a bottom positioned flyout when zoom is clicked. - this.targetWorkspace_.resize(); - } -}; - -/** - * Reflow blocks and their buttons. - */ -Blockly.Flyout.prototype.reflow = function() { - if (this.reflowWrapper_) { - this.workspace_.removeChangeListener(this.reflowWrapper_); - } - var blocks = this.workspace_.getTopBlocks(false); - if (this.horizontalLayout_) { - this.reflowHorizontal(blocks); - } else { - this.reflowVertical(blocks); - } - if (this.reflowWrapper_) { - this.workspace_.addChangeListener(this.reflowWrapper_); - } -}; diff --git a/core/inject.js.orig b/core/inject.js.orig deleted file mode 100644 index 57e3e51f4..000000000 --- a/core/inject.js.orig +++ /dev/null @@ -1,378 +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 Functions for injecting Blockly into a web page. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.inject'); - -goog.require('Blockly.Css'); -goog.require('Blockly.Options'); -goog.require('Blockly.WorkspaceSvg'); -goog.require('goog.dom'); -goog.require('goog.ui.Component'); -goog.require('goog.userAgent'); - - -/** - * Inject a Blockly editor into the specified container element (usually a div). - * @param {!Element|string} container Containing element, or its ID, - * or a CSS selector. - * @param {Object=} opt_options Optional dictionary of options. - * @return {!Blockly.Workspace} Newly created main workspace. - */ -Blockly.inject = function(container, opt_options) { - if (goog.isString(container)) { - container = document.getElementById(container) || - document.querySelector(container); - } - // Verify that the container is in document. - if (!goog.dom.contains(document, container)) { - throw 'Error: container is not in current document.'; - } - var options = new Blockly.Options(opt_options || {}); - var subContainer = goog.dom.createDom('div', 'injectionDiv'); - container.appendChild(subContainer); - var svg = Blockly.createDom_(subContainer, options); - var workspace = Blockly.createMainWorkspace_(svg, options); - Blockly.init_(workspace); - workspace.markFocused(); - Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused); - Blockly.svgResize(workspace); - return workspace; -}; - -/** - * Create the SVG image. - * @param {!Element} container Containing element. - * @param {!Blockly.Options} options Dictionary of options. - * @return {!Element} Newly created SVG image. - * @private - */ -Blockly.createDom_ = function(container, options) { - // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying - // out content in RTL mode. Therefore Blockly forces the use of LTR, - // then manually positions content in RTL as needed. - container.setAttribute('dir', 'LTR'); - // Closure can be trusted to create HTML widgets with the proper direction. - goog.ui.Component.setDefaultRightToLeft(options.RTL); - - // Load CSS. - Blockly.Css.inject(options.hasCss, options.pathToMedia); - - // Build the SVG DOM. - /* - - ... - - */ - var svg = Blockly.createSvgElement('svg', { - 'xmlns': 'http://www.w3.org/2000/svg', - 'xmlns:html': 'http://www.w3.org/1999/xhtml', - 'xmlns:xlink': 'http://www.w3.org/1999/xlink', - 'version': '1.1', - 'class': 'blocklySvg' - }, container); - /* - - ... filters go here ... - - */ - var defs = Blockly.createSvgElement('defs', {}, svg); - var rnd = String(Math.random()).substring(2); - /* - - - - - - - - - */ - var embossFilter = Blockly.createSvgElement('filter', - {'id': 'blocklyEmbossFilter' + rnd}, defs); - Blockly.createSvgElement('feGaussianBlur', - {'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter); - var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting', - {'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5, - 'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'}, - embossFilter); - Blockly.createSvgElement('fePointLight', - {'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting); - Blockly.createSvgElement('feComposite', - {'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in', - 'result': 'specOut'}, embossFilter); - Blockly.createSvgElement('feComposite', - {'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic', - 'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter); - options.embossFilterId = embossFilter.id; - /* - - - - - */ - var disabledPattern = Blockly.createSvgElement('pattern', - {'id': 'blocklyDisabledPattern' + rnd, - 'patternUnits': 'userSpaceOnUse', - 'width': 10, 'height': 10}, defs); - Blockly.createSvgElement('rect', - {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); - Blockly.createSvgElement('path', - {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); - options.disabledPatternId = disabledPattern.id; - /* - - - - - */ - var gridPattern = Blockly.createSvgElement('pattern', - {'id': 'blocklyGridPattern' + rnd, - 'patternUnits': 'userSpaceOnUse'}, defs); - if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { - Blockly.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - if (options.gridOptions['length'] > 1) { - Blockly.createSvgElement('line', - {'stroke': options.gridOptions['colour']}, - gridPattern); - } - // x1, y1, x1, x2 properties will be set later in updateGridPattern_. - } - options.gridPattern = gridPattern; - return svg; -}; - -/** - * Create a main workspace and add it to the SVG. - * @param {!Element} svg SVG element with pattern defined. - * @param {!Blockly.Options} options Dictionary of options. - * @return {!Blockly.Workspace} Newly created main workspace. - * @private - */ -Blockly.createMainWorkspace_ = function(svg, options) { - options.parentWorkspace = null; - var mainWorkspace = new Blockly.WorkspaceSvg(options); - mainWorkspace.scale = options.zoomOptions.startScale; - svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); - // A null translation will also apply the correct initial scale. - mainWorkspace.translate(0, 0); - mainWorkspace.markFocused(); - - if (!options.readOnly && !options.hasScrollbars) { - var workspaceChanged = function() { - if (Blockly.dragMode_ == Blockly.DRAG_NONE) { - var metrics = mainWorkspace.getMetrics(); - var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; - var edgeTop = metrics.viewTop + metrics.absoluteTop; - if (metrics.contentTop < edgeTop || - metrics.contentTop + metrics.contentHeight > - metrics.viewHeight + edgeTop || - metrics.contentLeft < - (options.RTL ? metrics.viewLeft : edgeLeft) || - metrics.contentLeft + metrics.contentWidth > (options.RTL ? - metrics.viewWidth : metrics.viewWidth + edgeLeft)) { - // One or more blocks may be out of bounds. Bump them back in. - var MARGIN = 25; - var blocks = mainWorkspace.getTopBlocks(false); - for (var b = 0, block; block = blocks[b]; b++) { - var blockXY = block.getRelativeToSurfaceXY(); - var blockHW = block.getHeightWidth(); - // Bump any block that's above the top back inside. - var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y; - if (overflowTop > 0) { - block.moveBy(0, overflowTop); - } - // Bump any block that's below the bottom back inside. - var overflowBottom = - edgeTop + metrics.viewHeight - MARGIN - blockXY.y; - if (overflowBottom < 0) { - block.moveBy(0, overflowBottom); - } - // Bump any block that's off the left back inside. - var overflowLeft = MARGIN + edgeLeft - - blockXY.x - (options.RTL ? 0 : blockHW.width); - if (overflowLeft > 0) { - block.moveBy(overflowLeft, 0); - } - // Bump any block that's off the right back inside. - var overflowRight = edgeLeft + metrics.viewWidth - MARGIN - - blockXY.x + (options.RTL ? blockHW.width : 0); - if (overflowRight < 0) { - block.moveBy(overflowRight, 0); - } - } - } - } - }; - mainWorkspace.addChangeListener(workspaceChanged); - } - // The SVG is now fully assembled. - Blockly.svgResize(mainWorkspace); - Blockly.WidgetDiv.createDom(); - Blockly.Tooltip.createDom(); - return mainWorkspace; -}; - -/** - * Initialize Blockly with various handlers. - * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace. - * @private - */ -Blockly.init_ = function(mainWorkspace) { - var options = mainWorkspace.options; - var svg = mainWorkspace.getParentSvg(); - - // Supress the browser's context menu. - Blockly.bindEvent_(svg, 'contextmenu', null, - function(e) { - if (!Blockly.isTargetInput_(e)) { - e.preventDefault(); - } - }); - - var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null, - function() { - Blockly.hideChaff(true); - Blockly.svgResize(mainWorkspace); - }); - mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler); - - Blockly.inject.bindDocumentEvents_(); - - if (options.languageTree) { - if (mainWorkspace.toolbox_) { - mainWorkspace.toolbox_.init(mainWorkspace); - } else if (mainWorkspace.flyout_) { - // Build a fixed flyout with the root blocks. - mainWorkspace.flyout_.init(mainWorkspace); - mainWorkspace.flyout_.show(options.languageTree.childNodes); - mainWorkspace.flyout_.scrollToStart(); - // Translate the workspace sideways to avoid the fixed flyout. - mainWorkspace.scrollX = mainWorkspace.flyout_.width_; - if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { - mainWorkspace.scrollX *= -1; - } - mainWorkspace.translate(mainWorkspace.scrollX, 0); - } - } - - if (options.hasScrollbars) { - mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); - mainWorkspace.scrollbar.resize(); - } - - // Load the sounds. - if (options.hasSounds) { - Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace); - } -}; - -/** - * Bind document events, but only once. Destroying and reinjecting Blockly - * should not bind again. - * Bind events for scrolling the workspace. - * Most of these events should be bound to the SVG's surface. - * However, 'mouseup' has to be on the whole document so that a block dragged - * out of bounds and released will know that it has been released. - * Also, 'keydown' has to be on the whole document since the browser doesn't - * understand a concept of focus on the SVG image. - * @private - */ -Blockly.inject.bindDocumentEvents_ = function() { - if (!Blockly.documentEventsBound_) { - Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_); - Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_); - Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_); - // Don't use bindEvent_ for document's mouseup since that would create a - // corresponding touch handler that would squeltch the ability to interact - // with non-Blockly elements. - document.addEventListener('mouseup', Blockly.onMouseUp_, false); - // Some iPad versions don't fire resize after portrait to landscape change. - if (goog.userAgent.IPAD) { - Blockly.bindEvent_(window, 'orientationchange', document, function() { - // TODO(#397): Fix for multiple blockly workspaces. - Blockly.svgResize(Blockly.getMainWorkspace()); - }); - } - } - Blockly.documentEventsBound_ = true; -}; - -/** - * Load sounds for the given workspace. - * @param {string} pathToMedia The path to the media directory. - * @param {!Blockly.Workspace} workspace The workspace to load sounds for. - * @private - */ -Blockly.inject.loadSounds_ = function(pathToMedia, workspace) { - workspace.loadAudio_( - [pathToMedia + 'click.mp3', - pathToMedia + 'click.wav', - pathToMedia + 'click.ogg'], 'click'); - workspace.loadAudio_( - [pathToMedia + 'disconnect.wav', - pathToMedia + 'disconnect.mp3', - pathToMedia + 'disconnect.ogg'], 'disconnect'); - workspace.loadAudio_( - [pathToMedia + 'delete.mp3', - pathToMedia + 'delete.ogg', - pathToMedia + 'delete.wav'], 'delete'); - - // Bind temporary hooks that preload the sounds. - var soundBinds = []; - var unbindSounds = function() { - while (soundBinds.length) { - Blockly.unbindEvent_(soundBinds.pop()); - } - workspace.preloadAudio_(); - }; - // Android ignores any sound not loaded as a result of a user action. - soundBinds.push( - Blockly.bindEvent_(document, 'mousemove', null, unbindSounds)); - soundBinds.push( - Blockly.bindEvent_(document, 'touchstart', null, unbindSounds)); -}; - -/** - * Modify the block tree on the existing toolbox. - * @param {Node|string} tree DOM tree of blocks, or text representation of same. - */ -Blockly.updateToolbox = function(tree) { - console.warn('Deprecated call to Blockly.updateToolbox, ' + - 'use workspace.updateToolbox instead.'); - Blockly.getMainWorkspace().updateToolbox(tree); -}; diff --git a/core/input.js.orig b/core/input.js.orig deleted file mode 100644 index 4b4eb1df9..000000000 --- a/core/input.js.orig +++ /dev/null @@ -1,241 +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 Object representing an input (value, statement, or dummy). - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Input'); - -goog.require('Blockly.Connection'); -goog.require('Blockly.FieldLabel'); -goog.require('goog.asserts'); - - -/** - * Class for an input with an optional field. - * @param {number} type The type of the input. - * @param {string} name Language-neutral identifier which may used to find this - * input again. - * @param {!Blockly.Block} block The block containing this input. - * @param {Blockly.Connection} connection Optional connection for this input. - * @constructor - */ -Blockly.Input = function(type, name, block, connection) { - /** @type {number} */ - this.type = type; - /** @type {string} */ - this.name = name; - /** - * @type {!Blockly.Block} - * @private - */ - this.sourceBlock_ = block; - /** @type {Blockly.Connection} */ - this.connection = connection; - /** @type {!Array.} */ - this.fieldRow = []; -}; - -/** - * Alignment of input's fields (left, right or centre). - * @type {number} - */ -Blockly.Input.prototype.align = Blockly.ALIGN_LEFT; - -/** - * Is the input visible? - * @type {boolean} - * @private - */ -Blockly.Input.prototype.visible_ = true; - -/** - * Add an item to the end of the input's field row. - * @param {string|!Blockly.Field} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. - * @return {!Blockly.Input} The input being append to (to allow chaining). - */ -Blockly.Input.prototype.appendField = function(field, opt_name) { - // Empty string, Null or undefined generates no field, unless field is named. - if (!field && !opt_name) { - return this; - } - // Generate a FieldLabel when given a plain text field. - if (goog.isString(field)) { - field = new Blockly.FieldLabel(/** @type {string} */ (field)); - } - field.setSourceBlock(this.sourceBlock_); - if (this.sourceBlock_.rendered) { - field.init(); - } - field.name = opt_name; - - if (field.prefixField) { - // Add any prefix. - this.appendField(field.prefixField); - } - // Add the field to the field row. - this.fieldRow.push(field); - if (field.suffixField) { - // Add any suffix. - this.appendField(field.suffixField); - } - - if (this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - // Adding a field will cause the block to change shape. - this.sourceBlock_.bumpNeighbours_(); - } - return this; -}; - -/** - * Add an item to the end of the input's field row. - * @param {*} field Something to add as a field. - * @param {string=} opt_name Language-neutral identifier which may used to find - * this field again. Should be unique to the host block. - * @return {!Blockly.Input} The input being append to (to allow chaining). - * @deprecated December 2013 - */ -Blockly.Input.prototype.appendTitle = function(field, opt_name) { - console.warn('Deprecated call to appendTitle, use appendField instead.'); - return this.appendField(field, opt_name); -}; - -/** - * Remove a field from this input. - * @param {string} name The name of the field. - * @throws {goog.asserts.AssertionError} if the field is not present. - */ -Blockly.Input.prototype.removeField = function(name) { - for (var i = 0, field; field = this.fieldRow[i]; i++) { - if (field.name === name) { - field.dispose(); - this.fieldRow.splice(i, 1); - if (this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - // Removing a field will cause the block to change shape. - this.sourceBlock_.bumpNeighbours_(); - } - return; - } - } - goog.asserts.fail('Field "%s" not found.', name); -}; - -/** - * Gets whether this input is visible or not. - * @return {boolean} True if visible. - */ -Blockly.Input.prototype.isVisible = function() { - return this.visible_; -}; - -/** - * Sets whether this input is visible or not. - * Used to collapse/uncollapse a block. - * @param {boolean} visible True if visible. - * @return {!Array.} List of blocks to render. - */ -Blockly.Input.prototype.setVisible = function(visible) { - var renderList = []; - if (this.visible_ == visible) { - return renderList; - } - this.visible_ = visible; - - var display = visible ? 'block' : 'none'; - for (var y = 0, field; field = this.fieldRow[y]; y++) { - field.setVisible(visible); - } - if (this.connection) { - // Has a connection. - if (visible) { - renderList = this.connection.unhideAll(); - } else { - this.connection.hideAll(); - } - var child = this.connection.targetBlock(); - if (child) { - child.getSvgRoot().style.display = display; - if (!visible) { - child.rendered = false; - } - } - } - return renderList; -}; - -/** - * Change a connection's compatibility. - * @param {string|Array.|null} check Compatible value type or - * list of value types. Null if all types are compatible. - * @return {!Blockly.Input} The input being modified (to allow chaining). - */ -Blockly.Input.prototype.setCheck = function(check) { - if (!this.connection) { - throw 'This input does not have a connection.'; - } - this.connection.setCheck(check); - return this; -}; - -/** - * Change the alignment of the connection's field(s). - * @param {number} align One of Blockly.ALIGN_LEFT, ALIGN_CENTRE, ALIGN_RIGHT. - * In RTL mode directions are reversed, and ALIGN_RIGHT aligns to the left. - * @return {!Blockly.Input} The input being modified (to allow chaining). - */ -Blockly.Input.prototype.setAlign = function(align) { - this.align = align; - if (this.sourceBlock_.rendered) { - this.sourceBlock_.render(); - } - return this; -}; - -/** - * Initialize the fields on this input. - */ -Blockly.Input.prototype.init = function() { - if (!this.sourceBlock_.workspace.rendered) { - return; // Headless blocks don't need fields initialized. - } - for (var i = 0; i < this.fieldRow.length; i++) { - this.fieldRow[i].init(); - } -}; - -/** - * Sever all links to this input. - */ -Blockly.Input.prototype.dispose = function() { - for (var i = 0, field; field = this.fieldRow[i]; i++) { - field.dispose(); - } - if (this.connection) { - this.connection.dispose(); - } - this.sourceBlock_ = null; -}; diff --git a/core/mutator.js.orig b/core/mutator.js.orig deleted file mode 100644 index 19a0fe86b..000000000 --- a/core/mutator.js.orig +++ /dev/null @@ -1,389 +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 Object representing a mutator dialog. A mutator allows the - * user to change the shape of a block using a nested blocks editor. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Mutator'); - -goog.require('Blockly.Bubble'); -goog.require('Blockly.Icon'); -goog.require('Blockly.WorkspaceSvg'); -goog.require('goog.Timer'); -goog.require('goog.dom'); - - -/** - * Class for a mutator dialog. - * @param {!Array.} quarkNames List of names of sub-blocks for flyout. - * @extends {Blockly.Icon} - * @constructor - */ -Blockly.Mutator = function(quarkNames) { - Blockly.Mutator.superClass_.constructor.call(this, null); - this.quarkNames_ = quarkNames; -}; -goog.inherits(Blockly.Mutator, Blockly.Icon); - -/** - * Width of workspace. - * @private - */ -Blockly.Mutator.prototype.workspaceWidth_ = 0; - -/** - * Height of workspace. - * @private - */ -Blockly.Mutator.prototype.workspaceHeight_ = 0; - -/** - * Draw the mutator icon. - * @param {!Element} group The icon group. - * @private - */ -Blockly.Mutator.prototype.drawIcon_ = function(group) { - // Square with rounded corners. - Blockly.createSvgElement('rect', - {'class': 'blocklyIconShape', - 'rx': '4', 'ry': '4', - 'height': '16', 'width': '16'}, - group); - // Gear teeth. - Blockly.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'}, - group); - // Axle hole. - Blockly.createSvgElement('circle', - {'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'}, - group); -}; - -/** - * Clicking on the icon toggles if the mutator bubble is visible. - * Disable if block is uneditable. - * @param {!Event} e Mouse click event. - * @private - * @override - */ -Blockly.Mutator.prototype.iconClick_ = function(e) { - if (this.block_.isEditable()) { - Blockly.Icon.prototype.iconClick_.call(this, e); - } -}; - -/** - * Create the editor for the mutator's bubble. - * @return {!Element} The top-level node of the editor. - * @private - */ -Blockly.Mutator.prototype.createEditor_ = function() { - /* Create the editor. Here's the markup that will be generated: - - [Workspace] - - */ - this.svgDialog_ = Blockly.createSvgElement('svg', - {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH}, - null); - // Convert the list of names into a list of XML objects for the flyout. - if (this.quarkNames_.length) { - var quarkXml = goog.dom.createDom('xml'); - for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) { - quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName})); - } - } else { - var quarkXml = null; - } - var workspaceOptions = { - languageTree: quarkXml, - parentWorkspace: this.block_.workspace, - pathToMedia: this.block_.workspace.options.pathToMedia, - RTL: this.block_.RTL, - toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT : - Blockly.TOOLBOX_AT_LEFT, - horizontalLayout: false, - getMetrics: this.getFlyoutMetrics_.bind(this), - setMetrics: null - }; - this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions); - this.workspace_.isMutator = true; - this.svgDialog_.appendChild( - this.workspace_.createDom('blocklyMutatorBackground')); - return this.svgDialog_; -}; - -/** - * Add or remove the UI indicating if this icon may be clicked or not. - */ -Blockly.Mutator.prototype.updateEditable = function() { - if (!this.block_.isInFlyout) { - if (this.block_.isEditable()) { - if (this.iconGroup_) { - Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); - } - } else { - // Close any mutator bubble. Icon is not clickable. - this.setVisible(false); - if (this.iconGroup_) { - Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_), - 'blocklyIconGroupReadonly'); - } - } - } - // Default behaviour for an icon. - Blockly.Icon.prototype.updateEditable.call(this); -}; - -/** - * Callback function triggered when the bubble has resized. - * Resize the workspace accordingly. - * @private - */ -Blockly.Mutator.prototype.resizeBubble_ = function() { - var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH; - var workspaceSize = this.workspace_.getCanvas().getBBox(); - var width; - if (this.block_.RTL) { - width = -workspaceSize.x; - } else { - width = workspaceSize.width + workspaceSize.x; - } - var height = workspaceSize.height + doubleBorderWidth * 3; - if (this.workspace_.flyout_) { - var flyoutMetrics = this.workspace_.flyout_.getMetrics_(); - height = Math.max(height, flyoutMetrics.contentHeight + 20); - } - width += doubleBorderWidth * 3; - // Only resize if the size difference is significant. Eliminates shuddering. - if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth || - Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) { - // Record some layout information for getFlyoutMetrics_. - this.workspaceWidth_ = width; - this.workspaceHeight_ = height; - // Resize the bubble. - this.bubble_.setBubbleSize(width + doubleBorderWidth, - height + doubleBorderWidth); - this.svgDialog_.setAttribute('width', this.workspaceWidth_); - this.svgDialog_.setAttribute('height', this.workspaceHeight_); - } - - if (this.block_.RTL) { - // Scroll the workspace to always left-align. - var translation = 'translate(' + this.workspaceWidth_ + ',0)'; - this.workspace_.getCanvas().setAttribute('transform', translation); - } - this.workspace_.resize(); -}; - -/** - * Show or hide the mutator bubble. - * @param {boolean} visible True if the bubble should be visible. - */ -Blockly.Mutator.prototype.setVisible = function(visible) { - if (visible == this.isVisible()) { - // No change. - return; - } - Blockly.Events.fire( - new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible)); - if (visible) { - // Create the bubble. - this.bubble_ = new Blockly.Bubble( - /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), - this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null); - var tree = this.workspace_.options.languageTree; - if (tree) { - this.workspace_.flyout_.init(this.workspace_); - this.workspace_.flyout_.show(tree.childNodes); - } - - this.rootBlock_ = this.block_.decompose(this.workspace_); - var blocks = this.rootBlock_.getDescendants(); - for (var i = 0, child; child = blocks[i]; i++) { - child.render(); - } - // The root block should not be dragable or deletable. - this.rootBlock_.setMovable(false); - this.rootBlock_.setDeletable(false); - if (this.workspace_.flyout_) { - var margin = this.workspace_.flyout_.CORNER_RADIUS * 2; - var x = this.workspace_.flyout_.width_ + margin; - } else { - var margin = 16; - var x = margin; - } - if (this.block_.RTL) { - x = -x; - } - this.rootBlock_.moveBy(x, margin); - // Save the initial connections, then listen for further changes. - if (this.block_.saveConnections) { - var thisMutator = this; - this.block_.saveConnections(this.rootBlock_); - this.sourceListener_ = function() { - thisMutator.block_.saveConnections(thisMutator.rootBlock_); - }; - this.block_.workspace.addChangeListener(this.sourceListener_); - } - this.resizeBubble_(); - // When the mutator's workspace changes, update the source block. - this.workspace_.addChangeListener(this.workspaceChanged_.bind(this)); - this.updateColour(); - } else { - // Dispose of the bubble. - this.svgDialog_ = null; - this.workspace_.dispose(); - this.workspace_ = null; - this.rootBlock_ = null; - this.bubble_.dispose(); - this.bubble_ = null; - this.workspaceWidth_ = 0; - this.workspaceHeight_ = 0; - if (this.sourceListener_) { - this.block_.workspace.removeChangeListener(this.sourceListener_); - this.sourceListener_ = null; - } - } -}; - -/** - * Update the source block when the mutator's blocks are changed. - * Bump down any block that's too high. - * Fired whenever a change is made to the mutator's workspace. - * @private - */ -Blockly.Mutator.prototype.workspaceChanged_ = function() { - if (Blockly.dragMode_ == Blockly.DRAG_NONE) { - var blocks = this.workspace_.getTopBlocks(false); - var MARGIN = 20; - for (var b = 0, block; block = blocks[b]; b++) { - var blockXY = block.getRelativeToSurfaceXY(); - var blockHW = block.getHeightWidth(); - if (blockXY.y + blockHW.height < MARGIN) { - // Bump any block that's above the top back inside. - block.moveBy(0, MARGIN - blockHW.height - blockXY.y); - } - } - } - - // When the mutator's workspace changes, update the source block. - if (this.rootBlock_.workspace == this.workspace_) { - Blockly.Events.setGroup(true); - var block = this.block_; - var oldMutationDom = block.mutationToDom(); - var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); - // Switch off rendering while the source block is rebuilt. - var savedRendered = block.rendered; - block.rendered = false; - // Allow the source block to rebuild itself. - block.compose(this.rootBlock_); - // Restore rendering and show the changes. - block.rendered = savedRendered; - // Mutation may have added some elements that need initalizing. - block.initSvg(); - var newMutationDom = block.mutationToDom(); - var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); - if (oldMutation != newMutation) { - Blockly.Events.fire(new Blockly.Events.Change( - block, 'mutation', null, oldMutation, newMutation)); - // Ensure that any bump is part of this mutation's event group. - var group = Blockly.Events.getGroup(); - setTimeout(function() { - Blockly.Events.setGroup(group); - block.bumpNeighbours_(); - Blockly.Events.setGroup(false); - }, Blockly.BUMP_DELAY); - } - if (block.rendered) { - block.render(); - } - this.resizeBubble_(); - Blockly.Events.setGroup(false); - } -}; - -/** - * Return an object with all the metrics required to size scrollbars for the - * mutator flyout. The following properties are computed: - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .absoluteTop: Top-edge of view. - * .absoluteLeft: Left-edge of view. - * @return {!Object} Contains size and position metrics of mutator dialog's - * workspace. - * @private - */ -Blockly.Mutator.prototype.getFlyoutMetrics_ = function() { - return { - viewHeight: this.workspaceHeight_, - viewWidth: this.workspaceWidth_, - absoluteTop: 0, - absoluteLeft: 0 - }; -}; - -/** - * Dispose of this mutator. - */ -Blockly.Mutator.prototype.dispose = function() { - this.block_.mutator = null; - Blockly.Icon.prototype.dispose.call(this); -}; - -/** - * Reconnect an block to a mutated input. - * @param {Blockly.Connection} connectionChild Connection on child block. - * @param {!Blockly.Block} block Parent block. - * @param {string} inputName Name of input on parent block. - * @return {boolean} True iff a reconnection was made, false otherwise. - */ -Blockly.Mutator.reconnect = function(connectionChild, block, inputName) { - if (!connectionChild || !connectionChild.getSourceBlock().workspace) { - return false; // No connection or block has been deleted. - } - var connectionParent = block.getInput(inputName).connection; - var currentParent = connectionChild.targetBlock(); - if ((!currentParent || currentParent == block) && - connectionParent.targetConnection != connectionChild) { - if (connectionParent.isConnected()) { - // There's already something connected here. Get rid of it. - connectionParent.disconnect(); - } - connectionParent.connect(connectionChild); - return true; - } - return false; -}; - -// Export symbols that would otherwise be renamed by Closure compiler. -if (!goog.global['Blockly']) { - goog.global['Blockly'] = {}; -} -if (!goog.global['Blockly']['Mutator']) { - goog.global['Blockly']['Mutator'] = {}; -} -goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect; diff --git a/core/procedures.js.orig b/core/procedures.js.orig deleted file mode 100644 index beb4a17b8..000000000 --- a/core/procedures.js.orig +++ /dev/null @@ -1,287 +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 Utility functions for handling procedures. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Procedures'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.Field'); -goog.require('Blockly.Names'); -goog.require('Blockly.Workspace'); - - -/** - * Category to separate procedure names from variables and generated functions. - */ -Blockly.Procedures.NAME_TYPE = 'PROCEDURE'; - -/** - * Find all user-created procedure definitions in a workspace. - * @param {!Blockly.Workspace} root Root workspace. - * @return {!Array.>} Pair of arrays, the - * first contains procedures without return variables, the second with. - * Each procedure is defined by a three-element list of name, parameter - * list, and return value boolean. - */ -Blockly.Procedures.allProcedures = function(root) { - var blocks = root.getAllBlocks(); - var proceduresReturn = []; - var proceduresNoReturn = []; - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureDef) { - var tuple = blocks[i].getProcedureDef(); - if (tuple) { - if (tuple[2]) { - proceduresReturn.push(tuple); - } else { - proceduresNoReturn.push(tuple); - } - } - } - } - proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_); - proceduresReturn.sort(Blockly.Procedures.procTupleComparator_); - return [proceduresNoReturn, proceduresReturn]; -}; - -/** - * Comparison function for case-insensitive sorting of the first element of - * a tuple. - * @param {!Array} ta First tuple. - * @param {!Array} tb Second tuple. - * @return {number} -1, 0, or 1 to signify greater than, equality, or less than. - * @private - */ -Blockly.Procedures.procTupleComparator_ = function(ta, tb) { - return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase()); -}; - -/** - * Ensure two identically-named procedures don't exist. - * @param {string} name Proposed procedure name. - * @param {!Blockly.Block} block Block to disambiguate. - * @return {string} Non-colliding name. - */ -Blockly.Procedures.findLegalName = function(name, block) { - if (block.isInFlyout) { - // Flyouts can have multiple procedures called 'do something'. - return name; - } - while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) { - // Collision with another procedure. - var r = name.match(/^(.*?)(\d+)$/); - if (!r) { - name += '2'; - } else { - name = r[1] + (parseInt(r[2], 10) + 1); - } - } - return name; -}; - -/** - * Does this procedure have a legal name? Illegal names include names of - * procedures already defined. - * @param {string} name The questionable name. - * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. - * @param {Blockly.Block=} opt_exclude Optional block to exclude from - * comparisons (one doesn't want to collide with oneself). - * @return {boolean} True if the name is legal. - * @private - */ -Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) { - var blocks = workspace.getAllBlocks(); - // Iterate through every block and check the name. - for (var i = 0; i < blocks.length; i++) { - if (blocks[i] == opt_exclude) { - continue; - } - if (blocks[i].getProcedureDef) { - var procName = blocks[i].getProcedureDef(); - if (Blockly.Names.equals(procName[0], name)) { - return false; - } - } - } - return true; -}; - -/** - * Rename a procedure. Called by the editable field. - * @param {string} name The proposed new name. - * @return {string} The accepted name. - * @this {!Blockly.Field} - */ -Blockly.Procedures.rename = function(name) { - // Strip leading and trailing whitespace. Beyond this, all names are legal. - name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); - - // Ensure two identically-named procedures don't exist. - var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_); - var oldName = this.text_; - if (oldName != name && oldName != legalName) { - // Rename any callers. - var blocks = this.sourceBlock_.workspace.getAllBlocks(); - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].renameProcedure) { - blocks[i].renameProcedure(oldName, legalName); - } - } - } - return legalName; -}; - -/** - * Construct the blocks required by the flyout for the procedure category. - * @param {!Blockly.Workspace} workspace The workspace contianing procedures. - * @return {!Array.} Array of XML block elements. - */ -Blockly.Procedures.flyoutCategory = function(workspace) { - var xmlList = []; - if (Blockly.Blocks['procedures_defnoreturn']) { - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'procedures_defnoreturn'); - block.setAttribute('gap', 16); - xmlList.push(block); - } - if (Blockly.Blocks['procedures_defreturn']) { - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'procedures_defreturn'); - block.setAttribute('gap', 16); - xmlList.push(block); - } - if (Blockly.Blocks['procedures_ifreturn']) { - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'procedures_ifreturn'); - block.setAttribute('gap', 16); - xmlList.push(block); - } - if (xmlList.length) { - // Add slightly larger gap between system blocks and user calls. - xmlList[xmlList.length - 1].setAttribute('gap', 24); - } - - function populateProcedures(procedureList, templateName) { - for (var i = 0; i < procedureList.length; i++) { - var name = procedureList[i][0]; - var args = procedureList[i][1]; - // - // - // - // - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', templateName); - block.setAttribute('gap', 16); - var mutation = goog.dom.createDom('mutation'); - mutation.setAttribute('name', name); - block.appendChild(mutation); - for (var j = 0; j < args.length; j++) { - var arg = goog.dom.createDom('arg'); - arg.setAttribute('name', args[j]); - mutation.appendChild(arg); - } - xmlList.push(block); - } - } - - var tuple = Blockly.Procedures.allProcedures(workspace); - populateProcedures(tuple[0], 'procedures_callnoreturn'); - populateProcedures(tuple[1], 'procedures_callreturn'); - return xmlList; -}; - -/** - * Find all the callers of a named procedure. - * @param {string} name Name of procedure. - * @param {!Blockly.Workspace} workspace The workspace to find callers in. - * @return {!Array.} Array of caller blocks. - */ -Blockly.Procedures.getCallers = function(name, workspace) { - var callers = []; - var blocks = workspace.getAllBlocks(); - // Iterate through every block and check the name. - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureCall) { - var procName = blocks[i].getProcedureCall(); - // Procedure name may be null if the block is only half-built. - if (procName && Blockly.Names.equals(procName, name)) { - callers.push(blocks[i]); - } - } - } - return callers; -}; - -/** - * When a procedure definition changes its parameters, find and edit all its - * callers. - * @param {!Blockly.Block} defBlock Procedure definition block. - */ -Blockly.Procedures.mutateCallers = function(defBlock) { - var oldRecordUndo = Blockly.Events.recordUndo; - var name = defBlock.getProcedureDef()[0]; - var xmlElement = defBlock.mutationToDom(true); - var callers = Blockly.Procedures.getCallers(name, defBlock.workspace); - for (var i = 0, caller; caller = callers[i]; i++) { - var oldMutationDom = caller.mutationToDom(); - var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom); - caller.domToMutation(xmlElement); - var newMutationDom = caller.mutationToDom(); - var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom); - if (oldMutation != newMutation) { - // Fire a mutation on every caller block. But don't record this as an - // undo action since it is deterministically tied to the procedure's - // definition mutation. - Blockly.Events.recordUndo = false; - Blockly.Events.fire(new Blockly.Events.Change( - caller, 'mutation', null, oldMutation, newMutation)); - Blockly.Events.recordUndo = oldRecordUndo; - } - } -}; - -/** - * Find the definition block for the named procedure. - * @param {string} name Name of procedure. - * @param {!Blockly.Workspace} workspace The workspace to search. - * @return {Blockly.Block} The procedure definition block, or null not found. - */ -Blockly.Procedures.getDefinition = function(name, workspace) { - // Assume that a procedure definition is a top block. - var blocks = workspace.getTopBlocks(false); - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureDef) { - var tuple = blocks[i].getProcedureDef(); - if (tuple && Blockly.Names.equals(tuple[0], name)) { - return blocks[i]; - } - } - } - return null; -}; diff --git a/core/scrollbar.js.orig b/core/scrollbar.js.orig deleted file mode 100644 index da2dd942d..000000000 --- a/core/scrollbar.js.orig +++ /dev/null @@ -1,750 +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 Library for creating scrollbars. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Scrollbar'); -goog.provide('Blockly.ScrollbarPair'); - -goog.require('goog.dom'); -goog.require('goog.events'); - - -/** - * Class for a pair of scrollbars. Horizontal and vertical. - * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to. - * @constructor - */ -Blockly.ScrollbarPair = function(workspace) { - this.workspace_ = workspace; - this.hScroll = new Blockly.Scrollbar(workspace, true, true); - this.vScroll = new Blockly.Scrollbar(workspace, false, true); - this.corner_ = Blockly.createSvgElement('rect', - {'height': Blockly.Scrollbar.scrollbarThickness, - 'width': Blockly.Scrollbar.scrollbarThickness, - 'class': 'blocklyScrollbarBackground'}, null); - Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas()); -}; - -/** - * Previously recorded metrics from the workspace. - * @type {Object} - * @private - */ -Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null; - -/** - * Dispose of this pair of scrollbars. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.ScrollbarPair.prototype.dispose = function() { - goog.dom.removeNode(this.corner_); - this.corner_ = null; - this.workspace_ = null; - this.oldHostMetrics_ = null; - this.hScroll.dispose(); - this.hScroll = null; - this.vScroll.dispose(); - this.vScroll = null; -}; - -/** - * Recalculate both of the scrollbars' locations and lengths. - * Also reposition the corner rectangle. - */ -Blockly.ScrollbarPair.prototype.resize = function() { - // Look up the host metrics once, and use for both scrollbars. - var hostMetrics = this.workspace_.getMetrics(); - if (!hostMetrics) { - // Host element is likely not visible. - return; - } - - // Only change the scrollbars if there has been a change in metrics. - var resizeH = false; - var resizeV = false; - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || - this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop || - this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { - // The window has been resized or repositioned. - resizeH = true; - resizeV = true; - } else { - // Has the content been resized or moved? - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth || - this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft || - this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) { - resizeH = true; - } - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight || - this.oldHostMetrics_.viewTop != hostMetrics.viewTop || - this.oldHostMetrics_.contentTop != hostMetrics.contentTop) { - resizeV = true; - } - } - if (resizeH) { - this.hScroll.resize(hostMetrics); - } - if (resizeV) { - this.vScroll.resize(hostMetrics); - } - - // Reposition the corner square. - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth || - this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) { - this.corner_.setAttribute('x', this.vScroll.position_.x); - } - if (!this.oldHostMetrics_ || - this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight || - this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) { - this.corner_.setAttribute('y', this.hScroll.position_.y); - } - - // Cache the current metrics to potentially short-cut the next resize event. - this.oldHostMetrics_ = hostMetrics; -}; - -/** - * Set the sliders of both scrollbars to be at a certain position. - * @param {number} x Horizontal scroll value. - * @param {number} y Vertical scroll value. - */ -Blockly.ScrollbarPair.prototype.set = function(x, y) { - // This function is equivalent to: - // this.hScroll.set(x); - // this.vScroll.set(y); - // However, that calls setMetrics twice which causes a chain of - // getAttribute->setAttribute->getAttribute resulting in an extra layout pass. - // Combining them speeds up rendering. - var xyRatio = {}; - - var hHandlePosition = x * this.hScroll.ratio_; - var vHandlePosition = y * this.vScroll.ratio_; - - var hBarLength = this.hScroll.scrollViewSize_; - var vBarLength = this.vScroll.scrollViewSize_; - - xyRatio.x = this.getRatio_(hHandlePosition, hBarLength); - xyRatio.y = this.getRatio_(vHandlePosition, vBarLength); - this.workspace_.setMetrics(xyRatio); - - this.hScroll.setHandlePosition(hHandlePosition); - this.vScroll.setHandlePosition(vHandlePosition); -}; - -/** - * Helper to calculate the ratio of handle position to scrollbar view size. - * @param {number} handlePosition The value of the handle. - * @param {number} viewSize The total size of the scrollbar's view. - * @return {number} Ratio. - * @private - */ -Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) { - var ratio = handlePosition / viewSize; - if (isNaN(ratio)) { - return 0; - } - return ratio; -}; - -// -------------------------------------------------------------------- - -/** - * Class for a pure SVG scrollbar. - * This technique offers a scrollbar that is guaranteed to work, but may not - * look or behave like the system's scrollbars. - * @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to. - * @param {boolean} horizontal True if horizontal, false if vertical. - * @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair. - * @constructor - */ -Blockly.Scrollbar = function(workspace, horizontal, opt_pair) { - this.workspace_ = workspace; - this.pair_ = opt_pair || false; - this.horizontal_ = horizontal; - this.oldHostMetrics_ = null; - - this.createDom_(); - - /** - * The upper left corner of the scrollbar's svg group. - * @type {goog.math.Coordinate} - * @private - */ - this.position_ = new goog.math.Coordinate(0, 0); - - if (horizontal) { - this.svgBackground_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness); - this.svgHandle_.setAttribute('height', - Blockly.Scrollbar.scrollbarThickness - 5); - this.svgHandle_.setAttribute('y', 2.5); - - this.lengthAttribute_ = 'width'; - this.positionAttribute_ = 'x'; - } else { - this.svgBackground_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness); - this.svgHandle_.setAttribute('width', - Blockly.Scrollbar.scrollbarThickness - 5); - this.svgHandle_.setAttribute('x', 2.5); - - this.lengthAttribute_ = 'height'; - this.positionAttribute_ = 'y'; - } - var scrollbar = this; - this.onMouseDownBarWrapper_ = Blockly.bindEvent_(this.svgBackground_, - 'mousedown', scrollbar, scrollbar.onMouseDownBar_); - this.onMouseDownHandleWrapper_ = Blockly.bindEvent_(this.svgHandle_, - 'mousedown', scrollbar, scrollbar.onMouseDownHandle_); -}; - -/** - * The size of the area within which the scrollbar handle can move. - * @type {number} - * @private - */ -Blockly.Scrollbar.prototype.scrollViewSize_ = 0; - -/** - * The length of the scrollbar handle. - * @type {number} - * @private - */ -Blockly.Scrollbar.prototype.handleLength_ = 0; - -/** - * The offset of the start of the handle from the start of the scrollbar range. - * @type {number} - * @private - */ -Blockly.Scrollbar.prototype.handlePosition_ = 0; - -/** - * Whether the scrollbar handle is visible. - * @type {boolean} - * @private - */ -Blockly.Scrollbar.prototype.isVisible_ = true; - -/** - * Width of vertical scrollbar or height of horizontal scrollbar. - * Increase the size of scrollbars on touch devices. - * Don't define if there is no document object (e.g. node.js). - */ -Blockly.Scrollbar.scrollbarThickness = 15; -if (goog.events.BrowserFeature.TOUCH_ENABLED) { - Blockly.Scrollbar.scrollbarThickness = 25; -} - -/** - * @param {!Object} first An object containing computed measurements of a - * workspace. - * @param {!Object} second Another object containing computed measurements of a - * workspace. - * @return {boolean} Whether the two sets of metrics are equivalent. - * @private - */ -Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) { - if (!(first && second)) { - return false; - } - - if (first.viewWidth != second.viewWidth || - first.viewHeight != second.viewHeight || - first.viewLeft != second.viewLeft || - first.viewTop != second.viewTop || - first.absoluteTop != second.absoluteTop || - first.absoluteLeft != second.absoluteLeft || - first.contentWidth != second.contentWidth || - first.contentHeight != second.contentHeight || - first.contentLeft != second.contentLeft || - first.contentTop != second.contentTop) { - return false; - } - - return true; -}; - -/** - * Dispose of this scrollbar. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Scrollbar.prototype.dispose = function() { - this.onMouseUpHandle_(); - Blockly.unbindEvent_(this.onMouseDownBarWrapper_); - this.onMouseDownBarWrapper_ = null; - Blockly.unbindEvent_(this.onMouseDownHandleWrapper_); - this.onMouseDownHandleWrapper_ = null; - - goog.dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; - this.svgBackground_ = null; - this.svgHandle_ = null; - this.workspace_ = null; -}; - -/** - * Set the length of the scrollbar's handle and change the SVG attribute - * accordingly. - * @param {number} newLength The new scrollbar handle length. - */ -Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) { - this.handleLength_ = newLength; - this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_); -}; - -/** - * Set the offset of the scrollbar's handle and change the SVG attribute - * accordingly. - * @param {number} newPosition The new scrollbar handle offset. - */ -Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) { - this.handlePosition_ = newPosition; - this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_); -}; - -/** - * Set the size of the scrollbar's background and change the SVG attribute - * accordingly. - * @param {number} newSize The new scrollbar background length. - * @private - */ -Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) { - this.scrollViewSize_ = newSize; - this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_); -}; - -/** - * Set the position of the scrollbar's svg group. - * @param {number} x The new x coordinate. - * @param {number} y The new y coordinate. - */ -Blockly.Scrollbar.prototype.setPosition = function(x, y) { - this.position_.x = x; - this.position_.y = y; - - this.svgGroup_.setAttribute('transform', - 'translate(' + this.position_.x + ',' + this.position_.y + ')'); -}; - -/** - * Recalculate the scrollbar's location and its length. - * @param {Object=} opt_metrics A data structure of from the describing all the - * required dimensions. If not provided, it will be fetched from the host - * object. - */ -Blockly.Scrollbar.prototype.resize = function(opt_metrics) { - // Determine the location, height and width of the host element. - var hostMetrics = opt_metrics; - if (!hostMetrics) { - hostMetrics = this.workspace_.getMetrics(); - if (!hostMetrics) { - // Host element is likely not visible. - return; - } - } - - if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics, - this.oldHostMetrics_)) { - return; - } - this.oldHostMetrics_ = hostMetrics; - - /* hostMetrics is an object with the following properties. - * .viewHeight: Height of the visible rectangle, - * .viewWidth: Width of the visible rectangle, - * .contentHeight: Height of the contents, - * .contentWidth: Width of the content, - * .viewTop: Offset of top edge of visible rectangle from parent, - * .viewLeft: Offset of left edge of visible rectangle from parent, - * .contentTop: Offset of the top-most content from the y=0 coordinate, - * .contentLeft: Offset of the left-most content from the x=0 coordinate, - * .absoluteTop: Top-edge of view. - * .absoluteLeft: Left-edge of view. - */ - if (this.horizontal_) { - this.resizeHorizontal_(hostMetrics); - } else { - this.resizeVertical_(hostMetrics); - } - // Resizing may have caused some scrolling. - this.onScroll_(); -}; - -/** - * Recalculate a horizontal scrollbar's location and length. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - * @private - */ -Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) { - // TODO: Inspect metrics to determine if we can get away with just a content - // resize. - this.resizeViewHorizontal(hostMetrics); -}; - -/** - * Recalculate a horizontal scrollbar's location on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - */ -Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) { - var viewSize = hostMetrics.viewWidth - 1; - if (this.pair_) { - // Shorten the scrollbar to make room for the corner square. - viewSize -= Blockly.Scrollbar.scrollbarThickness; - } - this.setScrollViewSize_(Math.max(0, viewSize)); - - var xCoordinate = hostMetrics.absoluteLeft + 0.5; - if (this.pair_ && this.workspace_.RTL) { - xCoordinate += Blockly.Scrollbar.scrollbarThickness; - } - - // Horizontal toolbar should always be just above the bottom of the workspace. - var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight - - Blockly.Scrollbar.scrollbarThickness - 0.5; - this.setPosition(xCoordinate, yCoordinate); - - // If the view has been resized, a content resize will also be necessary. The - // reverse is not true. - this.resizeContentHorizontal(hostMetrics); -}; - -/** - * Recalculate a horizontal scrollbar's location within its path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - */ -Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) { - if (!this.pair_) { - // Only show the scrollbar if needed. - // Ideally this would also apply to scrollbar pairs, but that's a bigger - // headache (due to interactions with the corner square). - this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth); - } - - this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth; - if (this.ratio_ == -Infinity || this.ratio_ == Infinity || - isNaN(this.ratio_)) { - this.ratio_ = 0; - } - - var handleLength = hostMetrics.viewWidth * this.ratio_; - this.setHandleLength_(Math.max(0, handleLength)); - - var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) * - this.ratio_; - this.setHandlePosition(this.constrainHandle_(handlePosition)); -}; - -/** - * Recalculate a vertical scrollbar's location and length. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - * @private - */ -Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) { - // TODO: Inspect metrics to determine if we can get away with just a content - // resize. - this.resizeViewVertical(hostMetrics); -}; - -/** - * Recalculate a vertical scrollbar's location on the screen and path length. - * This should be called when the layout or size of the window has changed. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - */ -Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) { - var viewSize = hostMetrics.viewHeight - 1; - if (this.pair_) { - // Shorten the scrollbar to make room for the corner square. - viewSize -= Blockly.Scrollbar.scrollbarThickness; - } - this.setScrollViewSize_(Math.max(0, viewSize)); - - var xCoordinate = hostMetrics.absoluteLeft + 0.5; - if (!this.workspace_.RTL) { - xCoordinate += hostMetrics.viewWidth - - Blockly.Scrollbar.scrollbarThickness - 1; - } - var yCoordinate = hostMetrics.absoluteTop + 0.5; - this.setPosition(xCoordinate, yCoordinate); - - // If the view has been resized, a content resize will also be necessary. The - // reverse is not true. - this.resizeContentVertical(hostMetrics); -}; - -/** - * Recalculate a vertical scrollbar's location within its path and length. - * This should be called when the contents of the workspace have changed. - * @param {!Object} hostMetrics A data structure describing all the - * required dimensions, possibly fetched from the host object. - */ -Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) { - if (!this.pair_) { - // Only show the scrollbar if needed. - this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight); - } - - this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight; - if (this.ratio_ == -Infinity || this.ratio_ == Infinity || - isNaN(this.ratio_)) { - this.ratio_ = 0; - } - - var handleLength = hostMetrics.viewHeight * this.ratio_; - this.setHandleLength_(Math.max(0, handleLength)); - - var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) * - this.ratio_; - this.setHandlePosition(this.constrainHandle_(handlePosition)); -}; - -/** - * Create all the DOM elements required for a scrollbar. - * The resulting widget is not sized. - * @private - */ -Blockly.Scrollbar.prototype.createDom_ = function() { - /* Create the following DOM: - - - - - */ - var className = 'blocklyScrollbar' + - (this.horizontal_ ? 'Horizontal' : 'Vertical'); - this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null); - this.svgBackground_ = Blockly.createSvgElement('rect', - {'class': 'blocklyScrollbarBackground'}, this.svgGroup_); - var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2); - this.svgHandle_ = Blockly.createSvgElement('rect', - {'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius}, - this.svgGroup_); - Blockly.Scrollbar.insertAfter_(this.svgGroup_, - this.workspace_.getBubbleCanvas()); -}; - -/** - * Is the scrollbar visible. Non-paired scrollbars disappear when they aren't - * needed. - * @return {boolean} True if visible. - */ -Blockly.Scrollbar.prototype.isVisible = function() { - return this.isVisible_; -}; - -/** - * Set whether the scrollbar is visible. - * Only applies to non-paired scrollbars. - * @param {boolean} visible True if visible. - */ -Blockly.Scrollbar.prototype.setVisible = function(visible) { - if (visible == this.isVisible()) { - return; - } - // Ideally this would also apply to scrollbar pairs, but that's a bigger - // headache (due to interactions with the corner square). - if (this.pair_) { - throw 'Unable to toggle visibility of paired scrollbars.'; - } - - this.isVisible_ = visible; - - if (visible) { - this.svgGroup_.setAttribute('display', 'block'); - } else { - // Hide the scrollbar. - this.workspace_.setMetrics({x: 0, y: 0}); - this.svgGroup_.setAttribute('display', 'none'); - } -}; - -/** - * Scroll by one pageful. - * Called when scrollbar background is clicked. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) { - this.onMouseUpHandle_(); - if (Blockly.isRightButton(e)) { - // Right-click. - // Scrollbars have no context menu. - e.stopPropagation(); - return; - } - var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(), - this.workspace_.getInverseScreenCTM()); - var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y; - - var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_); - var handleStart = this.horizontal_ ? handleXY.x : handleXY.y; - var handlePosition = this.handlePosition_; - - var pageLength = this.handleLength_ * 0.95; - if (mouseLocation <= handleStart) { - // Decrease the scrollbar's value by a page. - handlePosition -= pageLength; - } else if (mouseLocation >= handleStart + this.handleLength_) { - // Increase the scrollbar's value by a page. - handlePosition += pageLength; - } - - this.setHandlePosition(this.constrainHandle_(handlePosition)); - - this.onScroll_(); - e.stopPropagation(); - e.preventDefault(); -}; - -/** - * Start a dragging operation. - * Called when scrollbar handle is clicked. - * @param {!Event} e Mouse down event. - * @private - */ -Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) { - this.onMouseUpHandle_(); - if (Blockly.isRightButton(e)) { - // Right-click. - // Scrollbars have no context menu. - e.stopPropagation(); - return; - } - // Look up the current translation and record it. - this.startDragHandle = this.handlePosition_; - // Record the current mouse position. - this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY; - Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEvent_(document, - 'mouseup', this, this.onMouseUpHandle_); - Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEvent_(document, - 'mousemove', this, this.onMouseMoveHandle_); - e.stopPropagation(); - e.preventDefault(); -}; - -/** - * Drag the scrollbar's handle. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) { - var currentMouse = this.horizontal_ ? e.clientX : e.clientY; - var mouseDelta = currentMouse - this.startDragMouse; - var handlePosition = this.startDragHandle + mouseDelta; - // Position the bar. - this.setHandlePosition(this.constrainHandle_(handlePosition)); - this.onScroll_(); -}; - -/** - * Stop binding to the global mouseup and mousemove events. - * @private - */ -Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() { - Blockly.hideChaff(true); - if (Blockly.Scrollbar.onMouseUpWrapper_) { - Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_); - Blockly.Scrollbar.onMouseUpWrapper_ = null; - } - if (Blockly.Scrollbar.onMouseMoveWrapper_) { - Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_); - Blockly.Scrollbar.onMouseMoveWrapper_ = null; - } -}; - -/** - * Constrain the handle's position within the minimum (0) and maximum - * (length of scrollbar) values allowed for the scrollbar. - * @param {number} value Value that is potentially out of bounds. - * @return {number} Constrained value. - * @private - */ -Blockly.Scrollbar.prototype.constrainHandle_ = function(value) { - if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) { - value = 0; - } else { - value = Math.min(value, this.scrollViewSize_ - this.handleLength_); - } - return value; -}; - -/** - * Called when scrollbar is moved. - * @private - */ -Blockly.Scrollbar.prototype.onScroll_ = function() { - var ratio = this.handlePosition_ / this.scrollViewSize_; - if (isNaN(ratio)) { - ratio = 0; - } - var xyRatio = {}; - if (this.horizontal_) { - xyRatio.x = ratio; - } else { - xyRatio.y = ratio; - } - this.workspace_.setMetrics(xyRatio); -}; - -/** - * Set the scrollbar slider's position. - * @param {number} value The distance from the top/left end of the bar. - */ -Blockly.Scrollbar.prototype.set = function(value) { - this.setHandlePosition(this.constrainHandle_(value * this.ratio_)); - this.onScroll_(); -}; - -/** - * Insert a node after a reference node. - * Contrast with node.insertBefore function. - * @param {!Element} newNode New element to insert. - * @param {!Element} refNode Existing element to precede new node. - * @private - */ -Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) { - var siblingNode = refNode.nextSibling; - var parentNode = refNode.parentNode; - if (!parentNode) { - throw 'Reference node has no parent.'; - } - if (siblingNode) { - parentNode.insertBefore(newNode, siblingNode); - } else { - parentNode.appendChild(newNode); - } -}; diff --git a/core/trashcan.js.orig b/core/trashcan.js.orig deleted file mode 100644 index 28baa0f20..000000000 --- a/core/trashcan.js.orig +++ /dev/null @@ -1,332 +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 Object representing a trash can icon. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Trashcan'); - -goog.require('goog.Timer'); -goog.require('goog.dom'); -goog.require('goog.math'); -goog.require('goog.math.Rect'); - - -/** - * Class for a trash can. - * @param {!Blockly.Workspace} workspace The workspace to sit in. - * @constructor - */ -Blockly.Trashcan = function(workspace) { - this.workspace_ = workspace; -}; - -/** - * Width of both the trash can and lid images. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.WIDTH_ = 47; - -/** - * Height of the trashcan image (minus lid). - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.BODY_HEIGHT_ = 44; - -/** - * Height of the lid image. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.LID_HEIGHT_ = 16; - -/** - * Distance between trashcan and bottom edge of workspace. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = 20; - -/** - * Distance between trashcan and right edge of workspace. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.MARGIN_SIDE_ = 20; - -/** - * Extent of hotspot on all sides beyond the size of the image. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.MARGIN_HOTSPOT_ = 10; - -/** - * Location of trashcan in sprite image. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.SPRITE_LEFT_ = 0; - -/** - * Location of trashcan in sprite image. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.SPRITE_TOP_ = 32; - -/** - * Current open/close state of the lid. - * @type {boolean} - */ -Blockly.Trashcan.prototype.isOpen = false; - -/** - * The SVG group containing the trash can. - * @type {Element} - * @private - */ -Blockly.Trashcan.prototype.svgGroup_ = null; - -/** - * The SVG image element of the trash can lid. - * @type {Element} - * @private - */ -Blockly.Trashcan.prototype.svgLid_ = null; - -/** - * Task ID of opening/closing animation. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.lidTask_ = 0; - -/** - * Current state of lid opening (0.0 = closed, 1.0 = open). - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.lidOpen_ = 0; - -/** - * Left coordinate of the trash can. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.left_ = 0; - -/** - * Top coordinate of the trash can. - * @type {number} - * @private - */ -Blockly.Trashcan.prototype.top_ = 0; - -/** - * Create the trash can elements. - * @return {!Element} The trash can's SVG group. - */ -Blockly.Trashcan.prototype.createDom = function() { - /* Here's the markup that will be generated: - - - - - - - - - - - */ - this.svgGroup_ = Blockly.createSvgElement('g', - {'class': 'blocklyTrash'}, null); - var rnd = String(Math.random()).substring(2); - var clip = Blockly.createSvgElement('clipPath', - {'id': 'blocklyTrashBodyClipPath' + rnd}, - this.svgGroup_); - Blockly.createSvgElement('rect', - {'width': this.WIDTH_, 'height': this.BODY_HEIGHT_, - 'y': this.LID_HEIGHT_}, - clip); - var body = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'}, - this.svgGroup_); - body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - this.workspace_.options.pathToMedia + Blockly.SPRITE.url); - - var clip = Blockly.createSvgElement('clipPath', - {'id': 'blocklyTrashLidClipPath' + rnd}, - this.svgGroup_); - Blockly.createSvgElement('rect', - {'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip); - this.svgLid_ = Blockly.createSvgElement('image', - {'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_, - 'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_, - 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'}, - this.svgGroup_); - this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', - this.workspace_.options.pathToMedia + Blockly.SPRITE.url); - - Blockly.bindEvent_(this.svgGroup_, 'mouseup', this, this.click); - this.animateLid_(); - return this.svgGroup_; -}; - -/** - * Initialize the trash can. - * @param {number} bottom Distance from workspace bottom to bottom of trashcan. - * @return {number} Distance from workspace bottom to the top of trashcan. - */ -Blockly.Trashcan.prototype.init = function(bottom) { - this.bottom_ = this.MARGIN_BOTTOM_ + bottom; - this.setOpen_(false); - return this.bottom_ + this.BODY_HEIGHT_ + this.LID_HEIGHT_; -}; - -/** - * Dispose of this trash can. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Trashcan.prototype.dispose = function() { - if (this.svgGroup_) { - goog.dom.removeNode(this.svgGroup_); - this.svgGroup_ = null; - } - this.svgLid_ = null; - this.workspace_ = null; - goog.Timer.clear(this.lidTask_); -}; - -/** - * Move the trash can to the bottom-right corner. - */ -Blockly.Trashcan.prototype.position = function() { - var metrics = this.workspace_.getMetrics(); - if (!metrics) { - // There are no metrics available (workspace is probably not visible). - return; - } - if (this.workspace_.RTL) { - this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness; - if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) { - this.left_ += metrics.flyoutWidth; - if (this.workspace_.toolbox_) { - this.left_ += metrics.absoluteLeft; - } - } - } else { - this.left_ = metrics.viewWidth + metrics.absoluteLeft - - this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness; - - if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { - this.left_ -= metrics.flyoutWidth; - } - } - this.top_ = metrics.viewHeight + metrics.absoluteTop - - (this.BODY_HEIGHT_ + this.LID_HEIGHT_) - this.bottom_; - - if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) { - this.top_ -= metrics.flyoutHeight; - } - this.svgGroup_.setAttribute('transform', - 'translate(' + this.left_ + ',' + this.top_ + ')'); -}; - -/** - * Return the deletion rectangle for this trash can. - * @return {goog.math.Rect} Rectangle in which to delete. - */ -Blockly.Trashcan.prototype.getClientRect = function() { - if (!this.svgGroup_) { - return null; - } - - var trashRect = this.svgGroup_.getBoundingClientRect(); - var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_; - var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_; - var width = this.WIDTH_ + 2 * this.MARGIN_HOTSPOT_; - var height = this.LID_HEIGHT_ + this.BODY_HEIGHT_ + 2 * this.MARGIN_HOTSPOT_; - return new goog.math.Rect(left, top, width, height); - -}; - -/** - * Flip the lid open or shut. - * @param {boolean} state True if open. - * @private - */ -Blockly.Trashcan.prototype.setOpen_ = function(state) { - if (this.isOpen == state) { - return; - } - goog.Timer.clear(this.lidTask_); - this.isOpen = state; - this.animateLid_(); -}; - -/** - * Rotate the lid open or closed by one step. Then wait and recurse. - * @private - */ -Blockly.Trashcan.prototype.animateLid_ = function() { - this.lidOpen_ += this.isOpen ? 0.2 : -0.2; - this.lidOpen_ = goog.math.clamp(this.lidOpen_, 0, 1); - var lidAngle = this.lidOpen_ * 45; - this.svgLid_.setAttribute('transform', 'rotate(' + - (this.workspace_.RTL ? -lidAngle : lidAngle) + ',' + - (this.workspace_.RTL ? 4 : this.WIDTH_ - 4) + ',' + - (this.LID_HEIGHT_ - 2) + ')'); - var opacity = goog.math.lerp(0.4, 0.8, this.lidOpen_); - this.svgGroup_.style.opacity = opacity; - if (this.lidOpen_ > 0 && this.lidOpen_ < 1) { - this.lidTask_ = goog.Timer.callOnce(this.animateLid_, 20, this); - } -}; - -/** - * Flip the lid shut. - * Called externally after a drag. - */ -Blockly.Trashcan.prototype.close = function() { - this.setOpen_(false); -}; - -/** - * Inspect the contents of the trash. - */ -Blockly.Trashcan.prototype.click = function() { - var dx = this.workspace_.startScrollX - this.workspace_.scrollX; - var dy = this.workspace_.startScrollY - this.workspace_.scrollY; - if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) { - return; - } - console.log('TODO: Inspect trash.'); -}; diff --git a/core/variables.js.orig b/core/variables.js.orig deleted file mode 100644 index 00c38ad21..000000000 --- a/core/variables.js.orig +++ /dev/null @@ -1,273 +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 Utility functions for handling variables. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Variables'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.Workspace'); -goog.require('goog.string'); - - -/** - * Category to separate variable names from procedures and generated functions. - */ -Blockly.Variables.NAME_TYPE = 'VARIABLE'; - -/** - * Find all user-created variables that are in use in the workspace. - * For use by generators. - * @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace. - * @return {!Array.} Array of variable names. - */ -Blockly.Variables.allUsedVariables = function(root) { - var blocks; - if (root instanceof Blockly.Block) { - // Root is Block. - blocks = root.getDescendants(); - } else if (root.getAllBlocks) { - // Root is Workspace. - blocks = root.getAllBlocks(); - } else { - throw 'Not Block or Workspace: ' + root; - } - var variableHash = Object.create(null); - // Iterate through every block and add each variable to the hash. - for (var x = 0; x < blocks.length; x++) { - var blockVariables = blocks[x].getVars(); - if (blockVariables) { - for (var y = 0; y < blockVariables.length; y++) { - var varName = blockVariables[y]; - // Variable name may be null if the block is only half-built. - if (varName) { - variableHash[varName.toLowerCase()] = varName; - } - } - } - } - // Flatten the hash into a list. - var variableList = []; - for (var name in variableHash) { - variableList.push(variableHash[name]); - } - return variableList; -}; - -/** - * Find all variables that the user has created through the workspace or - * toolbox. For use by generators. - * @param {!Blockly.Workspace} root The workspace to inspect. - * @return {!Array.} Array of variable names. - */ -Blockly.Variables.allVariables = function(root) { - if (root instanceof Blockly.Block) { - // Root is Block. - console.warn('Deprecated call to Blockly.Variables.allVariables ' + - 'with a block instead of a workspace. You may want ' + - 'Blockly.Variables.allUsedVariables'); - } - return root.variableList; -}; - -/** - * Construct the blocks required by the flyout for the variable category. - * @param {!Blockly.Workspace} workspace The workspace contianing variables. - * @return {!Array.} Array of XML block elements. - */ -Blockly.Variables.flyoutCategory = function(workspace) { - var variableList = workspace.variableList; - variableList.sort(goog.string.caseInsensitiveCompare); - - var xmlList = []; - var button = goog.dom.createDom('button'); - button.setAttribute('text', Blockly.Msg.NEW_VARIABLE); - xmlList.push(button); - - if (variableList.length > 0) { - if (Blockly.Blocks['variables_set']) { - // - // item - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'variables_set'); - if (Blockly.Blocks['math_change']) { - block.setAttribute('gap', 8); - } else { - block.setAttribute('gap', 24); - } - var field = goog.dom.createDom('field', null, variableList[0]); - field.setAttribute('name', 'VAR'); - block.appendChild(field); - xmlList.push(block); - } - if (Blockly.Blocks['math_change']) { - // - // - // - // 1 - // - // - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'math_change'); - if (Blockly.Blocks['variables_get']) { - block.setAttribute('gap', 20); - } - var value = goog.dom.createDom('value'); - value.setAttribute('name', 'DELTA'); - block.appendChild(value); - - var field = goog.dom.createDom('field', null, variableList[0]); - field.setAttribute('name', 'VAR'); - block.appendChild(field); - - var shadowBlock = goog.dom.createDom('shadow'); - shadowBlock.setAttribute('type', 'math_number'); - value.appendChild(shadowBlock); - - var numberField = goog.dom.createDom('field', null, '1'); - numberField.setAttribute('name', 'NUM'); - shadowBlock.appendChild(numberField); - - xmlList.push(block); - } - - for (var i = 0; i < variableList.length; i++) { - if (Blockly.Blocks['variables_get']) { - // - // item - // - var block = goog.dom.createDom('block'); - block.setAttribute('type', 'variables_get'); - if (Blockly.Blocks['variables_set']) { - block.setAttribute('gap', 8); - } - var field = goog.dom.createDom('field', null, variableList[i]); - field.setAttribute('name', 'VAR'); - block.appendChild(field); - xmlList.push(block); - } - } - } - return xmlList; -}; - -/** -* Return a new variable name that is not yet being used. This will try to -* generate single letter variable names in the range 'i' to 'z' to start with. -* If no unique name is located it will try 'i' to 'z', 'a' to 'h', -* then 'i2' to 'z2' etc. Skip 'l'. - * @param {!Blockly.Workspace} workspace The workspace to be unique in. -* @return {string} New variable name. -*/ -Blockly.Variables.generateUniqueName = function(workspace) { - var variableList = workspace.variableList; - var newName = ''; - if (variableList.length) { - var nameSuffix = 1; - var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'. - var letterIndex = 0; - var potName = letters.charAt(letterIndex); - while (!newName) { - var inUse = false; - for (var i = 0; i < variableList.length; i++) { - if (variableList[i].toLowerCase() == potName) { - // This potential name is already used. - inUse = true; - break; - } - } - if (inUse) { - // Try the next potential name. - letterIndex++; - if (letterIndex == letters.length) { - // Reached the end of the character sequence so back to 'i'. - // a new suffix. - letterIndex = 0; - nameSuffix++; - } - potName = letters.charAt(letterIndex); - if (nameSuffix > 1) { - potName += nameSuffix; - } - } else { - // We can use the current potential name. - newName = potName; - } - } - } else { - newName = 'i'; - } - return newName; -}; - -/** - * Create a new variable on the given workspace. - * @param {!Blockly.Workspace} workspace The workspace on which to create the - * variable. - * @return {null|undefined|string} An acceptable new variable name, or null if - * change is to be aborted (cancel button), or undefined if an existing - * variable was chosen. - */ -Blockly.Variables.createVariable = function(workspace) { - while (true) { - var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, ''); - if (text) { - if (workspace.variableIndexOf(text) != -1) { - window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1', - text.toLowerCase())); - } else { - workspace.createVariable(text); - break; - } - } else { - text = null; - break; - } - } - return text; -}; - -/** - * Prompt the user for a new variable name. - * @param {string} promptText The string of the prompt. - * @param {string} defaultText The default value to show in the prompt's field. - * @return {?string} The new variable name, or null if the user picked - * something illegal. - */ -Blockly.Variables.promptName = function(promptText, defaultText) { - var newVar = window.prompt(promptText, defaultText); - // Merge runs of whitespace. Strip leading and trailing whitespace. - // Beyond this, all names are legal. - if (newVar) { - newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, ''); - if (newVar == Blockly.Msg.RENAME_VARIABLE || - newVar == Blockly.Msg.NEW_VARIABLE) { - // Ok, not ALL names are legal... - newVar = null; - } - } - return newVar; -}; diff --git a/core/warning.js.orig b/core/warning.js.orig deleted file mode 100644 index bffbf06df..000000000 --- a/core/warning.js.orig +++ /dev/null @@ -1,185 +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 Object representing a warning. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Warning'); - -goog.require('Blockly.Bubble'); -goog.require('Blockly.Icon'); - - -/** - * Class for a warning. - * @param {!Blockly.Block} block The block associated with this warning. - * @extends {Blockly.Icon} - * @constructor - */ -Blockly.Warning = function(block) { - Blockly.Warning.superClass_.constructor.call(this, block); - this.createIcon(); - // The text_ object can contain multiple warnings. - this.text_ = {}; -}; -goog.inherits(Blockly.Warning, Blockly.Icon); - -/** - * Does this icon get hidden when the block is collapsed. - */ -Blockly.Warning.prototype.collapseHidden = false; - -/** - * Draw the warning icon. - * @param {!Element} group The icon group. - * @private - */ -Blockly.Warning.prototype.drawIcon_ = function(group) { - // Triangle with rounded corners. - Blockly.createSvgElement('path', - {'class': 'blocklyIconShape', - 'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'}, - group); - // Can't use a real '!' text character since different browsers and operating - // systems render it differently. - // Body of exclamation point. - Blockly.createSvgElement('path', - {'class': 'blocklyIconSymbol', - 'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'}, - group); - // Dot of exclamation point. - Blockly.createSvgElement('rect', - {'class': 'blocklyIconSymbol', - 'x': '7', 'y': '11', 'height': '2', 'width': '2'}, - group); -}; - -/** - * Create the text for the warning's bubble. - * @param {string} text The text to display. - * @return {!SVGTextElement} The top-level node of the text. - * @private - */ -Blockly.Warning.textToDom_ = function(text) { - var paragraph = /** @type {!SVGTextElement} */ ( - Blockly.createSvgElement('text', - {'class': 'blocklyText blocklyBubbleText', - 'y': Blockly.Bubble.BORDER_WIDTH}, - null)); - var lines = text.split('\n'); - for (var i = 0; i < lines.length; i++) { - var tspanElement = Blockly.createSvgElement('tspan', - {'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph); - var textNode = document.createTextNode(lines[i]); - tspanElement.appendChild(textNode); - } - return paragraph; -}; - -/** - * Show or hide the warning bubble. - * @param {boolean} visible True if the bubble should be visible. - */ -Blockly.Warning.prototype.setVisible = function(visible) { - if (visible == this.isVisible()) { - // No change. - return; - } - Blockly.Events.fire( - new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible)); - if (visible) { - // Create the bubble to display all warnings. - var paragraph = Blockly.Warning.textToDom_(this.getText()); - this.bubble_ = new Blockly.Bubble( - /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace), - paragraph, this.block_.svgPath_, this.iconXY_, null, null); - if (this.block_.RTL) { - // Right-align the paragraph. - // This cannot be done until the bubble is rendered on screen. - var maxWidth = paragraph.getBBox().width; - for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) { - textElement.setAttribute('text-anchor', 'end'); - textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH); - } - } - this.updateColour(); - // Bump the warning into the right location. - var size = this.bubble_.getBubbleSize(); - this.bubble_.setBubbleSize(size.width, size.height); - } else { - // Dispose of the bubble. - this.bubble_.dispose(); - this.bubble_ = null; - this.body_ = null; - } -}; - -/** - * Bring the warning to the top of the stack when clicked on. - * @param {!Event} e Mouse up event. - * @private - */ -Blockly.Warning.prototype.bodyFocus_ = function(e) { - this.bubble_.promote_(); -}; - -/** - * Set this warning's text. - * @param {string} text Warning text (or '' to delete). - * @param {string} id An ID for this text entry to be able to maintain - * multiple warnings. - */ -Blockly.Warning.prototype.setText = function(text, id) { - if (this.text_[id] == text) { - return; - } - if (text) { - this.text_[id] = text; - } else { - delete this.text_[id]; - } - if (this.isVisible()) { - this.setVisible(false); - this.setVisible(true); - } -}; - -/** - * Get this warning's texts. - * @return {string} All texts concatenated into one string. - */ -Blockly.Warning.prototype.getText = function() { - var allWarnings = []; - for (var id in this.text_) { - allWarnings.push(this.text_[id]); - } - return allWarnings.join('\n'); -}; - -/** - * Dispose of this warning. - */ -Blockly.Warning.prototype.dispose = function() { - this.block_.warning = null; - Blockly.Icon.prototype.dispose.call(this); -}; diff --git a/core/workspace.js.orig b/core/workspace.js.orig deleted file mode 100644 index a8e97c04b..000000000 --- a/core/workspace.js.orig +++ /dev/null @@ -1,501 +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 Object representing a workspace. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Workspace'); - -goog.require('goog.math'); - - -/** - * Class for a workspace. This is a data structure that contains blocks. - * There is no UI, and can be created headlessly. - * @param {Blockly.Options} opt_options Dictionary of options. - * @constructor - */ -Blockly.Workspace = function(opt_options) { - /** @type {string} */ - this.id = Blockly.genUid(); - Blockly.Workspace.WorkspaceDB_[this.id] = this; - /** @type {!Blockly.Options} */ - this.options = opt_options || {}; - /** @type {boolean} */ - this.RTL = !!this.options.RTL; - /** @type {boolean} */ - this.horizontalLayout = !!this.options.horizontalLayout; - /** @type {number} */ - this.toolboxPosition = this.options.toolboxPosition; - - /** - * @type {!Array.} - * @private - */ - this.topBlocks_ = []; - /** - * @type {!Array.} - * @private - */ - this.listeners_ = []; - /** - * @type {!Array.} - * @private - */ - this.undoStack_ = []; - /** - * @type {!Array.} - * @private - */ - this.redoStack_ = []; - /** - * @type {!Object} - * @private - */ - this.blockDB_ = Object.create(null); - /* - * @type {!Array.} - * A list of all of the named variables in the workspace, including variables - * that are not currently in use. - */ - this.variableList = []; -}; - -/** - * Workspaces may be headless. - * @type {boolean} True if visible. False if headless. - */ -Blockly.Workspace.prototype.rendered = false; - -/** - * Maximum number of undo events in stack. - * @type {number} 0 to turn off undo, Infinity for unlimited. - */ -Blockly.Workspace.prototype.MAX_UNDO = 1024; - -/** - * Dispose of this workspace. - * Unlink from all DOM elements to prevent memory leaks. - */ -Blockly.Workspace.prototype.dispose = function() { - this.listeners_.length = 0; - this.clear(); - // Remove from workspace database. - delete Blockly.Workspace.WorkspaceDB_[this.id]; -}; - -/** - * Angle away from the horizontal to sweep for blocks. Order of execution is - * generally top to bottom, but a small angle changes the scan to give a bit of - * a left to right bias (reversed in RTL). Units are in degrees. - * See: http://tvtropes.org/pmwiki/pmwiki.php/Main/DiagonalBilling. - */ -Blockly.Workspace.SCAN_ANGLE = 3; - -/** - * Add a block to the list of top blocks. - * @param {!Blockly.Block} block Block to remove. - */ -Blockly.Workspace.prototype.addTopBlock = function(block) { - this.topBlocks_.push(block); - if (this.isFlyout) { - // This is for the (unlikely) case where you have a variable in a block in - // an always-open flyout. It needs to be possible to edit the block in the - // flyout, so the contents of the dropdown need to be correct. - var variables = Blockly.Variables.allUsedVariables(block); - for (var i = 0; i < variables.length; i++) { - if (this.variableList.indexOf(variables[i]) == -1) { - this.variableList.push(variables[i]); - } - } - } -}; - -/** - * Remove a block from the list of top blocks. - * @param {!Blockly.Block} block Block to remove. - */ -Blockly.Workspace.prototype.removeTopBlock = function(block) { - var found = false; - for (var child, i = 0; child = this.topBlocks_[i]; i++) { - if (child == block) { - this.topBlocks_.splice(i, 1); - found = true; - break; - } - } - if (!found) { - throw 'Block not present in workspace\'s list of top-most blocks.'; - } -}; - -/** - * Finds the top-level blocks and returns them. Blocks are optionally sorted - * by position; top to bottom (with slight LTR or RTL bias). - * @param {boolean} ordered Sort the list if true. - * @return {!Array.} The top-level block objects. - */ -Blockly.Workspace.prototype.getTopBlocks = function(ordered) { - // Copy the topBlocks_ list. - var blocks = [].concat(this.topBlocks_); - if (ordered && blocks.length > 1) { - var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE)); - if (this.RTL) { - offset *= -1; - } - blocks.sort(function(a, b) { - var aXY = a.getRelativeToSurfaceXY(); - var bXY = b.getRelativeToSurfaceXY(); - return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x); - }); - } - return blocks; -}; - -/** - * Find all blocks in workspace. No particular order. - * @return {!Array.} Array of blocks. - */ -Blockly.Workspace.prototype.getAllBlocks = function() { - var blocks = this.getTopBlocks(false); - for (var i = 0; i < blocks.length; i++) { - blocks.push.apply(blocks, blocks[i].getChildren()); - } - return blocks; -}; - -/** - * Dispose of all blocks in workspace. - */ -Blockly.Workspace.prototype.clear = function() { - var existingGroup = Blockly.Events.getGroup(); - if (!existingGroup) { - Blockly.Events.setGroup(true); - } - while (this.topBlocks_.length) { - this.topBlocks_[0].dispose(); - } - if (!existingGroup) { - Blockly.Events.setGroup(false); - } - - this.variableList.length = 0; -}; - -/** - * Walk the workspace and update the list of variables to only contain ones in - * use on the workspace. Use when loading new workspaces from disk. - * @param {boolean} clearList True if the old variable list should be cleared. - */ -Blockly.Workspace.prototype.updateVariableList = function(clearList) { - // TODO: Sort - if (!this.isFlyout) { - // Update the list in place so that the flyout's references stay correct. - if (clearList) { - this.variableList.length = 0; - } - var allVariables = Blockly.Variables.allUsedVariables(this); - for (var i = 0; i < allVariables.length; i++) { - this.createVariable(allVariables[i]); - } - } -}; - -/** - * Rename a variable by updating its name in the variable list. - * TODO: #468 - * @param {string} oldName Variable to rename. - * @param {string} newName New variable name. - */ -Blockly.Workspace.prototype.renameVariable = function(oldName, newName) { - // Find the old name in the list. - var variableIndex = this.variableIndexOf(oldName); - var newVariableIndex = this.variableIndexOf(newName); - - // We might be renaming to an existing name but with different case. If so, - // we will also update all of the blocks using the new name to have the - // correct case. - if (newVariableIndex != -1 && - this.variableList[newVariableIndex] != newName) { - var oldCase = this.variableList[newVariableIndex]; - } - - Blockly.Events.setGroup(true); - var blocks = this.getAllBlocks(); - // Iterate through every block. - for (var i = 0; i < blocks.length; i++) { - blocks[i].renameVar(oldName, newName); - if (oldCase) { - blocks[i].renameVar(oldCase, newName); - } - } - Blockly.Events.setGroup(false); - - - if (variableIndex == newVariableIndex || - variableIndex != -1 && newVariableIndex == -1) { - // Only changing case, or renaming to a completely novel name. - this.variableList[variableIndex] = newName; - } else if (variableIndex != -1 && newVariableIndex != -1) { - // Renaming one existing variable to another existing variable. - this.variableList.splice(variableIndex, 1); - // The case might have changed. - this.variableList[newVariableIndex] = newName; - } else { - this.variableList.push(newName); - console.log('Tried to rename an non-existent variable.'); - } -}; - -/** - * Create a variable with the given name. - * TODO: #468 - * @param {string} name The new variable's name. - */ -Blockly.Workspace.prototype.createVariable = function(name) { - var index = this.variableIndexOf(name); - if (index == -1) { - this.variableList.push(name); - } -}; - -/** - * Find all the uses of a named variable. - * @param {string} name Name of variable. - * @return {!Array.} Array of block usages. - */ -Blockly.Workspace.prototype.getVariableUses = function(name) { - var uses = []; - var blocks = this.getAllBlocks(); - // Iterate through every block and check the name. - for (var i = 0; i < blocks.length; i++) { - var blockVariables = blocks[i].getVars(); - if (blockVariables) { - for (var j = 0; j < blockVariables.length; j++) { - var varName = blockVariables[j]; - // Variable name may be null if the block is only half-built. - if (varName && Blockly.Names.equals(varName, name)) { - uses.push(blocks[i]); - } - } - } - } - return uses; -}; - -/** - * Delete a variables and all of its uses from this workspace. - * @param {string} name Name of variable to delete. - */ -Blockly.Workspace.prototype.deleteVariable = function(name) { - var variableIndex = this.variableIndexOf(name); - if (variableIndex != -1) { - var uses = this.getVariableUses(name); - if (uses.length > 1) { - for (var i = 0, block; block = uses[i]; i++) { - if (block.type == 'procedures_defnoreturn' || - block.type == 'procedures_defreturn') { - var procedureName = block.getFieldValue('NAME'); - window.alert( - Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name). - replace('%2', procedureName)); - return; - } - } - var ok = window.confirm( - Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length). - replace('%2', name)); - if (!ok) { - return; - } - } - - Blockly.Events.setGroup(true); - for (var i = 0; i < uses.length; i++) { - uses[i].dispose(true, false); - } - Blockly.Events.setGroup(false); - this.variableList.splice(variableIndex, 1); - } -}; - -/** - * Check whether a variable exists with the given name. The check is - * case-insensitive. - * @param {string} name The name to check for. - * @return {number} The index of the name in the variable list, or -1 if it is - * not present. - */ -Blockly.Workspace.prototype.variableIndexOf = function(name) { - for (var i = 0, varname; varname = this.variableList[i]; i++) { - if (Blockly.Names.equals(varname, name)) { - return i; - } - } - return -1; -}; - -/** - * Returns the horizontal offset of the workspace. - * Intended for LTR/RTL compatibility in XML. - * Not relevant for a headless workspace. - * @return {number} Width. - */ -Blockly.Workspace.prototype.getWidth = function() { - return 0; -}; - -/** - * Obtain a newly created block. - * @param {?string} prototypeName Name of the language object containing - * type-specific functions for this block. - * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise - * create a new id. - * @return {!Blockly.Block} The created block. - */ -Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) { - return new Blockly.Block(this, prototypeName, opt_id); -}; - -/** - * The number of blocks that may be added to the workspace before reaching - * the maxBlocks. - * @return {number} Number of blocks left. - */ -Blockly.Workspace.prototype.remainingCapacity = function() { - if (isNaN(this.options.maxBlocks)) { - return Infinity; - } - return this.options.maxBlocks - this.getAllBlocks().length; -}; - -/** - * Undo or redo the previous action. - * @param {boolean} redo False if undo, true if redo. - */ -Blockly.Workspace.prototype.undo = function(redo) { - var inputStack = redo ? this.redoStack_ : this.undoStack_; - var outputStack = redo ? this.undoStack_ : this.redoStack_; - var inputEvent = inputStack.pop(); - if (!inputEvent) { - return; - } - var events = [inputEvent]; - // Do another undo/redo if the next one is of the same group. - while (inputStack.length && inputEvent.group && - inputEvent.group == inputStack[inputStack.length - 1].group) { - events.push(inputStack.pop()); - } - // Push these popped events on the opposite stack. - for (var i = 0, event; event = events[i]; i++) { - outputStack.push(event); - } - events = Blockly.Events.filter(events, redo); - Blockly.Events.recordUndo = false; - for (var i = 0, event; event = events[i]; i++) { - event.run(redo); - } - Blockly.Events.recordUndo = true; -}; - -/** - * Clear the undo/redo stacks. - */ -Blockly.Workspace.prototype.clearUndo = function() { - this.undoStack_.length = 0; - this.redoStack_.length = 0; - // Stop any events already in the firing queue from being undoable. - Blockly.Events.clearPendingUndo(); -}; - -/** - * When something in this workspace changes, call a function. - * @param {!Function} func Function to call. - * @return {!Function} Function that can be passed to - * removeChangeListener. - */ -Blockly.Workspace.prototype.addChangeListener = function(func) { - this.listeners_.push(func); - return func; -}; - -/** - * Stop listening for this workspace's changes. - * @param {Function} func Function to stop calling. - */ -Blockly.Workspace.prototype.removeChangeListener = function(func) { - var i = this.listeners_.indexOf(func); - if (i != -1) { - this.listeners_.splice(i, 1); - } -}; - -/** - * Fire a change event. - * @param {!Blockly.Events.Abstract} event Event to fire. - */ -Blockly.Workspace.prototype.fireChangeListener = function(event) { - if (event.recordUndo) { - this.undoStack_.push(event); - this.redoStack_.length = 0; - if (this.undoStack_.length > this.MAX_UNDO) { - this.undoStack_.unshift(); - } - } - for (var i = 0, func; func = this.listeners_[i]; i++) { - func(event); - } -}; - -/** - * Find the block on this workspace with the specified ID. - * @param {string} id ID of block to find. - * @return {Blockly.Block} The sought after block or null if not found. - */ -Blockly.Workspace.prototype.getBlockById = function(id) { - return this.blockDB_[id] || null; -}; - -/** - * Database of all workspaces. - * @private - */ -Blockly.Workspace.WorkspaceDB_ = Object.create(null); - -/** - * Find the workspace with the specified ID. - * @param {string} id ID of workspace to find. - * @return {Blockly.Workspace} The sought after workspace or null if not found. - */ -Blockly.Workspace.getById = function(id) { - return Blockly.Workspace.WorkspaceDB_[id] || null; -}; - -// Export symbols that would otherwise be renamed by Closure compiler. -Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear; -Blockly.Workspace.prototype['clearUndo'] = - Blockly.Workspace.prototype.clearUndo; -Blockly.Workspace.prototype['addChangeListener'] = - Blockly.Workspace.prototype.addChangeListener; -Blockly.Workspace.prototype['removeChangeListener'] = - Blockly.Workspace.prototype.removeChangeListener; diff --git a/core/xml.js.orig b/core/xml.js.orig deleted file mode 100644 index 2567560cd..000000000 --- a/core/xml.js.orig +++ /dev/null @@ -1,566 +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 XML reader and writer. - * @author fraser@google.com (Neil Fraser) - */ -'use strict'; - -goog.provide('Blockly.Xml'); - -goog.require('goog.asserts'); -goog.require('goog.dom'); - - -/** - * Encode a block tree as XML. - * @param {!Blockly.Workspace} workspace The workspace containing blocks. - * @return {!Element} XML document. - */ -Blockly.Xml.workspaceToDom = function(workspace) { - var xml = goog.dom.createDom('xml'); - var blocks = workspace.getTopBlocks(true); - for (var i = 0, block; block = blocks[i]; i++) { - xml.appendChild(Blockly.Xml.blockToDomWithXY(block)); - } - return xml; -}; - -/** - * Encode a block subtree as XML with XY coordinates. - * @param {!Blockly.Block} block The root block to encode. - * @return {!Element} Tree of XML elements. - */ -Blockly.Xml.blockToDomWithXY = function(block) { - var width; // Not used in LTR. - if (block.workspace.RTL) { - width = block.workspace.getWidth(); - } - var element = Blockly.Xml.blockToDom(block); - var xy = block.getRelativeToSurfaceXY(); - element.setAttribute('x', - Math.round(block.workspace.RTL ? width - xy.x : xy.x)); - element.setAttribute('y', Math.round(xy.y)); - return element; -}; - -/** - * Encode a block subtree as XML. - * @param {!Blockly.Block} block The root block to encode. - * @return {!Element} Tree of XML elements. - */ -Blockly.Xml.blockToDom = function(block) { - var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block'); - element.setAttribute('type', block.type); - element.setAttribute('id', block.id); - if (block.mutationToDom) { - // Custom data for an advanced block. - var mutation = block.mutationToDom(); - if (mutation && (mutation.hasChildNodes() || mutation.hasAttributes())) { - element.appendChild(mutation); - } - } - function fieldToDom(field) { - if (field.name && field.EDITABLE) { - var container = goog.dom.createDom('field', null, field.getValue()); - container.setAttribute('name', field.name); - element.appendChild(container); - } - } - for (var i = 0, input; input = block.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - fieldToDom(field); - } - } - - var commentText = block.getCommentText(); - if (commentText) { - var commentElement = goog.dom.createDom('comment', null, commentText); - if (typeof block.comment == 'object') { - commentElement.setAttribute('pinned', block.comment.isVisible()); - var hw = block.comment.getBubbleSize(); - commentElement.setAttribute('h', hw.height); - commentElement.setAttribute('w', hw.width); - } - element.appendChild(commentElement); - } - - if (block.data) { - var dataElement = goog.dom.createDom('data', null, block.data); - element.appendChild(dataElement); - } - - for (var i = 0, input; input = block.inputList[i]; i++) { - var container; - var empty = true; - if (input.type == Blockly.DUMMY_INPUT) { - continue; - } else { - var childBlock = input.connection.targetBlock(); - if (input.type == Blockly.INPUT_VALUE) { - container = goog.dom.createDom('value'); - } else if (input.type == Blockly.NEXT_STATEMENT) { - container = goog.dom.createDom('statement'); - } - var shadow = input.connection.getShadowDom(); - if (shadow && (!childBlock || !childBlock.isShadow())) { - container.appendChild(Blockly.Xml.cloneShadow_(shadow)); - } - if (childBlock) { - container.appendChild(Blockly.Xml.blockToDom(childBlock)); - empty = false; - } - } - container.setAttribute('name', input.name); - if (!empty) { - element.appendChild(container); - } - } - if (block.inputsInlineDefault != block.inputsInline) { - element.setAttribute('inline', block.inputsInline); - } - if (block.isCollapsed()) { - element.setAttribute('collapsed', true); - } - if (block.disabled) { - element.setAttribute('disabled', true); - } - if (!block.isDeletable() && !block.isShadow()) { - element.setAttribute('deletable', false); - } - if (!block.isMovable() && !block.isShadow()) { - element.setAttribute('movable', false); - } - if (!block.isEditable()) { - element.setAttribute('editable', false); - } - - var nextBlock = block.getNextBlock(); - if (nextBlock) { - var container = goog.dom.createDom('next', null, - Blockly.Xml.blockToDom(nextBlock)); - element.appendChild(container); - } - var shadow = block.nextConnection && block.nextConnection.getShadowDom(); - if (shadow && (!nextBlock || !nextBlock.isShadow())) { - container.appendChild(Blockly.Xml.cloneShadow_(shadow)); - } - - return element; -}; - -/** - * Deeply clone the shadow's DOM so that changes don't back-wash to the block. - * @param {!Element} shadow A tree of XML elements. - * @return {!Element} A tree of XML elements. - * @private - */ -Blockly.Xml.cloneShadow_ = function(shadow) { - shadow = shadow.cloneNode(true); - // Walk the tree looking for whitespace. Don't prune whitespace in a tag. - var node = shadow; - var textNode; - while (node) { - if (node.firstChild) { - node = node.firstChild; - } else { - while (node && !node.nextSibling) { - textNode = node; - node = node.parentNode; - if (textNode.nodeType == 3 && textNode.data.trim() == '' && - node.firstChild != textNode) { - // Prune whitespace after a tag. - goog.dom.removeNode(textNode); - } - } - if (node) { - textNode = node; - node = node.nextSibling; - if (textNode.nodeType == 3 && textNode.data.trim() == '') { - // Prune whitespace before a tag. - goog.dom.removeNode(textNode); - } - } - } - } - return shadow; -}; - -/** - * Converts a DOM structure into plain text. - * Currently the text format is fairly ugly: all one line with no whitespace. - * @param {!Element} dom A tree of XML elements. - * @return {string} Text representation. - */ -Blockly.Xml.domToText = function(dom) { - var oSerializer = new XMLSerializer(); - return oSerializer.serializeToString(dom); -}; - -/** - * Converts a DOM structure into properly indented text. - * @param {!Element} dom A tree of XML elements. - * @return {string} Text representation. - */ -Blockly.Xml.domToPrettyText = function(dom) { - // This function is not guaranteed to be correct for all XML. - // But it handles the XML that Blockly generates. - var blob = Blockly.Xml.domToText(dom); - // Place every open and close tag on its own line. - var lines = blob.split('<'); - // Indent every line. - var indent = ''; - for (var i = 1; i < lines.length; i++) { - var line = lines[i]; - if (line[0] == '/') { - indent = indent.substring(2); - } - lines[i] = indent + '<' + line; - if (line[0] != '/' && line.slice(-2) != '/>') { - indent += ' '; - } - } - // Pull simple tags back together. - // E.g. - var text = lines.join('\n'); - text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1'); - // Trim leading blank line. - return text.replace(/^\n/, ''); -}; - -/** - * Converts plain text into a DOM structure. - * Throws an error if XML doesn't parse. - * @param {string} text Text representation. - * @return {!Element} A tree of XML elements. - */ -Blockly.Xml.textToDom = function(text) { - var oParser = new DOMParser(); - var dom = oParser.parseFromString(text, 'text/xml'); - // The DOM should have one and only one top-level node, an XML tag. - if (!dom || !dom.firstChild || - dom.firstChild.nodeName.toLowerCase() != 'xml' || - dom.firstChild !== dom.lastChild) { - // Whatever we got back from the parser is not XML. - goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.'); - } - return dom.firstChild; -}; - -/** - * Decode an XML DOM and create blocks on the workspace. - * @param {!Element} xml XML DOM. - * @param {!Blockly.Workspace} workspace The workspace. - */ -Blockly.Xml.domToWorkspace = function(xml, workspace) { - if (xml instanceof Blockly.Workspace) { - var swap = xml; - xml = workspace; - workspace = swap; - console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' + - 'swap the arguments.'); - } - var width; // Not used in LTR. - if (workspace.RTL) { - width = workspace.getWidth(); - } - Blockly.Field.startCache(); - // Safari 7.1.3 is known to provide node lists with extra references to - // children beyond the lists' length. Trust the length, do not use the - // looping pattern of checking the index for an object. - var childCount = xml.childNodes.length; - var existingGroup = Blockly.Events.getGroup(); - if (!existingGroup) { - Blockly.Events.setGroup(true); - } - for (var i = 0; i < childCount; i++) { - var xmlChild = xml.childNodes[i]; - var name = xmlChild.nodeName.toLowerCase(); - if (name == 'block' || - (name == 'shadow' && !Blockly.Events.recordUndo)) { - // Allow top-level shadow blocks if recordUndo is disabled since - // that means an undo is in progress. Such a block is expected - // to be moved to a nested destination in the next operation. - var block = Blockly.Xml.domToBlock(xmlChild, workspace); - var blockX = parseInt(xmlChild.getAttribute('x'), 10); - var blockY = parseInt(xmlChild.getAttribute('y'), 10); - if (!isNaN(blockX) && !isNaN(blockY)) { - block.moveBy(workspace.RTL ? width - blockX : blockX, blockY); - } - } else if (name == 'shadow') { - goog.asserts.fail('Shadow block cannot be a top-level block.'); - } - } - if (!existingGroup) { - Blockly.Events.setGroup(false); - } - Blockly.Field.stopCache(); - - workspace.updateVariableList(false); -}; - -/** - * Decode an XML block tag and create a block (and possibly sub blocks) on the - * workspace. - * @param {!Element} xmlBlock XML block element. - * @param {!Blockly.Workspace} workspace The workspace. - * @return {!Blockly.Block} The root block created. - */ -Blockly.Xml.domToBlock = function(xmlBlock, workspace) { - if (xmlBlock instanceof Blockly.Workspace) { - var swap = xmlBlock; - xmlBlock = workspace; - workspace = swap; - console.warn('Deprecated call to Blockly.Xml.domToBlock, ' + - 'swap the arguments.'); - } - // Create top-level block. - Blockly.Events.disable(); - try { - var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace); - if (workspace.rendered) { - // Hide connections to speed up assembly. - topBlock.setConnectionsHidden(true); - // Generate list of all blocks. - var blocks = topBlock.getDescendants(); - // Render each block. - for (var i = blocks.length - 1; i >= 0; i--) { - blocks[i].initSvg(); - } - for (var i = blocks.length - 1; i >= 0; i--) { - blocks[i].render(false); - } - // Populating the connection database may be defered until after the - // blocks have rendered. - setTimeout(function() { - if (topBlock.workspace) { // Check that the block hasn't been deleted. - topBlock.setConnectionsHidden(false); - } - }, 1); - topBlock.updateDisabled(); - // Allow the scrollbars to resize and move based on the new contents. - // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. - workspace.resizeContents(); - } - } finally { - Blockly.Events.enable(); - } - if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.Create(topBlock)); - } - return topBlock; -}; - -/** - * Decode an XML block tag and create a block (and possibly sub blocks) on the - * workspace. - * @param {!Element} xmlBlock XML block element. - * @param {!Blockly.Workspace} workspace The workspace. - * @return {!Blockly.Block} The root block created. - * @private - */ -Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) { - var block = null; - var prototypeName = xmlBlock.getAttribute('type'); - goog.asserts.assert(prototypeName, 'Block type unspecified: %s', - xmlBlock.outerHTML); - var id = xmlBlock.getAttribute('id'); - block = workspace.newBlock(prototypeName, id); - - var blockChild = null; - for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) { - if (xmlChild.nodeType == 3) { - // Ignore any text at the level. It's all whitespace anyway. - continue; - } - var input; - - // Find any enclosed blocks or shadows in this tag. - var childBlockNode = null; - var childShadowNode = null; - for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j]; - j++) { - if (grandchildNode.nodeType == 1) { - if (grandchildNode.nodeName.toLowerCase() == 'block') { - childBlockNode = grandchildNode; - } else if (grandchildNode.nodeName.toLowerCase() == 'shadow') { - childShadowNode = grandchildNode; - } - } - } - // Use the shadow block if there is no child block. - if (!childBlockNode && childShadowNode) { - childBlockNode = childShadowNode; - } - - var name = xmlChild.getAttribute('name'); - switch (xmlChild.nodeName.toLowerCase()) { - case 'mutation': - // Custom data for an advanced block. - if (block.domToMutation) { - block.domToMutation(xmlChild); - if (block.initSvg) { - // Mutation may have added some elements that need initalizing. - block.initSvg(); - } - } - break; - case 'comment': - block.setCommentText(xmlChild.textContent); - var visible = xmlChild.getAttribute('pinned'); - if (visible && !block.isInFlyout) { - // Give the renderer a millisecond to render and position the block - // before positioning the comment bubble. - setTimeout(function() { - if (block.comment && block.comment.setVisible) { - block.comment.setVisible(visible == 'true'); - } - }, 1); - } - var bubbleW = parseInt(xmlChild.getAttribute('w'), 10); - var bubbleH = parseInt(xmlChild.getAttribute('h'), 10); - if (!isNaN(bubbleW) && !isNaN(bubbleH) && - block.comment && block.comment.setVisible) { - block.comment.setBubbleSize(bubbleW, bubbleH); - } - break; - case 'data': - block.data = xmlChild.textContent; - break; - case 'title': - // Titles were renamed to field in December 2013. - // Fall through. - case 'field': - var field = block.getField(name); - if (!field) { - console.warn('Ignoring non-existent field ' + name + ' in block ' + - prototypeName); - break; - } - field.setValue(xmlChild.textContent); - break; - case 'value': - case 'statement': - input = block.getInput(name); - if (!input) { - console.warn('Ignoring non-existent input ' + name + ' in block ' + - prototypeName); - break; - } - if (childShadowNode) { - input.connection.setShadowDom(childShadowNode); - } - if (childBlockNode) { - blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, - workspace); - if (blockChild.outputConnection) { - input.connection.connect(blockChild.outputConnection); - } else if (blockChild.previousConnection) { - input.connection.connect(blockChild.previousConnection); - } else { - goog.asserts.fail( - 'Child block does not have output or previous statement.'); - } - } - break; - case 'next': - if (childShadowNode && block.nextConnection) { - block.nextConnection.setShadowDom(childShadowNode); - } - if (childBlockNode) { - goog.asserts.assert(block.nextConnection, - 'Next statement does not exist.'); - // If there is more than one XML 'next' tag. - goog.asserts.assert(!block.nextConnection.isConnected(), - 'Next statement is already connected.'); - blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode, - workspace); - goog.asserts.assert(blockChild.previousConnection, - 'Next block does not have previous statement.'); - block.nextConnection.connect(blockChild.previousConnection); - } - break; - default: - // Unknown tag; ignore. Same principle as HTML parsers. - console.warn('Ignoring unknown tag: ' + xmlChild.nodeName); - } - } - - var inline = xmlBlock.getAttribute('inline'); - if (inline) { - block.setInputsInline(inline == 'true'); - } - var disabled = xmlBlock.getAttribute('disabled'); - if (disabled) { - block.setDisabled(disabled == 'true'); - } - var deletable = xmlBlock.getAttribute('deletable'); - if (deletable) { - block.setDeletable(deletable == 'true'); - } - var movable = xmlBlock.getAttribute('movable'); - if (movable) { - block.setMovable(movable == 'true'); - } - var editable = xmlBlock.getAttribute('editable'); - if (editable) { - block.setEditable(editable == 'true'); - } - var collapsed = xmlBlock.getAttribute('collapsed'); - if (collapsed) { - block.setCollapsed(collapsed == 'true'); - } - if (xmlBlock.nodeName.toLowerCase() == 'shadow') { - // Ensure all children are also shadows. - var children = block.getChildren(); - for (var i = 0, child; child = children[i]; i++) { - goog.asserts.assert(child.isShadow(), - 'Shadow block not allowed non-shadow child.'); - } - block.setShadow(true); - } - return block; -}; - -/** - * Remove any 'next' block (statements in a stack). - * @param {!Element} xmlBlock XML block element. - */ -Blockly.Xml.deleteNext = function(xmlBlock) { - for (var i = 0, child; child = xmlBlock.childNodes[i]; i++) { - if (child.nodeName.toLowerCase() == 'next') { - xmlBlock.removeChild(child); - break; - } - } -}; - -// Export symbols that would otherwise be renamed by Closure compiler. -if (!goog.global['Blockly']) { - goog.global['Blockly'] = {}; -} -if (!goog.global['Blockly']['Xml']) { - goog.global['Blockly']['Xml'] = {}; -} -goog.global['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText; -goog.global['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace; -goog.global['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom; -goog.global['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom;