<commit_before>/**
 * @license
 * Copyright 2012 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Methods for graphically rendering a block as SVG.
 * @author <EMAIL> (<NAME>)
 */
'use strict';

goog.provide('Blockly.BlockSvg');

goog.require('Blockly.ASTNode');
goog.require('Blockly.Block');
goog.require('Blockly.blockAnimations');
goog.require('Blockly.blockRendering.IPathObject');
goog.require('Blockly.ContextMenu');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Msg');
goog.require('Blockly.navigation');
goog.require('Blockly.RenderedConnection');
goog.require('Blockly.TabNavigateCursor');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');


/**
 * Class for a block's SVG representation.
 * Not normally called directly, workspace.newBlock() is preferred.
 * @param {!Blockly.WorkspaceSvg} workspace The block's workspace.
 * @param {?string} prototypeName Name of the language object containing
 *     type-specific functions for this block.
 * @param {string=} opt_id Optional ID.  Use this ID if provided, otherwise
 *     create a new ID.
 * @extends {Blockly.Block}
 * @constructor
 */
Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
  // Create core elements for the block.
  /**
   * @type {!SVGElement}
   * @private
   */
  this.svgGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
  this.svgGroup_.translate_ = '';

  /**
   * A block style object.
   * @type {!Blockly.Theme.BlockStyle}
   */
  this.style = workspace.getRenderer().getConstants().getBlockStyle(null);

  /**
   * The renderer's path object.
   * @type {Blockly.blockRendering.IPathObject}
   * @package
   */
  this.pathObject = workspace.getRenderer().makePathObject(
      this.svgGroup_, this.style);

  /** @type {boolean} */
  this.rendered = false;

  /** @type {!Blockly.WorkspaceSvg} */
  this.workspace = workspace;

  /** @type {Blockly.RenderedConnection} */
  this.outputConnection = null;
  /** @type {Blockly.RenderedConnection} */
  this.nextConnection = null;
  /** @type {Blockly.RenderedConnection} */
  this.previousConnection = null;

  /**
   * Whether to move the block to the drag surface when it is dragged.
   * True if it should move, false if it should be translated directly.
   * @type {boolean}
   * @private
   */
  this.useDragSurface_ =
      Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface();

  var svgPath = this.pathObject.svgPath;
  svgPath.tooltip = this;
  Blockly.Tooltip.bindMouseEvents(svgPath);
  Blockly.BlockSvg.superClass_.constructor.call(this,
      workspace, prototypeName, opt_id);

  // Expose this block's ID on its top-level SVG group.
  if (this.svgGroup_.dataset) {
    this.svgGroup_.dataset['id'] = this.id;
  }
};
Blockly.utils.object.inherits(Blockly.BlockSvg, Blockly.Block);

/**
 * Height of this block, not including any statement blocks above or below.
 * Height is in workspace units.
 */
Blockly.BlockSvg.prototype.height = 0;

/**
 * Width of this block, including any connected value blocks.
 * Width is in workspace units.
 */
Blockly.BlockSvg.prototype.width = 0;

/**
 * Original location of block being dragged.
 * @type {Blockly.utils.Coordinate}
 * @private
 */
Blockly.BlockSvg.prototype.dragStartXY_ = null;

/**
 * Map from IDs for warnings text to PIDs of functions to apply them.
 * Used to be able to maintain multiple warnings.
 * @type {Object.<string, number>}
 * @private
 */
Blockly.BlockSvg.prototype.warningTextDb_ = null;

/**
 * Constant for identifying rows that are to be rendered inline.
 * Don't collide with Blockly.INPUT_VALUE and friends.
 * @const
 */
Blockly.BlockSvg.INLINE = -1;

/**
 * ID to give the "collapsed warnings" warning. Allows us to remove the
 * "collapsed warnings" warning without removing any warnings that belong to
 * the block.
 * @type {string}
 * @const
 */
Blockly.BlockSvg.COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';

/**
 * An optional method called when a mutator dialog is first opened.
 * This function must create and initialize a top-level block for the mutator
 * dialog, and return it. This function should also populate this top-level
 * block with any sub-blocks which are appropriate. This method must also be
 * coupled with defining a `compose` method for the default mutation dialog
 * button and UI to appear.
 * @type {?function(Blockly.WorkspaceSvg):!Blockly.BlockSvg}
 */
Blockly.BlockSvg.prototype.decompose;

/**
 * An optional method called when a mutator dialog saves its content.
 * This function is called to modify the original block according to new
 * settings. This method must also be coupled with defining a `decompose`
 * method for the default mutation dialog button and UI to appear.
 * @type {?function(!Blockly.BlockSvg)}
 */
Blockly.BlockSvg.prototype.compose;

/**
 * An optional method for defining custom block context menu items.
 * @type {?function(!Array.<!Object>)}
 */
Blockly.BlockSvg.prototype.customContextMenu;

/**
 * An property used internally to reference the block's rendering debugger.
 * @type {?Blockly.blockRendering.Debug}
 * @package
 */
Blockly.BlockSvg.prototype.renderingDebugger;

/**
 * Create and initialize the SVG representation of the block.
 * May be called more than once.
 */
