| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /**
- * @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;
- /**
- * List of "backlight" blocks (drawn with highlighting, not selectable)
- * @type {Blockly.Block}
- * Added for BlocksCAD typing system
- */
- Blockly.backlight = [];
- /**
- * Currently highlighted illegal connection (during a drag). For BlocksCAD!
- * @type {Blockly.Connection}
- * @private
- */
- Blockly.highlightedConnectionBad_ = 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.<!Blockly.Connection>}
- * @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.<!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();
- }
- }
- };
- /**
- * Return an object with all the metrics required to size scrollbars for the
- * main workspace. The following properties are computed:
- * .viewHeight: Height of the visible rectangle,
- * .viewWidth: Width of the visible rectangle,
- * .contentHeight: Height of the contents,
- * .contentWidth: Width of the content,
- * .viewTop: Offset of top edge of visible rectangle from parent,
- * .viewLeft: Offset of left edge of visible rectangle from parent,
- * .contentTop: Offset of the top-most content from the y=0 coordinate,
- * .contentLeft: Offset of the left-most content from the x=0 coordinate.
- * .absoluteTop: Top-edge of view.
- * .absoluteLeft: Left-edge of view.
- * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero.
- * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero.
- * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero.
- * .flyoutHeight: Height of flyout if it is always open. Otherwise zero.
- * .toolboxPosition: Top, bottom, left or right.
- * @return {Object} Contains size and position metrics of main workspace.
- * @private
- * @this Blockly.WorkspaceSvg
- */
- Blockly.getMainWorkspaceMetrics_ = function() {
- var svgSize = Blockly.svgSize(this.getParentSvg());
- if (this.toolbox_) {
- if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
- this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
- svgSize.height -= this.toolbox_.getHeight();
- } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
- this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
- svgSize.width -= this.toolbox_.getWidth();
- }
- }
- // Set the margin to match the flyout's margin so that the workspace does
- // not jump as blocks are added.
- var MARGIN = Blockly.Flyout.prototype.CORNER_RADIUS - 1;
- var viewWidth = svgSize.width - MARGIN;
- var viewHeight = svgSize.height - MARGIN;
- var blockBox = this.getBlocksBoundingBox();
- // Fix scale.
- var contentWidth = blockBox.width * this.scale;
- var contentHeight = blockBox.height * this.scale;
- var contentX = blockBox.x * this.scale;
- var contentY = blockBox.y * this.scale;
- if (this.scrollbar) {
- // Add a border around the content that is at least half a screenful wide.
- // Ensure border is wide enough that blocks can scroll over entire screen.
- var leftEdge = Math.min(contentX - viewWidth / 2,
- contentX + contentWidth - viewWidth);
- var rightEdge = Math.max(contentX + contentWidth + viewWidth / 2,
- contentX + viewWidth);
- var topEdge = Math.min(contentY - viewHeight / 2,
- contentY + contentHeight - viewHeight);
- var bottomEdge = Math.max(contentY + contentHeight + viewHeight / 2,
- contentY + viewHeight);
- } else {
- var leftEdge = blockBox.x;
- var rightEdge = leftEdge + blockBox.width;
- var topEdge = blockBox.y;
- var bottomEdge = topEdge + blockBox.height;
- }
- var absoluteLeft = 0;
- if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
- absoluteLeft = this.toolbox_.getWidth();
- }
- var absoluteTop = 0;
- if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
- absoluteTop = this.toolbox_.getHeight();
- }
- var metrics = {
- viewHeight: svgSize.height,
- viewWidth: svgSize.width,
- contentHeight: bottomEdge - topEdge,
- contentWidth: rightEdge - leftEdge,
- viewTop: -this.scrollY,
- viewLeft: -this.scrollX,
- contentTop: topEdge,
- contentLeft: leftEdge,
- absoluteTop: absoluteTop,
- absoluteLeft: absoluteLeft,
- toolboxWidth: this.toolbox_ ? this.toolbox_.getWidth() : 0,
- toolboxHeight: this.toolbox_ ? this.toolbox_.getHeight() : 0,
- flyoutWidth: this.flyout_ ? this.flyout_.getWidth() : 0,
- flyoutHeight: this.flyout_ ? this.flyout_.getHeight() : 0,
- toolboxPosition: this.toolboxPosition
- };
- return metrics;
- };
- /**
- * Sets the X/Y translations of the main workspace to match the scrollbars.
- * @param {!Object} xyRatio Contains an x and/or y property which is a float
- * between 0 and 1 specifying the degree of scrolling.
- * @private
- * @this Blockly.WorkspaceSvg
- */
- Blockly.setMainWorkspaceMetrics_ = function(xyRatio) {
- if (!this.scrollbar) {
- throw 'Attempt to set main workspace scroll without scrollbars.';
- }
- var metrics = this.getMetrics();
- if (goog.isNumber(xyRatio.x)) {
- this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
- }
- if (goog.isNumber(xyRatio.y)) {
- this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
- }
- var x = this.scrollX + metrics.absoluteLeft;
- var y = this.scrollY + metrics.absoluteTop;
- this.translate(x, y);
- if (this.options.gridPattern) {
- this.options.gridPattern.setAttribute('x', x);
- this.options.gridPattern.setAttribute('y', y);
- if (goog.userAgent.IE) {
- // IE doesn't notice that the x/y offsets have changed. Force an update.
- this.updateGridPattern_();
- }
- }
- };
- /**
- * When something in Blockly's workspace changes, call a function.
- * @param {!Function} func Function to call.
- * @return {!Array.<!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;
|