Blame mono/Diagram/ActiveDiagram.cs

777717
/*
777717
    ......... 2015 Ivan Mahonin
777717
777717
    This program is free software: you can redistribute it and/or modify
777717
    it under the terms of the GNU General Public License as published by
777717
    the Free Software Foundation, either version 3 of the License, or
777717
    (at your option) any later version.
777717
777717
    This program is distributed in the hope that it will be useful,
777717
    but WITHOUT ANY WARRANTY; without even the implied warranty of
777717
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
777717
    GNU General Public License for more details.
777717
777717
    You should have received a copy of the GNU General Public License
777717
    along with this program.  If not, see <http: licenses="" www.gnu.org="">.</http:>
777717
*/
777717
777717
using System;
8cb222
using System.Collections.Generic;
8cb222
using System.Data.Linq;
8cb222
using System.Drawing;
8cb222
using System.IO;
8cb222
using System.Globalization;
8cb222
8cb222
namespace Diagram {
8cb222
8cb222
    public class ActiveBlock {
8cb222
        public Block block;
8cb222
        public readonly List<activelink> links = new List<activelink>();</activelink></activelink>
8cb222
        public PointF position;
8cb222
8cb222
        public Font captionFont;
8cb222
        public Font textFont;
8cb222
        public float margin;
8cb222
        public float padding;
8cb222
        public Pen pen;
8cb222
        public Brush brush;
8cb222
8cb222
        public string caption;
8cb222
        public string text;
8cb222
        public SizeF captionSize;
8cb222
        public SizeF textSize;
8cb222
        public float distance;
8cb222
8cb222
        public SizeF size;
8cb222
        public SizeF clientSize;
8cb222
8cb222
        public PointF getCenter() {
8cb222
            return new PointF(
8cb222
                position.X + 0.5f*size.Width,
8cb222
                position.Y + 0.5f*size.Height );
8cb222
        }
8cb222
8cb222
        public void measure() {
8cb222
            float width = captionFont.GetHeight() * 15f;
8cb222
            caption = TextUtils.wrap(block.caption, width, captionFont);
8cb222
            captionSize = TextUtils.measure(caption, captionFont);
8cb222
            text = TextUtils.wrap(block.text, width, textFont);
8cb222
            textSize = TextUtils.measure(text, textFont);
8cb222
            if (caption != "" && text != "")
8cb222
                distance = 0.3f * Math.Max(captionFont.GetHeight(), textFont.GetHeight());
8cb222
            clientSize = new SizeF(
8cb222
                Math.Max(captionSize.Width, textSize.Width),
8cb222
                captionSize.Height + distance + textSize.Height );
8cb222
            size = new SizeF(clientSize.Width + 2f*padding, clientSize.Height + 2f*padding);
8cb222
        }
8cb222
8cb222
        public void draw(Graphics g) {
8cb222
            Shapes.drawRoundRect(g, new RectangleF(position, size), 2f*padding, pen, brush);
8cb222
8cb222
            g.DrawString(
8cb222
                caption,
8cb222
                captionFont,
8cb222
                Brushes.Black,
8cb222
                new RectangleF(
8cb222
                    position.X + 0.5f*(size.Width - captionSize.Width),
8cb222
                    position.Y + padding,
8cb222
                    captionSize.Width,
8cb222
                    captionSize.Height ));
8cb222
8cb222
            g.DrawString(
8cb222
                text,
8cb222
                textFont,
8cb222
                Brushes.Black,
8cb222
                new RectangleF(
8cb222
                    position.X + padding,
8cb222
                    position.Y + padding + captionSize.Height + distance,
8cb222
                    clientSize.Width,
8cb222
                    clientSize.Height - captionSize.Height - distance ));
8cb222
        }
8cb222
8cb222
        class LinkDesc {
8cb222
            public ActiveLink link;
8cb222
            public PointF src;
8cb222
            public PointF dst;
8cb222
        }
8cb222
8cb222
        class SideDesc {
8cb222
            public PointF a;
8cb222
            public PointF b;
8cb222
            public PointF normal;
8cb222
            public readonly List<linkdesc> links = new List<linkdesc>();</linkdesc></linkdesc>
8cb222
8cb222
            public SideDesc(PointF a, PointF b) {
8cb222
                this.a = a;
8cb222
                this.b = b;
8cb222
                float l = Geometry.lineLength(a, b);
8cb222
                normal = new PointF((b.Y - a.Y)/l, (a.X - b.X)/l);
8cb222
            }
8cb222
        }
8cb222
8cb222
        public void placeLinks() {
8cb222
            PointF lt = position;
8cb222
            PointF rb = new PointF(lt.X + size.Width, lt.Y + size.Height);
8cb222
            PointF lb = new PointF(lt.X, rb.Y);
8cb222
            PointF rt = new PointF(rb.X, lt.Y);
8cb222
            PointF center = getCenter();
8cb222
8cb222
            List<sidedesc> sides = new List<sidedesc>() {</sidedesc></sidedesc>
8cb222
                new SideDesc(lt, rt),
8cb222
                new SideDesc(rt, rb),
8cb222
                new SideDesc(rb, lb),
8cb222
                new SideDesc(lb, lt) };
8cb222
8cb222
            foreach(ActiveLink link in links) {
8cb222
                link.visible = false;
8cb222
                LinkDesc linkDesc = new LinkDesc();
8cb222
                linkDesc.link = link;
8cb222
                if (link.src == this) {
8cb222
                    linkDesc.src = center;
8cb222
                    linkDesc.dst = link.dst.getCenter();
8cb222
                } else {
8cb222
                    linkDesc.src = link.src.getCenter();
8cb222
                    linkDesc.dst = center;
8cb222
                }
8cb222
                foreach(SideDesc side in sides) {
8cb222
                    PointF p;
8cb222
                    if (Geometry.findIntersection(side.a, side.b, linkDesc.src, linkDesc.dst, out p)) {
8cb222
                        link.visible = true;
8cb222
                        linkDesc.src = p;
8cb222
                        side.links.Add(linkDesc);
8cb222
                        break;
8cb222
                    }
8cb222
                }
8cb222
            }
8cb222
8cb222
            foreach(SideDesc side in sides) {
8cb222
                side.links.Sort(delegate(LinkDesc a, LinkDesc b) {
8cb222
                    return Geometry.comparePointsAtLine(a.src, b.src, side.a, side.b); });
8cb222
                for(int i = 0; i < side.links.Count; ++i) {
8cb222
                    ActiveLink link = side.links[i].link;
8cb222
                    PointF p = Geometry.pointAtLine(side.a, side.b, i, side.links.Count, padding);
8cb222
                    if (link.src == this) {
8cb222
                        link.srcBase = p;
8cb222
                        link.srcTangent = side.normal;
8cb222
                        link.pen = pen;
8cb222
                    } else {
8cb222
                        link.dstBase = p;
8cb222
                        link.dstTangent = new PointF(-side.normal.X, -side.normal.Y);
8cb222
                    }
8cb222
                }
8cb222
            }
8cb222
        }
8cb222
    }
8cb222
8cb222
    public class ActiveLink {
8cb222
        public Link link;
8cb222
        public ActiveBlock src;
8cb222
        public ActiveBlock dst;
8cb222
8cb222
        public SizeF arrowSize;
8cb222
        public Pen pen;
8cb222
8cb222
        public bool visible;
8cb222
        public PointF srcBase;
8cb222
        public PointF srcTangent;
8cb222
        public PointF dstBase;
8cb222
        public PointF dstTangent;
8cb222
8cb222
        public PointF[] bezier;
8cb222
        public PointF[] arrow;
8cb222
8cb222
        public static readonly PointF[] arrowTemplate = new PointF[] {
8cb222
            new PointF(0f, 0f),
8cb222
            new PointF(1f, 1f),
8cb222
            new PointF(0f, 0.75f) };
8cb222
8cb222
        public static readonly float arrowOffset = 0.75f;
8cb222
8cb222
        public static PointF[] makeArrow(PointF point, PointF tangent, SizeF size) {
8cb222
            PointF[] arrow = new PointF[2*arrowTemplate.Length];
8cb222
            PointF tx = new PointF( -0.5f*tangent.Y*size.Width, 0.5f*tangent.X*size.Width  );
8cb222
            PointF ty = new PointF( -tangent.X*size.Height, -tangent.Y*size.Height );
8cb222
            for(int i = 0; i < arrowTemplate.Length; ++i) {
8cb222
                arrow[i] = new PointF(
8cb222
                    point.X + tx.X*arrowTemplate[i].X + ty.X*arrowTemplate[i].Y,
8cb222
                    point.Y + tx.Y*arrowTemplate[i].X + ty.Y*arrowTemplate[i].Y );
8cb222
                arrow[2*arrowTemplate.Length - i - 1] = new PointF(
8cb222
                    point.X - tx.X*arrowTemplate[i].X + ty.X*arrowTemplate[i].Y,
8cb222
                    point.Y - tx.Y*arrowTemplate[i].X + ty.Y*arrowTemplate[i].Y );
8cb222
            }
8cb222
            return arrow;
8cb222
        }
8cb222
8cb222
        public void place() {
8cb222
            if (!visible) return;
8cb222
8cb222
            float dx = 0.5f*Math.Abs(dstBase.X - srcBase.X);
8cb222
            float dy = 0.5f*Math.Abs(dstBase.Y - srcBase.Y);
8cb222
            float l = (float)Math.Sqrt(dx*dx + dy*dy);
8cb222
            if (l < Geometry.precision) { visible = false; return; }
8cb222
            dx = Math.Max(dx, 0.25f*l);
8cb222
            dy = Math.Max(dy, 0.25f*l);
8cb222
8cb222
            float sl = l;
8cb222
            if (Math.Abs(srcTangent.X) > Geometry.precision)
8cb222
                sl = Math.Min(sl, dx/Math.Abs(srcTangent.X));
8cb222
            if (Math.Abs(srcTangent.Y) > Geometry.precision)
8cb222
                sl = Math.Min(sl, dy/Math.Abs(srcTangent.Y));
8cb222
            PointF st = new PointF(srcTangent.X*sl, srcTangent.Y*sl);
8cb222
8cb222
            float dl = l;
8cb222
            if (Math.Abs(dstTangent.X) > Geometry.precision)
8cb222
                dl = Math.Min(dl, dx/Math.Abs(dstTangent.X));
8cb222
            if (Math.Abs(dstTangent.Y) > Geometry.precision)
8cb222
                dl = Math.Min(dl, dy/Math.Abs(dstTangent.Y));
8cb222
            PointF dt = new PointF(dstTangent.X*sl, dstTangent.Y*sl);
8cb222
8cb222
            bezier = new PointF[] {
8cb222
                srcBase,
8cb222
                new PointF(srcBase.X + st.X, srcBase.Y + st.Y),
8cb222
                new PointF(dstBase.X - dt.X, dstBase.Y - dt.Y),
8cb222
                new PointF(dstBase.X - dstTangent.X*(arrowOffset*arrowSize.Height + 0.5f*pen.Width),
8cb222
                           dstBase.Y - dstTangent.Y*(arrowOffset*arrowSize.Height + 0.5f*pen.Width))
8cb222
            };
8cb222
8cb222
            arrow = makeArrow(
8cb222
                new PointF(dstBase.X - 0.5f*dstTangent.X*pen.Width,
8cb222
                           dstBase.Y - 0.5f*dstTangent.Y*pen.Width),
8cb222
                dstTangent,
8cb222
                arrowSize );
8cb222
        }
8cb222
8cb222
        public void draw(Graphics g) {
8cb222
            if (!visible) return;
8cb222
            g.DrawBeziers(pen, bezier);
8cb222
            g.FillPolygon(new SolidBrush(pen.Color), arrow);
8cb222
            //g.DrawPolygon(pen, arrow);
8cb222
        }
8cb222
    }
8cb222
8cb222
    public class ActiveDiagram {
8cb222
        public Diagram diagram;
8cb222
8cb222
        public readonly Dictionary<string, pointf=""> positions = new Dictionary<string, pointf="">();</string,></string,>
8cb222
8cb222
        public Font captionFont;
8cb222
        public Font textFont;
8cb222
        public float margin;
8cb222
        public float padding;
8cb222
        public SizeF arrowSize;
8cb222
        public Pen pen;
8cb222
        public Brush brush;
8cb222
8cb222
        public readonly Dictionary<string, activeblock=""> blocks = new Dictionary<string, activeblock="">();</string,></string,>
8cb222
        public readonly Dictionary<string, activelink=""> links = new Dictionary<string, activelink="">();</string,></string,>
8cb222
8cb222
        public RectangleF bounds;
8cb222
8cb222
        public void savePositions(string filename) {
8cb222
            remeberPositions();
8cb222
            CultureInfo ci = new CultureInfo("en-US");
8cb222
            List<string> lines = new List<string>();</string></string>
8cb222
            foreach(KeyValuePair<string, pointf=""> pair in positions) {</string,>
8cb222
                lines.Add(pair.Key);
8cb222
                lines.Add(pair.Value.X.ToString(ci));
8cb222
                lines.Add(pair.Value.Y.ToString(ci));
8cb222
            }
8cb222
            File.Create(filename).Close();
8cb222
            File.WriteAllLines(filename, lines);
8cb222
        }
8cb222
8cb222
        public void loadPositions(string filename) {
8cb222
            CultureInfo ci = new CultureInfo("en-US");
8cb222
            string[] lines = File.ReadAllLines(filename);
8cb222
            for(int i = 0; i < lines.Length - 2; i += 3) {
8cb222
                string k = lines[i];
8cb222
                PointF p = new PointF(
8cb222
                    float.Parse(lines[i+1], ci),
8cb222
                    float.Parse(lines[i+2], ci) );
8cb222
                if (positions.ContainsKey(k))
8cb222
                    positions[k] = p;
8cb222
                else
8cb222
                    positions.Add(k, p);
8cb222
            }
8cb222
            restorePositions();
8cb222
        }
8cb222
8cb222
        public void remeberPositions() {
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                if (positions.ContainsKey(pair.Key))
8cb222
                    positions[pair.Key] = pair.Value.position;
8cb222
                else
8cb222
                    positions.Add(pair.Key, pair.Value.position);
8cb222
        }
8cb222
8cb222
        public void restorePositions() {
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                if (positions.ContainsKey(pair.Key))
8cb222
                    pair.Value.position = positions[pair.Key];
8cb222
            placeLinks();
8cb222
        }
8cb222
8cb222
        // insert/remove blocks and links calculate sizes
8cb222
        public void reloadDiagram() {
8cb222
            remeberPositions();
8cb222
8cb222
            bool retry;
8cb222
8cb222
            // blocks
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                pair.Value.block = null;
8cb222
            foreach(KeyValuePair<string, block=""> pair in diagram.blocks) {</string,>
8cb222
                if (!blocks.ContainsKey(pair.Key))
8cb222
                    blocks.Add(pair.Key, new ActiveBlock());
8cb222
                ActiveBlock b = blocks[pair.Key];
8cb222
                b.block = pair.Value;
8cb222
                b.links.Clear();
8cb222
                b.captionFont = captionFont;
8cb222
                b.textFont = textFont;
8cb222
                b.margin = margin;
8cb222
                b.padding = padding;
8cb222
                b.pen = new Pen(new SolidBrush(b.block.color), pen.Width);
8cb222
                b.brush = brush;
8cb222
            }
8cb222
            retry = true;
8cb222
            while(retry) {
8cb222
                retry = false;
8cb222
                foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                    if (pair.Value.block == null)
8cb222
                        { blocks.Remove(pair.Key); retry = true; break; }
8cb222
            }
8cb222
8cb222
            // links
8cb222
            foreach(KeyValuePair<string, activelink=""> pair in links)</string,>
8cb222
                pair.Value.link = null;
8cb222
            foreach(KeyValuePair<string, link=""> pair in diagram.links) {</string,>
8cb222
                if ( blocks.ContainsKey(pair.Value.srcId)
8cb222
                  && blocks.ContainsKey(pair.Value.dstId) )
8cb222
                {
8cb222
                    if (!links.ContainsKey(pair.Key))
8cb222
                        links.Add(pair.Key, new ActiveLink());
8cb222
                    ActiveLink l = links[pair.Key];
8cb222
                    l.link = pair.Value;
8cb222
                    l.src = blocks[l.link.srcId];
8cb222
                    l.src.links.Add(l);
8cb222
                    l.dst = blocks[l.link.dstId];
8cb222
                    l.dst.links.Add(l);
8cb222
                    l.arrowSize = arrowSize;
8cb222
                    l.pen = pen;
8cb222
                }
8cb222
            }
8cb222
            retry = true;
8cb222
            while(retry) {
8cb222
                retry = false;
8cb222
                foreach(KeyValuePair<string, activelink=""> pair in links)</string,>
8cb222
                    if (pair.Value.link == null)
8cb222
                        { links.Remove(pair.Key); retry = true; break; }
8cb222
            }
8cb222
8cb222
            measureBlocks();
8cb222
        }
8cb222
8cb222
        public void measureBlocks() {
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                pair.Value.measure();
8cb222
            placeLinks();
8cb222
        }
8cb222
8cb222
        public void placeLinks() {
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                pair.Value.placeLinks();
8cb222
            foreach(KeyValuePair<string, activelink=""> pair in links)</string,>
8cb222
                pair.Value.place();
8cb222
            recalcBounds();
8cb222
        }
8cb222
8cb222
        public void recalcBounds() {
8cb222
            if (blocks.Count == 0) { bounds = new RectangleF(); return; }
8cb222
            float minx = float.PositiveInfinity;
8cb222
            float miny = float.PositiveInfinity;
8cb222
            float maxx = float.NegativeInfinity;
8cb222
            float maxy = float.NegativeInfinity;
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks) {</string,>
8cb222
                minx = Math.Min(minx, pair.Value.position.X - pair.Value.margin);
8cb222
                miny = Math.Min(miny, pair.Value.position.Y - pair.Value.margin);
8cb222
                maxx = Math.Max(maxx, pair.Value.position.X + pair.Value.size.Width + pair.Value.margin);
8cb222
                maxy = Math.Max(maxy, pair.Value.position.Y + pair.Value.size.Height + pair.Value.margin);
8cb222
            }
8cb222
            bounds = new RectangleF(minx, miny, maxx - minx, maxy - miny);
8cb222
        }
8cb222
8cb222
        public void draw(Graphics g) {
8cb222
            foreach(KeyValuePair<string, activelink=""> pair in links)</string,>
8cb222
                pair.Value.draw(g);
8cb222
            foreach(KeyValuePair<string, activeblock=""> pair in blocks)</string,>
8cb222
                pair.Value.draw(g);
8cb222
        }
8cb222
    }
8cb222
}
8cb222