Blockly.BlockSvg.prototype.initSvg = function() {
  if (!this.workspace.rendered) {
    throw TypeError('Workspace is headless.');
  }
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    input.init();
  }
  var icons = this.getIcons();
  for (var i = 0; i < icons.length; i++) {
    icons[i].createIcon();
  }
  this.applyColour();
  this.pathObject.updateMovable(this.isMovable());
  var svg = this.getSvgRoot();
  if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
    Blockly.bindEventWithChecks_(
        svg, 'mousedown', this, this.onMouseDown_);
  }
  this.eventsInit_ = true;

  if (!svg.parentNode) {
    this.workspace.getCanvas().appendChild(svg);
  }
};

/**
 * Get the secondary colour of a block.
 * @return {?string} #RRGGBB string.
 */
Blockly.BlockSvg.prototype.getColourSecondary = function() {
  return this.style.colourSecondary;
};

/**
 * Get the tertiary colour of a block.
 * @return {?string} #RRGGBB string.
 */
Blockly.BlockSvg.prototype.getColourTertiary = function() {
  return this.style.colourTertiary;
};

/**
 * Get the shadow colour of a block.
 * @return {?string} #RRGGBB string.
 * @deprecated Use style.colourSecondary. (2020 January 21)
 */
Blockly.BlockSvg.prototype.getColourShadow = function() {
  return this.getColourSecondary();
};

/**
 * Get the border colour(s) of a block.
 * @return {{colourDark, colourLight, colourBorder}} An object containing
 *     colour values for the border(s) of the block. If the block is using a
 *     style the colourBorder will be defined and equal to the tertiary colour
 *     of the style (#RRGGBB string). Otherwise the colourDark and colourLight
 *     attributes will be defined (#RRGGBB strings).
 * @deprecated Use style.colourTertiary. (2020 January 21)
 */
Blockly.BlockSvg.prototype.getColourBorder = function() {
  var colourTertiary = this.getColourTertiary();
  return {
    colourBorder: colourTertiary,
    colourLight: null,
    colourDark: null
  };
};

/**
 * Select this block.  Highlight it visually.
 */
Blockly.BlockSvg.prototype.select = function() {
  if (this.isShadow() && this.getParent()) {
    // Shadow blocks should not be selected.
    this.getParent().select();
    return;
  }
  if (Blockly.selected == this) {
    return;
  }
  var oldId = null;
  if (Blockly.selected) {
    oldId = Blockly.selected.id;
    // Unselect any previously selected block.
    Blockly.Events.disable();
    try {
      Blockly.selected.unselect();
    } finally {
      Blockly.Events.enable();
    }
  }
  var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
  event.workspaceId = this.workspace.id;
  Blockly.Events.fire(event);
  Blockly.selected = this;
  this.addSelect();
};

/**
 * Unselect this block.  Remove its highlighting.
 */
Blockly.BlockSvg.prototype.unselect = function() {
  if (Blockly.selected != this) {
    return;
  }
  var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
  event.workspaceId = this.workspace.id;
  Blockly.Events.fire(event);
  Blockly.selected = null;
  this.removeSelect();
};

/**
 * Block's mutator icon (if any).
 * @type {Blockly.Mutator}
 */
Blockly.BlockSvg.prototype.mutator = null;

/**
 * Block's comment icon (if any).
 * @type {Blockly.Comment}
 * @deprecated August 2019. Use getCommentIcon instead.
 */
Blockly.BlockSvg.prototype.comment = null;

/**
 * Block's comment icon (if any).
 * @type {Blockly.Comment}
 * @private
 */
Blockly.BlockSvg.prototype.commentIcon_ = null;

/**
 * Block's warning icon (if any).
 * @type {Blockly.Warning}
 */
Blockly.BlockSvg.prototype.warning = null;

/**
 * Returns a list of mutator, comment, and warning icons.
 * @return {!Array} List of icons.
 */
Blockly.BlockSvg.prototype.getIcons = function() {
  var icons = [];
  if (this.mutator) {
    icons.push(this.mutator);
  }
  if (this.commentIcon_) {
    icons.push(this.commentIcon_);
  }
  if (this.warning) {
    icons.push(this.warning);
  }
  return icons;
};

/**
 * Set parent of this block to be a new block or null.
 * @param {Blockly.Block} newParent New parent block.
 * @override
 */
Blockly.BlockSvg.prototype.setParent = function(newParent) {
  var oldParent = this.parentBlock_;
  if (newParent == oldParent) {
    return;
  }

  Blockly.utils.dom.startTextWidthCache();
  Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
  Blockly.utils.dom.stopTextWidthCache();

  var svgRoot = this.getSvgRoot();

  // Bail early if workspace is clearing, or we aren't rendered.
  // We won't need to reattach ourselves anywhere.
  if (this.workspace.isClearing || !svgRoot) {
    return;
  }

  var oldXY = this.getRelativeToSurfaceXY();
  if (newParent) {
    newParent.getSvgRoot().appendChild(svgRoot);
    var newXY = this.getRelativeToSurfaceXY();
    // Move the connections to match the child's new position.
    this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
  }
  // If we are losing a parent, we want to move our DOM element to the
  // root of the workspace.
  else if (oldParent) {
    this.workspace.getCanvas().appendChild(svgRoot);
    this.translate(oldXY.x, oldXY.y);
  }

  this.applyColour();
};

