/// <summary> /// Copy a block onto the local clipboard. /// </summary> /// <param name="block">Block to be copied.</param> private static void copy_(Block block) { var xmlBlock = Xml.blockToDom(block); if (Core.dragMode_ != Core.DRAG_FREE) { Xml.deleteNext(xmlBlock); } // Encode start position in XML. var xy = block.getRelativeToSurfaceXY(); xmlBlock.SetAttribute("x", (block.RTL ? -xy.x : xy.x).ToString()); xmlBlock.SetAttribute("y", xy.y.ToString()); Core.clipboardXml_ = xmlBlock; Core.clipboardSource_ = (WorkspaceSvg)block.workspace; }
/// <summary> /// Encode a block subtree as XML with XY coordinates. /// </summary> /// <param name="block">The root block to encode.</param> /// <param name="opt_noId">True if the encoder should skip the block id.</param> /// <returns>Tree of XML elements.</returns> public static Element blockToDomWithXY(Block block, bool opt_noId = false) { var width = 0.0; // Not used in LTR. if (block.workspace.RTL) { width = block.workspace.getWidth(); } var element = Xml.blockToDom(block, opt_noId); var xy = block.getRelativeToSurfaceXY(); element.SetAttribute("x", System.Math.Round(block.workspace.RTL ? width - xy.x : xy.x).ToString()); element.SetAttribute("y", System.Math.Round(xy.y).ToString()); return(element); }
/// <summary> /// Encode a block subtree as XML. /// </summary> /// <param name="block">The root block to encode.</param> /// <returns>Tree of XML elements.</returns> public static Element blockToDom(Block block, bool opt_noId = false) { var element = goog.dom.createDom(block.isShadow() ? "shadow" : "block"); element.SetAttribute("type", block.type); if (!opt_noId) { element.SetAttribute("id", block.id); } if (true /*block.mutationToDom*/) { // Custom data for an advanced block. var mutation = block.mutationToDom(); if (mutation != null && (mutation.HasChildNodes() || mutation.Attributes.Length > 0)) { element.AppendChild(mutation); } } Element container = null; var fieldToDom = new Action <Field>((field) => { if (field.name != null && field.EDITABLE) { container = goog.dom.createDom("field", null, field.getValue()); container.SetAttribute("name", field.name); element.AppendChild(container); } }); foreach (var input in block.inputList) { foreach (var field in input.fieldRow) { fieldToDom(field); } } var commentText = block.getCommentText(); if (!String.IsNullOrEmpty(commentText)) { var commentElement = goog.dom.createDom("comment", null, commentText); if (((BlockSvg)block).comment != null) { commentElement.SetAttribute("pinned", ((BlockSvg)block).comment.isVisible().ToString()); var hw = ((BlockSvg)block).comment.getBubbleSize(); commentElement.SetAttribute("h", hw.height.ToString()); commentElement.SetAttribute("w", hw.width.ToString()); } element.AppendChild(commentElement); } if (block.data != null) { var dataElement = goog.dom.createDom("data", null, block.data); element.AppendChild(dataElement); } Element shadow; foreach (var input in block.inputList) { var empty = true; if (input.type == Core.DUMMY_INPUT) { continue; } else { var childBlock = input.connection.targetBlock(); if (input.type == Core.INPUT_VALUE) { container = goog.dom.createDom("value"); } else if (input.type == Core.NEXT_STATEMENT) { container = goog.dom.createDom("statement"); } shadow = input.connection.getShadowDom(); if (shadow != null && (childBlock == null || !childBlock.isShadow())) { container.AppendChild(Xml.cloneShadow_(shadow)); } if (childBlock != null) { container.AppendChild(Xml.blockToDom(childBlock, opt_noId)); empty = false; } } container.SetAttribute("name", input.name); if (!empty) { element.AppendChild(container); } } if (block.inputsInlineDefault != block.inputsInline) { element.SetAttribute("inline", block.inputsInline.ToString()); } if (block.isCollapsed()) { element.SetAttribute("collapsed", true.ToString()); } if (block.disabled) { element.SetAttribute("disabled", true.ToString()); } if (!block.isDeletable() && !block.isShadow()) { element.SetAttribute("deletable", false.ToString()); } if (!block.isMovable() && !block.isShadow()) { element.SetAttribute("movable", false.ToString()); } if (!block.isEditable()) { element.SetAttribute("editable", false.ToString()); } var nextBlock = block.getNextBlock(); if (nextBlock != null) { container = goog.dom.createDom("next", null, Xml.blockToDom(nextBlock, opt_noId)); element.AppendChild(container); } shadow = block.nextConnection?.getShadowDom(); if (shadow != null && (nextBlock == null || !nextBlock.isShadow())) { container.AppendChild(Xml.cloneShadow_(shadow)); } return(element); }
/// <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); } }