1 /*
2  *  "GEDKeeper", the personal genealogical database editor.
3  *  Copyright (C) 2009-2021 by Sergey V. Zhdanovskih.
4  *
5  *  This file is part of "GEDKeeper".
6  *
7  *  This program is free software: you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation, either version 3 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 using System;
22 using System.Drawing;
23 using System.Drawing.Drawing2D;
24 using System.Windows.Forms;
25 using BSLib.DataViz.ArborGVT;
26 using GKCore;
27 using GKUI.Platform;
28 
29 namespace GKUI.Components
30 {
31     public sealed class ArborNodeEx : ArborNode
32     {
33         public Color Color;
34         public RectangleF Box;
35 
ArborNodeEx(string sign)36         public ArborNodeEx(string sign) : base(sign)
37         {
38             Color = Color.Gray;
39         }
40     }
41 
42     public sealed class ArborSystemEx : ArborSystem
43     {
44         private WinUITimer fTimer;
45 
ArborSystemEx(double repulsion, double stiffness, double friction, IArborRenderer renderer)46         public ArborSystemEx(double repulsion, double stiffness, double friction, IArborRenderer renderer)
47             : base(repulsion, stiffness, friction, renderer)
48         {
49             this.fTimer = null;
50         }
51 
TimerElapsed(object sender, EventArgs e)52         private void TimerElapsed(object sender, EventArgs e)
53         {
54             TickTimer();
55         }
56 
StartTimer()57         protected override void StartTimer()
58         {
59             fTimer = new WinUITimer(TimerInterval/* / 1000*/, TimerElapsed);
60             fTimer.Start();
61         }
62 
StopTimer()63         protected override void StopTimer()
64         {
65             if (fTimer != null) {
66                 fTimer.Stop();
67                 fTimer.Dispose();
68                 fTimer = null;
69             }
70         }
71 
CreateNode(string sign)72         protected override ArborNode CreateNode(string sign)
73         {
74             return new ArborNodeEx(sign);
75         }
76 
CreateEdge(ArborNode src, ArborNode tgt, double len, double stiffness, bool directed = false)77         protected override ArborEdge CreateEdge(ArborNode src, ArborNode tgt, double len, double stiffness, bool directed = false)
78         {
79             return new ArborEdge(src, tgt, len, stiffness, directed);
80         }
81     }
82 
83     public sealed class ArborViewer : UserControl, IArborRenderer
84     {
85         private bool fEnergyDebug;
86         private ArborNode fDragged;
87         private readonly Font fDrawFont;
88         private bool fNodesDragging;
89         private readonly StringFormat fStrFormat;
90         private readonly ArborSystemEx fSys;
91         private readonly SolidBrush fBlackBrush;
92         private readonly SolidBrush fWhiteBrush;
93 
94         public bool EnergyDebug
95         {
96             get { return fEnergyDebug; }
97             set { fEnergyDebug = value; }
98         }
99 
100         public bool NodesDragging
101         {
102             get { return fNodesDragging; }
103             set { fNodesDragging = value; }
104         }
105 
106         public ArborSystemEx Sys
107         {
108             get { return fSys; }
109         }
110 
ArborViewer()111         public ArborViewer()
112         {
113             BorderStyle = BorderStyle.Fixed3D;
114             TabStop = true;
115             BackColor = Color.White;
116 
117             DoubleBuffered = true;
118             SetStyle(ControlStyles.AllPaintingInWmPaint, true);
119             SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
120 
121             // repulsion - отталкивание, stiffness - тугоподвижность, friction - сила трения
122             fSys = new ArborSystemEx(10000, 500/*1000*/, 0.1, this);
123             fSys.SetViewSize(Width, Height);
124             fSys.AutoStop = false;
125 
126             fEnergyDebug = false;
127             fDrawFont = new Font("Calibri", 9);
128 
129             fStrFormat = new StringFormat();
130             fStrFormat.Alignment = StringAlignment.Center;
131             fStrFormat.LineAlignment = StringAlignment.Center;
132 
133             fBlackBrush = new SolidBrush(Color.Black);
134             fWhiteBrush = new SolidBrush(Color.White);
135             fDragged = null;
136             fNodesDragging = false;
137         }
138 
Dispose(bool disposing)139         protected override void Dispose(bool disposing)
140         {
141             if (disposing)
142             {
143                 fSys.Dispose();
144                 fDrawFont.Dispose();
145                 fWhiteBrush.Dispose();
146                 fBlackBrush.Dispose();
147                 fStrFormat.Dispose();
148             }
149             base.Dispose(disposing);
150         }
151 
OnSizeChanged(EventArgs e)152         protected override void OnSizeChanged(EventArgs e)
153         {
154             base.OnSizeChanged(e);
155 
156             fSys.SetViewSize(Width, Height);
157             Invalidate();
158         }
159 
OnPaint(PaintEventArgs e)160         protected override void OnPaint(PaintEventArgs e)
161         {
162             Graphics gfx = e.Graphics;
163 
164             try {
165                 gfx.SmoothingMode = SmoothingMode.AntiAlias;
166 
167                 foreach (ArborNode node in fSys.Nodes) {
168                     var xnode = node as ArborNodeEx;
169 
170                     xnode.Box = getNodeRect(gfx, node);
171                     gfx.FillRectangle(new SolidBrush(xnode.Color), xnode.Box);
172                     gfx.DrawString(node.Sign, fDrawFont, fWhiteBrush, xnode.Box, fStrFormat);
173                 }
174 
175                 using (Pen grayPen = new Pen(Color.Gray, 1)) {
176                     grayPen.StartCap = LineCap.NoAnchor;
177                     grayPen.EndCap = LineCap.ArrowAnchor;
178 
179                     foreach (ArborEdge edge in fSys.Edges) {
180                         var srcNode = edge.Source as ArborNodeEx;
181                         var tgtNode = edge.Target as ArborNodeEx;
182 
183                         ArborPoint pt1 = fSys.GetViewCoords(srcNode.Pt);
184                         ArborPoint pt2 = fSys.GetViewCoords(tgtNode.Pt);
185 
186                         ArborPoint tail = intersect_line_box(pt1, pt2, srcNode.Box);
187                         ArborPoint head = (tail.IsNull()) ? ArborPoint.Null : intersect_line_box(tail, pt2, tgtNode.Box);
188 
189                         if (!head.IsNull() && !tail.IsNull()) {
190                             gfx.DrawLine(grayPen, (int)tail.X, (int)tail.Y, (int)head.X, (int)head.Y);
191                         }
192                     }
193                 }
194 
195                 if (fEnergyDebug) {
196                     string energy = "max=" + fSys.EnergyMax.ToString("0.00000") + ", mean=" + fSys.EnergyMean.ToString("0.00000");
197                     gfx.DrawString(energy, fDrawFont, fBlackBrush, 10, 10);
198                 }
199             } catch (Exception ex) {
200                 Logger.WriteError("ArborViewer.OnPaint()", ex);
201             }
202 
203             base.OnPaint(e);
204         }
205 
intersect_line_line(ArborPoint p1, ArborPoint p2, ArborPoint p3, ArborPoint p4)206         public static ArborPoint intersect_line_line(ArborPoint p1, ArborPoint p2, ArborPoint p3, ArborPoint p4)
207         {
208             double denom = ((p4.Y - p3.Y) * (p2.X - p1.X) - (p4.X - p3.X) * (p2.Y - p1.Y));
209             if (denom == 0) return ArborPoint.Null; // lines are parallel
210 
211             double ua = ((p4.X - p3.X) * (p1.Y - p3.Y) - (p4.Y - p3.Y) * (p1.X - p3.X)) / denom;
212             double ub = ((p2.X - p1.X) * (p1.Y - p3.Y) - (p2.Y - p1.Y) * (p1.X - p3.X)) / denom;
213 
214             if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return ArborPoint.Null;
215 
216             return new ArborPoint(p1.X + ua * (p2.X - p1.X), p1.Y + ua * (p2.Y - p1.Y));
217         }
218 
intersect_line_box(ArborPoint p1, ArborPoint p2, RectangleF boxTuple)219         public ArborPoint intersect_line_box(ArborPoint p1, ArborPoint p2, RectangleF boxTuple)
220         {
221             double bx = boxTuple.X;
222             double by = boxTuple.Y;
223             double bw = boxTuple.Width;
224             double bh = boxTuple.Height;
225 
226             ArborPoint tl = new ArborPoint(bx, by);
227             ArborPoint tr = new ArborPoint(bx + bw, by);
228             ArborPoint bl = new ArborPoint(bx, by + bh);
229             ArborPoint br = new ArborPoint(bx + bw, by + bh);
230 
231             ArborPoint pt;
232 
233             pt = intersect_line_line(p1, p2, tl, tr);
234             if (!pt.IsNull()) return pt;
235 
236             pt = intersect_line_line(p1, p2, tr, br);
237             if (!pt.IsNull()) return pt;
238 
239             pt = intersect_line_line(p1, p2, br, bl);
240             if (!pt.IsNull()) return pt;
241 
242             pt = intersect_line_line(p1, p2, bl, tl);
243             if (!pt.IsNull()) return pt;
244 
245             return ArborPoint.Null;
246         }
247 
start()248         public void start()
249         {
250             fSys.Start();
251         }
252 
OnMouseDown(MouseEventArgs e)253         protected override void OnMouseDown(MouseEventArgs e)
254         {
255             base.OnMouseDown(e);
256             if (!Focused) Focus();
257 
258             Point mpt = e.Location;
259             if (fNodesDragging) {
260                 fDragged = fSys.GetNearestNode(mpt.X, mpt.Y);
261 
262                 if (fDragged != null) {
263                     fDragged.Fixed = true;
264                 }
265             }
266         }
267 
OnMouseUp(MouseEventArgs e)268         protected override void OnMouseUp(MouseEventArgs e)
269         {
270             base.OnMouseUp(e);
271 
272             if (fNodesDragging && fDragged != null) {
273                 fDragged.Fixed = false;
274                 //fDragged.Mass = 1000;
275                 fDragged = null;
276             }
277         }
278 
OnMouseMove(MouseEventArgs e)279         protected override void OnMouseMove(MouseEventArgs e)
280         {
281             base.OnMouseMove(e);
282 
283             Point mpt = e.Location;
284             if (fNodesDragging && fDragged != null) {
285                 fDragged.Pt = fSys.GetModelCoords(mpt.X, mpt.Y);
286             }
287         }
288 
getNodeRect(Graphics gfx, ArborNode node)289         public RectangleF getNodeRect(Graphics gfx, ArborNode node)
290         {
291             SizeF tsz = gfx.MeasureString(node.Sign, fDrawFont);
292             float w = tsz.Width + 10;
293             float h = tsz.Height + 4;
294             ArborPoint pt = fSys.GetViewCoords(node.Pt);
295             pt.X = Math.Floor(pt.X);
296             pt.Y = Math.Floor(pt.Y);
297 
298             return new RectangleF((float)pt.X - w / 2, (float)pt.Y - h / 2, w, h);
299         }
300 
getNodeByCoord(int x, int y)301         public ArborNode getNodeByCoord(int x, int y)
302         {
303             return fSys.GetNearestNode(x, y);
304 
305             /*foreach (ArborNode node in fSys.Nodes)
306             {
307                 if (node.Box.Contains(x, y)) {
308                     return node;
309                 }
310             }
311             return null;*/
312         }
313     }
314 }
315