/**
 * Return the coordinates of the top-left corner of this block relative to the
 * drawing surface's origin (0,0), in workspace units.
 * If the block is on the workspace, (0, 0) is the origin of the workspace
 * coordinate system.
 * This does not change with workspace scale.
 * @return {!Blockly.utils.Coordinate} Object with .x and .y properties in
 *     workspace coordinates.
 */
Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
  var x = 0;
  var y = 0;

  var dragSurfaceGroup = this.useDragSurface_ ?
      this.workspace.getBlockDragSurface().getGroup() : null;

  var element = this.getSvgRoot();
  if (element) {
    do {
      // Loop through this block and every parent.
      var xy = Blockly.utils.getRelativeXY(element);
      x += xy.x;
      y += xy.y;
      // If this element is the current element on the drag surface, include
      // the translation of the drag surface itself.
      if (this.useDragSurface_ &&
          this.workspace.getBlockDragSurface().getCurrentBlock() == element) {
        var surfaceTranslation =
            this.workspace.getBlockDragSurface().getSurfaceTranslation();
        x += surfaceTranslation.x;
        y += surfaceTranslation.y;
      }
      element = /** @type {!SVGElement} */ (element.parentNode);
    } while (element && element != this.workspace.getCanvas() &&
        element != dragSurfaceGroup);
  }
  return new Blockly.utils.Coordinate(x, y);
};

/**
 * Move a block by a relative offset.
 * @param {number} dx Horizontal offset in workspace units.
 * @param {number} dy Vertical offset in workspace units.
 */
Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
  if (this.parentBlock_) {
    throw Error('Block has parent.');
  }
  var eventsEnabled = Blockly.Events.isEnabled();
  if (eventsEnabled) {
    var event = new Blockly.Events.BlockMove(this);
  }
  var xy = this.getRelativeToSurfaceXY();
  this.translate(xy.x + dx, xy.y + dy);
  this.moveConnections(dx, dy);
  if (eventsEnabled) {
    event.recordNew();
    Blockly.Events.fire(event);
  }
  this.workspace.resizeContents();
};

/**
 * Transforms a block by setting the translation on the transform attribute
 * of the block's SVG.
 * @param {number} x The x coordinate of the translation in workspace units.
 * @param {number} y The y coordinate of the translation in workspace units.
 */
Blockly.BlockSvg.prototype.translate = function(x, y) {
  this.getSvgRoot().setAttribute('transform',
      'translate(' + x + ',' + y + ')');
};

/**
 * Move this block to its workspace's drag surface, accounting for positioning.
 * Generally should be called at the same time as setDragging_(true).
 * Does nothing if useDragSurface_ is false.
 * @package
 */
Blockly.BlockSvg.prototype.moveToDragSurface = function() {
  if (!this.useDragSurface_) {
    return;
  }
  // The translation for drag surface blocks,
  // is equal to the current relative-to-surface position,
  // to keep the position in sync as it move on/off the surface.
  // This is in workspace coordinates.
  var xy = this.getRelativeToSurfaceXY();
  this.clearTransformAttributes_();
  this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y);
  // Execute the move on the top-level SVG component
  var svg = this.getSvgRoot();
  if (svg) {
    this.workspace.getBlockDragSurface().setBlocksAndShow(svg);
  }
};

/**
 * Move a block to a position.
 * @param {Blockly.utils.Coordinate} xy The position to move to in workspace units.
 */
Blockly.BlockSvg.prototype.moveTo = function(xy) {
  var curXY = this.getRelativeToSurfaceXY();
  this.moveBy(xy.x - curXY.x, xy.y - curXY.y);
};

/**
 * Move this block back to the workspace block canvas.
 * Generally should be called at the same time as setDragging_(false).
 * Does nothing if useDragSurface_ is false.
 * @param {!Blockly.utils.Coordinate} newXY The position the block should take on
 *     on the workspace canvas, in workspace coordinates.
 * @package
 */
Blockly.BlockSvg.prototype.moveOffDragSurface = function(newXY) {
  if (!this.useDragSurface_) {
    return;
  }
  // Translate to current position, turning off 3d.
  this.translate(newXY.x, newXY.y);
  this.workspace.getBlockDragSurface().clearAndHide(this.workspace.getCanvas());
};

/**
 * Move this block during a drag, taking into account whether we are using a
 * drag surface to translate blocks.
 * This block must be a top-level block.
 * @param {!Blockly.utils.Coordinate} newLoc The location to translate to, in
 *     workspace coordinates.
 * @package
 */
Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) {
  if (this.useDragSurface_) {
    this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y);
  } else {
    this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
    this.svgGroup_.setAttribute('transform',
        this.svgGroup_.translate_ + this.svgGroup_.skew_);
  }
};

/**
 * Clear the block of transform="..." attributes.
 * Used when the block is switching from 3d to 2d transform or vice versa.
 * @private
 */
Blockly.BlockSvg.prototype.clearTransformAttributes_ = function() {
  this.getSvgRoot().removeAttribute('transform');
};

/**
 * Snap this block to the nearest grid point.
 */
