text: /**
   * Modifications by Sharon Zhou for Ingenium, noted by comments with ***, eg. //***
   */
  
  /**
   * @license
   * Visual Blocks Editor
   *
   * Copyright 2012 Google Inc.
   * https://developers.google.com/blockly/
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  /**
   * @fileoverview Methods for graphically rendering a block as SVG.
   * @author fraser@google.com (Neil Fraser)
   */
  'use strict';
  
  goog.provide('Blockly.BlockSvg');
  
  goog.require('Blockly.Block');
  goog.require('Blockly.ContextMenu');
  goog.require('goog.asserts');
  goog.require('goog.userAgent');
  
  /**
   * Class for a block's SVG representation.
   * @extends {Blockly.Block}
   * @constructor
   */
  Blockly.BlockSvg = function() {
    // Create core elements for the block.
    this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
    this.svgPathDark_ = Blockly.createSvgElement('path',
        {'class': 'blocklyPathDark', 'transform': 'translate(1, 1)'},
        this.svgGroup_);
    this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'},
        this.svgGroup_);
    this.svgPathLight_ = Blockly.createSvgElement('path',
        {'class': 'blocklyPathLight'}, this.svgGroup_);
    this.svgPath_.tooltip = this;
    Blockly.Tooltip.bindMouseEvents(this.svgPath_);
    this.updateMovable();
  };
  goog.inherits(Blockly.BlockSvg, Blockly.Block);
  
  /**
   * Height of this block, not including any statement blocks above or below.
   */
  Blockly.BlockSvg.prototype.height = 0;
  /**
   * Width of this block, including any connected value blocks.
   */
  Blockly.BlockSvg.prototype.width = 0;
  
  /**
   * Constant for identifying rows that are to be rendered inline.
   * Don't collide with Blockly.INPUT_VALUE and friends.
   * @const
   */
  Blockly.BlockSvg.INLINE = -1;
  
  /**
   * Create and initialize the SVG representation of the block.
   * May be called more than once.
   */
  Blockly.BlockSvg.prototype.initSvg = function() {
    goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
    for (var x = 0, input; input = this.inputList[x]; x++) {
      input.init();
    }
    if (this.mutator) {
      this.mutator.createIcon();
    }
    this.updateColour();
    if (!Blockly.readOnly && !this.eventsInit_) {
      Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this,
                         this.onMouseDown_);
    }
    // Bind an onchange function, if it exists.
    if (goog.isFunction(this.onchange) && !this.eventsInit_) {
      Blockly.bindEvent_(this.workspace.getCanvas(), 'blocklyWorkspaceChange',
          this, this.onchange);
    }
    this.eventsInit_ = true;
  
    if (!this.getSvgRoot().parentNode) {
      this.workspace.getCanvas().appendChild(this.getSvgRoot());
    }
  };
  
  /**
   * Select this block.  Highlight it visually.
   */
  Blockly.BlockSvg.prototype.select = function() {
    if (Blockly.selected) {
      // Unselect any previously selected block.
      Blockly.selected.unselect();
    }
    Blockly.selected = this;
    this.addSelect();
    Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
  };
  
  /**
   * Unselect this block.  Remove its highlighting.
   */
  Blockly.BlockSvg.prototype.unselect = function() {
    Blockly.selected = null;
    this.removeSelect();
    Blockly.fireUiEvent(this.workspace.getCanvas(), 'blocklySelectChange');
  };
  
  /**
   * Block's mutator icon (if any).
   * @type {Blockly.Mutator}
   */
  Blockly.BlockSvg.prototype.mutator = null;
  
  /**
   * Block's comment icon (if any).
   * @type {Blockly.Comment}
   */
  Blockly.BlockSvg.prototype.comment = null;
  
  /**
   * Block's warning icon (if any).
   * @type {Blockly.Warning}
   */
  Blockly.BlockSvg.prototype.warning = null;
  
  /**
   * Returns a list of mutator, comment, and warning icons.
   * @return {!Array} List of icons.
   */
  Blockly.BlockSvg.prototype.getIcons = function() {
    var icons = [];
    if (this.mutator) {
      icons.push(this.mutator);
    }
    if (this.comment) {
      icons.push(this.comment);
    }
    if (this.warning) {
      icons.push(this.warning);
    }
    return icons;
  };
  
  /**
   * Wrapper function called when a mouseUp occurs during a drag operation.
   * @type {Array.<!Array>}
   * @private
   */
  Blockly.BlockSvg.onMouseUpWrapper_ = null;
  
  /**
   * Wrapper function called when a mouseMove occurs during a drag operation.
   * @type {Array.<!Array>}
   * @private
   */
  Blockly.BlockSvg.onMouseMoveWrapper_ = null;
  
  /**
   * Stop binding to the global mouseup and mousemove events.
   * @private
   */
  Blockly.BlockSvg.terminateDrag_ = function() {
    if (Blockly.BlockSvg.onMouseUpWrapper_) {
      Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
      Blockly.BlockSvg.onMouseUpWrapper_ = null;
    }
    if (Blockly.BlockSvg.onMouseMoveWrapper_) {
      Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
      Blockly.BlockSvg.onMouseMoveWrapper_ = null;
    }
    var selected = Blockly.selected;
    if (Blockly.dragMode_ == 2) {
      // Terminate a drag operation.
      if (selected) {
        // Update the connection locations.
        var xy = selected.getRelativeToSurfaceXY();
        var dx = xy.x - selected.startDragX;
        var dy = xy.y - selected.startDragY;
        selected.moveConnections_(dx, dy);
        delete selected.draggedBubbles_;
        selected.setDragging_(false);
        selected.render();
        goog.Timer.callOnce(
            selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected);
        // Fire an event to allow scrollbars to resize.
        Blockly.fireUiEvent(window, 'resize');
        selected.workspace.fireChangeEvent();
      }
    }
    Blockly.dragMode_ = 0;
    Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  };
  
  /**
   * Set parent of this block to be a new block or null.
   * @param {Blockly.BlockSvg} newParent New parent block.
   */
  Blockly.BlockSvg.prototype.setParent = function(newParent) {
    var svgRoot = this.getSvgRoot();
    if (this.parentBlock_ && svgRoot) {
      // Move this block up the DOM.  Keep track of x/y translations.
      var xy = this.getRelativeToSurfaceXY();
      this.workspace.getCanvas().appendChild(svgRoot);
      svgRoot.setAttribute('transform',
          'translate(' + xy.x + ', ' + xy.y + ')');
    }
  
    Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
  
    if (newParent) {
      var oldXY = this.getRelativeToSurfaceXY();
      newParent.getSvgRoot().appendChild(svgRoot);
      var newXY = this.getRelativeToSurfaceXY();
      // Move the connections to match the child's new position.
      this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y);
    }
  };
  
  /**
   * Return the coordinates of the top-left corner of this block relative to the
   * drawing surface's origin (0,0).
   * @return {!goog.math.Coordinate} Object with .x and .y properties.
   */
  Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
    var x = 0;
    var y = 0;
    var element = this.getSvgRoot();
    if (element) {
      do {
        // Loop through this block and every parent.
        var xy = Blockly.getRelativeXY_(element);
        x += xy.x;
        y += xy.y;
        element = element.parentNode;
      } while (element && element != this.workspace.getCanvas());
    }
    return new goog.math.Coordinate(x, y);
  };
  
  /**
   * Move a block by a relative offset.
   * @param {number} dx Horizontal offset.
   * @param {number} dy Vertical offset.
   */
  Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
    var xy = this.getRelativeToSurfaceXY();
    this.getSvgRoot().setAttribute('transform',
        'translate(' + (xy.x + dx) + ', ' + (xy.y + dy) + ')');
    this.moveConnections_(dx, dy);
    Blockly.Realtime.blockChanged(this);
  };
  
  /**
   * Returns a bounding box describing the dimensions of this block
   * and any blocks stacked below it.
   * @return {!Object} Object with height and width properties.
   */
  Blockly.BlockSvg.prototype.getHeightWidth = function() {
    var height = this.height;
    var width = this.width;
    // Recursively add size of subsequent blocks.
    var nextBlock = this.getNextBlock();
    if (nextBlock) {
      var nextHeightWidth = nextBlock.getHeightWidth();
      height += nextHeightWidth.height - 4;  // Height of tab.
      width = Math.max(width, nextHeightWidth.width);
    }
    return {height: height, width: width};
  };
  
  /**
   * Set whether the block is collapsed or not.
   * @param {boolean} collapsed True if collapsed.
   */
  Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
    if (this.collapsed_ == collapsed) {
      return;
    }
    Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
    var renderList = [];
    // Show/hide the inputs.
    for (var x = 0, input; input = this.inputList[x]; x++) {
      renderList.push.apply(renderList, input.setVisible(!collapsed));
    }
  
    var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
    if (collapsed) {
      var icons = this.getIcons();
      for (var x = 0; x < icons.length; x++) {
        icons[x].setVisible(false);
      }
      var text = this.toString(Blockly.COLLAPSE_CHARS);
      this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();
    } else {
      this.removeInput(COLLAPSED_INPUT_NAME);
    }
  
    if (!renderList.length) {
      // No child blocks, just render this block.
      renderList[0] = this;
    }
    if (this.rendered) {
      for (var x = 0, block; block = renderList[x]; x++) {
        block.render();
      }
      this.bumpNeighbours_();
    }
  };
  
  /**
   * Handle a mouse-down on an SVG block.
   * @param {!Event} e Mouse down event.
   * @private
   */
  Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
    if (this.isInFlyout) {
      return;
    }
    // Update Blockly's knowledge of its own location.
    Blockly.svgResize();
    Blockly.terminateDrag_();
    this.select();
    Blockly.hideChaff();
  
    if (Blockly.isRightButton(e)) {
      // Right-click.
      // *** Do not allow right click action
      // this.showContextMenu_(e);
      // *** Instead, show the tooltip when right clicking
      Blockly.Tooltip.show_();
      //***Click to record glossed form
      // COMMENTED OUT FOR PRODUCTION: 
      // console.log('right clicked to see glossed form', this);
      // rightClick = true;
    } else if (!this.isMovable()) {
      // Allow unmovable blocks to be selected and context menued, but not
      // dragged.  Let this event bubble up to document, so the workspace may be
      // dragged instead.
      //***Click data - for disabled [CHOOSE] pieces
      // COMMENTED OUT FOR PRODUCTION: 
      // console.log('clicked on unmovable', this);
      // rightClick = false;
      return;
    } else {
      // rightClick = false;
      // Left-click (or middle click)
      Blockly.removeAllRanges();
      Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
      // Look up the current translation and record it.
      var xy = this.getRelativeToSurfaceXY();
      this.startDragX = xy.x;
      this.startDragY = xy.y;
      // Record the current mouse position.
      this.startDragMouseX = e.clientX;
      this.startDragMouseY = e.clientY;
  
      //***Click data - for non-disabled/[CHOOSE] pieces
      // COMMENTED OUT FOR PRODUCTION: 
      // console.log('drag', this, e.clientX, e.clientY);
  
      Blockly.dragMode_ = 1;
      Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document,
          'mouseup', this, this.onMouseUp_);
      Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
          'mousemove', this, this.onMouseMove_);
      // Build a list of bubbles that need to be moved and where they started.
      this.draggedBubbles_ = [];
      var descendants = this.getDescendants();
      for (var x = 0, descendant; descendant = descendants[x]; x++) {
        var icons = descendant.getIcons();
        for (var y = 0; y < icons.length; y++) {
          var data = icons[y].getIconLocation();
          data.bubble = icons[y];
          this.draggedBubbles_.push(data);
        }
      }
    }
    // This event has been handled.  No need to bubble up to the document.
    e.stopPropagation();
  };
  
  /**
   * Handle a mouse-up anywhere in the SVG pane.  Is only registered when a
   * block is clicked.  We can't use mouseUp on the block since a fast-moving
   * cursor can briefly escape the block before it catches up.
   * @param {!Event} e Mouse up event.
   * @private
   */
  Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
    var this_ = this;
    // COMMENTED OUT FOR PRODUCTION: 
    // console.log('mouseup', this_);
  
    Blockly.doCommand(function() {
      Blockly.terminateDrag_();
      if (Blockly.selected && Blockly.highlightedConnection_) {
        // Connect two blocks together.
        Blockly.localConnection_.connect(Blockly.highlightedConnection_);
        if (this_.rendered) {
          // Trigger a connection animation.
          // Determine which connection is inferior (lower in the source stack).
          var inferiorConnection;
          if (Blockly.localConnection_.isSuperior()) {
            inferiorConnection = Blockly.highlightedConnection_;
          } else {
            inferiorConnection = Blockly.localConnection_;
          }
          inferiorConnection.sourceBlock_.connectionUiEffect();
        }
        if (this_.workspace.trashcan) {
          // Don't throw an object in the trash can if it just got connected.
          this_.workspace.trashcan.close();
        }
        // Bump any disabled blocks out of the way
        var blocks = Blockly.mainWorkspace.getTopBlocks(true);
        for (var i = 0; i < blocks.length; i++) {
          var block = blocks[i];
          if (block.disabled) {
            var blockXY = block.getRelativeToSurfaceXY(), blockHW = block.getHeightWidth();
            var blockLeft = blockXY.x, blockRight = blockXY.x + blockHW.width, 
              blockTop = blockXY.y, blockBottom = blockXY.y + blockHW.height;
            var rootXY = Blockly.highlightedConnection_.sourceBlock_.getRelativeToSurfaceXY(), 
              rootHW = Blockly.highlightedConnection_.sourceBlock_.getHeightWidth();
            var rootLeft = rootXY.x, rootRight = rootXY.x + rootHW.width, 
              rootTop = rootXY.y, rootBottom = rootXY.y + rootHW.height;
            if (rootRight < blockLeft || blockRight < rootLeft || 
              rootBottom < blockTop || blockBottom < rootTop) {
              // No intersection && no puzzle bc disabled piece exists
              // COMMENTED OUT FOR PRODUCTION: 
              // console.log('previous none', rootRight < blockLeft, blockRight < rootLeft, rootBottom < blockTop, blockBottom < rootTop);
            } else {
              // Bump because there is intersection
              // COMMENTED OUT FOR PRODUCTION: 
              // console.log('intersection exists!!');
  
              var metrics = Blockly.mainWorkspace.getMetrics();
              var edgeLeft = metrics.viewLeft + metrics.absoluteLeft, edgeTop = metrics.viewTop + metrics.absoluteTop, 
                edgeRight = edgeLeft + metrics.viewWidth, edgeBottom = edgeTop + metrics.viewHeight;
              var dx = 0, dy = 0;
              var overlapX, overlapY;
              var distanceToRightEdge = edgeRight - blockRight, 
                  distanceToLeftEdge = blockLeft - edgeLeft,
                  distanceToTopEdge = blockTop - edgeTop,
                  distanceToBottomEdge = edgeBottom - blockBottom;
              var rootDistanceToRightEdge = edgeRight - rootRight,
                  rootDistanceToLeftEdge = rootLeft - edgeLeft,
                  rootDistanceToTopEdge = rootTop - edgeTop,
                  rootDistanceToBottomEdge = edgeBottom - rootBottom;
              // Bump right
              if (rootRight > blockLeft && rootLeft < blockLeft) {
                overlapX = rootRight - blockLeft;
                dx = 40;
                dx = overlapX;
                // COMMENTED OUT FOR PRODUCTION: 
                // console.log('bump right with positive', dx);
              // Bump left
              } else if (blockRight > rootLeft && blockRight < rootRight) {
                overlapX = blockRight - rootLeft;
                dx = -40;
                dx = Math.abs(overlapX) * -1;
                // COMMENTED OUT FOR PRODUCTION: 
                // console.log('bump left with negative', dx);
              }
              // Bump down
              if (rootBottom > blockTop && rootBottom < blockBottom && rootTop < blockTop) {
                overlapY = rootBottom - blockTop;
                dy = 40;
                dy = overlapY;
                // COMMENTED OUT FOR PRODUCTION: 
                // console.log('bump down with positive', dy);
              // Bump up
              } else if (blockBottom > rootTop && blockBottom < rootBottom && blockTop < rootTop) {
                overlapY = blockBottom - rootTop;
                dy = -40;
                dy = Math.abs(overlapY) * -1;
                // COMMENTED OUT FOR PRODUCTION: 
                // console.log('bump up with negative', dy);
              }
              // if (Math.abs(overlapX) > Math.abs(overlapY)) {
              //   dy = 0;
              // } else {
              //   dx = 0;
              // }
  
              block.moveBy(dx, dy);
              block.addSelect();
            }
          }
        } 
      } else if (this_.workspace.isDeleteArea(e)) {
        var trashcan = this_.workspace.trashcan;
        if (trashcan) {
          goog.Timer.callOnce(trashcan.close, 100, trashcan);
        }
        if (this_.deletable_) {
          // Cannot delete workspace pieces -- only flyout pieces
          // COMMENTED OUT FOR PRODUCTION: 
          // console.log('deletable?', this_, this_.deletable_);
          Blockly.selected.dispose(false, true);
        }
        // Dropping a block on the trash can will usually cause the workspace to
        // resize to contain the newly positioned block.  Force a second resize
        // now that the block has been deleted.
        Blockly.fireUiEvent(window, 'resize');
      }
      if (Blockly.highlightedConnection_) {
        Blockly.highlightedConnection_.unhighlight();
        Blockly.highlightedConnection_ = null;
      }
      Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
    });
  };
  
  /**
   * Load the block's help page in a new window.
   * @private
   */
  Blockly.BlockSvg.prototype.showHelp_ = function() {
    var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
    if (url) {
      window.open(url);
    }
  };
  
  /**
   * Show the context menu for this block.
   * @param {!Event} e Mouse event.
   * @private
   */
  Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
    if (Blockly.readOnly || !this.contextMenu) {
      return;
    }
    // Save the current block in a variable for use in closures.
    var block = this;
    var options = [];
  
    if (this.deletable_ && this.isMovable() && !block.isInFlyout) {
      // Option to duplicate this block.
      var duplicateOption = {
        text: Blockly.Msg.DUPLICATE_BLOCK,
        enabled: true,
        callback: function() {
          block.duplicate_();
        }
      };
      if (this.getDescendants().length > this.workspace.remainingCapacity()) {
        duplicateOption.enabled = false;
      }
      options.push(duplicateOption);
  
      if (this.isEditable() && !this.collapsed_ && Blockly.comments) {
        // Option to add/remove a comment.
        var commentOption = {enabled: true};
        if (this.comment) {
          commentOption.text = Blockly.Msg.REMOVE_COMMENT;
          commentOption.callback = function() {
            block.setCommentText(null);
          };
        } else {
          commentOption.text = Blockly.Msg.ADD_COMMENT;
          commentOption.callback = function() {
            block.setCommentText('');
          };
        }
        options.push(commentOption);
      }
  
      // Option to make block inline.
      if (!this.collapsed_) {
        for (var i = 0; i < this.inputList.length; i++) {
          if (this.inputList[i].type == Blockly.INPUT_VALUE) {
            // Only display this option if there is a value input on the block.
            var inlineOption = {enabled: true};
            inlineOption.text = this.inputsInline ? Blockly.Msg.EXTERNAL_INPUTS :
                                                    Blockly.Msg.INLINE_INPUTS;
            inlineOption.callback = function() {
              block.setInputsInline(!block.inputsInline);
            };
            options.push(inlineOption);
            break;
          }
        }
      }
  
      if (Blockly.collapse) {
        // Option to collapse/expand block.
        if (this.collapsed_) {
          var expandOption = {enabled: true};
          expandOption.text = Blockly.Msg.EXPAND_BLOCK;
          expandOption.callback = function() {
            block.setCollapsed(false);
          };
          options.push(expandOption);
        } else {
          var collapseOption = {enabled: true};
          collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK;
          collapseOption.callback = function() {
            block.setCollapsed(true);
          };
          options.push(collapseOption);
        }
      }
  
      if (Blockly.disable) {
        // Option to disable/enable block.
        var disableOption = {
          text: this.disabled ?
              Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK,
          enabled: !this.getInheritedDisabled(),
          callback: function() {
            block.setDisabled(!block.disabled);
          }
        };
        options.push(disableOption);
      }
  
      // Option to delete this block.
      // Count the number of blocks that are nested in this block.
      var descendantCount = this.getDescendants().length;
      var nextBlock = this.getNextBlock();
      if (nextBlock) {
        // Blocks in the current stack would survive this block's deletion.
        descendantCount -= nextBlock.getDescendants().length;
      }
      var deleteOption = {
        text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
            Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
        enabled: true,
        callback: function() {
          block.dispose(true, true);
        }
      };
      options.push(deleteOption);
    }
  
    // Option to get help.
    var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
    var helpOption = {enabled: !!url};
    helpOption.text = Blockly.Msg.HELP;
    helpOption.callback = function() {
      block.showHelp_();
    };
    options.push(helpOption);
  
    // Allow the block to add or modify options.
    if (this.customContextMenu && !block.isInFlyout) {
      this.customContextMenu(options);
    }
  
    Blockly.ContextMenu.show(e, options);
    Blockly.ContextMenu.currentBlock = this;
  };
  
  /**
   * Move the connections for this block and all blocks attached under it.
   * Also update any attached bubbles.
   * @param {number} dx Horizontal offset from current location.
   * @param {number} dy Vertical offset from current location.
   * @private
   */
  Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
    if (!this.rendered) {
      // Rendering is required to lay out the blocks.
      // This is probably an invisible block attached to a collapsed block.
      return;
    }
    var myConnections = this.getConnections_(false);
    for (var x = 0; x < myConnections.length; x++) {
      myConnections[x].moveBy(dx, dy);
    }
    var icons = this.getIcons();
    for (var x = 0; x < icons.length; x++) {
      icons[x].computeIconLocation();
    }
  
    // Recurse through all blocks attached under this one.
    for (var x = 0; x < this.childBlocks_.length; x++) {
      this.childBlocks_[x].moveConnections_(dx, dy);
    }
  };
  
  /**
   * Recursively adds or removes the dragging class to this node and its children.
   * @param {boolean} adding True if adding, false if removing.
   * @private
   */
  Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
    if (adding) {
      this.addDragging();
    } else {
      this.removeDragging();
    }
    // Recurse through all blocks attached under this one.
    for (var x = 0; x < this.childBlocks_.length; x++) {
      this.childBlocks_[x].setDragging_(adding);
    }
  };
  
  /**
   * Drag this block to follow the mouse.
   * @param {!Event} e Mouse move event.
   * @private
   */
  Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
    var this_ = this;
    Blockly.doCommand(function() {
      if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
          e.button == 0) {
        /* HACK:
         Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
         events on certain touch actions. Ignore events with these signatures.
         This may result in a one-pixel blind spot in other browsers,
         but this shouldn't be noticeable. */
        e.stopPropagation();
        return;
      }
      Blockly.removeAllRanges();
      var dx = e.clientX - this_.startDragMouseX;
      var dy = e.clientY - this_.startDragMouseY;
      if (Blockly.dragMode_ == 1) {
        // Still dragging within the sticky DRAG_RADIUS.
        var dr = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
        if (dr > Blockly.DRAG_RADIUS) {
          // Switch to unrestricted dragging.
          Blockly.dragMode_ = 2;
          // Push this block to the very top of the stack.
          this_.setParent(null);
          this_.setDragging_(true);
          this_.workspace.recordDeleteAreas();
        }
      }
  
      if (Blockly.dragMode_ == 2) {
        // Unrestricted dragging.
        var x = this_.startDragX + dx;
        var y = this_.startDragY + dy;
        
        // *** Prevent block from being dragged past workspace edges
        var MARGIN = 25;
        var HIGHLIGHT = 2;
        var OUTLINE_OFFSET = 4;
        var metrics = Blockly.mainWorkspace.getMetrics();
        var edgeLeft = metrics.viewLeft + metrics.absoluteLeft + OUTLINE_OFFSET;
        var edgeTop = metrics.viewTop + metrics.absoluteTop + OUTLINE_OFFSET;
        // COMMENTED OUT FOR PRODUCTION: 
        // console.log('edgeleft edgetop', edgeLeft, edgeTop);
  
        // var blockXY = this_.getRelativeToSurfaceXY();
        var blockXY = Blockly.getSvgXY_(/** @type {!Element} */ (this_.svgGroup_));
        var blockHW = this_.getHeightWidth();
  
        // Bump any block that's above the top back inside.
        if (edgeTop == blockXY.y + HIGHLIGHT && dy < 0) {
          y -= (dy - HIGHLIGHT - OUTLINE_OFFSET);
        }
        // Bump any block that's below the bottom back inside.
        if (edgeTop + metrics.viewHeight - OUTLINE_OFFSET == blockXY.y + blockHW.height + HIGHLIGHT && dy > 0) {
          y -= (dy - HIGHLIGHT - OUTLINE_OFFSET);
        }
        // Bump any block that's off the left back inside.
        var overflow = edgeLeft - blockXY.x + HIGHLIGHT;
        // *** Trying to account for flyout width...
        // if (Blockly.mainWorkspace.flyout_) {
        //   var flyoutMetrics = Blockly.mainWorkspace.flyout_.getMetrics_();
        //   overflow += flyoutMetrics.viewWidth * 2;
        // }
        if (this_.getFieldValue("noun-clamp") != null || 
            this_.nominativeOutputConnection != null || this_.genitiveOutputConnection != null || 
            this_.dativeOutputConnection != null || this_.accusativeOutputConnection != null ||
            this_.ablativeOutputConnection != null || this_.vocativeOutputConnection != null ||
            this_.outputConnection != null) {
          overflow += Blockly.BlockSvg.TAB_WIDTH;
        }
        if (overflow >= 0 && dy < 0) {
          x -= dx + overflow;
        }
        // Bump any block that's off the right back inside.
        var overflow = edgeLeft + metrics.viewWidth - OUTLINE_OFFSET - blockXY.x - blockHW.width + HIGHLIGHT*8;
        // if (this_.getFieldValue("noun-clamp") != null || 
        //     this_.nominativeOutputConnection != null || this_.genitiveOutputConnection != null || 
        //     this_.dativeOutputConnection != null || this_.accusativeOutputConnection != null ||
        //     this_.ablativeOutputConnection != null || this_.vocativeOutputConnection != null ||
        //     this_.outputConnection != null) {
        //   overflow += Blockly.BlockSvg.TAB_WIDTH;
        // }
        if (overflow == 0 && dy > 0) {
          x -= dx + overflow;
        }
  
        this_.getSvgRoot().setAttribute('transform',
            'translate(' + x + ', ' + y + ')');
        // Drag all the nested bubbles.
        for (var i = 0; i < this_.draggedBubbles_.length; i++) {
          var commentData = this_.draggedBubbles_[i];
          commentData.bubble.setIconLocation(commentData.x + dx,
              commentData.y + dy);
        }
  
        // Check to see if any of this block's connections are within range of
        // another block's connection.
        var myConnections = this_.getConnections_(false);
        var closestConnection = null;
        var localConnection = null;
        var radiusConnection = Blockly.SNAP_RADIUS;
        for (var i = 0; i < myConnections.length; i++) {
          var myConnection = myConnections[i];
          var neighbour = myConnection.closest(radiusConnection, dx, dy);
          if (neighbour.connection) {
            closestConnection = neighbour.connection;
            localConnection = myConnection;
            radiusConnection = neighbour.radius;
          }
        }
  
        // Remove connection highlighting if needed.
        if (Blockly.highlightedConnection_ &&
            Blockly.highlightedConnection_ != closestConnection) {
          Blockly.highlightedConnection_.unhighlight();
          Blockly.highlightedConnection_ = null;
          Blockly.localConnection_ = null;
        }
        // Add connection highlighting if needed.
        if (closestConnection &&
            closestConnection != Blockly.highlightedConnection_) {
          closestConnection.highlight();
          Blockly.highlightedConnection_ = closestConnection;
          Blockly.localConnection_ = localConnection;
        }
        // Provide visual indication of whether the block will be deleted if
        // dropped here.
        if (this_.deletable_) {
          this_.workspace.isDeleteArea(e);
        }
      }
      // This event has been handled.  No need to bubble up to the document.
      e.stopPropagation();
    });
  };
  
  /**
   * Add or remove the UI indicating if this block is movable or not.
   */
  Blockly.BlockSvg.prototype.updateMovable = function() {
    if (this.isMovable()) {
      Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
                        'blocklyDraggable');
    } else {
      Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
                           'blocklyDraggable');
    }
  };
  
  /**
   * Return the root node of the SVG or null if none exists.
   * @return {Element} The root SVG node (probably a group).
   */
  Blockly.BlockSvg.prototype.getSvgRoot = function() {
    return this.svgGroup_;
  };
  
  // UI constants for rendering blocks.
  /**
   * Horizontal space between elements. - horizontal padding on everything
   * @const
   */
  // Blockly.BlockSvg.SEP_SPACE_X = 10; 
  Blockly.BlockSvg.SEP_SPACE_X = 12; 
  /**
   * Vertical space between elements. - vertical padding on everything
   * @const
   */
  // Blockly.BlockSvg.SEP_SPACE_Y = 10;
  Blockly.BlockSvg.SEP_SPACE_Y = 12;
  /**
   * Vertical padding around inline elements. - vertical padding on top and bottom of inside inputs
   * @const
   */
  // Blockly.BlockSvg.INLINE_PADDING_Y = 5;
  Blockly.BlockSvg.INLINE_PADDING_Y = 15;
  /**
   * Minimum height of a block.
   * @const
   */
  Blockly.BlockSvg.MIN_BLOCK_Y = 25;
  /**
   * Height of horizontal puzzle tab. - y position of nobby thing on left
   * @const
   */
  Blockly.BlockSvg.TAB_HEIGHT = 20; 
  /**
   * Width of horizontal puzzle tab. - width of nobby thing on left
   * @const
   */
  Blockly.BlockSvg.TAB_WIDTH = 8;
  /**
   * Width of vertical tab (inc left margin). - width of entire puzzle piece
   * @const
   */
  Blockly.BlockSvg.NOTCH_WIDTH = 30;
  /**
   * Rounded corner radius. - left roundedness of clamps and things that fit into clamps
   * @const
   */
  Blockly.BlockSvg.CORNER_RADIUS = 8;
  /**
   * Minimum height of field rows. - position of text on blocks
   * @const
   */
  Blockly.BlockSvg.FIELD_HEIGHT = 18;
  /**
   * Distance from shape edge to intersect with a curved corner at 45 degrees.
   * Applies to highlighting on around the inside of a curve. - make outer curves look 3-D (color) ***DISABLED for new inputs
   * @const
   */
  Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
        (Blockly.BlockSvg.CORNER_RADIUS - 1) + 1;
  /**
   * Distance from shape edge to intersect with a curved corner at 45 degrees.
   * Applies to highlighting on around the outside of a curve. - make inside curves look 3-D (color) ***DISABLED for new inputs
   * @const
   */
  Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
        (Blockly.BlockSvg.CORNER_RADIUS + 1) - 1;
  /**
   * SVG path for drawing next/previous notch from left to right. - vertical depth/length of top notch on fortis
   * @const
   */
  Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 6,4 3,0 6,-4';
  // ***STACK Rectangular Clamp: 
  Blockly.BlockSvg.RECTANGULAR_CLAMP_IN = 'l 0,4 17,0 0,-4';
  // ***STACK Triangular Clamp: 
  Blockly.BlockSvg.TRIANGULAR_CLAMP_IN = 'l 7.5 4 l 7.5 -4';
  // ***STACK Circular Clamp: 
  Blockly.BlockSvg.CIRCULAR_CLAMP_IN = 'l .28,-.22 q 7.25,7 15.125,0';
  /**
   * SVG path for drawing next/previous notch from left to right with
   * highlighting.
   * @const
   */
  Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT = 'l 6.5,4 2,0 6.5,-4';
  // ***STACK Rectangular Clamp: 
  Blockly.BlockSvg.RECTANGULAR_CLAMP_IN_HIGHLIGHT = 'l 0,4 17,0 0,-4';
  // ***STACK Triangular Clamp: 
  Blockly.BlockSvg.TRIANGULAR_CLAMP_IN_HIGHLIGHT = 'l 7.5 4 l 7.5 -4';
  // ***STACK Circular Clamp: 
  Blockly.BlockSvg.CIRCULAR_CLAMP_IN_HIGHLIGHT = 'l .28,-.22 q 7.25,7 15.125,0';
  
  /**
   * SVG path for drawing next/previous notch from right to left. - bottom notch on fortis and on dat's clamp, mirrors above (notch path left)
   * @const
   */
  Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -6,4 -3,0 -6,-4';
  // ***STACK Rectangular Clamp: 
  Blockly.BlockSvg.RECTANGULAR_CLAMP_OUT = 'l 0,4 -15,0 0,-4';
  // ***STACK Triangular Clamp: 
  Blockly.BlockSvg.TRIANGULAR_CLAMP_OUT = 'l -7.5 4 l -7.5 -4';
  // ***STACK Circular Clamp: 
  Blockly.BlockSvg.CIRCULAR_CLAMP_OUT = 'l -.28,.22 q -7.25,7 -15.125,0'; 
  // NOTE THAT HIGHLIGHT FOR THIS IS DRAWN LATER...SHOULD JUST GET RID OF IT
  
  Blockly.BlockSvg.NOTCH_SHAPE = 'Rectangular';
  
  /**
   * SVG path for drawing jagged teeth at the end of collapsed blocks.
   * @const
   */
  Blockly.BlockSvg.JAGGED_TEETH = 'l 8,0 0,4 8,4 -16,8 8,4';
  /**
   * Height of SVG path for jagged teeth at the end of collapsed blocks.
   * @const
   */
  Blockly.BlockSvg.JAGGED_TEETH_HEIGHT = 20;
  /**
   * Width of SVG path for jagged teeth at the end of collapsed blocks.
   * @const
   */
  Blockly.BlockSvg.JAGGED_TEETH_WIDTH = 15;
  /**
   * SVG path for drawing a horizontal puzzle tab from top to bottom. 
   - v indicates vertical start position on puzzle piece, for inner inputs
   - c indicates roundiness of of top of nobby part of inner inputs
   - 3rd coordinate determines height of entire nobby part
   - 4th coordinate... weird warping with nobby part
   - s indicates roundiness and height of bottom of the nobby part of inner inputs
   - last coordinate determines height of rectangle part of the inner input
   * @const
   */
  Blockly.BlockSvg.TAB_PATH_DOWN = 'v 5 c 0,10 -' + Blockly.BlockSvg.TAB_WIDTH +
      ',-8 -' + Blockly.BlockSvg.TAB_WIDTH + ',7.5 s ' +
      Blockly.BlockSvg.TAB_WIDTH + ',-2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',7.5';
  // *** actual path above is d = v 5 c 0,10 -8,-8 -8,7.5 s 8,-2.5 8,7.5
  // Blockly.BlockSvg.TAB_PATH_DOWN = 'v 12 h -5 v 8 h 5';
  /**
   * SVG path for drawing a horizontal puzzle tab from top to bottom with
   * highlighting from the upper-right.
   * @const
   */
  Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL = 'v 6.5 m -' +
      (Blockly.BlockSvg.TAB_WIDTH * 0.98) + ',2.5 q -' +
      (Blockly.BlockSvg.TAB_WIDTH * .05) + ',10 ' +
      (Blockly.BlockSvg.TAB_WIDTH * .27) + ',10 m ' +
      (Blockly.BlockSvg.TAB_WIDTH * .71) + ',-2.5 v 1.5';
  
  /**
   * SVG start point for drawing the top-left corner.
   * @const
   */
  Blockly.BlockSvg.TOP_LEFT_CORNER_START =
      'm 0,' + Blockly.BlockSvg.CORNER_RADIUS;
  /**
   * SVG start point for drawing the top-left corner's highlight in RTL.
   * @const
   */
  Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL =
      'm ' + Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
      Blockly.BlockSvg.DISTANCE_45_INSIDE;
  /**
   * SVG start point for drawing the top-left corner's highlight in LTR.
   * @const
   */
  Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR =
      'm 1,' + (Blockly.BlockSvg.CORNER_RADIUS - 1);
  /**
   * SVG path for drawing the rounded top-left corner.
   * @const
   */
  Blockly.BlockSvg.TOP_LEFT_CORNER =
      'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
      Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
      Blockly.BlockSvg.CORNER_RADIUS + ',0';
  /**
   * SVG path for drawing the highlight on the rounded top-left corner.
   * @const
   */
  Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT =
      'A ' + (Blockly.BlockSvg.CORNER_RADIUS - 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS - 1) + ' 0 0,1 ' +
      Blockly.BlockSvg.CORNER_RADIUS + ',1';
  /**
   * SVG path for drawing the top-left corner of a statement input.
   * Includes the top notch, a horizontal space, and the rounded inside corner.
   * @const
   */
  Blockly.BlockSvg.INNER_TOP_LEFT_CORNER =
      ' h -' +
      (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS) +
      ' a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
      Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' +
      Blockly.BlockSvg.CORNER_RADIUS + ',' +
      Blockly.BlockSvg.CORNER_RADIUS;
  /**
   * SVG path for drawing the bottom-left corner of a statement input.
   * Includes the rounded inside corner.
   * @const
   */
  Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER =
      // 'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
      // Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
      // Blockly.BlockSvg.CORNER_RADIUS + ',' +
      // Blockly.BlockSvg.CORNER_RADIUS;
      //*** add a tooth to the bottom of the clamp for verb, eg. dat, and nouns. 
      'a ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
      Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
      Blockly.BlockSvg.CORNER_RADIUS + ',' +
      Blockly.BlockSvg.CORNER_RADIUS + 
      ' h ' + (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS); // +
      // (Blockly.BlockSvg.NOTCH_SHAPE == 'Triangular' ? Blockly.BlockSvg.TRIANGULAR_CLAMP_IN : Blockly.BlockSvg.RECTANGULAR_CLAMP_IN);
      // Blockly.BlockSvg.NOTCH_PATH_LEFT;
   
  /**
   * SVG path for drawing highlight on the top-left corner of a statement
   * input in RTL.
   * @const
   */
  Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL =
      'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS + 1) + ' 0 0,0 ' +
      (-Blockly.BlockSvg.DISTANCE_45_OUTSIDE - 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS -
      Blockly.BlockSvg.DISTANCE_45_OUTSIDE);
  /**
   * SVG path for drawing highlight on the bottom-left corner of a statement
   * input in RTL.
   * @const
   */
  Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL =
      'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS + 1) + ' 0 0,0 ' +
      (Blockly.BlockSvg.CORNER_RADIUS + 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS + 1);
  /**
   * SVG path for drawing highlight on the bottom-left corner of a statement
   * input in LTR.
   * @const
   */
  Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR =
      // 'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 1) + ',' +
      // (Blockly.BlockSvg.CORNER_RADIUS + 1) + ' 0 0,0 ' +
      // (Blockly.BlockSvg.CORNER_RADIUS -
      // Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
      // (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 1);    
      //***
      'a ' + (Blockly.BlockSvg.CORNER_RADIUS + 1) + ',' +
      (Blockly.BlockSvg.CORNER_RADIUS + 1) + ' 0 0,0 ' +
      (Blockly.BlockSvg.CORNER_RADIUS -
      Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
      (Blockly.BlockSvg.DISTANCE_45_OUTSIDE + 1) + 
      ' h ' + (Blockly.BlockSvg.NOTCH_WIDTH - 15 - Blockly.BlockSvg.CORNER_RADIUS); // +
      // (Blockly.BlockSvg.NOTCH_SHAPE == 'Triangular' ? Blockly.BlockSvg.TRIANGULAR_CLAMP_IN_HIGHLIGHT : Blockly.BlockSvg.RECTANGULAR_CLAMP_IN_HIGHLIGHT);
      // Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT;
  
  /* 
   * ENUMs: Shapes for nobby things (tabs) with Stems
   * Usage: 
   * var path = Blockly.BlockSvg.STEM_START + Blockly.BlockSvg.CROSS_NOBBY + Blockly.BlockSvg.STEM_END;
   * steps.push(path);
   */
  // // Start and end of the stem the jutting out shape sits on 
  // Blockly.BlockSvg.STEM_START = 'v -3 h -4';
  // Blockly.BlockSvg.STEM_END = 'h 4';
  // // Start and end of the stem the inline shape sits on 
  // Blockly.BlockSvg.STEM_START_INLINE = 'v 7 h -4';
  // Blockly.BlockSvg.STEM_END_INLINE = 'h 4 v 7';
  
  // // Circular for the nominative
  // Blockly.BlockSvg.CIRCULAR_NOBBY = 'a 6,6.9 0 1 1 0,-6 h 4';
  
  // // Funnel for the genitive
  // Blockly.BlockSvg.FUNNEL_NOBBY = 'l -9,6 v -16 l 9,6';
  
  // // Cross for the dative
  // Blockly.BlockSvg.CROSS_NOBBY = 'v 5 h -5 v -5 h -5 v -8 h 5 v -5 h 5 v 5';
  
  // // Rectangular for the accusative
  // Blockly.BlockSvg.RECTANGULAR_NOBBY = 'v 3 h -10 v -13 h 10 v 3';
  // Blockly.BlockSvg.RECTANGULAR_NOBBY_INLINE = 'v -3 h -10 v 13 h 10 v -3';
  
  // // Triangular for the ablative
  // Blockly.BlockSvg.TRIANGULAR_NOBBY = 'v 5 l -10,-7 10,-7 v 5';
  
  /* 
   * ENUMs: Shapes for nobby things (tabs) - without stems
   */
  // RECTANGULAR w/o stem: 
  Blockly.BlockSvg.RECTANGULAR_NOBBY = 'v -2 h -8 v -14 h 8';
  Blockly.BlockSvg.RECTANGULAR_NOBBY_INLINE = 'v 5 h -7 v 14 h 7';
  // TRIANGULAR w/o stem: 
  Blockly.BlockSvg.TRIANGULAR_NOBBY = 'v -2 l -8,-7 8,-7';
  Blockly.BlockSvg.TRIANGULAR_NOBBY_INLINE = 'v 5 l -7,7 7,7';
  // CIRCULAR w/o stem: 
  Blockly.BlockSvg.CIRCULAR_NOBBY = 'a 7,7 0 1 1 0,-14';
  Blockly.BlockSvg.CIRCULAR_NOBBY_INLINE = 'v 6 a 7,7 0 1 0 0,14';
  // FUNNEL w/o stem: 
  Blockly.BlockSvg.FUNNEL_NOBBY = 'v -7 l -8,5 v -14 l 8,5 v -5';
  Blockly.BlockSvg.FUNNEL_NOBBY_INLINE = 'v 10 l -7,-5 v 14 l 7,-4 v 5';
  // CROSS w/o stem: 
  Blockly.BlockSvg.CROSS_NOBBY = 'v -2 h -4 v -4.5 h -4 v -5 h 4 v -4.5 h 4';
  Blockly.BlockSvg.CROSS_NOBBY_INLINE = 'v 5 h -3 v 4.5 h -4 v 5 h 4 v 4.5 h 3';
  // ORIGINAL TOOTH:
  Blockly.BlockSvg.TOOTH_NOBBY = 'c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' +
          Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH +
          ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5';
  Blockly.BlockSvg.TOOTH_NOBBY_INLINE = Blockly.BlockSvg.TAB_PATH_DOWN;
  
  /**
   * Dispose of this block.
   * @param {boolean} healStack If true, then try to heal any gap by connecting
   *     the next statement with the previous statement.  Otherwise, dispose of
   *     all children of this block.
   * @param {boolean} animate If true, show a disposal animation and sound.
   * @param {boolean} opt_dontRemoveFromWorkspace If true, don't remove this
   *     block from the workspace's list of top blocks.
   */
  Blockly.BlockSvg.prototype.dispose = function(healStack, animate,
                                                opt_dontRemoveFromWorkspace) {
    // If there's a drag in-progress, unlink the mouse events.
    Blockly.terminateDrag_();
    // If this block has a context menu open, close it.
    if (Blockly.ContextMenu.currentBlock == this) {
      Blockly.ContextMenu.hide();
    }
    // Bring block to the top of the workspace.
    this.unplug(healStack, false);
  
    if (animate && this.rendered) {
      this.disposeUiEffect();
    }
    // Stop rerendering.
    this.rendered = false;
  
    var icons = this.getIcons();
    for (var x = 0; x < icons.length; x++) {
      icons[x].dispose();
    }
  
    Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
  
    goog.dom.removeNode(this.svgGroup_);
    // Sever JavaScript to DOM connections.
    this.svgGroup_ = null;
    this.svgPath_ = null;
    this.svgPathLight_ = null;
    this.svgPathDark_ = null;
  };
  
  /**
   * Play some UI effects (sound, animation) when disposing of a block.
   */
  Blockly.BlockSvg.prototype.disposeUiEffect = function() {
    Blockly.playAudio('delete');
  
    var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_));
    // Deeply clone the current block.
    var clone = this.svgGroup_.cloneNode(true);
    clone.translateX_ = xy.x;
    clone.translateY_ = xy.y;
    clone.setAttribute('transform',
        'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')');
    Blockly.svg.appendChild(clone);
    clone.bBox_ = clone.getBBox();
    // Start the animation.
    clone.startDate_ = new Date();
    Blockly.BlockSvg.disposeUiStep_(clone);
  };
  
  /**
   * Animate a cloned block and eventually dispose of it.
   * @param {!Element} clone SVG element to animate and dispose of.
   * @private
   */
  Blockly.BlockSvg.disposeUiStep_ = function(clone) {
    var ms = (new Date()) - clone.startDate_;
    var percent = ms / 150;
    if (percent > 1) {
      goog.dom.removeNode(clone);
    } else {
      var x = clone.translateX_ +
          (Blockly.RTL ? -1 : 1) * clone.bBox_.width / 2 * percent;
      var y = clone.translateY_ + clone.bBox_.height * percent;
      var translate = x + ', ' + y;
      var scale = 1 - percent;
      clone.setAttribute('transform', 'translate(' + translate + ')' +
          ' scale(' + scale + ')');
      var closure = function() {
        Blockly.BlockSvg.disposeUiStep_(clone);
      };
      setTimeout(closure, 10);
    }
  };
  
  /**
   * Play some UI effects (sound, ripple) after a connection has been established.
   */
  Blockly.BlockSvg.prototype.connectionUiEffect = function() {
    Blockly.playAudio('click');
  
    // Determine the absolute coordinates of the inferior block.
    var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_));
    // Offset the coordinates based on the two connection types.
    // if (this.outputConnection) {
    //***
    if (this.outputConnection || this.nominativeOutputConnection || this.genitiveOutputConnection || 
      this.dativeOutputConnection || this.accusativeOutputConnection || this.ablativeOutputConnection || this.vocativeOutputConnection) {
      xy.x += Blockly.RTL ? 3 : -3;
      xy.y += 13;
    } else if (this.previousConnection) {
      xy.x += Blockly.RTL ? -23 : 23;
      xy.y += 3;
    }
    var ripple = Blockly.createSvgElement('circle',
        {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
         'stroke': '#888', 'stroke-width': 10},
        Blockly.svg);
    // Start the animation.
    ripple.startDate_ = new Date();
    Blockly.BlockSvg.connectionUiStep_(ripple);
  };
  
  // Completion UI Effection
  Blockly.BlockSvg.prototype.completionUiEffect = function() {
    Blockly.playAudio('click');
  
    // Determine the absolute coordinates of the inferior block.
    var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_));
    var blockHW = this.getHeightWidth();
    xy.x += blockHW.width / 3;
    xy.y += blockHW.height / 3;
    var ripple = Blockly.createSvgElement('circle',
        {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
         'stroke': '#FFD700', 'stroke-width': 50},
        Blockly.svg);
    // Start the animation.
    ripple.startDate_ = new Date();
    Blockly.BlockSvg.completionUiStep_(ripple);
    var children = this.getChildren();
    if (children) {
      for (var x = 0, child; child = children[x]; x++) {
        child.completionUiEffect();
      }
    }
  };
  
  /**
   * Expand a ripple around a connection.
   * @param {!Element} ripple Element to animate.
   * @private
   */
  Blockly.BlockSvg.connectionUiStep_ = function(ripple) {
    var ms = (new Date()) - ripple.startDate_;
    var percent = ms / 150;
    if (percent > 1) {
      goog.dom.removeNode(ripple);
    } else {
      ripple.setAttribute('r', percent * 250);
      ripple.style.opacity = 1 - percent;
      var closure = function() {
        Blockly.BlockSvg.connectionUiStep_(ripple);
      };
      setTimeout(closure, 10);
    }
  };
  
  Blockly.BlockSvg.completionUiStep_ = function(ripple) {
    var ms = (new Date()) - ripple.startDate_;
    var percent = ms / 50;
    if (percent > 1) {
      goog.dom.removeNode(ripple);
    } else {
      ripple.setAttribute('r', percent * 25);
      ripple.style.opacity = 1 - percent;
      var closure = function() {
        Blockly.BlockSvg.connectionUiStep_(ripple);
      };
      setTimeout(closure, .5);
    }
  };
  
  /**
   * Change the colour of a block.
   */
  Blockly.BlockSvg.prototype.updateColour = function() {
    if (this.disabled) {
      // Disabled blocks don't have colour.
      return;
    }
    var hexColour = Blockly.makeColour(this.getColour());
    var rgb = goog.color.hexToRgb(hexColour);
    var rgbLight = goog.color.lighten(rgb, 0.3);
    var rgbDark = goog.color.darken(rgb, 0.4);
    // ***Put same colour on dark highlights and light highlights
    // this.svgPathLight_.setAttribute('stroke', goog.color.rgbArrayToHex(rgbLight));
    this.svgPathLight_.setAttribute('stroke', hexColour);
    this.svgPathDark_.setAttribute('fill', goog.color.rgbArrayToHex(rgbDark));
    // this.svgPathDark_.setAttribute('fill', hexColour);
    this.svgPath_.setAttribute('fill', hexColour);
  
    var icons = this.getIcons();
    for (var x = 0; x < icons.length; x++) {
      icons[x].updateColour();
    }
  
    // Bump every dropdown to change its colour.
    for (var x = 0, input; input = this.inputList[x]; x++) {
      for (var y = 0, field; field = input.fieldRow[y]; y++) {
        field.setText(null);
      }
    }
  };
  
  /**
   * Enable or disable a block.
   */
  Blockly.BlockSvg.prototype.updateDisabled = function() {
    var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_),
                                     'blocklyDisabled');
    if (this.disabled || this.getInheritedDisabled()) {
      if (!hasClass) {
        Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
                          'blocklyDisabled');
        this.svgPath_.setAttribute('fill', 'url(#blocklyDisabledPattern)');
      }
    } else {
      if (hasClass) {
        Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
                             'blocklyDisabled');
        this.updateColour();
      }
    }
    var children = this.getChildren();
    for (var x = 0, child; child = children[x]; x++) {
      child.updateDisabled();
    }
  };
  
  /**
   * Returns the comment on this block (or '' if none).
   * @return {string} Block's comment.
   */
  Blockly.BlockSvg.prototype.getCommentText = function() {
    if (this.comment) {
      var comment = this.comment.getText();
      // Trim off trailing whitespace.
      return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
    }
    return '';
  };
  
  /**
   * Set this block's comment text.
   * @param {?string} text The text, or null to delete.
   */
  Blockly.BlockSvg.prototype.setCommentText = function(text) {
    var changedState = false;
    if (goog.isString(text)) {
      if (!this.comment) {
        this.comment = new Blockly.Comment(this);
        changedState = true;
      }
      this.comment.setText(/** @type {string} */ (text));
    } else {
      if (this.comment) {
        this.comment.dispose();
        changedState = true;
      }
    }
    if (changedState && this.rendered) {
      this.render();
      // Adding or removing a comment icon will cause the block to change shape.
      this.bumpNeighbours_();
    }
  };
  
  /**
   * Set this block's warning text.
   * @param {?string} text The text, or null to delete.
   */
  Blockly.BlockSvg.prototype.setWarningText = function(text) {
    if (this.isInFlyout) {
      text = null;
    }
    var changedState = false;
    if (goog.isString(text)) {
      if (!this.warning) {
        this.warning = new Blockly.Warning(this);
        changedState = true;
      }
      this.warning.setText(/** @type {string} */ (text));
    } else {
      if (this.warning) {
        this.warning.dispose();
        changedState = true;
      }
    }
    if (changedState && this.rendered) {
      this.render();
      // Adding or removing a warning icon will cause the block to change shape.
      this.bumpNeighbours_();
    }
  };
  
  /**
   * Give this block a mutator dialog.
   * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
   */
  Blockly.BlockSvg.prototype.setMutator = function(mutator) {
    if (this.mutator && this.mutator !== mutator) {
      this.mutator.dispose();
    }
    if (mutator) {
      mutator.block_ = this;
      this.mutator = mutator;
      if (this.rendered) {
        mutator.createIcon();
      }
    }
  };
  
  /**
   * Set whether the block is disabled or not.
   * @param {boolean} disabled True if disabled.
   */
  Blockly.BlockSvg.prototype.setDisabled = function(disabled) {
    if (this.disabled == disabled) {
      return;
    }
    Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled);
    this.updateDisabled();
    this.workspace.fireChangeEvent();
  };
  
  /**
   * Select this block.  Highlight it visually.
   */
  Blockly.BlockSvg.prototype.addSelect = function() {
    Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
                      'blocklySelected');
    // Move the selected block to the top of the stack.
    this.svgGroup_.parentNode.appendChild(this.svgGroup_);
  };
  
  /**
   * Unselect this block.  Remove its highlighting.
   */
  Blockly.BlockSvg.prototype.removeSelect = function() {
    Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
                         'blocklySelected');
  };
  
  /**
   * Adds the dragging class to this block.
   * Also disables the highlights/shadows to improve performance.
   */
  Blockly.BlockSvg.prototype.addDragging = function() {
    Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
                      'blocklyDragging');
  };
  
  /**
   * Removes the dragging class from this block.
   */
  Blockly.BlockSvg.prototype.removeDragging = function() {
    Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
                         'blocklyDragging');
  };
  
  /**
   * Render the block.
   * Lays out and reflows a block based on its contents and settings.
   */
  Blockly.BlockSvg.prototype.render = function() {
    this.rendered = true;
  
    var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
    if (Blockly.RTL) {
      cursorX = -cursorX;
    }
    // Move the icons into position.
    var icons = this.getIcons();
    for (var x = 0; x < icons.length; x++) {
      cursorX = icons[x].renderIcon(cursorX);
    }
    cursorX += Blockly.RTL ?
        Blockly.BlockSvg.SEP_SPACE_X : -Blockly.BlockSvg.SEP_SPACE_X;
    // If there are no icons, cursorX will be 0, otherwise it will be the
    // width that the first label needs to move over by.
  
    var inputRows = this.renderCompute_(cursorX);
    this.renderDraw_(cursorX, inputRows);
  
    // Render all blocks above this one (propagate a reflow).
    var parentBlock = this.getParent();
    if (parentBlock) {
      parentBlock.render();
    } else {
      // Top-most block.  Fire an event to allow scrollbars to resize.
      Blockly.fireUiEvent(window, 'resize');
    }
    Blockly.Realtime.blockChanged(this);
  };
  
  /**
   * Render a list of fields starting at the specified location.
   * @param {!Array.<!Blockly.Field>} fieldList List of fields.
   * @param {number} cursorX X-coordinate to start the fields.
   * @param {number} cursorY Y-coordinate to start the fields.
   * @return {number} X-coordinate of the end of the field row (plus a gap).
   * @private
   */
  Blockly.BlockSvg.prototype.renderFields_ =
      function(fieldList, cursorX, cursorY) {
    if (Blockly.RTL) {
      cursorX = -cursorX;
    }
    for (var t = 0, field; field = fieldList[t]; t++) {
      var root = field.getSvgRoot();
      if (!root) {
        continue;
      }
      if (Blockly.RTL) {
        cursorX -= field.renderSep + field.renderWidth;
        root.setAttribute('transform',
            'translate(' + cursorX + ', ' + cursorY + ')');
        if (field.renderWidth) {
          cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
        }
      } else {
        root.setAttribute('transform',
            'translate(' + (cursorX + field.renderSep) + ', ' + cursorY + ')');
        if (field.renderWidth) {
          cursorX += field.renderSep + field.renderWidth +
              Blockly.BlockSvg.SEP_SPACE_X;
        }
      }
    }
    return Blockly.RTL ? -cursorX : cursorX;
  };
  
  /**
   * Computes the height and widths for each row and field.
   * @param {number} iconWidth Offset of first row due to icons.
   * @return {!Array.<!Array.<!Object>>} 2D array of objects, each containing
   *     position information.
   * @private
   */
  Blockly.BlockSvg.prototype.renderCompute_ = function(iconWidth) {
    var inputList = this.inputList;
    var inputRows = [];
    inputRows.rightEdge = iconWidth + Blockly.BlockSvg.SEP_SPACE_X * 2;
    if (this.previousConnection || this.nextConnection) {
      inputRows.rightEdge = Math.max(inputRows.rightEdge,
          Blockly.BlockSvg.NOTCH_WIDTH + Blockly.BlockSvg.SEP_SPACE_X);
    }
    var fieldValueWidth = 0;  // Width of longest external value field.
    var fieldStatementWidth = 0;  // Width of longest statement field.
    var hasValue = false;
    var hasStatement = false;
    var hasDummy = false;
    var lastType = undefined;
    var isInline = this.inputsInline && !this.isCollapsed();
    //***
    var hasNominativeValue = false;
    var hasGenitiveValue = false;
    var hasDativeValue = false;
    var hasAccusativeValue = false;
    var hasAblativeValue = false;
    var hasVocativeValue = false;
  
    for (var i = 0, input; input = inputList[i]; i++) {
      if (!input.isVisible()) {
        continue;
      }
      var row;
      if (!isInline || !lastType ||
          lastType == Blockly.NEXT_STATEMENT ||
          input.type == Blockly.NEXT_STATEMENT) {
        // Create new row.
        lastType = input.type;
        row = [];
        if (isInline && input.type != Blockly.NEXT_STATEMENT) {
          row.type = Blockly.BlockSvg.INLINE;
        } else {
          row.type = input.type;
        }
        row.height = 0;
        inputRows.push(row);
      } else {
        row = inputRows[inputRows.length - 1];
      }
      row.push(input);
  
      // Compute minimum input size.
      input.renderHeight = Blockly.BlockSvg.MIN_BLOCK_Y;
      // The width is currently only needed for inline value inputs.
      if (isInline && input.type == Blockly.INPUT_VALUE) {
        input.renderWidth = Blockly.BlockSvg.TAB_WIDTH +
            Blockly.BlockSvg.SEP_SPACE_X * 1.25;
      } 
      //*** TODO: determine actual width needed for the inside/inline block in dat (the verb)
      else if (isInline) {
        input.renderWidth = Blockly.BlockSvg.TAB_WIDTH +
            Blockly.BlockSvg.SEP_SPACE_X * 1.25;
      } 
      else {
        input.renderWidth = 0;
      }
      // Expand input size if there is a connection.
      if (input.connection && input.connection.targetConnection) {
        var linkedBlock = input.connection.targetBlock();
        var bBox = linkedBlock.getHeightWidth();
        input.renderHeight = Math.max(input.renderHeight, bBox.height);
        input.renderWidth = Math.max(input.renderWidth, bBox.width);
      }
  
      if (i == inputList.length - 1) {
        // Last element should overhang slightly due to shadow.
        input.renderHeight--;
        // *** Statements / clamps need to fit their pieces more tightly
        if (input.type == Blockly.NEXT_STATEMENT) {
          input.renderHeight -= 2;   
        }
      }
      row.height = Math.max(row.height, input.renderHeight);
      input.fieldWidth = 0;
      if (inputRows.length == 1) {
        // The first row gets shifted to accommodate any icons.
        input.fieldWidth += Blockly.RTL ? -iconWidth : iconWidth;
      }
      var previousFieldEditable = false;
      for (var j = 0, field; field = input.fieldRow[j]; j++) {
        if (j != 0) {
          input.fieldWidth += Blockly.BlockSvg.SEP_SPACE_X;
        }
        // Get the dimensions of the field.
        var fieldSize = field.getSize();
        field.renderWidth = fieldSize.width;
        field.renderSep = (previousFieldEditable && field.EDITABLE) ?
            Blockly.BlockSvg.SEP_SPACE_X : 0;
        input.fieldWidth += field.renderWidth + field.renderSep;
        row.height = Math.max(row.height, fieldSize.height);
        previousFieldEditable = field.EDITABLE;
      }
  
      if (row.type != Blockly.BlockSvg.INLINE) {
        if (row.type == Blockly.NEXT_STATEMENT) {
          hasStatement = true;
          fieldStatementWidth = Math.max(fieldStatementWidth, input.fieldWidth);
        } else {
          if (row.type == Blockly.INPUT_VALUE) {
            hasValue = true;
          } 
          //***
          else if (row.type == Blockly.NOMINATIVE_INPUT_VALUE) {
            hasNominativeValue = true;
          } 
          else if (row.type == Blockly.GENITIVE_INPUT_VALUE) {
            hasGenitiveValue = true;
          } 
          else if (row.type == Blockly.DATIVE_INPUT_VALUE) {
            hasDativeValue = true;
          } 
          else if (row.type == Blockly.ACCUSATIVE_INPUT_VALUE) {
            hasAccusativeValue = true;
          } 
          else if (row.type == Blockly.ABLATIVE_INPUT_VALUE) {
            hasAblativeValue = true;
          } 
          else if (row.type == Blockly.VOCATIVE_INPUT_VALUE) {
            hasVocativeValue = true;
          } 
          else if (row.type == Blockly.DUMMY_INPUT) {
            hasDummy = true;
          }
          fieldValueWidth = Math.max(fieldValueWidth, input.fieldWidth);
        }
      }
    }
  
    // Make inline rows a bit thicker in order to enclose the values.
    for (var y = 0, row; row = inputRows[y]; y++) {
      row.thicker = false;
      if (row.type == Blockly.BlockSvg.INLINE) {
        for (var z = 0, input; input = row[z]; z++) {
          if (input.type == Blockly.INPUT_VALUE || Blockly.NOMINATIVE_INPUT_VALUE || Blockly.GENITIVE_INPUT_VALUE ||
            Blockly.DATIVE_INPUT_VALUE || Blockly.ACCUSATIVE_INPUT_VALUE || Blockly.ABLATIVE_INPUT_VALUE || Blockly.VOCATIVE_INPUT_VALUE) {
            row.height += 2 * Blockly.BlockSvg.INLINE_PADDING_Y;
            row.thicker = true;
            break;
          }
        }
      }
    }
  
    // Compute the statement edge.
    // This is the width of a block where statements are nested.
    inputRows.statementEdge = 2 * Blockly.BlockSvg.SEP_SPACE_X +
        fieldStatementWidth;
    // Compute the preferred right edge.  Inline blocks may extend beyond.
    // This is the width of the block where external inputs connect.
    if (hasStatement) {
      inputRows.rightEdge = Math.max(inputRows.rightEdge,
          inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH);
      //*** Increase length of bottom clamp on nouns and verbs / statement edge, so that nob thing doesn't look like it's falling off
      inputRows.rightEdge *= 1.5;
    }
    if (hasValue) {
      inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
          Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH);
    } 
    //*** TODO: figure out how much extra width is needed, if different from regular input value above
    else if (hasNominativeValue || hasGenitiveValue || hasDativeValue || hasAccusativeValue || hasAblativeValue || hasVocativeValue) {
      inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
          Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.TAB_WIDTH);
    } 
    else if (hasDummy) {
      inputRows.rightEdge = Math.max(inputRows.rightEdge, fieldValueWidth +
          Blockly.BlockSvg.SEP_SPACE_X * 2);
    }
  
    inputRows.hasValue = hasValue;
    inputRows.hasStatement = hasStatement;
    inputRows.hasDummy = hasDummy;
    //***
    inputRows.hasNominativeValue = hasNominativeValue;
    inputRows.hasGenitiveValue = hasGenitiveValue;
    inputRows.hasDativeValue = hasDativeValue;
    inputRows.hasAccusativeValue = hasAccusativeValue;
    inputRows.hasAblativeValue = hasAblativeValue;
    inputRows.hasVocativeValue = hasVocativeValue;
    return inputRows;
  };
  
  
  /**
   * Draw the path of the block.
   * Move the fields to the correct locations.
   * @param {number} iconWidth Offset of first row due to icons.
   * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
   *     containing position information.
   * @private
   */
  Blockly.BlockSvg.prototype.renderDraw_ = function(iconWidth, inputRows) {
    // Should the top and bottom left corners be rounded or square?
    // if (this.outputConnection) {
    //***
    if (this.outputConnection || this.nominativeOutputConnection || this.genitiveOutputConnection || 
      this.dativeOutputConnection || this.accusativeOutputConnection || this.ablativeOutputConnection || this.vocativeOutputConnection) {
      this.squareTopLeftCorner_ = true;
      this.squareBottomLeftCorner_ = true;
    } else {
      this.squareTopLeftCorner_ = false;
      this.squareBottomLeftCorner_ = false;
      // If this block is in the middle of a stack, square the corners.
      if (this.previousConnection) {
        var prevBlock = this.previousConnection.targetBlock();
        if (prevBlock && prevBlock.getNextBlock() == this) {
          this.squareTopLeftCorner_ = true;
         }
      }
      var nextBlock = this.getNextBlock();
      if (nextBlock) {
        this.squareBottomLeftCorner_ = true;
      }
    }
  
    // Fetch the block's coordinates on the surface for use in anchoring
    // the connections.
    var connectionsXY = this.getRelativeToSurfaceXY();
  
    // Assemble the block's path.
    var steps = [];
    var inlineSteps = [];
    // The highlighting applies to edges facing the upper-left corner.
    // Since highlighting is a two-pixel wide border, it would normally overhang
    // the edge of the block by a pixel. So undersize all measurements by a pixel.
    var highlightSteps = [];
    var highlightInlineSteps = [];
  
    this.renderDrawTop_(steps, highlightSteps, connectionsXY,
        inputRows.rightEdge);
    var cursorY = this.renderDrawRight_(steps, highlightSteps, inlineSteps,
        highlightInlineSteps, connectionsXY, inputRows, iconWidth);
    this.renderDrawBottom_(steps, highlightSteps, connectionsXY, cursorY);
    this.renderDrawLeft_(steps, highlightSteps, connectionsXY, cursorY);
  
    var pathString = steps.join(' ') + '\n' + inlineSteps.join(' ');
    this.svgPath_.setAttribute('d', pathString);
    //*** get rid of dark shadows if comment below out
    this.svgPathDark_.setAttribute('d', pathString);
    pathString = highlightSteps.join(' ') + '\n' + highlightInlineSteps.join(' ');
    //*** get rid of light shadows if comment below out
    this.svgPathLight_.setAttribute('d', pathString);
    if (Blockly.RTL) {
      // Mirror the block's path.
      this.svgPath_.setAttribute('transform', 'scale(-1 1)');
      this.svgPathLight_.setAttribute('transform', 'scale(-1 1)');
      this.svgPathDark_.setAttribute('transform', 'translate(1,1) scale(-1 1)');
    }
  };
  
  /**
   * Render the top edge of the block.
   * @param {!Array.<string>} steps Path of block outline.
   * @param {!Array.<string>} highlightSteps Path of block highlights.
   * @param {!Object} connectionsXY Location of block.
   * @param {number} rightEdge Minimum width of block.
   * @private
   */
  Blockly.BlockSvg.prototype.renderDrawTop_ =
      function(steps, highlightSteps, connectionsXY, rightEdge) {
    // Position the cursor at the top-left starting point.
    if (this.squareTopLeftCorner_) {
      steps.push('m 0,0');
      highlightSteps.push('m 1,1');
    } else {
      steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
      highlightSteps.push(Blockly.RTL ?
          Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_RTL :
          Blockly.BlockSvg.TOP_LEFT_CORNER_START_HIGHLIGHT_LTR);
      // Top-left rounded corner.
      steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
      highlightSteps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_HIGHLIGHT);
    }
  
    // Top edge.
    if (this.previousConnection) {
      steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
      highlightSteps.push('H', Blockly.BlockSvg.NOTCH_WIDTH - 15);
      //*** Change top notches of adjectives and prepositions 
      // Default connectionCheck = 0, which is a piece that fits into a noun clamp, eg. an adjective
      var connectionCheck = 0;
      var checkTypes = this.previousConnection.check_;
      for (var i = 0, checkType = checkTypes[i]; i < checkTypes.length; i++) {
        if (checkType == "preposition" || checkType == "adverb" || checkType == "ut-clause") {
          // It's a preposition that fits into a verb clamp -- add other types as necessary
          connectionCheck = 1;
        }
      }
      if (connectionCheck) {
        // Fits into verb clamp - rectangular
        steps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_IN);
        highlightSteps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_IN_HIGHLIGHT);
      } else {
        // Fits into a noun clamp - triangular
        steps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_IN);
        highlightSteps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_IN_HIGHLIGHT);      
      }
  
      // Old code for consistent notches for everything
      // steps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT);
      // highlightSteps.push(Blockly.BlockSvg.NOTCH_PATH_LEFT_HIGHLIGHT);
  
      // Create previous block connection.
      var connectionX = connectionsXY.x + (Blockly.RTL ?
          -Blockly.BlockSvg.NOTCH_WIDTH : Blockly.BlockSvg.NOTCH_WIDTH);
      var connectionY = connectionsXY.y;
      this.previousConnection.moveTo(connectionX, connectionY);
      // This connection will be tightened when the parent renders.
    }
    steps.push('H', rightEdge);
    highlightSteps.push('H', rightEdge + (Blockly.RTL ? -1 : 0));
    this.width = rightEdge;
  };
  
  /**
   * Render the right edge of the block.
   * @param {!Array.<string>} steps Path of block outline.
   * @param {!Array.<string>} highlightSteps Path of block highlights.
   * @param {!Array.<string>} inlineSteps Inline block outlines.
   * @param {!Array.<string>} highlightInlineSteps Inline block highlights.
   * @param {!Object} connectionsXY Location of block.
   * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
   *     containing position information.
   * @param {number} iconWidth Offset of first row due to icons.
   * @return {number} Height of block.
   * @private
   */
  Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, highlightSteps,
      inlineSteps, highlightInlineSteps, connectionsXY, inputRows, iconWidth) {
    var cursorX;
    var cursorY = 0;
    var connectionX, connectionY;
    for (var y = 0, row; row = inputRows[y]; y++) {
      cursorX = Blockly.BlockSvg.SEP_SPACE_X;
      if (y == 0) {
        cursorX += Blockly.RTL ? -iconWidth : iconWidth;
      }
      highlightSteps.push('M', (inputRows.rightEdge - 1) + ',' + (cursorY + 1));
      if (this.isCollapsed()) {
        // Jagged right edge.
        var input = row[0];
        var fieldX = cursorX;
        var fieldY = cursorY + Blockly.BlockSvg.FIELD_HEIGHT;
        this.renderFields_(input.fieldRow, fieldX, fieldY);
        steps.push(Blockly.BlockSvg.JAGGED_TEETH);
        if (Blockly.RTL) {
          highlightSteps.push('l 8,0 0,3.8 7,3.2 m -14.5,9 l 8,4');
        } else {
          highlightSteps.push('h 8');
        }
        var remainder = row.height - Blockly.BlockSvg.JAGGED_TEETH_HEIGHT;
        steps.push('v', remainder);
        if (Blockly.RTL) {
          highlightSteps.push('v', remainder - 2);
        }
        this.width += Blockly.BlockSvg.JAGGED_TEETH_WIDTH;
      } else if (row.type == Blockly.BlockSvg.INLINE) {
        // Inline inputs.
        for (var x = 0, input; input = row[x]; x++) {
          var fieldX = cursorX;
          var fieldY = cursorY + Blockly.BlockSvg.FIELD_HEIGHT;
          if (row.thicker) {
            // Lower the field slightly.
            fieldY += Blockly.BlockSvg.INLINE_PADDING_Y;
          }
          // TODO: Align inline field rows (left/right/centre).
          cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
          if (input.type != Blockly.DUMMY_INPUT) {
            cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
          }
  
          if (input.type == Blockly.INPUT_VALUE) {
            // Shape of nobby thing (puzzle piece, default regular)
            inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) +
                             ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y));
            inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
                             input.renderWidth);
            inlineSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
            inlineSteps.push('v', input.renderHeight + 1 -
                                  Blockly.BlockSvg.TAB_HEIGHT);
            inlineSteps.push('h', input.renderWidth + 2 -
                             Blockly.BlockSvg.TAB_WIDTH);
            inlineSteps.push('z');
            if (Blockly.RTL) {
              // Highlight right edge, around back of tab, and bottom.
              highlightInlineSteps.push('M',
                  (cursorX - Blockly.BlockSvg.SEP_SPACE_X - 3 +
                   Blockly.BlockSvg.TAB_WIDTH - input.renderWidth) + ',' +
                  (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1));
              highlightInlineSteps.push(
                  Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
              highlightInlineSteps.push('v',
                  input.renderHeight - Blockly.BlockSvg.TAB_HEIGHT + 3);
              highlightInlineSteps.push('h',
                  input.renderWidth - Blockly.BlockSvg.TAB_WIDTH + 1);
            } else {
              // Highlight right edge, bottom.
              highlightInlineSteps.push('M',
                  (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 1) + ',' +
                  (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1));
              highlightInlineSteps.push('v', input.renderHeight + 1);
              highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
                                             input.renderWidth);
              // Short highlight glint at bottom of tab.
              highlightInlineSteps.push('M',
                  (cursorX - input.renderWidth - Blockly.BlockSvg.SEP_SPACE_X +
                   1.8) + ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y +
                   Blockly.BlockSvg.TAB_HEIGHT - 0.4));
              highlightInlineSteps.push('l',
                  (Blockly.BlockSvg.TAB_WIDTH * 0.38) + ',-1.8');
            }
            // Create inline input connection.
            if (Blockly.RTL) {
              connectionX = connectionsXY.x - cursorX -
                  Blockly.BlockSvg.TAB_WIDTH + Blockly.BlockSvg.SEP_SPACE_X +
                  input.renderWidth + 1;
            } else {
              connectionX = connectionsXY.x + cursorX +
                  Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X -
                  input.renderWidth - 1;
            }
            connectionY = connectionsXY.y + cursorY +
                Blockly.BlockSvg.INLINE_PADDING_Y + 1;
            input.connection.moveTo(connectionX, connectionY);
            if (input.connection.targetConnection) {
              input.connection.tighten_();
            }
          }
          //*** for INLINE/inside, not external, nominative input value pieces (shape + highlight in the verb, eg. dat)
          // Shape of nobby thing for inline
          var path_type = null;
          if (input.type == Blockly.NOMINATIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.TOOTH_NOBBY_INLINE;
          }
          if (input.type == Blockly.GENITIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.CROSS_NOBBY_INLINE;
          }        
          if (input.type == Blockly.DATIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.RECTANGULAR_NOBBY_INLINE;
          }
          if (input.type == Blockly.ACCUSATIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.CIRCULAR_NOBBY_INLINE;
          }
          if (input.type == Blockly.ABLATIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.TRIANGULAR_NOBBY_INLINE;
          }
          if (input.type == Blockly.VOCATIVE_INPUT_VALUE) {
            path_type = Blockly.BlockSvg.FUNNEL_NOBBY_INLINE;
          }
          if (path_type != null) {
            inlineSteps.push('M', (cursorX - Blockly.BlockSvg.SEP_SPACE_X) +
                             ',' + (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y));
            inlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
                             input.renderWidth);
            // path_type inserted here
            inlineSteps.push(path_type);
            inlineSteps.push('v', input.renderHeight + 1 -
                                  Blockly.BlockSvg.TAB_HEIGHT);
            inlineSteps.push('h', input.renderWidth + 2 -
                             Blockly.BlockSvg.TAB_WIDTH);
            inlineSteps.push('z');
  
            // Highlight right edge, bottom.
            highlightInlineSteps.push('M',
                (cursorX - Blockly.BlockSvg.SEP_SPACE_X + 1) + ',' +
                (cursorY + Blockly.BlockSvg.INLINE_PADDING_Y + 1));
            highlightInlineSteps.push('v', input.renderHeight + 1);
            highlightInlineSteps.push('h', Blockly.BlockSvg.TAB_WIDTH - 2 -
                                           input.renderWidth);
  
            // Create inline input connection.
            connectionX = connectionsXY.x + cursorX +
                Blockly.BlockSvg.TAB_WIDTH - Blockly.BlockSvg.SEP_SPACE_X -
                input.renderWidth - 1;
            connectionY = connectionsXY.y + cursorY + 
                Blockly.BlockSvg.INLINE_PADDING_Y + 1;
            input.connection.moveTo(connectionX, connectionY);
            if (input.connection.targetConnection) {
              input.connection.tighten_();
            }
          }
        }
  
        cursorX = Math.max(cursorX, inputRows.rightEdge);
        this.width = Math.max(this.width, cursorX);
        steps.push('H', cursorX);
        highlightSteps.push('H', cursorX + (Blockly.RTL ? -1 : 0));
        steps.push('v', row.height);
        if (Blockly.RTL) {
          highlightSteps.push('v', row.height - 2);
        }
      } else if (row.type == Blockly.INPUT_VALUE || row.type == Blockly.NOMINATIVE_INPUT_VALUE || 
        row.type == Blockly.GENITIVE_INPUT_VALUE || row.type == Blockly.DATIVE_INPUT_VALUE || 
        row.type == Blockly.ACCUSATIVE_INPUT_VALUE || row.type == Blockly.ABLATIVE_INPUT_VALUE || 
        row.type == Blockly.VOCATIVE_INPUT_VALUE) {
        // External input.
        var input = row[0];
        var fieldX = cursorX;
        var fieldY = cursorY + Blockly.BlockSvg.FIELD_HEIGHT;
        if (input.align != Blockly.ALIGN_LEFT) {
          var fieldRightX = inputRows.rightEdge - input.fieldWidth -
              Blockly.BlockSvg.TAB_WIDTH - 2 * Blockly.BlockSvg.SEP_SPACE_X;
          if (input.align == Blockly.ALIGN_RIGHT) {
            fieldX += fieldRightX;
          } else if (input.align == Blockly.ALIGN_CENTRE) {
            fieldX += (fieldRightX + fieldX) / 2;
          }
        }
        this.renderFields_(input.fieldRow, fieldX, fieldY);
        var path_type = null;
        if (input.type == Blockly.NOMINATIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.TOOTH_NOBBY_INLINE;
        }
        if (input.type == Blockly.GENITIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.CROSS_NOBBY_INLINE;
        }        
        if (input.type == Blockly.DATIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.RECTANGULAR_NOBBY_INLINE;
        }
        if (input.type == Blockly.ACCUSATIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.CIRCULAR_NOBBY_INLINE;
        }
        if (input.type == Blockly.ABLATIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.TRIANGULAR_NOBBY_INLINE;
        }
        if (input.type == Blockly.VOCATIVE_INPUT_VALUE) {
          path_type = Blockly.BlockSvg.FUNNEL_NOBBY_INLINE;
        }
        if (path_type != null) {
          // path_type inserted here
          steps.push(path_type);
        } else {
          steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
        }
        var v = row.height - Blockly.BlockSvg.TAB_HEIGHT;
        steps.push('v', v);
        // if (Blockly.RTL) {
        //   // Highlight around back of tab.
        //   highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
        //   highlightSteps.push('v', v);
        // } else {
        //   // Short highlight glint at bottom of tab.
        //   highlightSteps.push('M', (inputRows.rightEdge - 4.2) + ',' +
        //       (cursorY + Blockly.BlockSvg.TAB_HEIGHT - 0.4));
        //   highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * 0.38) +
        //       ',-1.8');
        // }
        // Create external input connection.
        connectionX = connectionsXY.x +
            (Blockly.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1);
        connectionY = connectionsXY.y + cursorY;
        input.connection.moveTo(connectionX, connectionY);
        if (input.connection.targetConnection) {
          input.connection.tighten_();
          this.width = Math.max(this.width, inputRows.rightEdge +
              input.connection.targetBlock().getHeightWidth().width -
              Blockly.BlockSvg.TAB_WIDTH + 1);
        }
      } else if (row.type == Blockly.DUMMY_INPUT) {
        // External naked field.
        var input = row[0];
        var fieldX = cursorX;
        var fieldY = cursorY + Blockly.BlockSvg.FIELD_HEIGHT;
        if (input.align != Blockly.ALIGN_LEFT) {
          var fieldRightX = inputRows.rightEdge - input.fieldWidth -
              2 * Blockly.BlockSvg.SEP_SPACE_X;
          if (inputRows.hasValue) {
            fieldRightX -= Blockly.BlockSvg.TAB_WIDTH;
          } 
          //*** TODO: compute this -- when a dummy input exists. This appears nonessential, however.
          if (inputRows.hasNominativeValue || inputRows.hasGenitiveValue || inputRows.hasDativeValue ||
            inputRows.hasAccusativeValue || inputRows.hasAblativeValue || inputRows.hasVocativeValue) {
            fieldRightX -= Blockly.BlockSvg.TAB_WIDTH;
          }         
          if (input.align == Blockly.ALIGN_RIGHT) {
            fieldX += fieldRightX;
          } else if (input.align == Blockly.ALIGN_CENTRE) {
            fieldX += (fieldRightX + fieldX) / 2;
          }
        }
        this.renderFields_(input.fieldRow, fieldX, fieldY);
        steps.push('v', row.height);
        if (Blockly.RTL) {
          highlightSteps.push('v', row.height - 2);
        }
      } else if (row.type == Blockly.NEXT_STATEMENT) {
        // Nested statement.
        var input = row[0];
        if (y == 0) {
          // If the first input is a statement stack, add a small row on top.
          steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
          if (Blockly.RTL) {
            highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
          }
          cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
        }
        var fieldX = cursorX;
        var fieldY = cursorY + Blockly.BlockSvg.FIELD_HEIGHT;
        if (input.align != Blockly.ALIGN_LEFT) {
          var fieldRightX = inputRows.statementEdge - input.fieldWidth -
              2 * Blockly.BlockSvg.SEP_SPACE_X;
          if (input.align == Blockly.ALIGN_RIGHT) {
            fieldX += fieldRightX;
          } else if (input.align == Blockly.ALIGN_CENTRE) {
            fieldX += (fieldRightX + fieldX) / 2;
          }
        }
        this.renderFields_(input.fieldRow, fieldX, fieldY);
        cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;
        steps.push('H', cursorX);
        // *** It's a noun, use a triangular noun clamp
        if (this.pos == "noun") {
          steps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_OUT);
        } else {
          steps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_OUT);
        }
        steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
        steps.push('v', row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
        steps.push(Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER);
        if (this.pos == "noun") {
          steps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_IN);
        } else {
          steps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_IN);
        }
        steps.push('H', inputRows.rightEdge);
        if (Blockly.RTL) {
          highlightSteps.push('M',
              (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
               Blockly.BlockSvg.DISTANCE_45_OUTSIDE) +
              ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
          highlightSteps.push(
              Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL);
          highlightSteps.push('v',
              row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
          highlightSteps.push(
              Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL);
          highlightSteps.push('H', inputRows.rightEdge - 1);
        } else {
          highlightSteps.push('M',
              (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
               Blockly.BlockSvg.DISTANCE_45_OUTSIDE) + ',' +
              (cursorY + row.height - Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
          highlightSteps.push(
              Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_LTR);
          if (this.pos == "noun") {
            highlightSteps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_IN_HIGHLIGHT);
          } else {
            highlightSteps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_IN_HIGHLIGHT);
          }
          highlightSteps.push('H', inputRows.rightEdge);
        }
        // Create statement connection.
        connectionX = connectionsXY.x + (Blockly.RTL ? -cursorX : cursorX);
        connectionY = connectionsXY.y + cursorY + 1;
        input.connection.moveTo(connectionX, connectionY);
        if (input.connection.targetConnection) {
          input.connection.tighten_();
          this.width = Math.max(this.width, inputRows.statementEdge +
              input.connection.targetBlock().getHeightWidth().width);
        }
        if (y == inputRows.length - 1 ||
            inputRows[y + 1].type == Blockly.NEXT_STATEMENT) {
          // If the final input is a statement stack, add a small row underneath.
          // Consecutive statement stacks are also separated by a small divider.
          steps.push('v', Blockly.BlockSvg.SEP_SPACE_Y);
          if (Blockly.RTL) {
            highlightSteps.push('v', Blockly.BlockSvg.SEP_SPACE_Y - 1);
          }
          cursorY += Blockly.BlockSvg.SEP_SPACE_Y;
        }
      }
      cursorY += row.height;
    }
    if (!inputRows.length) {
      cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
      steps.push('V', cursorY);
      if (Blockly.RTL) {
        highlightSteps.push('V', cursorY - 1);
      }
    }
    return cursorY;
  };
  
  /**
   * Render the bottom edge of the block.
   * @param {!Array.<string>} steps Path of block outline.
   * @param {!Array.<string>} highlightSteps Path of block highlights.
   * @param {!Object} connectionsXY Location of block.
   * @param {number} cursorY Height of block.
   * @private
   */
  Blockly.BlockSvg.prototype.renderDrawBottom_ =
      function(steps, highlightSteps, connectionsXY, cursorY) {
    this.height = cursorY + 1;  // Add one for the shadow.
    if (this.nextConnection) {
      steps.push('H', Blockly.BlockSvg.NOTCH_WIDTH + ' ');
          // Blockly.BlockSvg.NOTCH_PATH_RIGHT);
  
      //*** Change bottom notches of adjectives and prepositions 
      // Default connectionCheck = 0, which is a piece that fits into a noun clamp, eg. an adjective
      var connectionCheck = 0;
      var checkTypes = this.nextConnection.check_;
      for (var i = 0, checkType = checkTypes[i]; i < checkTypes.length; i++) {
        if (checkType == "preposition" || checkType ==  "adverb" || checkType == "ut-clause") {
          // It's a preposition that fits into a verb clamp -- add other types as necessary
          connectionCheck = 1;
        }
      }
      if (connectionCheck) {
        // Fits into verb clamp - rectangular
        steps.push(Blockly.BlockSvg.RECTANGULAR_CLAMP_OUT);
      } else {
        // Fits into a noun clamp - triangular
        steps.push(Blockly.BlockSvg.CIRCULAR_CLAMP_OUT);
      }
  
      // Create next block connection.
      var connectionX;
      if (Blockly.RTL) {
        connectionX = connectionsXY.x - Blockly.BlockSvg.NOTCH_WIDTH;
      } else {
        connectionX = connectionsXY.x + Blockly.BlockSvg.NOTCH_WIDTH;
      }
      var connectionY = connectionsXY.y + cursorY + 1;
      this.nextConnection.moveTo(connectionX, connectionY);
      if (this.nextConnection.targetConnection) {
        this.nextConnection.tighten_();
      }
      this.height += 4;  // Height of tab.
    }
  
    // Should the bottom-left corner be rounded or square?
    if (this.squareBottomLeftCorner_) {
      steps.push('H 0');
      if (!Blockly.RTL) {
        highlightSteps.push('M', '1,' + cursorY);
      }
    } else {
      steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
      steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
                 Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 -' +
                 Blockly.BlockSvg.CORNER_RADIUS + ',-' +
                 Blockly.BlockSvg.CORNER_RADIUS);
      if (!Blockly.RTL) {
        highlightSteps.push('M', Blockly.BlockSvg.DISTANCE_45_INSIDE + ',' +
            (cursorY - Blockly.BlockSvg.DISTANCE_45_INSIDE));
        highlightSteps.push('A', (Blockly.BlockSvg.CORNER_RADIUS - 1) + ',' +
            (Blockly.BlockSvg.CORNER_RADIUS - 1) + ' 0 0,1 ' +
            '1,' + (cursorY - Blockly.BlockSvg.CORNER_RADIUS));
      }
    }
  };
  
  /**
   * Render the left edge of the block.
   * @param {!Array.<string>} steps Path of block outline.
   * @param {!Array.<string>} highlightSteps Path of block highlights.
   * @param {!Object} connectionsXY Location of block.
   * @param {number} cursorY Height of block.
   * @private
   */
  Blockly.BlockSvg.prototype.renderDrawLeft_ =
      function(steps, highlightSteps, connectionsXY, cursorY) {
    if (this.outputConnection) {
      // Create output connection.
      this.outputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // This connection will be tightened when the parent renders.
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      steps.push('c 0,-10 -' + Blockly.BlockSvg.TAB_WIDTH + ',8 -' +
          Blockly.BlockSvg.TAB_WIDTH + ',-7.5 s ' + Blockly.BlockSvg.TAB_WIDTH +
          ',2.5 ' + Blockly.BlockSvg.TAB_WIDTH + ',-7.5');
      if (Blockly.RTL) {
        highlightSteps.push('M', (Blockly.BlockSvg.TAB_WIDTH * -0.3) + ',8.9');
        highlightSteps.push('l', (Blockly.BlockSvg.TAB_WIDTH * -0.45) + ',-2.1');
      } else {
        highlightSteps.push('V', Blockly.BlockSvg.TAB_HEIGHT - 1);
        highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * -0.92) +
                            ',-1 q ' + (Blockly.BlockSvg.TAB_WIDTH * -0.19) +
                            ',-5.5 0,-11');
        highlightSteps.push('m', (Blockly.BlockSvg.TAB_WIDTH * 0.92) +
                            ',1 V 1 H 2');
      }
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    } 
    //*** Add output connections for all cases
    else if (this.nominativeOutputConnection) {
      // Create nominative output connection
      this.nominativeOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.TOOTH_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    }  
    else if (this.genitiveOutputConnection) {
      // Create genitive output connection
      this.genitiveOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.CROSS_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    }  
    else if (this.dativeOutputConnection) {
      // Create dative output connection
      this.dativeOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.RECTANGULAR_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    }  
    else if (this.accusativeOutputConnection) {
      // Create accusative output connection
      this.accusativeOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.CIRCULAR_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    }  
    else if (this.ablativeOutputConnection) {
      // Create ablative output connection
      this.ablativeOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.TRIANGULAR_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    }
    else if (this.vocativeOutputConnection) {
      // Create ablative output connection
      this.vocativeOutputConnection.moveTo(connectionsXY.x, connectionsXY.y);
      // Need this tab height line to make sure the tab doesn't move its y position 
      // when making multiple connections (puer w/ dat + fortis simultaneously)
      steps.push('V', Blockly.BlockSvg.TAB_HEIGHT);
      // Shape of nobby thing on outside of noun piece (external)
      steps.push(Blockly.BlockSvg.FUNNEL_NOBBY);
      this.width += Blockly.BlockSvg.TAB_WIDTH;
    } 
  
    else if (!Blockly.RTL) {
      if (this.squareTopLeftCorner_) {
        highlightSteps.push('V', 1);
      } else {
        highlightSteps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
      }
    }
    steps.push('z');
  };
  