Example #1
0
        /**
         * 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);
                }
            }
        }
Example #2
0
        /// <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);
            }
        }
Example #3
0
        /// <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);
            }
        }