Blockly.BlockSvg.prototype.snapToGrid = function() {
  if (!this.workspace) {
    return;  // Deleted block.
  }
  if (this.workspace.isDragging()) {
    return;  // Don't bump blocks during a drag.
  }
  if (this.getParent()) {
    return;  // Only snap top-level blocks.
  }
  if (this.isInFlyout) {
    return;  // Don't move blocks around in a flyout.
  }
  var grid = this.workspace.getGrid();
  if (!grid || !grid.shouldSnap()) {
    return;  // Config says no snapping.
  }
  var spacing = grid.getSpacing();
  var half = spacing / 2;
  var xy = this.getRelativeToSurfaceXY();
  var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
  var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
  dx = Math.round(dx);
  dy = Math.round(dy);
  if (dx != 0 || dy != 0) {
    this.moveBy(dx, dy);
  }
};

/**
 * Returns the coordinates of a bounding box describing the dimensions of this
 * block and any blocks stacked below it.
 * Coordinate system: workspace coordinates.
 * @return {!Blockly.utils.Rect} Object with coordinates of the bounding box.
 */
Blockly.BlockSvg.prototype.getBoundingRectangle = function() {
  var blockXY = this.getRelativeToSurfaceXY();
  var blockBounds = this.getHeightWidth();
  var left, right;
  if (this.RTL) {
    left = blockXY.x - blockBounds.width;
    right = blockXY.x;
  } else {
    left = blockXY.x;
    right = blockXY.x + blockBounds.width;
  }
  return new Blockly.utils.Rect(
      blockXY.y, blockXY.y + blockBounds.height, left, right);
};

/**
 * Notify every input on this block to mark its fields as dirty.
 * A dirty field is a field that needs to be re-rendererd.
 */
Blockly.BlockSvg.prototype.markDirty = function() {
  this.pathObject.constants =
    (/** @type {!Blockly.WorkspaceSvg} */ (this.workspace))
        .getRenderer().getConstants();
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    input.markDirty();
  }
};

/**
 * Set whether the block is collapsed or not.
 * @param {boolean} collapsed True if collapsed.
 */
Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
  if (this.collapsed_ == collapsed) {
    return;
  }
  var renderList = [];
  // Show/hide the inputs.
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    renderList.push.apply(renderList, input.setVisible(!collapsed));
  }

  var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
  if (collapsed) {
    var icons = this.getIcons();
    for (var i = 0; i < icons.length; i++) {
      icons[i].setVisible(false);
    }
    var text = this.toString(Blockly.COLLAPSE_CHARS);
    this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();

    // Add any warnings on enclosed blocks to this block.
    var descendants = this.getDescendants(true);
    var nextBlock = this.getNextBlock();
    if (nextBlock) {
      var index = descendants.indexOf(nextBlock);
      descendants.splice(index, descendants.length - index);
    }
    for (var i = 1, block; (block = descendants[i]); i++) {
      if (block.warning) {
        this.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'],
            Blockly.BlockSvg.COLLAPSED_WARNING_ID);
        break;
      }
    }
  } else {
    this.removeInput(COLLAPSED_INPUT_NAME);
    // Clear any warnings inherited from enclosed blocks.
    if (this.warning) {
      this.warning.setText('', Blockly.BlockSvg.COLLAPSED_WARNING_ID);
      if (!Object.keys(this.warning.text_).length) {
        this.setWarningText(null);
      }
    }
  }
  Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);

  if (!renderList.length) {
    // No child blocks, just render this block.
    renderList[0] = this;
  }
  if (this.rendered) {
    for (var i = 0, block; (block = renderList[i]); i++) {
      block.render();
    }
    // Don't bump neighbours.
    // Although bumping neighbours would make sense, users often collapse
    // all their functions and store them next to each other.  Expanding and
    // bumping causes all their definitions to go out of alignment.
  }
};

/**
 * Open the next (or previous) FieldTextInput.
 * @param {!Blockly.Field} start Current field.
 * @param {boolean} forward If true go forward, otherwise backward.
 */
Blockly.BlockSvg.prototype.tab = function(start, forward) {
  var tabCursor = new Blockly.TabNavigateCursor();
  tabCursor.setCurNode(Blockly.ASTNode.createFieldNode(start));
  var currentNode = tabCursor.getCurNode();
  var action = forward ?
      Blockly.navigation.ACTION_NEXT : Blockly.navigation.ACTION_PREVIOUS;

  tabCursor.onBlocklyAction(action);

  var nextNode = tabCursor.getCurNode();
  if (nextNode && nextNode !== currentNode) {
    var nextField = /** @type {!Blockly.Field} */ (nextNode.getLocation());
    nextField.showEditor();
  }
};

