diff --git a/ControlsLibrary.Tests/SceneOperationsCancelTests.cs b/ControlsLibrary.Tests/SceneOperationsCancelTests.cs new file mode 100644 index 0000000..4088f73 --- /dev/null +++ b/ControlsLibrary.Tests/SceneOperationsCancelTests.cs @@ -0,0 +1,143 @@ +using ControlsLibrary.Controls.Scene; +using ControlsLibrary.Controls.Scene.Commands; +using ControlsLibrary.ViewModel; +using GraphX.Controls; +using NUnit.Framework; +using System.Threading; + +namespace ControlsLibrary.Tests +{ + public class SceneOperationsCancelTests + { + [Test] + public void SceneUndoingandRedoingCreateVertexAndEdgeControlsCommands() + { + var newWindowThread = new Thread(new ThreadStart(() => + { + var scene = new Scene(); + + var stack = new UndoRedoStack(); + scene.UndoRedoStack = stack; + + var graphArea = scene.GraphArea; + + var firstNodeData = new NodeViewModel() { Name = "S1", IsInitial = false, IsFinal = false }; + var firstVertexControl = new CustomVertexControl(firstNodeData); + var command = new CreateVertexCommand(graphArea, firstVertexControl); + command.Execute(); + stack.AddCommand(command); + + var secondNodeData = new NodeViewModel() { Name = "S2", IsInitial = false, IsFinal = false }; + var secondVertexControl = new CustomVertexControl(secondNodeData); + command = new CreateVertexCommand(graphArea, secondVertexControl); + command.Execute(); + stack.AddCommand(command); + + var edgeData = new EdgeViewModel(firstNodeData, secondNodeData); + var edgeControl = new EdgeControl(firstVertexControl, secondVertexControl, edgeData); + var createEdgeCommand = new CreateEdgeCommand(graphArea, edgeControl); + + stack.Undo(); + Assert.False(graphArea.EdgesList.ContainsKey(edgeData)); + Assert.False(graphArea.GetRelatedEdgeControls(firstVertexControl).Contains(edgeControl)); + Assert.IsNull(edgeControl.Source); + Assert.IsNull(edgeControl.Target); + Assert.IsNull(edgeControl.Edge); + + stack.Undo(); + Assert.False(graphArea.VertexList.ContainsKey(secondNodeData)); + Assert.True(graphArea.VertexList.ContainsKey(firstNodeData)); + + stack.Undo(); + Assert.False(graphArea.VertexList.ContainsKey(secondNodeData)); + Assert.False(graphArea.VertexList.ContainsKey(firstNodeData)); + + stack.Redo(); + Assert.False(graphArea.VertexList.ContainsKey(secondNodeData)); + Assert.True(graphArea.VertexList.ContainsKey(firstNodeData)); + + stack.Redo(); + + stack.Redo(); + Assert.True(graphArea.EdgesList.ContainsKey(edgeData)); + Assert.True(graphArea.GetRelatedEdgeControls(firstVertexControl).Contains(edgeControl)); + Assert.AreEqual(edgeControl.Source, firstVertexControl); + Assert.AreEqual(edgeControl.Target, secondVertexControl); + Assert.AreEqual(edgeControl.Edge as EdgeViewModel, edgeData); + + stack.Undo(); + Assert.False(graphArea.EdgesList.ContainsKey(edgeData)); + Assert.False(graphArea.GetRelatedEdgeControls(firstVertexControl).Contains(edgeControl)); + Assert.IsNull(edgeControl.Source); + Assert.IsNull(edgeControl.Target); + Assert.IsNull(edgeControl.Edge); + + // start the Dispatcher processing + System.Windows.Threading.Dispatcher.Run(); + })); + + // set the apartment state + newWindowThread.SetApartmentState(ApartmentState.STA); + + // make the thread a background thread + newWindowThread.IsBackground = true; + + // start the thread + newWindowThread.Start(); + } + + [Test] + public void SceneUndoingandRedoingRemoveVertexCommand() + { + var newWindowThread = new Thread(new ThreadStart(() => + { + var scene = new Scene(); + + var stack = new UndoRedoStack(); + scene.UndoRedoStack = stack; + + var graphArea = scene.GraphArea; + + var data = new NodeViewModel() { Name = "S", IsInitial = false, IsFinal = false }; + var vertexControl = new CustomVertexControl(data); + var createCommand = new CreateVertexCommand(graphArea, vertexControl); + createCommand.Execute(); + stack.AddCommand(createCommand); + + var oldVertexRelatedControls = graphArea.GetRelatedControls(vertexControl); + + var removeCommand = new RemoveVertexCommand(graphArea, vertexControl); + removeCommand.Execute(); + stack.AddCommand(removeCommand); + Assert.False(graphArea.VertexList.ContainsKey(data)); + Assert.False(graphArea.VertexList.Values.Contains(vertexControl)); + Assert.IsNull(vertexControl.Vertex); + + stack.Undo(); + Assert.True(graphArea.VertexList.ContainsKey(data)); + Assert.True(graphArea.VertexList.Values.Contains(vertexControl)); + Assert.AreEqual(vertexControl.Vertex as NodeViewModel, data); + + var newVertexRelatedControls = graphArea.GetRelatedControls(vertexControl); + Assert.AreEqual(oldVertexRelatedControls, newVertexRelatedControls); + + stack.Redo(); + Assert.False(graphArea.VertexList.ContainsKey(data)); + Assert.False(graphArea.VertexList.Values.Contains(vertexControl)); + Assert.IsNull(vertexControl.Vertex); + + // start the Dispatcher processing + System.Windows.Threading.Dispatcher.Run(); + })); + + // set the apartment state + newWindowThread.SetApartmentState(ApartmentState.STA); + + // make the thread a background thread + newWindowThread.IsBackground = true; + + // start the thread + newWindowThread.Start(); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/AdornerSelectedArea.cs b/ControlsLibrary/Controls/Scene/AdornerSelectedArea.cs new file mode 100644 index 0000000..3f01995 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/AdornerSelectedArea.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; + +namespace ControlsLibrary.Controls.Scene +{ + public class AdornerSelectedArea : Adorner + { + private SolidColorBrush renderBrush; + private Pen renderPen; + public AdornerSelectedArea(UIElement adornedElement) + : base(adornedElement) + { + SelectedRect = new Rect(new Size(0, 0)); + renderBrush = new SolidColorBrush(Colors.Blue); + renderBrush.Opacity = 0.2; + renderPen = new Pen(new SolidColorBrush(Colors.Navy), 1.5); + } + + public Rect SelectedRect { get; set; } + + protected override void OnRender(DrawingContext drawingContext) + { + drawingContext.DrawRectangle(renderBrush, renderPen, SelectedRect); + } + } +} + diff --git a/ControlsLibrary/Controls/Scene/Commands/CreateEdgeCommand.cs b/ControlsLibrary/Controls/Scene/Commands/CreateEdgeCommand.cs new file mode 100644 index 0000000..5d03fa2 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/CreateEdgeCommand.cs @@ -0,0 +1,36 @@ +using ControlsLibrary.ViewModel; +using GraphX.Controls; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class CreateEdgeCommand : ISceneCommand + { + private readonly GraphArea graphArea; + private EdgeControl edgeControl; + + public CreateEdgeCommand(GraphArea graphArea, EdgeControl edgeControl) + { + this.graphArea = graphArea; + this.edgeControl = edgeControl; + } + public bool CanBeUndone => true; + + public void Execute() + { + var data = edgeControl.Edge as EdgeViewModel; + var copy = new EdgeControl(edgeControl.Source, + edgeControl.Target, data); + graphArea.InsertEdgeAndData(data, edgeControl, 0, true); + ParallelEdgesProblemSolver.AvoidParallelEdges(graphArea, edgeControl); + edgeControl = copy; + //references to properties may be lost because of execution of Undo command or independent Remove- one + } + + public void Undo() + { + var command = new RemoveEdgeCommand(graphArea, edgeControl); + command.Execute(); + //the data isn't lost as far as it is preserved in Execute method of RemoveEdgeCommand + } + } +} diff --git a/ControlsLibrary/Controls/Scene/Commands/CreateVertexCommand.cs b/ControlsLibrary/Controls/Scene/Commands/CreateVertexCommand.cs new file mode 100644 index 0000000..e51ff95 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/CreateVertexCommand.cs @@ -0,0 +1,29 @@ +using ControlsLibrary.ViewModel; +using GraphX.Controls; +using System; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class CreateVertexCommand : ISceneCommand + { + private readonly GraphArea graphArea; + private readonly CustomVertexControl vertexControl; + public CreateVertexCommand(GraphArea graphArea, CustomVertexControl vertexControl) + { + this.graphArea = graphArea; + this.vertexControl = vertexControl; + } + + public bool CanBeUndone => true; + public void Execute() + { + graphArea.AddVertexAndData(vertexControl.Vertex as NodeViewModel, vertexControl, true); + } + + public void Undo() + { + var command = new RemoveVertexCommand(graphArea, vertexControl); + command.Execute(); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/Commands/DragCommand.cs b/ControlsLibrary/Controls/Scene/Commands/DragCommand.cs new file mode 100644 index 0000000..6d8cc86 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/DragCommand.cs @@ -0,0 +1,38 @@ +using GraphX.Controls; +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class DragCommand : ISceneCommand + { + private readonly GraphArea graphArea; + private readonly ICollection vertices; + private readonly double X_coordDiff; + private readonly double Y_coordDiff; + + public DragCommand(GraphArea graphArea, ICollection vertices, + double X_coordDiff, double Y_coordDiff) + { + this.graphArea = graphArea; + this.vertices = vertices; + this.X_coordDiff = X_coordDiff; + this.Y_coordDiff = Y_coordDiff; + } + public bool CanBeUndone => true; + + public void Execute() + { + foreach (var vc in vertices) + { + vc.SetPosition(vc.GetPosition().X + X_coordDiff, vc.GetPosition().Y + Y_coordDiff); + } + } + + public void Undo() + { + var command = new DragCommand(graphArea, vertices, -X_coordDiff, -Y_coordDiff); + command.Execute(); + } + } +} + diff --git a/ControlsLibrary/Controls/Scene/Commands/EditEdgeCommand.cs b/ControlsLibrary/Controls/Scene/Commands/EditEdgeCommand.cs new file mode 100644 index 0000000..83a8041 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/EditEdgeCommand.cs @@ -0,0 +1,50 @@ +using ControlsLibrary.ViewModel; +using System; +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class EditEdgeCommand : ISceneCommand + { + static Dictionary> setProperty = + new Dictionary> + { + {nameof(EdgeViewModel.IsEpsilon), + (edgeViewModel, value) => edgeViewModel.IsEpsilon = (bool)value }, + {nameof(EdgeViewModel.IsExpanded), + (edgeViewModel, value) => edgeViewModel.IsExpanded = (bool)value }, + {nameof(EdgeViewModel.TransitionTokensString), + (edgeViewModel, value) => edgeViewModel.TransitionTokensString = (string)value } + }; + + private readonly EdgeViewModel edgeViewModel; + private readonly string propertyName; + private readonly object oldValue; + private readonly object newValue; + + public EditEdgeCommand(EdgeViewModel edgeViewModel, string propertyName, object oldValue, object newValue) + { + this.edgeViewModel = edgeViewModel; + this.propertyName = propertyName; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public bool CanBeUndone => true; + + public void Execute() + { + try + { + setProperty[propertyName](edgeViewModel, newValue); + } + catch (InvalidCastException) { } + } + + public void Undo() + { + var command = new EditEdgeCommand(edgeViewModel, propertyName, newValue, oldValue); + command.Execute(); + } + } +} \ No newline at end of file diff --git a/ControlsLibrary/Controls/Scene/Commands/EditVertexCommand.cs b/ControlsLibrary/Controls/Scene/Commands/EditVertexCommand.cs new file mode 100644 index 0000000..6625ce7 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/EditVertexCommand.cs @@ -0,0 +1,51 @@ +using ControlsLibrary.ViewModel; +using System; +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class EditVertexCommand + { + static Dictionary> setProperty = + new Dictionary> + { + {nameof(NodeViewModel.IsInitial), + (nodeViewModel, value) => nodeViewModel.IsInitial = (bool)value }, + {nameof(NodeViewModel.IsFinal), + (nodeViewModel, value) => nodeViewModel.IsFinal = (bool)value }, + {nameof(NodeViewModel.Name), + (nodeViewModel, value) => nodeViewModel.Name = (string)value } + }; + + private readonly NodeViewModel nodeViewModel; + private readonly string propertyName; + private readonly object oldValue; + private readonly object newValue; + + public EditVertexCommand(NodeViewModel nodeViewModel, string propertyName, object oldValue, object newValue) + { + this.nodeViewModel = nodeViewModel; + this.propertyName = propertyName; + this.oldValue = oldValue; + this.newValue = newValue; + } + + public bool CanBeUndone => true; + + public void Execute() + { + try + { + setProperty[propertyName](nodeViewModel, newValue); + } + catch (InvalidCastException) { } + } + + public void Undo() + { + var command = new EditVertexCommand(nodeViewModel, propertyName, newValue, oldValue); + command.Execute(); + } + } +} + diff --git a/ControlsLibrary/Controls/Scene/Commands/RemoveEdgeCommand.cs b/ControlsLibrary/Controls/Scene/Commands/RemoveEdgeCommand.cs new file mode 100644 index 0000000..f52af5d --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/RemoveEdgeCommand.cs @@ -0,0 +1,54 @@ +using ControlsLibrary.ViewModel; +using GraphX.Controls; +using System.Linq; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class RemoveEdgeCommand : ISceneCommand + { + private readonly GraphArea graphArea; + private EdgeControl edgeControl; + public RemoveEdgeCommand(GraphArea graphArea, EdgeControl edgeControl) + { + this.graphArea = graphArea; + this.edgeControl = edgeControl; + } + public bool CanBeUndone => true; + + public void Execute() + { + var data = edgeControl.Edge as EdgeViewModel; + var source = data.Source; + var target = data.Target; + var copy = new EdgeControl(edgeControl.Source, edgeControl.Target, data); + graphArea.RemoveEdge(data, true); + UpdateEdgeRoutingPoints(source, target); + edgeControl = copy; + //make a copy in order to preserve the references to properties + } + + public void Undo() + { + var command = new CreateEdgeCommand(graphArea, + new EdgeControl(edgeControl.Source, edgeControl.Target, + edgeControl.Edge as EdgeViewModel)); + command.Execute(); + //pass a copy in order to preserve the references to properties + //which may be lost while undoing CreateEdgeCommand + } + + private void UpdateEdgeRoutingPoints(NodeViewModel source, NodeViewModel target) + { + var parallelEdge = graphArea.LogicCore.Graph.Edges.FirstOrDefault(e => e.Source == target && e.Target == source); + if (parallelEdge == null) + { + return; + } + + var newEdge = new EdgeViewModel(parallelEdge.Source, parallelEdge.Target) { TransitionTokensString = parallelEdge.TransitionTokensString }; + var ec = new EdgeControl(graphArea.VertexList[parallelEdge.Source], graphArea.VertexList[parallelEdge.Target], newEdge); + graphArea.RemoveEdge(parallelEdge, true); + graphArea.InsertEdgeAndData(newEdge, ec, 0, true); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/Commands/RemoveVertexCommand.cs b/ControlsLibrary/Controls/Scene/Commands/RemoveVertexCommand.cs new file mode 100644 index 0000000..9d63407 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/RemoveVertexCommand.cs @@ -0,0 +1,67 @@ +using ControlsLibrary.ViewModel; +using GraphX.Controls; +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class RemoveVertexCommand : ISceneCommand + { + private readonly GraphArea graphArea; + private readonly CustomVertexControl vertexControl; + private readonly List edges; + + private void AddAllEdges() + { + foreach (var gc in graphArea.GetRelatedEdgeControls(vertexControl)) + { + edges.Add((EdgeControl)gc); + } + } + + public RemoveVertexCommand(GraphArea graphArea, CustomVertexControl vc) + { + this.graphArea = graphArea; + this.vertexControl = vc; + this.edges = new List(); + AddAllEdges(); + } + + public bool CanBeUndone => true; + public void Execute() + { + EdgeControl ec; + EdgeControl copy; + EdgeViewModel data; + for (int i = 0; i < edges.Count; i++) + { + ec = edges[i]; + data = ec.Edge as EdgeViewModel; + copy = new EdgeControl(ec.Source, ec.Target, data); + graphArea.RemoveEdge(data, true); + edges[i] = copy; + } + + var vertex = vertexControl.Vertex as NodeViewModel; + graphArea.RemoveVertex(vertexControl.Vertex as NodeViewModel, true); + vertexControl.Vertex = vertex; + } + + public void Undo() + { + var command = new CompositeCommand(new List()); + command.AddCommand(new CreateVertexCommand(graphArea, vertexControl)); + + EdgeControl ec; + EdgeControl copy; + for (int i = 0; i < edges.Count; i++) + { + ec = edges[i]; + copy = new EdgeControl(ec.Source, ec.Target, ec.Edge as EdgeViewModel); + command.AddCommand(new CreateEdgeCommand(graphArea, ec)); + edges[i] = copy; + } + + command.Execute(); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/Commands/SelectCommand.cs b/ControlsLibrary/Controls/Scene/Commands/SelectCommand.cs new file mode 100644 index 0000000..094ca7e --- /dev/null +++ b/ControlsLibrary/Controls/Scene/Commands/SelectCommand.cs @@ -0,0 +1,37 @@ +using GraphX.Controls; +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene.Commands +{ + public class SelectCommand : ISceneCommand + { + private readonly ICollection vertices; + private readonly bool selected; + + public SelectCommand(ICollection vertices, bool selected) + { + this.vertices = vertices; + this.selected = selected; + } + private void SelectVertex(CustomVertexControl vc) + { + DragBehaviour.SetIsTagged(vc, selected); + vc.IsSelected = selected; + } + public bool CanBeUndone => true; + + public void Execute() + { + foreach (var vc in vertices) + { + SelectVertex(vc); + } + } + + public void Undo() + { + var command = new SelectCommand(vertices, !selected); + command.Execute(); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/CompositeCommand.cs b/ControlsLibrary/Controls/Scene/CompositeCommand.cs new file mode 100644 index 0000000..865fb71 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/CompositeCommand.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ControlsLibrary.Controls.Scene +{ + public class CompositeCommand : ISceneCommand + { + private readonly IList commands; + + public CompositeCommand(IList commands) + { + this.commands = commands; + } + public void Execute() + { + foreach (var command in commands) + { + command.Execute(); + } + } + + public bool CanBeUndone => commands.All(c => c.CanBeUndone); + + public void Undo() + { + if (CanBeUndone) + { + foreach (var command in commands.Reverse()) + { + command.Undo(); + } + } + } + + public void AddCommand(ISceneCommand command) + { + this.commands.Add(command); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/CustomEdgeControl.cs b/ControlsLibrary/Controls/Scene/CustomEdgeControl.cs new file mode 100644 index 0000000..d701f7f --- /dev/null +++ b/ControlsLibrary/Controls/Scene/CustomEdgeControl.cs @@ -0,0 +1,25 @@ +using GraphX.Controls; +using System.Windows; + +namespace ControlsLibrary.Controls.Scene +{ + class CustomEdgeControl: EdgeControl + { + public CustomEdgeControl(VertexControl source, VertexControl target, object edge, bool showArrows = true) + :base(source, target, edge, showArrows) + { + IsSelected = false; + } + + public static readonly DependencyProperty IsSelectedProperty; + static CustomEdgeControl() + { + IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(CustomVertexControl)); + } + public bool IsSelected + { + get => (bool)GetValue(IsSelectedProperty); + set => SetValue(IsSelectedProperty, value); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/CustomVertexControl.cs b/ControlsLibrary/Controls/Scene/CustomVertexControl.cs new file mode 100644 index 0000000..9621d31 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/CustomVertexControl.cs @@ -0,0 +1,25 @@ +using GraphX.Controls; +using System.Windows; + +namespace ControlsLibrary.Controls.Scene +{ + public class CustomVertexControl: VertexControl + { + public CustomVertexControl(object vertexData, bool tracePositionChange = true, bool bindToDataObject = true) + : base(vertexData, tracePositionChange, bindToDataObject) + { + IsSelected = false; + } + + public static readonly DependencyProperty IsSelectedProperty; + static CustomVertexControl() + { + IsSelectedProperty = DependencyProperty.Register("IsSelected", typeof(bool), typeof(CustomVertexControl)); + } + public bool IsSelected + { + get => (bool)GetValue(IsSelectedProperty); + set => SetValue(IsSelectedProperty, value); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/EdgeBlueprint.cs b/ControlsLibrary/Controls/Scene/EdgeBlueprint.cs index 49feb41..29e22d5 100644 --- a/ControlsLibrary/Controls/Scene/EdgeBlueprint.cs +++ b/ControlsLibrary/Controls/Scene/EdgeBlueprint.cs @@ -15,7 +15,7 @@ internal class EdgeBlueprint : IDisposable /// /// The source vertex of an edge /// - public VertexControl Source { get; set; } + public CustomVertexControl Source { get; set; } /// /// The current target position of an edge @@ -32,7 +32,7 @@ internal class EdgeBlueprint : IDisposable /// /// Source vertex of a new edge /// Brush to draw a blueprint - public EdgeBlueprint(VertexControl source, Brush brush) + public EdgeBlueprint(CustomVertexControl source, Brush brush) { EdgePath = new Path { Stroke = brush, Data = new LineGeometry() }; Source = source; diff --git a/ControlsLibrary/Controls/Scene/EditorObjectManager.cs b/ControlsLibrary/Controls/Scene/EditorObjectManager.cs index 616c8fd..5eaf534 100644 --- a/ControlsLibrary/Controls/Scene/EditorObjectManager.cs +++ b/ControlsLibrary/Controls/Scene/EditorObjectManager.cs @@ -37,7 +37,7 @@ public EditorObjectManager(GraphArea graphArea, ZoomControl zoomControl) /// Creates a new edge blueprint on the scene /// /// Source vertex for a edge bluepting - public void CreateVirtualEdge(VertexControl source) + public void CreateVirtualEdge(CustomVertexControl source) { edgeBlueprint = new EdgeBlueprint(source, (SolidColorBrush)resourceDictionary["EdgeArrowBrush"]); graphArea.InsertCustomChildControl(0, edgeBlueprint.EdgePath); diff --git a/ControlsLibrary/Controls/Scene/ISceneCommand.cs b/ControlsLibrary/Controls/Scene/ISceneCommand.cs new file mode 100644 index 0000000..963fd95 --- /dev/null +++ b/ControlsLibrary/Controls/Scene/ISceneCommand.cs @@ -0,0 +1,9 @@ +namespace ControlsLibrary.Controls.Scene +{ + public interface ISceneCommand + { + public bool CanBeUndone { get; } + public void Execute(); + public void Undo(); + } +} diff --git a/ControlsLibrary/Controls/Scene/ParallelEdgesProblemSolver.cs b/ControlsLibrary/Controls/Scene/ParallelEdgesProblemSolver.cs new file mode 100644 index 0000000..a9fff6f --- /dev/null +++ b/ControlsLibrary/Controls/Scene/ParallelEdgesProblemSolver.cs @@ -0,0 +1,61 @@ +using ControlsLibrary.ViewModel; +using GraphX.Controls; +using System; +using System.Linq; + +namespace ControlsLibrary.Controls.Scene +{ + class ParallelEdgesProblemSolver + { + /// + /// Creates edge routing points to avoid overlapping of an edge by a parallel one + /// + /// Edge control which was overlapped or overlaps other + public static void AvoidParallelEdges(GraphArea graphArea, EdgeControl edgeControl) + { + var edge = edgeControl.Edge as EdgeViewModel; + if (edge == null) + { + return; + } + var parallelEdge = graphArea.LogicCore.Graph.Edges.FirstOrDefault(e => e.Source == edge.Target && edge.Source == e.Target); + + if (parallelEdge == null) + { + return; + } + + var sourcePos = edgeControl.Source.GetCenterPosition().ToGraphX(); + var targetPos = edgeControl.Target.GetCenterPosition().ToGraphX(); + + var middleX = (sourcePos.X + targetPos.X) / 2; + var middleY = (sourcePos.Y + targetPos.Y) / 2; + + var distance = Geometry.GetDistance(sourcePos, targetPos); + var diagonal = Math.Min(Math.Max(distance / 25, 20), 80); + + var bypassPoint1 = new GraphX.Measure.Point(middleX, middleY); + var bypassPoint2 = new GraphX.Measure.Point(middleX, middleY); + + if ((sourcePos.X - targetPos.X) * (sourcePos.Y - targetPos.Y) > 0) + { + bypassPoint1.X -= diagonal; + bypassPoint1.Y += diagonal; + bypassPoint2.X += diagonal; + bypassPoint2.Y -= diagonal; + } + else + { + bypassPoint1.X -= diagonal; + bypassPoint1.Y -= diagonal; + bypassPoint2.X += diagonal; + bypassPoint2.Y += diagonal; + } + + edge.RoutingPoints = new[] { sourcePos, bypassPoint1, targetPos }; + parallelEdge.RoutingPoints = new[] { targetPos, bypassPoint2, targetPos }; + graphArea.UpdateAllEdges(); + } + + } +} diff --git a/ControlsLibrary/Controls/Scene/Scene.xaml.cs b/ControlsLibrary/Controls/Scene/Scene.xaml.cs index dad96d9..b83726d 100644 --- a/ControlsLibrary/Controls/Scene/Scene.xaml.cs +++ b/ControlsLibrary/Controls/Scene/Scene.xaml.cs @@ -1,5 +1,6 @@ using ControlsLibrary.Controls.ErrorReporter; using ControlsLibrary.Controls.Executor; +using ControlsLibrary.Controls.Scene.Commands; using ControlsLibrary.Controls.TestPanel; using ControlsLibrary.Controls.Toolbar; using ControlsLibrary.Controls.TypeAnalyzer; @@ -14,11 +15,14 @@ using GraphX.Logic.Models; using QuickGraph; using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; +using System.Windows.Documents; using System.Windows.Input; + namespace ControlsLibrary.Controls.Scene { /// @@ -83,7 +87,7 @@ private void InSimulationChanged(object sender, PropertyChangedEventArgs e) return; } - ClearSelectMode(true); + ClearSelectMode(); ClearEditMode(); foreach (var node in graphArea.LogicCore.Graph.Vertices) { @@ -218,6 +222,7 @@ public void Open(string path) } GraphEdited?.Invoke(this, EventArgs.Empty); + undoRedoStack.Clear(); } /// @@ -229,6 +234,22 @@ public Scene() SetZoomControlProperties(); SetGraphAreaProperties(); editor = new EditorObjectManager(graphArea, zoomControl); + undoRedoStack = new UndoRedoStack(); + this.VertexRemoved += SingleVertexRemoved; + this.SelectionStarted += StartSelection; + } + + private UndoRedoStack undoRedoStack; + + public UndoRedoStack UndoRedoStack + { + get => undoRedoStack; + set => undoRedoStack = value; + } + + public GraphArea GraphArea + { + get => graphArea; } /// @@ -253,6 +274,24 @@ public void OnSceneKeyDown(object sender, KeyEventArgs e) toolBar.SelectedTool = SelectedTool.Edit; return; } + case Key.Z: + { + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + undoRedoStack.Undo(); + // GraphEdited - in case of creation and deletion + } + return; + } + case Key.Y: + { + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + undoRedoStack.Redo(); + // GraphEdited - in case of creation and deletion + } + return; + } } } @@ -265,12 +304,15 @@ private void SetZoomControlProperties() zoomControl.IsAnimationEnabled = false; ZoomControl.SetViewFinderVisibility(zoomControl, Visibility.Hidden); zoomControl.MouseDown += OnSceneMouseDown; + zoomControl.MouseMove += OnSceneMouseMove; + zoomControl.PreviewMouseUp += OnSceneMouseUp; using var deletionCursorStream = Application.GetResourceStream(new Uri("pack://application:,,,/ControlsLibrary;component/Controls/Scene/Assets/deletionCursor.cur", UriKind.RelativeOrAbsolute))?.Stream; if (deletionCursorStream != null) { deletionCursor = new Cursor(deletionCursorStream); } + SetZoomControlFixed(); } //TODO: learn how to extract drag information from the graphArea @@ -294,9 +336,10 @@ private void SetGraphAreaProperties() logic.EdgeCurvingEnabled = false; graphArea.VertexSelected += OnSceneVertexSelected; + graphArea.VertexSelected += VertexDraggingStarted; graphArea.EdgeSelected += EdgeSelected; graphArea.SetEdgesDrag(false); - graphArea.VertexMouseUp += VertexDragged; + graphArea.VertexMouseUp += VertexDragged; } /// @@ -309,25 +352,6 @@ private void SetGraphAreaProperties() /// public event EventHandler GraphEdited; - /// - /// Update edge route if graph was edited - /// - /// Source vertex - /// Target vertex - private void UpdateEdgeRoutingPoints(NodeViewModel source, NodeViewModel target) - { - var parallelEdge = graphArea.LogicCore.Graph.Edges.FirstOrDefault(e => e.Source == target && e.Target == source); - if (parallelEdge == null) - { - return; - } - - var newEdge = new EdgeViewModel(parallelEdge.Source, parallelEdge.Target) { TransitionTokensString = parallelEdge.TransitionTokensString }; - var ec = new EdgeControl(graphArea.VertexList[parallelEdge.Source], graphArea.VertexList[parallelEdge.Target], newEdge); - graphArea.RemoveEdge(parallelEdge, true); - graphArea.InsertEdgeAndData(newEdge, ec, 0, true); - } - /// /// Handles edge selection and removes edge if scene in the deletion mode /// @@ -346,15 +370,18 @@ private void EdgeSelected(object sender, EdgeSelectedEventArgs args) return; } - var source = edgeViewModel.Source; - var target = edgeViewModel.Target; - graphArea.RemoveEdge(edgeViewModel, true); - UpdateEdgeRoutingPoints(source, target); + ClearSelectMode(true); + ClearSelectedVertices(); + + var command = new RemoveEdgeCommand(graphArea, args.EdgeControl); + command.Execute(); + undoRedoStack.AddCommand(command); + GraphEdited?.Invoke(this, EventArgs.Empty); } } - private VertexControl selectedVertex; + private CustomVertexControl selectedVertex; private readonly EditorObjectManager editor; @@ -362,7 +389,7 @@ private void EdgeSelected(object sender, EdgeSelectedEventArgs args) /// Creates new vertices by click on the scene and creates targeted edges if scene in the creating of the new edge state /// private void OnSceneMouseDown(object sender, MouseButtonEventArgs e) - { + { if (e.LeftButton == MouseButtonState.Pressed) { if (Toolbar.SelectedTool == SelectedTool.Edit) @@ -371,6 +398,8 @@ private void OnSceneMouseDown(object sender, MouseButtonEventArgs e) { return; } + ClearSelectMode(true); + ClearSelectedVertices(); var position = zoomControl.TranslatePoint(e.GetPosition(zoomControl), graphArea); position.Offset(-60, -60); //Offset should be the half of the vertex controls width @@ -380,10 +409,32 @@ private void OnSceneMouseDown(object sender, MouseButtonEventArgs e) CreateEdgeControl(vc); } } - else if (Toolbar.SelectedTool == SelectedTool.Select) + else { ClearSelectMode(true); + ClearSelectedVertices(); + SelectionStarted?.Invoke(this, e); + } + } + + else if (e.RightButton == MouseButtonState.Pressed) + { + ClearSelectMode(true); + ClearSelectedVertices(); + SelectionStarted?.Invoke(this, e); + } + } + + private void ClearSelectedVertices() + { + if (selectedVertices != null) + { + if (selectedVertices.Count > 0) + { + var command = new SelectCommand(selectedVertices, false); + undoRedoStack.AddCommand(command); } + selectedVertices = null; } } @@ -394,107 +445,91 @@ private void VertexDragged(object sender, VertexSelectedEventArgs args) { foreach (var edge in graphArea.EdgesList.Where(e => e.Value.Source == args.VertexControl || e.Value.Target == args.VertexControl)) { - AvoidParallelEdges(edge.Value); + ParallelEdgesProblemSolver.AvoidParallelEdges(graphArea, edge.Value); } + CreateDragCommand((CustomVertexControl)args.VertexControl); } - private void CreateEdgeControl(VertexControl vc) + private void CreateDragCommand(CustomVertexControl vc) { - if (ExecutorViewModel.InSimulation) - { - return; - } - if (selectedVertex == null) + var currentPosition = vc.GetPosition(); + if (currentPosition != dragStartPosition) { - editor.CreateVirtualEdge(vc); - selectedVertex = vc; - HighlightBehaviour.SetHighlighted(selectedVertex, true); - return; + DragCommand command; + if (selectedVertices != null && selectedVertices.Contains(vc)) + { + command = new DragCommand(graphArea, selectedVertices, + currentPosition.X - dragStartPosition.X, currentPosition.Y - dragStartPosition.Y); + } + else + { + command = new DragCommand(graphArea, new HashSet { vc }, + currentPosition.X - dragStartPosition.X, currentPosition.Y - dragStartPosition.Y); + } + undoRedoStack.AddCommand(command); } + } - var data = new EdgeViewModel((NodeViewModel)selectedVertex.Vertex, (NodeViewModel)vc.Vertex); - - // Doesn't create new edges with the same direction - // TODO: should somehow notice user that edge wasn't created - if (graphArea.LogicCore.Graph.Edges.Any(e => e.Source == (NodeViewModel)selectedVertex.Vertex && e.Target == (NodeViewModel)vc.Vertex)) - { - HighlightBehaviour.SetHighlighted(selectedVertex, false); - selectedVertex = null; - editor.DestroyVirtualEdge(); - return; - } - data.PropertyChanged += EdgeEdited; - var ec = new EdgeControl(selectedVertex, vc, data); - graphArea.InsertEdgeAndData(data, ec, 0, true); + private Point dragStartPosition; + private void VertexDraggingStarted(object sender, VertexSelectedEventArgs args) + { + dragStartPosition = args.VertexControl.GetPosition(); + } - AvoidParallelEdges(ec); + private void CreateEdgeBlueprint(CustomVertexControl vc) + { + editor.CreateVirtualEdge(vc); + selectedVertex = vc; + HighlightBehaviour.SetHighlighted(selectedVertex, true); + } + private void DestroyEdgeBlueprint() + { HighlightBehaviour.SetHighlighted(selectedVertex, false); selectedVertex = null; editor.DestroyVirtualEdge(); } - /// - /// Creates edge routing points to avoid overlapping of an edge by a parallel one - /// - /// Edge control which was overlapped or overlaps other - private void AvoidParallelEdges(EdgeControl edgeControl) + private void CreateEdgeControl(CustomVertexControl vc) { - var edge = edgeControl.Edge as EdgeViewModel; - if (edge == null) - { - return; - } - var parallelEdge = graphArea.LogicCore.Graph.Edges.FirstOrDefault(e => e.Source == edge.Target && edge.Source == e.Target); - - if (parallelEdge == null) + if (ExecutorViewModel.InSimulation) { return; } - var sourcePos = edgeControl.Source.GetCenterPosition().ToGraphX(); - var targetPos = edgeControl.Target.GetCenterPosition().ToGraphX(); - - var middleX = (sourcePos.X + targetPos.X) / 2; - var middleY = (sourcePos.Y + targetPos.Y) / 2; - - var distance = Geometry.GetDistance(sourcePos, targetPos); - var diagonal = Math.Min(Math.Max(distance / 25, 20), 80); - - var bypassPoint1 = new GraphX.Measure.Point(middleX, middleY); - var bypassPoint2 = new GraphX.Measure.Point(middleX, middleY); + var data = new EdgeViewModel((NodeViewModel)selectedVertex.Vertex, (NodeViewModel)vc.Vertex); - if ((sourcePos.X - targetPos.X) * (sourcePos.Y - targetPos.Y) > 0) - { - bypassPoint1.X -= diagonal; - bypassPoint1.Y += diagonal; - bypassPoint2.X += diagonal; - bypassPoint2.Y -= diagonal; - } - else + // Doesn't create new edges with the same direction + // TODO: should somehow notice user that edge wasn't created + if (graphArea.LogicCore.Graph.Edges.Any(e => e.Source == (NodeViewModel)selectedVertex.Vertex && e.Target == (NodeViewModel)vc.Vertex)) { - bypassPoint1.X -= diagonal; - bypassPoint1.Y -= diagonal; - bypassPoint2.X += diagonal; - bypassPoint2.Y += diagonal; + DestroyEdgeBlueprint(); + return; } - - edge.RoutingPoints = new[] { sourcePos, bypassPoint1, targetPos }; - parallelEdge.RoutingPoints = new[] { targetPos, bypassPoint2, targetPos }; - graphArea.UpdateAllEdges(); + data.PropertyChanged += EdgeEdited; + var ec = new EdgeControl(selectedVertex, vc, data); + var command = new CreateEdgeCommand(graphArea, ec); + command.Execute(); + undoRedoStack.AddCommand(command); + //GraphEdited + DestroyEdgeBlueprint(); } private int numberOfVertex; - private VertexControl CreateVertexControl(Point position) + private CustomVertexControl CreateVertexControl(Point position) { var data = new NodeViewModel() { Name = "S" + numberOfVertex, IsFinal = false, IsInitial = false, IsExpanded = false }; data.PropertyChanged += VertexEdited; numberOfVertex++; - var vc = new VertexControl(data); + var vc = new CustomVertexControl(data); data.PropertyChanged += errorReporter.GraphEdited; vc.SetPosition(position); - graphArea.AddVertexAndData(data, vc, true); + + var command = new CreateVertexCommand(graphArea, vc); + command.Execute(); + undoRedoStack.AddCommand(command); + GraphEdited?.Invoke(this, EventArgs.Empty); return vc; } @@ -512,7 +547,7 @@ private void ToolSelected(object sender, EventArgs e) { zoomControl.Cursor = deletionCursor; ClearEditMode(); - ClearSelectMode(); + graphArea.SetVerticesDrag(false); graphArea.SetEdgesDrag(false); return; } @@ -538,6 +573,22 @@ private void ToolSelected(object sender, EventArgs e) } } + private void SetZoomControlFixed() + { + initialTranslateX = zoomControl.TranslateX; + initialTranslateY = zoomControl.TranslateY; + zoomControl.PropertyChanged += zoomControl_PropertyChanged; + } + + private void zoomControl_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(zoomControl.Presenter)) + { + zoomControl.TranslateX = initialTranslateX; + zoomControl.TranslateY = initialTranslateY; + } + } + private void ClearSelectMode(bool soft = false) { graphArea.VertexList.Values @@ -545,7 +596,7 @@ private void ClearSelectMode(bool soft = false) .ToList() .ForEach(a => { - HighlightBehaviour.SetHighlighted(a, false); + ((CustomVertexControl)a).IsSelected = false; DragBehaviour.SetIsTagged(a, false); }); @@ -568,24 +619,32 @@ private void ClearEditMode() /// /// Selects node view model by the vertex control /// - private NodeViewModel SelectNode(VertexControl vertexControl) + private NodeViewModel SelectNode(CustomVertexControl vertexControl) => graphArea.VertexList.FirstOrDefault(x => x.Value == vertexControl).Key; private void OnSceneVertexSelected(object sender, VertexSelectedEventArgs args) { + var vertexControl = (CustomVertexControl)args.VertexControl; if (args.MouseArgs.LeftButton == MouseButtonState.Pressed) { - NodeSelected?.Invoke(this, new NodeSelectedEventArgs() { Node = SelectNode(args.VertexControl) }); + NodeSelected?.Invoke(this, new NodeSelectedEventArgs() { Node = SelectNode(vertexControl) }); switch (Toolbar.SelectedTool) { case SelectedTool.Edit: { - CreateEdgeControl(args.VertexControl); + if (selectedVertex == null) + { + CreateEdgeBlueprint(vertexControl); + } + else + { + CreateEdgeControl(vertexControl); + } break; } case SelectedTool.Delete: { - SafeRemoveVertex(args.VertexControl); + SafeRemoveVertex(vertexControl); break; } default: @@ -603,7 +662,7 @@ private void OnSceneVertexSelected(object sender, VertexSelectedEventArgs args) { if (Toolbar.SelectedTool == SelectedTool.Select) { - SelectNode(args.VertexControl).IsExpanded = !SelectNode(args.VertexControl).IsExpanded; + SelectNode(vertexControl).IsExpanded = !SelectNode(vertexControl).IsExpanded; } } } @@ -622,22 +681,171 @@ private static void SelectVertex(DependencyObject vc) } } - private void SafeRemoveVertex(VertexControl vc) + private void SafeRemoveVertex(CustomVertexControl vc) { if (ExecutorViewModel.InSimulation) { return; } - foreach (var edge in graphArea.LogicCore.Graph.Edges) + + var removeCommand = new CompositeCommand(new List()); + removeCommand.AddCommand(new RemoveVertexCommand(graphArea, vc)); + removeCommand.Execute(); + GraphEdited?.Invoke(this, EventArgs.Empty); + + VertexRemoved?.Invoke(this, new VertexRemovedEventArgs() + { VertexControl = vc, RemoveCommand = removeCommand }); + + undoRedoStack.AddCommand(removeCommand); + } + + private EventHandler VertexRemoved; + + private void SingleVertexRemoved(object sender, VertexRemovedEventArgs args) + { + if (selectedVertices == null) + return; + + var vc = args.VertexControl; + if (selectedVertices.Remove(vc)) + { + SafeRemoveSelectedVertices(args.RemoveCommand); + selectedVertices.Add(vc); + } + + ClearSelectMode(true); + ClearSelectedVertices(); + } + + private void SafeRemoveSelectedVertices(CompositeCommand groupRemoveCommand) + { + foreach (var vc in selectedVertices) + { + var command = new RemoveVertexCommand(graphArea, vc); + command.Execute(); + GraphEdited?.Invoke(this, EventArgs.Empty); + groupRemoveCommand.AddCommand(command); + } + } + + private AdornerSelectedArea selectedArea; + private HashSet selectedVertices; + private Point mouseDownPosition; + private double initialTranslateX; + private double initialTranslateY; + + private EventHandler SelectionStarted; + private void SetVertexSelected(CustomVertexControl vc, bool selected) + { + vc.IsSelected = selected; + DragBehaviour.SetIsTagged(vc, selected); + if (selected) + { + selectedVertices.Add(vc); + } + else + { + selectedVertices.Remove(vc); + } + } + + private void UpdateSelectedVertices() + { + var selectedRect = selectedArea.SelectedRect; + Point centrePosition; + bool selected; + foreach (var vc in graphArea.VertexList.Values) + { + centrePosition = graphArea.TranslatePoint(vc.GetCenterPosition(), zoomControl); + selected = selectedRect.Contains(centrePosition); + SetVertexSelected((CustomVertexControl)vc, selected); + } + } + + private Rect UpdateSelectedRect(Point mouseCurrentPosition) + { + double x, y, width, height; + x = mouseDownPosition.X < mouseCurrentPosition.X + ? mouseDownPosition.X + : mouseCurrentPosition.X; + y = mouseDownPosition.Y < mouseCurrentPosition.Y + ? mouseDownPosition.Y + : mouseCurrentPosition.Y; + + width = Math.Abs(mouseCurrentPosition.X - mouseDownPosition.X); + height = Math.Abs(mouseCurrentPosition.Y - mouseDownPosition.Y); + + return new Rect(x, y, width, height); + } + + private void StartSelection(object sender, MouseButtonEventArgs e) + { + zoomControl.Cursor = Cursors.Arrow; + mouseDownPosition = e.GetPosition(zoomControl); + InitSelectedArea(); + selectedVertices = new HashSet(); + } + + private void InitSelectedArea() + { + selectedArea = new AdornerSelectedArea(zoomControl); + var adornerLayer = AdornerLayer.GetAdornerLayer(selectedArea.AdornedElement); + adornerLayer.Add(selectedArea); + } + + private void ClearSelectedArea() + { + if (selectedArea != null) + { + var adornerLayer = AdornerLayer.GetAdornerLayer(selectedArea.AdornedElement); + adornerLayer.Remove(selectedArea); + selectedArea = null; + } + } + + private void OnSceneMouseMove(object sender, MouseEventArgs e) + { + if (selectedArea != null) + { + selectedArea.SelectedRect = UpdateSelectedRect(e.GetPosition(selectedArea.AdornedElement)); + UpdateSelectedVertices(); + selectedArea.InvalidateVisual(); + } + } + + private void RecoverSelectedCursor() + { + switch (Toolbar.SelectedTool) { - if (edge.IsSelfLoop && edge.Source == SelectNode(vc)) + case SelectedTool.Select: + { + zoomControl.Cursor = Cursors.Hand; + return; + } + case SelectedTool.Edit: + { + zoomControl.Cursor = Cursors.Pen; + return; + } + case SelectedTool.Delete: + { + zoomControl.Cursor = deletionCursor; + return; + } + } + } + private void OnSceneMouseUp(object sender, MouseButtonEventArgs e) + { + if (selectedArea != null) + { + if (selectedVertices != null && selectedVertices.Count > 0) { - graphArea.RemoveEdge(edge); + var command = new SelectCommand(selectedVertices, true); + undoRedoStack.AddCommand(command); } + ClearSelectedArea(); + RecoverSelectedCursor(); } - - graphArea.RemoveVertexAndEdges(vc.Vertex as NodeViewModel); - GraphEdited?.Invoke(this, EventArgs.Empty); } public void Dispose() @@ -646,4 +854,5 @@ public void Dispose() graphArea?.Dispose(); } } -} \ No newline at end of file +} + diff --git a/ControlsLibrary/Controls/Scene/UndoRedoStack.cs b/ControlsLibrary/Controls/Scene/UndoRedoStack.cs new file mode 100644 index 0000000..8e8113c --- /dev/null +++ b/ControlsLibrary/Controls/Scene/UndoRedoStack.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace ControlsLibrary.Controls.Scene +{ + public class UndoRedoStack + { + private readonly Stack undoStack; + private readonly Stack redoStack; + + private bool isUndoAvailable; + private bool isRedoAvailable; + + public UndoRedoStack() + { + undoStack = new Stack(); + redoStack = new Stack(); + isUndoAvailable = true; + isRedoAvailable = true; + } + public void AddCommand(ISceneCommand sceneCommand) + { + if (sceneCommand.CanBeUndone) + { + undoStack.Push(sceneCommand); + redoStack.Clear(); + } + } + + public void SetUndoAvailable(bool available) + { + isUndoAvailable = available; + } + + public void SetRedoAvailable(bool available) + { + isRedoAvailable = available; + } + + public bool IsUndoAvailable => this.isUndoAvailable && this.undoStack.Count > 0; + + public bool IsRedoAvailable => this.isRedoAvailable && this.redoStack.Count > 0; + + public void Undo() + { + if (IsUndoAvailable) + { + var sceneCommand = undoStack.Pop(); + sceneCommand.Undo(); + redoStack.Push(sceneCommand); + } + } + + public void Redo() + { + if (IsRedoAvailable) + { + var sceneCommand = redoStack.Pop(); + sceneCommand.Execute(); + undoStack.Push(sceneCommand); + } + } + + public void Clear() + { + undoStack.Clear(); + redoStack.Clear(); + } + } +} diff --git a/ControlsLibrary/Controls/Scene/VertexRemovedEventArgs.cs b/ControlsLibrary/Controls/Scene/VertexRemovedEventArgs.cs new file mode 100644 index 0000000..66e7f6b --- /dev/null +++ b/ControlsLibrary/Controls/Scene/VertexRemovedEventArgs.cs @@ -0,0 +1,11 @@ +using GraphX.Controls; +using System; + +namespace ControlsLibrary.Controls.Scene +{ + public class VertexRemovedEventArgs : EventArgs + { + public CustomVertexControl VertexControl { get; set; } + public CompositeCommand RemoveCommand { get; set; } + } +} diff --git a/ControlsLibrary/View/Templates/EditorTemplates.xaml b/ControlsLibrary/View/Templates/EditorTemplates.xaml index 37ebc10..46505a8 100644 --- a/ControlsLibrary/View/Templates/EditorTemplates.xaml +++ b/ControlsLibrary/View/Templates/EditorTemplates.xaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:controls="http://schemas.panthernet.ru/graphx/" + xmlns:scene="clr-namespace:ControlsLibrary.Controls.Scene" xmlns:language="clr-namespace:ControlsLibrary.Properties.Langs" xmlns:sys="clr-namespace:System;assembly=mscorlib"> @@ -108,14 +109,14 @@ -