/** * Procedure calls cannot exist without the corresponding procedure * definition. Enforce this link whenever an event is fired. * @this Blockly.Block */ protected override void onchange(Events.Abstract e) { if (this.workspace == null || this.workspace.isFlyout) { // Block is deleted or is in a flyout. return; } if (e.type == Events.CREATE && Array.IndexOf(((Events.Create)e).ids, this.id) != -1) { // Look for the case where a procedure call was created (usually through // paste) and there is no matching definition. In this case, create // an empty definition block with the correct signature. var name = this.getProcedureCall(); var def = Core.Procedures.getDefinition(name, this.workspace); if (def != null && (def.type != this.defType_ || JSON.Stringify(def.arguments_.ToArray()) != JSON.Stringify(this.arguments_))) { // The signatures don't match. def = null; } if (def == null) { Events.setGroup(e.group); /** * Create matching definition block. * <xml> * <block type="procedures_defreturn" x="10" y="20"> * <mutation name="test"> * <arg name="x"></arg> * </mutation> * <field name="NAME">test</field> * </block> * </xml> */ var xml = goog.dom.createDom("xml"); var block = goog.dom.createDom("block"); block.SetAttribute("type", this.defType_); var xy = this.getRelativeToSurfaceXY(); var x = xy.x + Core.SNAP_RADIUS * (this.RTL ? -1 : 1); var y = xy.y + Core.SNAP_RADIUS * 2; block.SetAttribute("x", x.ToString()); block.SetAttribute("y", y.ToString()); var mutation = this.mutationToDom(); block.AppendChild(mutation); var field = goog.dom.createDom("field"); field.SetAttribute("name", "NAME"); field.AppendChild(Document.CreateTextNode(this.getProcedureCall())); block.AppendChild(field); xml.AppendChild(block); Xml.domToWorkspace(xml, this.workspace); Events.setGroup(false); } } else if (e.type == Events.DELETE) { // Look for the case where a procedure definition has been deleted, // leaving this block (a procedure call) orphaned. In this case, delete // the orphan. var name = this.getProcedureCall(); var def = Core.Procedures.getDefinition(name, this.workspace); if (def == null) { Events.setGroup(e.group); this.dispose(true, false); Events.setGroup(false); } } }
/// <summary> /// Update the source block when the mutator's blocks are changed. /// Bump down any block that's too high. /// Fired whenever a change is made to the mutator's workspace. /// </summary> private void workspaceChanged_() { if (this.workspace_ == null) { return; } if (Core.dragMode_ == Core.DRAG_NONE) { var blocks = this.workspace_.getTopBlocks(false); var MARGIN = 20; foreach (BlockSvg block in blocks) { var blockXY = block.getRelativeToSurfaceXY(); var blockHW = block.getHeightWidth(); if (blockXY.y + blockHW.height < MARGIN) { // Bump any block that's above the top back inside. block.moveBy(0, MARGIN - blockHW.height - blockXY.y); } } } // When the mutator's workspace changes, update the source block. if (this.rootBlock_.workspace == this.workspace_) { Events.setGroup(true); var block = this.block_; var oldMutationDom = block.mutationToDom(); var oldMutation = oldMutationDom != null && Xml.domToText(oldMutationDom) != null; // Switch off rendering while the source block is rebuilt. var savedRendered = block.rendered; block.rendered = false; // Allow the source block to rebuild itself. block.compose(this.rootBlock_); // Restore rendering and show the changes. block.rendered = savedRendered; // Mutation may have added some elements that need initalizing. block.initSvg(); var newMutationDom = block.mutationToDom(); var newMutation = newMutationDom != null && Xml.domToText(newMutationDom) != null; if (oldMutation != newMutation) { Events.fire(new Events.Change( block, "mutation", null, oldMutation.ToString(), newMutation.ToString())); // Ensure that any bump is part of this mutation's event group. var group = Events.getGroup(); Window.SetTimeout(() => { Events.setGroup(group); block.bumpNeighbours_(); Events.setGroup(false); }, Core.BUMP_DELAY); } if (block.rendered) { block.render(); } this.resizeBubble_(); Events.setGroup(false); } }
/// <summary> /// Connect two connections together. This is the connection on the superior /// block. /// </summary> /// <param name="childConnection"></param> protected virtual void connect_(Connection childConnection) { var parentConnection = this; var parentBlock = parentConnection.getSourceBlock(); var childBlock = childConnection.getSourceBlock(); // Disconnect any existing parent on the child connection. if (childConnection.isConnected()) { childConnection.disconnect(); } if (parentConnection.isConnected()) { // Other connection is already connected to something. // Disconnect it and reattach it or bump it as needed. var orphanBlock = parentConnection.targetBlock(); var shadowDom = parentConnection.getShadowDom(); // Temporarily set the shadow DOM to null so it does not respawn. parentConnection.setShadowDom(null); // Displaced shadow blocks dissolve rather than reattaching or bumping. if (orphanBlock.isShadow()) { // Save the shadow block so that field values are preserved. shadowDom = Xml.blockToDom(orphanBlock); orphanBlock.dispose(); orphanBlock = null; } else if (parentConnection.type == Core.INPUT_VALUE) { // Value connections. // If female block is already connected, disconnect and bump the male. if (orphanBlock.outputConnection == null) { throw new Exception("Orphan block does not have an output connection."); } // Attempt to reattach the orphan at the end of the newly inserted // block. Since this block may be a row, walk down to the end // or to the first (and only) shadow block. var connection = Connection.lastConnectionInRow_( childBlock, orphanBlock); if (connection != null) { orphanBlock.outputConnection.connect(connection); orphanBlock = null; } } else if (parentConnection.type == Core.NEXT_STATEMENT) { // Statement connections. // Statement blocks may be inserted into the middle of a stack. // Split the stack. if (orphanBlock.previousConnection == null) { throw new Exception("Orphan block does not have a previous connection."); } // Attempt to reattach the orphan at the bottom of the newly inserted // block. Since this block may be a stack, walk down to the end. var newBlock = childBlock; while (newBlock.nextConnection != null) { var nextBlock = newBlock.getNextBlock(); if (nextBlock != null && !nextBlock.isShadow()) { newBlock = nextBlock; } else { if (orphanBlock.previousConnection.checkType_( newBlock.nextConnection)) { newBlock.nextConnection.connect(orphanBlock.previousConnection); orphanBlock = null; } break; } } } if (orphanBlock != null) { // Unable to reattach orphan. parentConnection.disconnect(); if (Events.recordUndo) { // Bump it off to the side after a moment. var group = Events.getGroup(); Window.SetTimeout(() => { // Verify orphan hasn't been deleted or reconnected (user on meth). if (orphanBlock.workspace != null && orphanBlock.getParent() == null) { Events.setGroup(group); if (orphanBlock.outputConnection != null) { ((RenderedConnection)orphanBlock.outputConnection).bumpAwayFrom_(parentConnection); } else if (orphanBlock.previousConnection != null) { ((RenderedConnection)orphanBlock.previousConnection).bumpAwayFrom_(parentConnection); } Events.setGroup(false); } }, Core.BUMP_DELAY); } } // Restore the shadow DOM. parentConnection.setShadowDom(shadowDom); } Events.Move e = null; if (Events.isEnabled()) { e = new Events.Move(childBlock); } // Establish the connections. Connection.connectReciprocally_(parentConnection, childConnection); // Demote the inferior block so that one is a child of the superior one. childBlock.setParent(parentBlock); if (e != null) { e.recordNew(); Events.fire(e); } }