/**
 * Handle a mouse-down on an SVG block.
 * @param {!Event} e Mouse down event or touch start event.
 * @private
 */
Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {<commit_msg>Move cursor on tab if we're in keyboard nav mode. (#3738)<commit_after>/**
 * @license
 * Copyright 2012 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Methods for graphically rendering a block as SVG.
 * @author <EMAIL> (<NAME>)
 */
'use strict';

goog.provide('Blockly.BlockSvg');

goog.require('Blockly.ASTNode');
goog.require('Blockly.Block');
goog.require('Blockly.blockAnimations');
goog.require('Blockly.blockRendering.IPathObject');
goog.require('Blockly.ContextMenu');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Msg');
goog.require('Blockly.navigation');
goog.require('Blockly.RenderedConnection');
goog.require('Blockly.TabNavigateCursor');
goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');


/**
 * Class for a block's SVG representation.
 * Not normally called directly, workspace.newBlock() is preferred.
 * @param {!Blockly.WorkspaceSvg} workspace The block's workspace.
 * @param {?string} prototypeName Name of the language object containing
 *     type-specific functions for this block.
 * @param {string=} opt_id Optional ID.  Use this ID if provided, otherwise
 *     create a new ID.
 * @extends {Blockly.Block}
 * @constructor
 */
Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
  // Create core elements for the block.
  /**
   * @type {!SVGElement}
   * @private
   */
  this.svgGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
  this.svgGroup_.translate_ = '';

  /**
   * A block style object.
   * @type {!Blockly.Theme.BlockStyle}
   */
  this.style = workspace.getRenderer().getConstants().getBlockStyle(null);

  /**
   * The renderer's path object.
   * @type {Blockly.blockRendering.IPathObject}
   * @package
   */
  this.pathObject = workspace.getRenderer().makePathObject(
      this.svgGroup_, this.style);

  /** @type {boolean} */
  this.rendered = false;

  /** @type {!Blockly.WorkspaceSvg} */
  this.workspace = workspace;

  /** @type {Blockly.RenderedConnection} */
  this.outputConnection = null;
  /** @type {Blockly.RenderedConnection} */
  this.nextConnection = null;
  /** @type {Blockly.RenderedConnection} */
  this.previousConnection = null;

  /**
   * Whether to move the block to the drag surface when it is dragged.
   * True if it should move, false if it should be translated directly.
   * @type {boolean}
   * @private
   */
  this.useDragSurface_ =
      Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface();

  var svgPath = this.pathObject.svgPath;
  svgPath.tooltip = this;
  Blockly.Tooltip.bindMouseEvents(svgPath);
  Blockly.BlockSvg.superClass_.constructor.call(this,
      workspace, prototypeName, opt_id);

  // Expose this block's ID on its top-level SVG group.
  if (this.svgGroup_.dataset) {
    this.svgGroup_.dataset['id'] = this.id;
  }
};
Blockly.utils.object.inherits(Blockly.BlockSvg, Blockly.Block);

/**
 * Height of this block, not including any statement blocks above or below.
 * Height is in workspace units.
 */
Blockly.BlockSvg.prototype.height = 0;

/**
 * Width of this block, including any connected value blocks.
 * Width is in workspace units.
 */
Blockly.BlockSvg.prototype.width = 0;

/**
 * Original location of block being dragged.
 * @type {Blockly.utils.Coordinate}
 * @private
 */
Blockly.BlockSvg.prototype.dragStartXY_ = null;

/**
 * Map from IDs for warnings text to PIDs of functions to apply them.
 * Used to be able to maintain multiple warnings.
 * @type {Object.<string, number>}
 * @private
 */
Blockly.BlockSvg.prototype.warningTextDb_ = null;

/**
 * Constant for identifying rows that are to be rendered inline.
 * Don't collide with Blockly.INPUT_VALUE and friends.
 * @const
 */
Blockly.BlockSvg.INLINE = -1;

/**
 * ID to give the "collapsed warnings" warning. Allows us to remove the
 * "collapsed warnings" warning without removing any warnings that belong to
 * the block.
 * @type {string}
 * @const
 */
Blockly.BlockSvg.COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';

/**
 * An optional method called when a mutator dialog is first opened.
 * This function must create and initialize a top-level block for the mutator
 * dialog, and return it. This function should also populate this top-level
 * block with any sub-blocks which are appropriate. This method must also be
 * coupled with defining a `compose` method for the default mutation dialog
 * button and UI to appear.
 * @type {?function(Blockly.WorkspaceSvg):!Blockly.BlockSvg}
 */
Blockly.BlockSvg.prototype.decompose;

/**
 * An optional method called when a mutator dialog saves its content.
 * This function is called to modify the original block according to new
 * settings. This method must also be coupled with defining a `decompose`
 * method for the default mutation dialog button and UI to appear.
 * @type {?function(!Blockly.BlockSvg)}
 */
Blockly.BlockSvg.prototype.compose;

/**
 * An optional method for defining custom block context menu items.
 * @type {?function(!Array.<!Object>)}
 */
Blockly.BlockSvg.prototype.customContextMenu;

/**
 * An property used internally to reference the block's rendering debugger.
 * @type {?Blockly.blockRendering.Debug}
 * @package
 */
Blockly.BlockSvg.prototype.renderingDebugger;

/**
 * Create and initialize the SVG representation of the block.
 * May be called more than once.
 */
