public ModelModificationUndoHelper(UndoRedoRecorder recorder, IEnumerable <ModelBase> models) { this.recorder = recorder; this.models = new List <ModelBase>(models); existingConnectors = new Dictionary <Guid, XmlElement>(); existingPins = new Dictionary <Guid, XmlElement>(); remainingConnectors = new Dictionary <Guid, ConnectorModel>(); var allConnectors = new List <ConnectorModel>(); using (this.recorder.BeginActionGroup()) { // Assuming no connectors will be modified as part of this, we // record the node prior to it being modified. If for some // reason connectors are dropped/created along the way, then // this particular action group will be pop off the undo stack. // foreach (var model in this.models) { var nodeModel = model as NodeModel; if (nodeModel != null) { allConnectors.AddRange(nodeModel.AllConnectors); } this.recorder.RecordModificationForUndo(model); } } // Record the existing connectors... foreach (var connectorModel in allConnectors) { var element = connectorModel.Serialize( recorder.document, SaveContext.Undo); existingConnectors[connectorModel.GUID] = element; foreach (var connectorPin in connectorModel.ConnectorPinModels) { var pinElement = connectorPin.Serialize( recorder.document, SaveContext.Undo); existingPins[connectorPin.GUID] = pinElement; } } }
protected WorkspaceModel( String name, IEnumerable<NodeModel> e, IEnumerable<ConnectorModel> c, double x, double y) { Name = name; Nodes = new TrulyObservableCollection<NodeModel>(e); Connectors = new TrulyObservableCollection<ConnectorModel>(c); Notes = new ObservableCollection<NoteModel>(); X = x; Y = y; HasUnsavedChanges = false; LastSaved = DateTime.Now; WorkspaceSaved += OnWorkspaceSaved; WorkspaceVersion = AssemblyHelper.GetDynamoVersion(); undoRecorder = new UndoRedoRecorder(this); }
private void CommitChanges(UndoRedoRecorder recorder) { // Code block editor can lose focus in many scenarios (e.g. switching // of tabs or application), if there has not been any changes, do not // commit the change. // if (!codeBlockNode.Code.Equals(InnerTextEditor.Text)) { UpdateNodeValue("Code"); } if (createdForNewCodeBlock) { // If this editing was started due to a new code block node, // then by this point there would have been two action groups // recorded on the undo-stack: one for node creation, and // another for node editing (as part of ExecuteCommand above). // Pop off the two action groups... // recorder.PopFromUndoGroup(); // Pop off modification action. // Note that due to various external factors a code block node // loaded from file may be created empty. In such cases, the // creation step would not have been recorded (there was no // explicit creation of the node, it was created from loading // of a file), and nothing should be popped off of the undo stack. if (recorder.CanUndo) recorder.PopFromUndoGroup(); // Pop off creation action. // ... and record this new node as new creation. using (recorder.BeginActionGroup()) { recorder.RecordCreationForUndo(codeBlockNode); } } }
protected WorkspaceModel( IEnumerable<NodeModel> e, IEnumerable<NoteModel> n, IEnumerable<AnnotationModel> a, WorkspaceInfo info, NodeFactory factory, IEnumerable<PresetModel> presets) { guid = Guid.NewGuid(); nodes = new ObservableCollection<NodeModel>(e); notes = new ObservableCollection<NoteModel>(n); annotations = new ObservableCollection<AnnotationModel>(a); // Set workspace info from WorkspaceInfo object Name = info.Name; X = info.X; Y = info.Y; FileName = info.FileName; Zoom = info.Zoom; HasUnsavedChanges = false; LastSaved = DateTime.Now; WorkspaceVersion = AssemblyHelper.GetDynamoVersion(); undoRecorder = new UndoRedoRecorder(this); NodeFactory = factory; this.presets = new List<PresetModel>(presets); // Update ElementResolver from nodeGraph.Nodes (where node is CBN) ElementResolver = new ElementResolver(); foreach (var node in nodes) { RegisterNode(node); var cbn = node as CodeBlockNodeModel; if (cbn != null && cbn.ElementResolver != null) { ElementResolver.CopyResolutionMap(cbn.ElementResolver); } } foreach (var connector in Connectors) RegisterConnector(connector); SetModelEventOnAnnotation(); }
protected virtual bool HandleModelEventCore(string eventName, UndoRedoRecorder recorder) { return false; // Base class does not handle this. }
internal static void RecordModelsForUndo(Dictionary<ModelBase, UndoRedoRecorder.UserAction> models, UndoRedoRecorder recorder) { if (null == recorder) return; if (!ShouldProceedWithRecording(models)) return; using (recorder.BeginActionGroup()) { foreach (var modelPair in models) { switch (modelPair.Value) { case UndoRedoRecorder.UserAction.Creation: recorder.RecordCreationForUndo(modelPair.Key); break; case UndoRedoRecorder.UserAction.Deletion: recorder.RecordDeletionForUndo(modelPair.Key); break; case UndoRedoRecorder.UserAction.Modification: recorder.RecordModificationForUndo(modelPair.Key); break; } } } }
// See RecordModelsForModification below for more details. internal static void RecordModelForModification(ModelBase model, UndoRedoRecorder recorder) { if (null != model) { var models = new List<ModelBase> { model }; RecordModelsForModification(models, recorder); } }
private void RecordModels(UndoRedoRecorder recorder) { if (model.InPorts.Count == 0) return; var connectors = model.InPorts.Last().Connectors; if (connectors.Count != 0) { if (connectors.Count != 1) { throw new InvalidOperationException( "There should be only one connection to an input port"); } var models = new Dictionary<ModelBase, UndoRedoRecorder.UserAction> { { connectors[0], UndoRedoRecorder.UserAction.Deletion }, { model, UndoRedoRecorder.UserAction.Modification } }; WorkspaceModel.RecordModelsForUndo(models, recorder); } else WorkspaceModel.RecordModelForModification(model, recorder); }
internal DummyWorkspace() { undoRecorder = new UndoRedoRecorder(this); }
public ModelModificationUndoHelper(UndoRedoRecorder recorder, IEnumerable<ModelBase> models) { this.recorder = recorder; this.models = new List<ModelBase>(models); existingConnectors = new Dictionary<Guid, XmlElement>(); remainingConnectors = new Dictionary<Guid, ConnectorModel>(); var allConnectors = new List<ConnectorModel>(); using (this.recorder.BeginActionGroup()) { // Assuming no connectors will be modified as part of this, we // record the node prior to it being modified. If for some // reason connectors are dropped/created along the way, then // this particular action group will be pop off the undo stack. // foreach (var model in this.models) { var nodeModel = model as NodeModel; if (nodeModel != null) allConnectors.AddRange(nodeModel.AllConnectors); this.recorder.RecordModificationForUndo(model); } } // Record the existing connectors... foreach (var connectorModel in allConnectors) { var element = connectorModel.Serialize( recorder.document, SaveContext.Undo); existingConnectors[connectorModel.GUID] = element; } }
public ModelModificationUndoHelper(UndoRedoRecorder recorder, ModelBase model) : this(recorder, new [] { model }) { }
public ActionGroupDisposable(UndoRedoRecorder recorder) { this.recorder = recorder; }
/// <summary> /// This method is currently used as a way to send an event to ModelBase /// derived objects. Its primary use is in DynamoNodeButton class, which /// sends this event when clicked, to change the number of ports in a /// VariableInputNode. /// </summary> /// <param name="eventName">The name of the event.</param> /// <param name="value">For the SetInPortCount event, the number of /// ports desired. Ignored for other events.</param> /// <param name="recorder"></param> /// <returns>Returns true if the call has been handled, or false otherwise. /// </returns> internal bool HandleModelEvent(string eventName, int value, UndoRedoRecorder recorder) { return HandleModelEventCore(eventName, value, recorder); }
/// <summary> /// Collapse a set of nodes in a given workspace. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="isTestMode"></param> /// <param name="args"></param> public CustomNodeWorkspaceModel Collapse( IEnumerable <NodeModel> selectedNodes, WorkspaceModel currentWorkspace, bool isTestMode, FunctionNamePromptEventArgs args) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; CustomNodeWorkspaceModel newWorkspace; using (undoRecorder.BeginActionGroup()) { #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPortData.Count) .Where(node.HasConnectedInput) .Select(data => Tuple.Create(node, data, node.Inputs[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(node.HasOutput) .SelectMany( data => node.Outputs[data].Where( output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region Detect 1-node holes (higher-order function extraction) Log(Properties.Resources.CouldNotRepairOneNodeHoles, WarningLevel.Mild); // http://adsk-oss.myjetbrains.com/youtrack/issue/MAGN-5603 //var curriedNodeArgs = // new HashSet<NodeModel>( // inputs.Select(x => x.Item3.Item2) // .Intersect(outputs.Select(x => x.Item3.Item2))).Select( // outerNode => // { // //var node = new Apply1(); // var node = newNodeWorkspace.AddNode<Apply1>(); // node.SetNickNameFromAttribute(); // node.DisableReporting(); // node.X = outerNode.X; // node.Y = outerNode.Y; // //Fetch all input ports // // in order // // that have inputs // // and whose input comes from an inner node // List<int> inPortsConnected = // Enumerable.Range(0, outerNode.InPortData.Count) // .Where( // x => // outerNode.HasInput(x) // && selectedNodeSet.Contains( // outerNode.Inputs[x].Item2)) // .ToList(); // var nodeInputs = // outputs.Where(output => output.Item3.Item2 == outerNode) // .Select( // output => // new // { // InnerNodeInputSender = output.Item1, // OuterNodeInPortData = output.Item3.Item1 // }) // .ToList(); // nodeInputs.ForEach(_ => node.AddInput()); // node.RegisterAllPorts(); // return // new // { // OuterNode = outerNode, // InnerNode = node, // Outputs = // inputs.Where( // input => input.Item3.Item2 == outerNode) // .Select(input => input.Item3.Item1), // Inputs = nodeInputs, // OuterNodePortDataList = inPortsConnected // }; // }).ToList(); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); double leftShift = leftMost - 250; #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where( conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var connector in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (var connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Transfer nodes and connectors to new workspace var newNodes = new List <NodeModel>(); // Step 4: move all nodes to new workspace remove from old // PB: This could be more efficiently handled by a copy paste, but we // are preservering the node foreach (var node in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(node); currentWorkspace.RemoveNode(node); // Assign a new guid to this node, otherwise when node is // compiled to AST, literally it is still in global scope // instead of in function scope. node.GUID = Guid.NewGuid(); node.RenderPackages.Clear(); // shit nodes node.X = node.X - leftShift; node.Y = node.Y - topMost; newNodes.Add(node); } foreach (var conn in fullySelectedConns) { ConnectorModel.Make(conn.Start.Owner, conn.End.Owner, conn.Start.Index, conn.End.Index); } #endregion #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { inConnectors.Add(Tuple.Create(inputNode, inputData)); node = new Symbol { InputSymbol = inputReceiverNode.InPortData[inputReceiverData].NickName, X = 0 }; // Try to figure out the type of input of custom node // from the type of input of selected node. There are // two kinds of nodes whose input type are available: // function node and custom node. List <Library.TypedParameter> parameters = null; if (inputReceiverNode is Function) { var func = inputReceiverNode as Function; parameters = func.Controller.Definition.Parameters.ToList(); } else if (inputReceiverNode is DSFunctionBase) { var dsFunc = inputReceiverNode as DSFunctionBase; parameters = dsFunc.Controller.Definition.Parameters.ToList(); } // so the input of custom node has format // input_var_name : type if (parameters != null && parameters.Count() > inputReceiverData) { var typeName = parameters[inputReceiverData].DisplayTypeName; if (!string.IsNullOrEmpty(typeName)) { node.InputSymbol += " : " + typeName; } } node.SetNickNameFromAttribute(); node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; newNodes.Add(node); } //var curriedNode = curriedNodeArgs.FirstOrDefault(x => x.OuterNode == inputNode); //if (curriedNode == null) //{ ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData); //} //else //{ // //Connect it to the applier // newNodeWorkspace.AddConnection(node, curriedNode.InnerNode, 0, 0); // //Connect applier to the inner input receive // newNodeWorkspace.AddConnection( // curriedNode.InnerNode, // inputReceiverNode, // 0, // inputReceiverData); //} } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; //NodeModel outputReceiverNode = output.Item3.Item2; //if (curriedNodeArgs.Any(x => x.OuterNode == outputReceiverNode)) // continue; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPortData[outputSenderData].NickName, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNickNameFromAttribute(); newNodes.Add(node); ConnectorModel.Make(outputSenderNode, node, outputSenderData, 0); i++; } } //Connect outputs to new node outConnectors.AddRange( from output in outputs let outputSenderNode = output.Item1 let outputSenderData = output.Item2 let outputReceiverData = output.Item3.Item1 let outputReceiverNode = output.Item3.Item2 select Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPortData.Count) .Where(port => !node.HasOutput(port)) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = new Output { Symbol = hanging.node.OutPortData[hanging.port].NickName, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNickNameFromAttribute(); newNodes.Add(node); ConnectorModel.Make(hanging.node, node, hanging.port, 0); i++; } } #endregion var newId = Guid.NewGuid(); newWorkspace = new CustomNodeWorkspaceModel( args.Name, args.Category, args.Description, nodeFactory, newNodes, Enumerable.Empty <NoteModel>(), 0, 0, newId, currentWorkspace.ElementResolver, string.Empty); newWorkspace.HasUnsavedChanges = true; RegisterCustomNodeWorkspace(newWorkspace); var collapsedNode = CreateCustomNodeInstance(newId, isTestMode: isTestMode); collapsedNode.X = avgX; collapsedNode.Y = avgY; currentWorkspace.AddNode(collapsedNode, centered: false); undoRecorder.RecordCreationForUndo(collapsedNode); foreach (var connector in inConnectors.Select((x, idx) => new { node = x.Item1, from = x.Item2, to = idx }) .Select( nodeTuple => ConnectorModel.Make( nodeTuple.node, collapsedNode, nodeTuple.@from, nodeTuple.to)) .Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } foreach (var connector in outConnectors.Select( nodeTuple => ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3)).Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } } return(newWorkspace); }
private void DiscardChangesAndOptionallyRemoveNode(UndoRedoRecorder recorder) { if (!string.IsNullOrEmpty(InnerTextEditor.Text)) { throw new InvalidOperationException( "This method is meant only for empty text box"); } if (createdForNewCodeBlock) { // If this editing was started due to a new code block node, // then by this point the creation of the node would have been // recorded, we need to pop that off the undo stack. Note that // due to various external factors a code block node loaded // from file may be created empty. In such cases, the creation // step would not have been recorded (there was no explicit // creation of the node, it was created from loading of a file), // and nothing should be popped off of the undo stack. // if (recorder.CanUndo) recorder.PopFromUndoGroup(); // Pop off creation action. } else { // If the editing was started for an existing code block node, // and user deletes the text contents, it should be restored to // the original codes. InnerTextEditor.Text = nodeModel.Code; } }
public void SetupTests() { workspace = new DummyWorkspace(); recorder = workspace.Recorder; }
public void TestConstructor() { Assert.Throws<ArgumentNullException>(() => { UndoRedoRecorder temp = new UndoRedoRecorder(null); }); }
internal override bool HandleModelEventCore(string eventName, int value, UndoRedoRecorder recorder) { return VariableInputController.HandleModelEventCore(eventName, value, recorder) || base.HandleModelEventCore(eventName, value, recorder); }
internal bool HandleModelEventCore(string eventName, int value, UndoRedoRecorder recorder) { switch (eventName) { case "AddInPort": AddInputToModel(); return true; // Handled here. case "RemoveInPort": RemoveInputFromModel(); return true; // Handled here. case "SetInPortCount": SetNumInputs(value); return true; // Handled here. } return false; // base.HandleModelEventCore(eventName); }
internal bool HandleModelEventCore(string eventName, UndoRedoRecorder recorder) { if (eventName == "AddInPort") { AddInputToModel(); model.RegisterAllPorts(); return true; // Handled here. } if (eventName == "RemoveInPort") { RemoveInputFromModel(); model.RegisterAllPorts(); return true; // Handled here. } return false; // base.HandleModelEventCore(eventName); }
/// <summary> /// TODO(Ben): This method is exposed this way for external codes (e.g. /// the DragCanvas) to record models before they are modified. This is /// by no means ideal. The ideal case of course is for ALL codes that /// end up modifying models to be folded back into WorkspaceViewModel in /// the form of commands. These commands then internally record those /// affected models before updating them. We need this method to be gone /// sooner than later. /// </summary> /// <param name="models">The models to be recorded for undo.</param> /// <param name="recorder"></param> internal static void RecordModelsForModification(List<ModelBase> models, UndoRedoRecorder recorder) { if (null == recorder) return; if (!ShouldProceedWithRecording(models)) return; using (recorder.BeginActionGroup()) { foreach (var model in models) recorder.RecordModificationForUndo(model); } }
/// <summary> /// Collapse a set of nodes in a given workspace. /// </summary> /// <param name="selectedNodes"> The function definition for the user-defined node </param> /// <param name="selectedNotes"> The note models in current selection </param> /// <param name="currentWorkspace"> The workspace where</param> /// <param name="args"></param> internal CustomNodeWorkspaceModel Collapse( IEnumerable <NodeModel> selectedNodes, IEnumerable <NoteModel> selectedNotes, WorkspaceModel currentWorkspace, FunctionNamePromptEventArgs args) { var selectedNodeSet = new HashSet <NodeModel>(selectedNodes); // Note that undoable actions are only recorded for the "currentWorkspace", // the nodes which get moved into "newNodeWorkspace" are not recorded for undo, // even in the new workspace. Their creations will simply be treated as part of // the opening of that new workspace (i.e. when a user opens a file, she will // not expect the nodes that show up to be undoable). // // After local nodes are moved into "newNodeWorkspace" as the result of // conversion, if user performs an undo, new set of nodes will be created in // "currentWorkspace" (not moving those nodes in the "newNodeWorkspace" back // into "currentWorkspace"). In another word, undo recording is on a per- // workspace basis, it does not work across different workspaces. // UndoRedoRecorder undoRecorder = currentWorkspace.UndoRecorder; CustomNodeWorkspaceModel newWorkspace; Debug.WriteLine("Current workspace has {0} nodes and {1} connectors", currentWorkspace.Nodes.Count(), currentWorkspace.Connectors.Count()); using (undoRecorder.BeginActionGroup()) { #region Determine Inputs and Outputs //Step 1: determine which nodes will be inputs to the new node var inputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.InPorts.Count) .Where(index => node.InPorts[index].Connectors.Any()) .Select(data => Tuple.Create(node, data, node.InputNodes[data])) .Where(input => !selectedNodeSet.Contains(input.Item3.Item2)))); var outputs = new HashSet <Tuple <NodeModel, int, Tuple <int, NodeModel> > >( selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPorts.Count) .Where(index => node.OutPorts[index].Connectors.Any()) .SelectMany( data => node.OutputNodes[data].Where( output => !selectedNodeSet.Contains(output.Item2)) .Select(output => Tuple.Create(node, data, output))))); #endregion #region UI Positioning Calculations double avgX = selectedNodeSet.Average(node => node.X); double avgY = selectedNodeSet.Average(node => node.Y); double leftMost = selectedNodeSet.Min(node => node.X); double topMost = selectedNodeSet.Min(node => node.Y); double rightMost = selectedNodeSet.Max(node => node.X + node.Width); double leftShift = leftMost - 250; #endregion #region Handle full selected connectors // Step 2: Determine all the connectors whose start/end owners are // both in the selection set, and then move them from the current // workspace into the new workspace. var fullySelectedConns = new HashSet <ConnectorModel>( currentWorkspace.Connectors.Where( conn => { bool startSelected = selectedNodeSet.Contains(conn.Start.Owner); bool endSelected = selectedNodeSet.Contains(conn.End.Owner); return(startSelected && endSelected); })); foreach (var connector in fullySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Handle partially selected connectors // Step 3: Partially selected connectors (either one of its start // and end owners is in the selection) are to be destroyed. var partiallySelectedConns = currentWorkspace.Connectors.Where( conn => selectedNodeSet.Contains(conn.Start.Owner) || selectedNodeSet.Contains(conn.End.Owner)).ToList(); foreach (var connector in partiallySelectedConns) { undoRecorder.RecordDeletionForUndo(connector); connector.Delete(); } #endregion #region Transfer nodes and connectors to new workspace var newNodes = new List <NodeModel>(); var newNotes = new List <NoteModel>(); var newAnnotations = new List <AnnotationModel>(); // Step 4: move all nodes and notes to new workspace remove from old // PB: This could be more efficiently handled by a copy paste, but we // are preservering the node foreach (var node in selectedNodeSet) { undoRecorder.RecordDeletionForUndo(node); currentWorkspace.RemoveAndDisposeNode(node); // Assign a new guid to this node, otherwise when node is // compiled to AST, literally it is still in global scope // instead of in function scope. node.GUID = Guid.NewGuid(); // shift nodes node.X = node.X - leftShift; node.Y = node.Y - topMost; newNodes.Add(node); } foreach (var note in selectedNotes) { undoRecorder.RecordDeletionForUndo(note); currentWorkspace.RemoveNote(note); note.GUID = Guid.NewGuid(); note.X = note.X - leftShift; note.Y = note.Y - topMost; newNotes.Add(note); } //Copy the group from newNodes foreach (var group in DynamoSelection.Instance.Selection.OfType <AnnotationModel>()) { undoRecorder.RecordDeletionForUndo(group); currentWorkspace.RemoveGroup(group); group.GUID = Guid.NewGuid(); group.Nodes = group.DeletedModelBases; newAnnotations.Add(group); } // Now all selected nodes already moved to custom workspace, // clear the selection. DynamoSelection.Instance.ClearSelection(); foreach (var conn in fullySelectedConns) { ConnectorModel.Make(conn.Start.Owner, conn.End.Owner, conn.Start.Index, conn.End.Index); } #endregion #region Process inputs var inConnectors = new List <Tuple <NodeModel, int> >(); var uniqueInputSenders = new Dictionary <Tuple <NodeModel, int>, Symbol>(); //Step 3: insert variables (reference step 1) foreach (var input in Enumerable.Range(0, inputs.Count).Zip(inputs, Tuple.Create)) { int inputIndex = input.Item1; NodeModel inputReceiverNode = input.Item2.Item1; int inputReceiverData = input.Item2.Item2; NodeModel inputNode = input.Item2.Item3.Item2; int inputData = input.Item2.Item3.Item1; Symbol node; var key = Tuple.Create(inputNode, inputData); if (uniqueInputSenders.ContainsKey(key)) { node = uniqueInputSenders[key]; } else { inConnectors.Add(Tuple.Create(inputNode, inputData)); node = new Symbol { InputSymbol = inputReceiverNode.InPorts[inputReceiverData].Name, X = 0 }; // Try to figure out the type of input of custom node // from the type of input of selected node. There are // two kinds of nodes whose input type are available: // function node and custom node. List <Library.TypedParameter> parameters = null; if (inputReceiverNode is EFunction) { var func = inputReceiverNode as EFunction; parameters = func.Controller.Definition.Parameters.ToList(); } else if (inputReceiverNode is DSFunctionBase) { var dsFunc = inputReceiverNode as DSFunctionBase; var funcDesc = dsFunc.Controller.Definition; parameters = funcDesc.Parameters.ToList(); if (funcDesc.Type == Engine.FunctionType.InstanceMethod || funcDesc.Type == Engine.FunctionType.InstanceProperty) { var dummyType = new ProtoCore.Type() { Name = funcDesc.ClassName }; var instanceParam = new TypedParameter(funcDesc.ClassName, dummyType); parameters.Insert(0, instanceParam); } } // so the input of custom node has format // input_var_name : type if (parameters != null && parameters.Count() > inputReceiverData) { var typeName = parameters[inputReceiverData].DisplayTypeName; if (!string.IsNullOrEmpty(typeName)) { node.InputSymbol += " : " + typeName; } } node.SetNameFromNodeNameAttribute(); node.Y = inputIndex * (50 + node.Height); uniqueInputSenders[key] = node; newNodes.Add(node); } ConnectorModel.Make(node, inputReceiverNode, 0, inputReceiverData); } #endregion #region Process outputs //List of all inner nodes to connect an output. Unique. var outportList = new List <Tuple <NodeModel, int> >(); var outConnectors = new List <Tuple <NodeModel, int, int> >(); int i = 0; if (outputs.Any()) { foreach (var output in outputs) { if (outportList.All(x => !(x.Item1 == output.Item1 && x.Item2 == output.Item2))) { NodeModel outputSenderNode = output.Item1; int outputSenderData = output.Item2; outportList.Add(Tuple.Create(outputSenderNode, outputSenderData)); //Create Symbol Node var node = new Output { Symbol = outputSenderNode.OutPorts[outputSenderData].Name, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNameFromNodeNameAttribute(); newNodes.Add(node); ConnectorModel.Make(outputSenderNode, node, outputSenderData, 0); i++; } } //Connect outputs to new node outConnectors.AddRange( from output in outputs let outputSenderNode = output.Item1 let outputSenderData = output.Item2 let outputReceiverData = output.Item3.Item1 let outputReceiverNode = output.Item3.Item2 select Tuple.Create( outputReceiverNode, outportList.FindIndex( x => x.Item1 == outputSenderNode && x.Item2 == outputSenderData), outputReceiverData)); } else { foreach (var hanging in selectedNodeSet.SelectMany( node => Enumerable.Range(0, node.OutPorts.Count) .Where(index => !node.OutPorts[index].IsConnected) .Select(port => new { node, port })).Distinct()) { //Create Symbol Node var node = new Output { Symbol = hanging.node.OutPorts[hanging.port].Name, X = rightMost + 75 - leftShift }; node.Y = i * (50 + node.Height); node.SetNameFromNodeNameAttribute(); newNodes.Add(node); ConnectorModel.Make(hanging.node, node, hanging.port, 0); i++; } } #endregion var newId = Guid.NewGuid(); newWorkspace = new CustomNodeWorkspaceModel( nodeFactory, newNodes, newNotes, newAnnotations, Enumerable.Empty <PresetModel>(), currentWorkspace.ElementResolver, new WorkspaceInfo() { X = 0, Y = 0, Name = args.Name, Category = args.Category, Description = args.Description, ID = newId.ToString(), FileName = string.Empty, IsVisibleInDynamoLibrary = true }); newWorkspace.HasUnsavedChanges = true; RegisterCustomNodeWorkspace(newWorkspace); Debug.WriteLine("Collapsed workspace has {0} nodes and {1} connectors", newWorkspace.Nodes.Count(), newWorkspace.Connectors.Count()); var collapsedNode = CreateCustomNodeInstance(newId); collapsedNode.X = avgX; collapsedNode.Y = avgY; currentWorkspace.AddAndRegisterNode(collapsedNode, centered: false); undoRecorder.RecordCreationForUndo(collapsedNode); foreach (var connector in inConnectors.Select((x, idx) => new { node = x.Item1, from = x.Item2, to = idx }) .Select( nodeTuple => ConnectorModel.Make( nodeTuple.node, collapsedNode, nodeTuple.@from, nodeTuple.to)) .Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } foreach (var connector in outConnectors.Select( nodeTuple => ConnectorModel.Make( collapsedNode, nodeTuple.Item1, nodeTuple.Item2, nodeTuple.Item3)).Where(connector => connector != null)) { undoRecorder.RecordCreationForUndo(connector); } } return(newWorkspace); }
protected WorkspaceModel( IEnumerable<NodeModel> nodes, IEnumerable<NoteModel> notes, IEnumerable<AnnotationModel> annotations, WorkspaceInfo info, NodeFactory factory, IEnumerable<PresetModel> presets, ElementResolver resolver) { guid = Guid.NewGuid(); this.nodes = new List<NodeModel>(nodes); this.notes = new List<NoteModel>(notes); this.annotations = new List<AnnotationModel>(annotations); // Set workspace info from WorkspaceInfo object Name = info.Name; Description = info.Description; X = info.X; Y = info.Y; FileName = info.FileName; Zoom = info.Zoom; HasUnsavedChanges = false; LastSaved = DateTime.Now; WorkspaceVersion = AssemblyHelper.GetDynamoVersion(); undoRecorder = new UndoRedoRecorder(this); NodeFactory = factory; this.presets = new List<PresetModel>(presets); ElementResolver = resolver; foreach (var node in this.nodes) RegisterNode(node); foreach (var connector in Connectors) RegisterConnector(connector); SetModelEventOnAnnotation(); }
/// <summary> /// This method is currently used as a way to send an event to ModelBase /// derived objects. Its primary use is in DynamoNodeButton class, which /// sends this event when clicked. /// </summary> /// <param name="eventName">The name of the event.</param> /// <param name="recorder"></param> /// <returns>Returns true if the call has been handled, or false otherwise. /// </returns> public bool HandleModelEvent(string eventName, UndoRedoRecorder recorder) { return HandleModelEventCore(eventName, recorder); }