diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets index 305e736c..52f41c60 100644 --- a/.paket/Paket.Restore.targets +++ b/.paket/Paket.Restore.targets @@ -11,23 +11,49 @@ $(MSBuildThisFileDirectory)..\ $(PaketRootPath)paket-files\paket.restore.cached $(PaketRootPath)paket.lock + classic + proj + assembly + native /Library/Frameworks/Mono.framework/Commands/mono mono - - $(PaketRootPath)paket.exe - $(PaketToolsPath)paket.exe - "$(PaketExePath)" - $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" - + + $(PaketRootPath)paket.bootstrapper.exe + $(PaketToolsPath)paket.bootstrapper.exe + $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ + + + + + $(PaketRootPath)paket.exe + $(PaketToolsPath)paket.exe + $(PaketToolsPath)paket.exe + $(_PaketBootStrapperExeDir)paket.exe + paket.exe + + + $(PaketRootPath)paket + $(PaketToolsPath)paket + $(PaketToolsPath)paket + + + $(PaketRootPath)paket.exe + $(PaketToolsPath)paket.exe + + + $(PaketBootStrapperExeDir)paket.exe + + + paket + + <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) - dotnet "$(PaketExePath)" + dotnet "$(PaketExePath)" + $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" + "$(PaketExePath)" - - "$(PaketExePath)" - $(PaketRootPath)paket.bootstrapper.exe - $(PaketToolsPath)paket.bootstrapper.exe "$(PaketBootStrapperExePath)" $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" @@ -36,9 +62,16 @@ true true + + + True - + + + + + @@ -72,12 +105,16 @@ true - + + true - + @@ -141,16 +178,19 @@ + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) - $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) %(PaketReferencesFileLinesInfo.PackageVersion) All - runtime + runtime + runtime true + true @@ -182,19 +222,27 @@ false + $(MSBuildVersion) + 15.8.0 <_NuspecFilesNewLocation Include="$(BaseIntermediateOutputPath)$(Configuration)\*.nuspec"/> + + $(MSBuildProjectDirectory)/$(MSBuildProjectFile) true - false - true + false + true + false + true + false + true $(BaseIntermediateOutputPath)$(Configuration) $(BaseIntermediateOutputPath) @@ -209,9 +257,52 @@ - - + + - /// Checking whether point belongs to line. @@ -54,5 +55,25 @@ public static bool BelongsToLine(Point lineStart, Point lineEnd, Point point) /// Second point. /// Distance between two points. public static double GetDistance(Point p1, Point p2) => (p1 - p2).Length; + + /// + /// Round position of the vertex, so it would stand in the check + /// + /// Given position + /// The scale of the check in the net + /// Necessary for nodes which labels are wider then pictures + public static System.Windows.Point RoundPosition(System.Windows.Point currentPosition, double checkScale, double currentWidth = wantedWidth) + { + double difference = 0; + if (currentWidth != wantedWidth && currentWidth != 0) + { + difference = (currentWidth - wantedWidth) / 2; + } + + currentPosition.X = Math.Round(currentPosition.X / checkScale) * checkScale - difference; + currentPosition.Y = Math.Round(currentPosition.Y / checkScale) * checkScale; + + return currentPosition; + } } } diff --git a/src/WpfControlsLib/Controls/Scene/NetVisualHost.cs b/src/WpfControlsLib/Controls/Scene/NetVisualHost.cs new file mode 100644 index 00000000..92b6a6e2 --- /dev/null +++ b/src/WpfControlsLib/Controls/Scene/NetVisualHost.cs @@ -0,0 +1,100 @@ +using System; +using System.Windows; +using System.Windows.Media; + +namespace WpfControlsLib.Controls.Scene +{ + /// + /// Class that provides net for scene + /// + public class NetVisualHost : FrameworkElement + { + /// + /// Create a collection of child visual objects + /// + private readonly VisualCollection children; + + /// + /// Init parameters and create new visual collection + /// + /// Height of the scene on the screen + /// Width of the scene on the screen + /// The scale of net check + /// The point that user presses on to move scene + public NetVisualHost(double height, double width, double scale, Point point) + { + children = new VisualCollection(this) + { + CreateDrawingVisualLines(height, width, scale, point) + }; + } + + /// + /// Change the net when params have changed + /// + /// Height of the scene on the screen + /// Width of the scene on the screen + /// The scale of net check + /// The point that user presses on to move scene + public void ChangeNet(double height, double width, double scale, Point point) + { + children.Clear(); + children.Add(CreateDrawingVisualLines(height, width, scale, point)); + } + + /// + /// Draw the net + /// + /// Height of the scene on the screen + /// Width of the scene on the screen + /// The scale of net check + /// The point that user presses on to move scene + /// Net, element of visual collection + private DrawingVisual CreateDrawingVisualLines(double height, double width, double scale, Point point) + { + DrawingVisual drawingVisual = new DrawingVisual(); + DrawingContext drawingContext = drawingVisual.RenderOpen(); + + point = Geometry.RoundPosition(point, scale); + + Pen drawingpen = new Pen(Brushes.WhiteSmoke, 1); + for (var i = point.Y; i < height + point.Y; i += scale) + { + drawingContext.DrawLine(drawingpen, new Point(-width + point.X, i), new Point(width + point.X, i)); + } + for (var i = point.Y; i > -height + point.Y; i -= scale) + { + drawingContext.DrawLine(drawingpen, new Point(-width + point.X, i), new Point(width + point.X, i)); + } + for (var i = point.X; i < width + point.X; i += scale) + { + drawingContext.DrawLine(drawingpen, new Point(i, -height + point.Y), new Point(i, height + point.Y)); + } + for (var i = point.X; i > -width + point.X; i -= scale) + { + drawingContext.DrawLine(drawingpen, new Point(i, -height + point.Y), new Point(i, height + point.Y)); + } + + drawingContext.Close(); + return drawingVisual; + } + + /// + /// Provide a required override for the VisualChildrenCount property + /// + protected override int VisualChildrenCount => children.Count; + + /// + /// Provide a required override for the GetVisualChild method + /// + protected override Visual GetVisualChild(int index) + { + if (index < 0 || index >= children.Count) + { + throw new ArgumentOutOfRangeException(); + } + + return children[index]; + } + } +} diff --git a/src/WpfControlsLib/Controls/Scene/NewVertexName.cs b/src/WpfControlsLib/Controls/Scene/NewVertexName.cs new file mode 100644 index 00000000..ee0b16f1 --- /dev/null +++ b/src/WpfControlsLib/Controls/Scene/NewVertexName.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace WpfControlsLib.Controls.Scene +{ + /// + /// Class for work with vertices names + /// + public static class VertexName + { + /// + /// Change new element name so there would be no same names + /// + /// New element + public static void NewVertexName(Repo.IElement element, List vertexNames) + { + var name = element.Name; + if (element.Name.LastIndexOf('.') != -1) + { + name = string.Empty; + for (var i = 0; i < element.Name.LastIndexOf('.'); i++) + { + name += element.Name[i]; + } + } + + var counter = 1; + while (vertexNames.Contains("a" + name + "." + counter)) + { + counter++; + } + + element.Name = name + "." + counter; + vertexNames.Add("a" + element.Name); + } + } +} diff --git a/src/WpfControlsLib/Controls/Scene/Scene.xaml b/src/WpfControlsLib/Controls/Scene/Scene.xaml index 3dc4c8d0..c304b917 100644 --- a/src/WpfControlsLib/Controls/Scene/Scene.xaml +++ b/src/WpfControlsLib/Controls/Scene/Scene.xaml @@ -21,11 +21,14 @@ xmlns:graphx="http://schemas.panthernet.ru/graphx/" xmlns:scene="clr-namespace:WpfControlsLib.Controls.Scene" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="300"> + d:DesignHeight="300" d:DesignWidth="300" Loaded="LoadNet"> - + + + + diff --git a/src/WpfControlsLib/Controls/Scene/Scene.xaml.cs b/src/WpfControlsLib/Controls/Scene/Scene.xaml.cs index 996de698..9fc0dda0 100644 --- a/src/WpfControlsLib/Controls/Scene/Scene.xaml.cs +++ b/src/WpfControlsLib/Controls/Scene/Scene.xaml.cs @@ -15,6 +15,7 @@ namespace WpfControlsLib.Controls.Scene { using System; + using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -42,10 +43,13 @@ public partial class Scene : UserControl private VertexControl currentVertex; private EdgeControl edgeControl; private Point position; + private Point mousePosition; + private double checkScale = 26.7; private Model model; private Controller controller; private IElementProvider elementProvider; + private NetVisualHost visualHost; public Scene() { @@ -57,9 +61,14 @@ public Scene() this.graphArea.VertexSelected += this.VertexSelectedAction; this.graphArea.EdgeSelected += this.EdgeSelectedAction; + this.graphArea.LostMouseCapture += (sender, args) => this.FixPos(); this.zoomControl.Click += this.ClearSelection; this.zoomControl.MouseDown += this.OnSceneMouseDown; this.zoomControl.Drop += this.ZoomControlDrop; + this.zoomControl.MouseDown += (sender, args) => this.LoadNet(sender, args); + this.zoomControl.ZoomAnimationCompleted += (sender, args) => this.LoadNet(sender, args); + this.zoomControl.MaxZoom = 1.1; + mousePosition = new Point(0, 0); } public event EventHandler ElementManipulationDone; @@ -72,6 +81,11 @@ public Scene() public event EventHandler ElementRemoved; + /// + /// Send when there`s something to be told to the user + /// + public event EventHandler HaveMessage; + public Graph Graph { get; set; } private void InitGraphXLogicCore() @@ -109,6 +123,9 @@ public void Init(Model model, Controller controller, IElementProvider elementPro this.Graph.AddNewVertexControl += (sender, args) => this.AddNewVertexControl(args.DataVertex); this.Graph.AddNewEdgeControl += (sender, args) => this.AddNewEdgeControl(args.EdgeViewModel); this.InitGraphXLogicCore(); + this.model.SavePositions += (sender, args) => this.SavePositions(); + this.model.PlaceVertexCorrectly += (sender, args) => this.PlaceVertexCorrectly(); + this.model.InitVertexNames += (sender, args) => this.InitVertexNames(); } public void Clear() => this.Graph.DataGraph.Clear(); @@ -146,7 +163,7 @@ public void SelectNode(string name) if (this.Graph.DataGraph.Vertices.ToList()[i].Name == name) { var vertex = this.Graph.DataGraph.Vertices.ToList()[i]; - this.NodeSelected?.Invoke(this, new NodeSelectedEventArgs {Node = vertex}); + this.NodeSelected?.Invoke(this, new NodeSelectedEventArgs { Node = vertex }); foreach (var ed in this.graphArea.VertexList) { if (ed.Key == vertex) @@ -191,6 +208,7 @@ private void ZoomControlDrop(object sender, DragEventArgs e) if (element.Metatype == Repo.Metatype.Node) { this.position = this.zoomControl.TranslatePoint(e.GetPosition(this.zoomControl), this.graphArea); + VertexName.NewVertexName(element, this.model.VertexNames); this.CreateNewNode(element); this.ElementManipulationDone?.Invoke(this, EventArgs.Empty); } @@ -335,7 +353,7 @@ private void HandleRoutingPoints(EdgeViewModel edge, GraphX.Measure.Point mouseP private void AddNewVertexControl(NodeViewModel vertex) { var vc = new VertexControl(vertex); - vc.SetPosition(this.position); + vc.SetPosition(Geometry.RoundPosition(this.position, checkScale, vc.ActualWidth)); this.graphArea.AddVertex(vertex, vc); } @@ -379,6 +397,7 @@ private void MenuItemClickedOnVertex(object sender, EventArgs e) command.Add(new RemoveNodeCommand(this.model, vertex.Node)); this.controller.Execute(command); this.DrawGraph(); + PlaceVertexCorrectly(); } private void MenuItemClickEdge(object sender, EventArgs e) @@ -445,5 +464,113 @@ private void CreateNewNode(Repo.IElement element) var command = new CreateNodeCommand(this.model, element); this.controller.Execute(command); } + + /// + /// Save positions to model.positionsTable when the model is saved. + /// If there are some vertices have same names, user will be warned. + /// + private void SavePositions() + { + this.model.PositionsTable = new Dictionary(); + var currentPositions = this.graphArea.GetVertexPositions(); + var firstWarning = false; + foreach (var key in currentPositions.Keys) + { + Point point = new Point(currentPositions[key].X, currentPositions[key].Y); + try + { + this.model.PositionsTable.Add(key.Name, point); + } + catch (ArgumentException) + { + if (!firstWarning) + { + this.HaveMessage?.Invoke(this, "There are vertices with the same names in the model"); + firstWarning = true; + } + } + } + } + + /// + /// Arranges the vertices according to model.positionsTable + /// If not all vertices are in the positionsTable, first time user will be warned, + /// then absent vertices will be added just somewhere + /// + private void PlaceVertexCorrectly() + { + if (this.model.PositionsTable.Count == 0) + { + return; + } + + var vertexList = this.graphArea.VertexList; + var firstWarning = false; + foreach (var key in vertexList.Keys) + { + try + { + vertexList[key].SetPosition(this.model.PositionsTable[key.Name]); + } + catch (KeyNotFoundException) + { + if (!firstWarning) + { + this.HaveMessage?.Invoke(this, "Not all vertices are in the file with positions"); + firstWarning = true; + } + } + } + } + + /// + /// Fill the list of taken names + /// + private void InitVertexNames() + { + this.model.VertexNames = new List(); + foreach (var key in this.graphArea.GetVertexPositions().Keys) + { + this.model.VertexNames.Add(key.Name); + } + } + + /// + /// Fix positions so vertices would be in the checks after moving on scene + /// + private void FixPos() + { + var key = this.currentVertex.GetDataVertex(); + var vertexList = this.graphArea.VertexList; + var currentPosition = vertexList[key].GetPosition(); + var width = vertexList[key].ActualWidth; + vertexList[key].SetPosition(Geometry.RoundPosition(currentPosition, checkScale, width)); + } + + /// + /// Create a host visual derived from the FrameworkElement class + /// Take point from the screen and draw around it the net with necessary size and scale + /// + private void LoadNet(object sender, EventArgs e) + { + var chosenPoint = e as MouseButtonEventArgs; + if (chosenPoint != null) + { + mousePosition = chosenPoint.GetPosition(canvas); + } + + if (this.visualHost == null) + { + this.visualHost = new NetVisualHost(zoomControl.ActualHeight / zoomControl.Zoom, + zoomControl.ActualWidth / zoomControl.Zoom, checkScale, mousePosition); + canvas.Children.Add(visualHost); + } + else + { + this.visualHost.ChangeNet(zoomControl.ActualHeight / zoomControl.Zoom, + zoomControl.ActualWidth / zoomControl.Zoom, checkScale, mousePosition); + } + } } } + diff --git a/src/WpfControlsLib/Model/Model.cs b/src/WpfControlsLib/Model/Model.cs index 80029d36..79b873ce 100644 --- a/src/WpfControlsLib/Model/Model.cs +++ b/src/WpfControlsLib/Model/Model.cs @@ -15,6 +15,8 @@ namespace WpfControlsLib.Model { using System; + using System.Collections.Generic; + using System.Windows; using EditorPluginInterfaces; /// @@ -32,6 +34,7 @@ public Model() { this.Repo = global::Repo.RepoFactory.Create(); this.Constraints = new Constraints.Constraints(); + SaveStartModelsNames(); } public event EventHandler NewVertexAdded; @@ -44,6 +47,17 @@ public Model() public event EventHandler UnsavedChanges; + public event EventHandler SavePositions; + + public event EventHandler PlaceVertexCorrectly; + + public event EventHandler InitVertexNames; + + /// + /// Send when there`s something to be told to the user + /// + public event EventHandler HaveMessage; + /// /// Notifies all views that there are changes so massive that the model shall be completely reloaded /// (for example, when creating new or opening existing file). @@ -71,7 +85,8 @@ public bool HasUnsavedChanges { get => hasUnsavedChanges; - private set { + private set + { if (hasUnsavedChanges && !value) { hasUnsavedChanges = value; @@ -85,6 +100,16 @@ private set { } } + /// + /// Contains positions of the nodes of this model + /// + public Dictionary PositionsTable { get; set; } + + /// + /// List of vertex names in this model + /// + public List VertexNames { get; set; } + /// /// Clears the contents of current repository and creates new empty one. Things like "Do you want to save /// changes?" dialog or even new model selection are not supported yet. @@ -94,7 +119,9 @@ public void New() this.Repo = global::Repo.RepoFactory.Create(); this.CurrentFileName = ""; this.HasUnsavedChanges = false; + this.PositionsTable = new Dictionary(); this.Reinit?.Invoke(this, EventArgs.Empty); + SaveStartModelsNames(); } /// @@ -107,6 +134,17 @@ public void Open(string fileName) this.CurrentFileName = fileName; this.HasUnsavedChanges = false; this.Reinit?.Invoke(this, EventArgs.Empty); + this.InitVertexNames?.Invoke(this, null); + try + { + this.PositionsTable = PositionsLoad.OpenPositions(CurrentFileName); + } + catch (FormatException) + { + this.HaveMessage?.Invoke(this, "The file with vertices positions has data in wrong format"); + this.PositionsTable = new Dictionary(); + } + this.PlaceVertexCorrectly?.Invoke(this, null); } /// @@ -120,6 +158,8 @@ public void Save() } this.Repo.Save(CurrentFileName); + this.SavePositions?.Invoke(this, null); + PositionsLoad.SavePositionsTable(CurrentFileName, PositionsTable); this.HasUnsavedChanges = false; } @@ -170,8 +210,8 @@ public void CreateNode(Repo.IElement element) public void CreateEdge(Repo.IEdge edge, Repo.IElement source, Repo.IElement destination) { if (this.Constraints.AllowCreateOrExistEdge( - this.Repo.Model(this.ModelName).Edges, - source.Name, + this.Repo.Model(this.ModelName).Edges, + source.Name, destination.Name)) { var model = this.Repo.Model(this.ModelName); @@ -192,6 +232,9 @@ public void CreateEdge(Repo.IEdge edge, Repo.IElement source, Repo.IElement dest public void RemoveElement(Repo.IElement element) { + this.SavePositions?.Invoke(this, null); + PositionsLoad.SavePositionsTable(CurrentFileName, PositionsTable); + this.VertexNames.Remove(element.Name); var model = this.Repo.Model(this.ModelName); model.DeleteElement(element); HasUnsavedChanges = true; @@ -229,5 +272,23 @@ private void RaiseElementRemoved(Repo.IElement element) this.ElementRemoved?.Invoke(this, args); } + + /// + /// Adds to the VertexNames the names of the nodes in start models. + /// Need special method, because the first nodes on the scene are not in the graphArea yet + /// + private void SaveStartModelsNames() + { + this.VertexNames = new List(); + + var enumerator = this.Repo.Models.GetEnumerator(); + while (enumerator.MoveNext()) + { + foreach (var node in enumerator.Current.Nodes) + { + VertexNames.Add(node.Name); + } + } + } } } diff --git a/src/WpfControlsLib/Model/PositionsLoad.cs b/src/WpfControlsLib/Model/PositionsLoad.cs new file mode 100644 index 00000000..12b36710 --- /dev/null +++ b/src/WpfControlsLib/Model/PositionsLoad.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; + +namespace WpfControlsLib.Model +{ + /// + /// Is used to open and save positions from txt file with same name as model + /// + public static class PositionsLoad + { + /// + /// Save dictionary with node positions + /// + /// Name of the file with model + public static void SavePositionsTable(string fileName, Dictionary positionsTable) + { + var positionsFileName = GetDicFileName(fileName); + using (FileStream fstream = new FileStream(positionsFileName, FileMode.OpenOrCreate)) + { + foreach (var nodeName in positionsTable.Keys) + { + byte[] array1 = System.Text.Encoding.Default.GetBytes( + nodeName + " " + Convert.ToString(positionsTable[nodeName].X) + + " " + Convert.ToString(positionsTable[nodeName].Y + " ")); + fstream.Write(array1, 0, array1.Length); + } + } + } + + /// + /// Opens file with node positions and fills dictionary + /// + /// Name of the file with saved model to open + /// Whether the positionsFile existed + public static Dictionary OpenPositions(string fileName) + { + var positionsTable = new Dictionary(); + string positionsFileName = GetDicFileName(fileName); + + if (!File.Exists(positionsFileName)) + { + return new Dictionary(); + } + + using (var reader = new StreamReader(positionsFileName)) + { + var str = reader.ReadLine().Split(' '); + for (var i = 0; i < str.Length - 1; i += 3) + { + positionsTable.Add(str[i], new Point( + Convert.ToDouble(str[i + 1]), Convert.ToDouble(str[i + 2]))); + } + } + + return positionsTable; + } + + /// + /// Make name for file with node positions + /// + /// Name of the file containing model + /// the name for file with node positions + private static string GetDicFileName(string fileName) + { + if (fileName == "") + { + return "StartModel.txt"; + } + + string dicFileName = null; + for (var i = 0; i <= fileName.LastIndexOf('.'); i++) + { + dicFileName += fileName[i]; + } + dicFileName += "txt"; + return dicFileName; + } + } +} diff --git a/src/WpfControlsLib/WpfControlsLib.csproj b/src/WpfControlsLib/WpfControlsLib.csproj index 9e260150..d694df4f 100644 --- a/src/WpfControlsLib/WpfControlsLib.csproj +++ b/src/WpfControlsLib/WpfControlsLib.csproj @@ -128,6 +128,8 @@ + + @@ -149,6 +151,7 @@ + diff --git a/src/WpfEditor/View/MainWindow.xaml.cs b/src/WpfEditor/View/MainWindow.xaml.cs index 81cf0f2f..958b0d23 100644 --- a/src/WpfEditor/View/MainWindow.xaml.cs +++ b/src/WpfEditor/View/MainWindow.xaml.cs @@ -82,6 +82,7 @@ public MainWindow() this.scene.ElementRemoved += (sender, args) => this.modelExplorer.RemoveElement(args.Element); this.scene.NodeSelected += (sender, args) => this.attributesView.DataContext = args.Node; this.scene.EdgeSelected += (sender, args) => this.attributesView.DataContext = args.Edge; + this.scene.HaveMessage += ShowMessage; this.scene.Init(this.model, this.controller, new PaletteAdapter(this.palette)); @@ -90,6 +91,8 @@ public MainWindow() this.InitAndLaunchPlugins(); this.InitToolbar(); + + this.model.HaveMessage += ShowMessage; } private void Reinit(object sender, EventArgs e) @@ -246,5 +249,14 @@ private void SaveAs() model.SaveAs(dialog.FileName); } } + + /// + /// Show the message in extra window + /// + /// The message + private void ShowMessage(object sender, string message) + { + MessageBox.Show(message); + } } } \ No newline at end of file diff --git a/src/WpfEditor/View/Templates/GeneralTemplate.xaml b/src/WpfEditor/View/Templates/GeneralTemplate.xaml index ea6349ca..4942adf6 100644 --- a/src/WpfEditor/View/Templates/GeneralTemplate.xaml +++ b/src/WpfEditor/View/Templates/GeneralTemplate.xaml @@ -22,7 +22,7 @@