Blockly.BlockSvg.prototype.initSvg = function() {
  if (!this.workspace.rendered) {
    throw TypeError('Workspace is headless.');
  }
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    input.init();
  }
  var icons = this.getIcons();
  for (var i = 0; i < icons.length; i++) {
    icons[i].createIcon();
  }
  this.applyColour();
  this.pathObject.updateMovable(this.isMovable());
  var svg = this.getSvgRoot();
  if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
    Blockly.bindEventWithChecks_(
        svg, 'mousedown', this, this.onMouseDown_);
  }
  this.eventsInit_ = true;

  if (!svg.parentNode) {
    this.workspace.getCanvas().appendChild(svg);
  }
};

/**
 * Get the secondary colour of a block.
 * @return {?string} #RRGGBB string.
 */
Blockly.BlockSvg.prototype.getColourSecondary = function() {
  return this.style.colourSecondary;
};

/**
 * Get the tertiary colour of a block.
 * @return {?string} #RRGGBB string.
 */
Blockly.BlockSvg.prototype.getColourTertiary = function() {
  return this.style.colourTertiary;
};

/**
 * Get the shadow colour of a block.
 * @return {?string} #RRGGBB string.
 * @deprecated Use style.colourSecondary. (2020 January 21)
 */
Blockly.BlockSvg.prototype.getColourShadow = function() {
  return this.getColourSecondary();
};

/**
 * Get the border colour(s) of a block.
 * @return {{colourDark, colourLight, colourBorder}} An object containing
 *     colour values for the border(s) of the block. If the block is using a
 *     style the colourBorder will be defined and equal to the tertiary colour
 *     of the style (#RRGGBB string). Otherwise the colourDark and colourLight
 *     attributes will be defined (#RRGGBB strings).
 * @deprecated Use style.colourTertiary. (2020 January 21)
 */
Blockly.BlockSvg.prototype.getColourBorder = function() {
  var colourTertiary = this.getColourTertiary();
  return {
    colourBorder: colourTertiary,
    colourLight: null,
    colourDark: null
  };
};

/**
 * Select this block.  Highlight it visually.
 */
Blockly.BlockSvg.prototype.select = function() {
  if (this.isShadow() && this.getParent()) {
    // Shadow blocks should not be selected.
    this.getParent().select();
    return;
  }
  if (Blockly.selected == this) {
    return;
  }
  var oldId = null;
  if (Blockly.selected) {
    oldId = Blockly.selected.id;
    // Unselect any previously selected block.
    Blockly.Events.disable();
    try {
      Blockly.selected.unselect();
    } finally {
      Blockly.Events.enable();
    }
  }
  var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
  event.workspaceId = this.workspace.id;
  Blockly.Events.fire(event);
  Blockly.selected = this;
  this.addSelect();
};

/**
 * Unselect this block.  Remove its highlighting.
 */
Blockly.BlockSvg.prototype.unselect = function() {
  if (Blockly.selected != this) {
    return;
  }
  var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
  event.workspaceId = this.workspace.id;
  Blockly.Events.fire(event);
  Blockly.selected = null;
  this.removeSelect();
};

/**
 * Block's mutator icon (if any).
 * @type {Blockly.Mutator}
 */
Blockly.BlockSvg.prototype.mutator = null;

/**
 * Block's comment icon (if any).
 * @type {Blockly.Comment}
 * @deprecated August 2019. Use getCommentIcon instead.
 */
Blockly.BlockSvg.prototype.comment = null;

/**
 * Block's comment icon (if any).
 * @type {Blockly.Comment}
 * @private
 */
Blockly.BlockSvg.prototype.commentIcon_ = null;

/**
 * Block's warning icon (if any).
 * @type {Blockly.Warning}
 */
Blockly.BlockSvg.prototype.warning = null;

/**
 * Returns a list of mutator, comment, and warning icons.
 * @return {!Array} List of icons.
 */
Blockly.BlockSvg.prototype.getIcons = function() {
  var icons = [];
  if (this.mutator) {
    icons.push(this.mutator);
  }
  if (this.commentIcon_) {
    icons.push(this.commentIcon_);
  }
  if (this.warning) {
    icons.push(this.warning);
  }
  return icons;
};

/**
 * Set parent of this block to be a new block or null.
 * @param {Blockly.Block} newParent New parent block.
 * @override
 */
Blockly.BlockSvg.prototype.setParent = function(newParent) {
  var oldParent = this.parentBlock_;
  if (newParent == oldParent) {
    return;
  }

  Blockly.utils.dom.startTextWidthCache();
  Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
  Blockly.utils.dom.stopTextWidthCache();

  var svgRoot = this.getSvgRoot();

  // Bail early if workspace is clearing, or we aren't rendered.
  // We won't need to reattach ourselves anywhere.
  if (this.workspace.isClearing || !svgRoot) {
    return;
  }

  var oldXY = this.getRelativeToSurfaceXY();
  if (newParent) {
    newParent.getSvgRoot().appendChild(svgRoot);
    var newXY = this.getRelativeToSurfaceXY();
    // Move the connections to match the child's new position.
    this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
  }
  // If we are losing a parent, we want to move our DOM element to the
  // root of the workspace.
  else if (oldParent) {
    this.workspace.getCanvas().appendChild(svgRoot);
    this.translate(oldXY.x, oldXY.y);
  }

  this.applyColour();
};

/**
 * Return the coordinates of the top-left corner of this block relative to the
 * drawing surface's origin (0,0), in workspace units.
 * If the block is on the workspace, (0, 0) is the origin of the workspace
 * coordinate system.
 * This does not change with workspace scale.
 * @return {!Blockly.utils.Coordinate} Object with .x and .y properties in
 *     workspace coordinates.
 */
Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
  var x = 0;
  var y = 0;

  var dragSurfaceGroup = this.useDragSurface_ ?
      this.workspace.getBlockDragSurface().getGroup() : null;

  var element = this.getSvgRoot();
  if (element) {
    do {
      // Loop through this block and every parent.
      var xy = Blockly.utils.getRelativeXY(element);
      x += xy.x;
      y += xy.y;
      // If this element is the current element on the drag surface, include
      // the translation of the drag surface itself.
      if (this.useDragSurface_ &&
          this.workspace.getBlockDragSurface().getCurrentBlock() == element) {
        var surfaceTranslation =
            this.workspace.getBlockDragSurface().getSurfaceTranslation();
        x += surfaceTranslation.x;
        y += surfaceTranslation.y;
      }
      element = /** @type {!SVGElement} */ (element.parentNode);
    } while (element && element != this.workspace.getCanvas() &&
        element != dragSurfaceGroup);
  }
  return new Blockly.utils.Coordinate(x, y);
};

/**
 * Move a block by a relative offset.
 * @param {number} dx Horizontal offset in workspace units.
 * @param {number} dy Vertical offset in workspace units.
 */
Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
  if (this.parentBlock_) {
    throw Error('Block has parent.');
  }
  var eventsEnabled = Blockly.Events.isEnabled();
  if (eventsEnabled) {
    var event = new Blockly.Events.BlockMove(this);
  }
  var xy = this.getRelativeToSurfaceXY();
  this.translate(xy.x + dx, xy.y + dy);
  this.moveConnections(dx, dy);
  if (eventsEnabled) {
    event.recordNew();
    Blockly.Events.fire(event);
  }
  this.workspace.resizeContents();
};

/**
 * Transforms a block by setting the translation on the transform attribute
 * of the block's SVG.
 * @param {number} x The x coordinate of the translation in workspace units.
 * @param {number} y The y coordinate of the translation in workspace units.
 */
Blockly.BlockSvg.prototype.translate = function(x, y) {
  this.getSvgRoot().setAttribute('transform',
      'translate(' + x + ',' + y + ')');
};

/**
 * Move this block to its workspace's drag surface, accounting for positioning.
 * Generally should be called at the same time as setDragging_(true).
 * Does nothing if useDragSurface_ is false.
 * @package
 */
Blockly.BlockSvg.prototype.moveToDragSurface = function() {
  if (!this.useDragSurface_) {
    return;
  }
  // The translation for drag surface blocks,
  // is equal to the current relative-to-surface position,
  // to keep the position in sync as it move on/off the surface.
  // This is in workspace coordinates.
  var xy = this.getRelativeToSurfaceXY();
  this.clearTransformAttributes_();
  this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y);
  // Execute the move on the top-level SVG component
  var svg = this.getSvgRoot();
  if (svg) {
    this.workspace.getBlockDragSurface().setBlocksAndShow(svg);
  }
};

/**
 * Move a block to a position.
 * @param {Blockly.utils.Coordinate} xy The position to move to in workspace units.
 */
Blockly.BlockSvg.prototype.moveTo = function(xy) {
  var curXY = this.getRelativeToSurfaceXY();
  this.moveBy(xy.x - curXY.x, xy.y - curXY.y);
};

/**
 * Move this block back to the workspace block canvas.
 * Generally should be called at the same time as setDragging_(false).
 * Does nothing if useDragSurface_ is false.
 * @param {!Blockly.utils.Coordinate} newXY The position the block should take on
 *     on the workspace canvas, in workspace coordinates.
 * @package
 */
Blockly.BlockSvg.prototype.moveOffDragSurface = function(newXY) {
  if (!this.useDragSurface_) {
    return;
  }
  // Translate to current position, turning off 3d.
  this.translate(newXY.x, newXY.y);
  this.workspace.getBlockDragSurface().clearAndHide(this.workspace.getCanvas());
};

/**
 * Move this block during a drag, taking into account whether we are using a
 * drag surface to translate blocks.
 * This block must be a top-level block.
 * @param {!Blockly.utils.Coordinate} newLoc The location to translate to, in
 *     workspace coordinates.
 * @package
 */
Blockly.BlockSvg.prototype.moveDuringDrag = function(newLoc) {
  if (this.useDragSurface_) {
    this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y);
  } else {
    this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
    this.svgGroup_.setAttribute('transform',
        this.svgGroup_.translate_ + this.svgGroup_.skew_);
  }
};

/**
 * Clear the block of transform="..." attributes.
 * Used when the block is switching from 3d to 2d transform or vice versa.
 * @private
 */
Blockly.BlockSvg.prototype.clearTransformAttributes_ = function() {
  this.getSvgRoot().removeAttribute('transform');
};

/**
 * Snap this block to the nearest grid point.
 */
Blockly.BlockSvg.prototype.snapToGrid = function() {
  if (!this.workspace) {
    return;  // Deleted block.
  }
  if (this.workspace.isDragging()) {
    return;  // Don't bump blocks during a drag.
  }
  if (this.getParent()) {
    return;  // Only snap top-level blocks.
  }
  if (this.isInFlyout) {
    return;  // Don't move blocks around in a flyout.
  }
  var grid = this.workspace.getGrid();
  if (!grid || !grid.shouldSnap()) {
    return;  // Config says no snapping.
  }
  var spacing = grid.getSpacing();
  var half = spacing / 2;
  var xy = this.getRelativeToSurfaceXY();
  var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
  var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
  dx = Math.round(dx);
  dy = Math.round(dy);
  if (dx != 0 || dy != 0) {
    this.moveBy(dx, dy);
  }
};

/**
 * Returns the coordinates of a bounding box describing the dimensions of this
 * block and any blocks stacked below it.
 * Coordinate system: workspace coordinates.
 * @return {!Blockly.utils.Rect} Object with coordinates of the bounding box.
 */
Blockly.BlockSvg.prototype.getBoundingRectangle = function() {
  var blockXY = this.getRelativeToSurfaceXY();
  var blockBounds = this.getHeightWidth();
  var left, right;
  if (this.RTL) {
    left = blockXY.x - blockBounds.width;
    right = blockXY.x;
  } else {
    left = blockXY.x;
    right = blockXY.x + blockBounds.width;
  }
  return new Blockly.utils.Rect(
      blockXY.y, blockXY.y + blockBounds.height, left, right);
};

/**
 * Notify every input on this block to mark its fields as dirty.
 * A dirty field is a field that needs to be re-rendererd.
 */
Blockly.BlockSvg.prototype.markDirty = function() {
  this.pathObject.constants =
    (/** @type {!Blockly.WorkspaceSvg} */ (this.workspace))
        .getRenderer().getConstants();
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    input.markDirty();
  }
};

/**
 * Set whether the block is collapsed or not.
 * @param {boolean} collapsed True if collapsed.
 */
Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
  if (this.collapsed_ == collapsed) {
    return;
  }
  var renderList = [];
  // Show/hide the inputs.
  for (var i = 0, input; (input = this.inputList[i]); i++) {
    renderList.push.apply(renderList, input.setVisible(!collapsed));
  }

  var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
  if (collapsed) {
    var icons = this.getIcons();
    for (var i = 0; i < icons.length; i++) {
      icons[i].setVisible(false);
    }
    var text = this.toString(Blockly.COLLAPSE_CHARS);
    this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();

    // Add any warnings on enclosed blocks to this block.
    var descendants = this.getDescendants(true);
    var nextBlock = this.getNextBlock();
    if (nextBlock) {
      var index = descendants.indexOf(nextBlock);
      descendants.splice(index, descendants.length - index);
    }
    for (var i = 1, block; (block = descendants[i]); i++) {
      if (block.warning) {
        this.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'],
            Blockly.BlockSvg.COLLAPSED_WARNING_ID);
        break;
      }
    }
  } else {
    this.removeInput(COLLAPSED_INPUT_NAME);
    // Clear any warnings inherited from enclosed blocks.
    if (this.warning) {
      this.warning.setText('', Blockly.BlockSvg.COLLAPSED_WARNING_ID);
      if (!Object.keys(this.warning.text_).length) {
        this.setWarningText(null);
      }
    }
  }
  Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);

  if (!renderList.length) {
    // No child blocks, just render this block.
    renderList[0] = this;
  }
  if (this.rendered) {
    for (var i = 0, block; (block = renderList[i]); i++) {
      block.render();
    }
    // Don't bump neighbours.
    // Although bumping neighbours would make sense, users often collapse
    // all their functions and store them next to each other.  Expanding and
    // bumping causes all their definitions to go out of alignment.
  }
};

/**
 * Open the next (or previous) FieldTextInput.
 * @param {!Blockly.Field} start Current field.
 * @param {boolean} forward If true go forward, otherwise backward.
 */
Blockly.BlockSvg.prototype.tab = function(start, forward) {
  var tabCursor = new Blockly.TabNavigateCursor();
  tabCursor.setCurNode(Blockly.ASTNode.createFieldNode(start));
  var currentNode = tabCursor.getCurNode();
  var action = forward ?
      Blockly.navigation.ACTION_NEXT : Blockly.navigation.ACTION_PREVIOUS;

  tabCursor.onBlocklyAction(action);

  var nextNode = tabCursor.getCurNode();
  if (nextNode && nextNode !== currentNode) {
    var nextField = /** @type {!Blockly.Field} */ (nextNode.getLocation());
    nextField.showEditor();

    // Also move the cursor if we're in keyboard nav mode.
    if (this.workspace.keyboardAccessibilityMode) {
      this.workspace.getCursor().setCurNode(nextNode);
    }
  }
};

/**
 * Handle a mouse-down on an SVG block.
 * @param {!Event} e Mouse down event or touch start event.
 * @private
 */
Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
{'fraction_non_alphanumeric': 0.1299820895522388, 'fraction_numerical': 0.002292537313432836, 'mean_word_length': 3.5315442051726005, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4693007', 'n_tokens_mistral': 13335, 'n_tokens_neox': 13069, 'n_words': 3643}