703 lines
22 KiB
C#

using System;
using System.Drawing;
using System.Collections;
using DevComponents.DotNetBar;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Collections.Generic;
namespace DevComponents.AdvTree.Layout
{
/// <summary>
/// Summary description for NodeLayout.
/// </summary>
internal abstract class NodeLayout
{
#region Private Variables
protected int m_Height=0;
protected int m_Width=0;
protected AdvTree m_Tree=null;
protected Rectangle m_ClientArea;
//protected int m_ExpandAreaWidth=8;
protected Size m_ExpandPartSize=new Size(8,8);
private Size _CachedExpandPartSize = Size.Empty;
private int m_CommandAreaWidth=10;
private int m_TreeLayoutChildNodeIndent = 16;
private System.Windows.Forms.LeftRightAlignment m_LeftRight=System.Windows.Forms.LeftRightAlignment.Left;
private int m_NodeVerticalSpacing=3;
private int m_NodeHorizontalSpacing=0;
private CellLayout m_CellLayout=null;
private Graphics m_Graphics=null;
#endregion
public NodeLayout(AdvTree treeControl, Rectangle clientArea, LayoutSettings layoutSettings)
{
m_Tree=treeControl;
m_ClientArea=clientArea;
_LayoutSettings = layoutSettings;
}
/// <summary>
/// Performs layout of the nodes inside of the tree control.
/// </summary>
public virtual void PerformLayout()
{
}
public virtual void UpdateTopLevelColumnsWidth()
{
}
private LayoutSettings _LayoutSettings;
/// <summary>
/// Gets or sets layout settings.
/// </summary>
public LayoutSettings LayoutSettings
{
get { return _LayoutSettings; }
set { _LayoutSettings = value; }
}
/// <summary>
/// Performs layout for single unassigned node. Node does not have to be part of the tree control.
/// </summary>
/// <param name="node">Node to perform layout on.</param>
public virtual void PerformSingleNodeLayout(Node node)
{
if(node==null)
return;
this.PrepareStyles();
// Get default Columns
System.Drawing.Graphics g=this.GetGraphics();
SmoothingMode sm = g.SmoothingMode;
TextRenderingHint th = g.TextRenderingHint;
if (m_Tree.AntiAlias)
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
//g.TextRenderingHint = DisplayHelp.AntiAliasTextRenderingHint;
}
try
{
NodeLayoutContextInfo layoutInfo=this.GetDefaultNodeLayoutContextInfo(g);
layoutInfo.ContextNode=node;
if (node.IsDragNode)
layoutInfo.DefaultColumns = new NodeColumnInfo(new List<ColumnInfo>(), false);
LayoutNode(layoutInfo);
}
finally
{
if (m_Tree.AntiAlias)
{
g.SmoothingMode = sm;
//g.TextRenderingHint = th;
}
g.Dispose();
}
}
public int Width
{
get {return m_Width;}
}
public int Height
{
get {return m_Height;}
}
public Rectangle ClientArea
{
get {return m_ClientArea;}
set {m_ClientArea=value;}
}
public Graphics Graphics
{
get { return m_Graphics;}
set { m_Graphics = value;}
}
internal bool DisposeGraphics
{
get
{
return (m_Graphics == null);
}
}
protected virtual System.Drawing.Graphics GetGraphics()
{
if(m_Graphics!=null)
return m_Graphics;
Graphics g=m_Tree.CreateGraphics();
return g;
}
/// <summary>
/// Resizes all styles and prepares them for layout.
/// </summary>
protected virtual void PrepareStyles()
{
// Resize styles if needed
foreach(ElementStyle es in m_Tree.Styles)
{
if(es.SizeChanged)
ElementStyleLayout.CalculateStyleSize(es,m_Tree.Font);
}
if (_LayoutSettings != null)
_CachedExpandPartSize = Dpi.Size(_LayoutSettings.ExpandPartSize);
else
_CachedExpandPartSize = Dpi.Size(m_ExpandPartSize);
}
/// <summary>
/// Returns default top-level columns for tree control.
/// </summary>
/// <returns>Returns array list of ColumnInfo objects.</returns>
protected virtual NodeColumnInfo GetDefaultColumnInfo()
{
List<ColumnInfo> ci = new List<ColumnInfo>();
NodeColumnInfo info = new NodeColumnInfo(ci, false);
ColumnHeaderCollection columns=m_Tree.Columns;
//int treeWidth = m_Tree.ClientRectangle.Width;
if(columns!=null)
{
for (int i = 0; i < columns.Count; i++)
{
int columnIndex = columns.DisplayIndexMap[i];
ColumnHeader h = columns[columnIndex];
ColumnInfo columnInfo = new ColumnInfo(h.Bounds.Width, h.Visible, h, columnIndex);
ci.Add(columnInfo);
info.HasAutoSizeColumn |= columnInfo.AutoSize;
}
}
return info;
}
/// <summary>
/// Returns column information for a given node.
/// </summary>
/// <param name="node">Node to return column information for</param>
/// <returns>Returns array list of ColumnInfo objects or null if there are no columns defined.</returns>
protected virtual NodeColumnInfo GetNodeColumnInfo(Node node)
{
if (!node.HasColumns)
{
return null;
}
List<ColumnInfo> ci = new List<ColumnInfo>();
NodeColumnInfo info = new NodeColumnInfo(ci, false);
ColumnHeaderCollection columns = node.NodesColumns;
for (int i = 0; i < columns.Count; i++)
{
int columnIndex = columns.DisplayIndexMap[i];
ColumnHeader h = columns[columnIndex];
ColumnInfo columnInfo = new ColumnInfo(h.Bounds.Width, h.Visible, h, columnIndex);
ci.Add(columnInfo);
info.HasAutoSizeColumn |= columnInfo.AutoSize;
}
return info;
}
///// <summary>
///// Gets or sets the vertical spacing between nodes in pixels.
///// </summary>
//public virtual int NodeVerticalSpacing
//{
// get {return m_NodeVerticalSpacing;}
// set {m_NodeVerticalSpacing=value;}
//}
///// <summary>
///// Gets or sets the horizontal spacing between nodes in pixels.
///// </summary>
//public virtual int NodeHorizontalSpacing
//{
// get {return m_NodeHorizontalSpacing;}
// set {m_NodeHorizontalSpacing=value;}
//}
/// <summary>
/// Gets or sets the child node indent in pixels.
/// </summary>
public virtual int TreeLayoutChildNodeIndent
{
get {return m_TreeLayoutChildNodeIndent; }
set { m_TreeLayoutChildNodeIndent = value; }
}
/// <summary>
/// Returns column header collection for the given column template name.
/// </summary>
/// <param name="name">Name of the column template.</param>
/// <returns>Column header collection or null if template name cannot be found.</returns>
public virtual ColumnHeaderCollection GetColumnHeader(string name)
{
if(name=="" || name==null)
return null;
return m_Tree.Headers.GetByName(name).Columns;
}
//private int _ExpandAreaWidth = 24;
///// <summary>
///// Returns width of the expand button area. Default is 24 pixels.
///// </summary>
//public virtual int ExpandAreaWidth
//{
// get { return _ExpandAreaWidth; }
// set
// {
// _ExpandAreaWidth = value;
// }
//}
///// <summary>
///// Gets or sets width of command button area. Default is 8 pixels.
///// </summary>
//public virtual int CommandAreaWidth
//{
// get {return m_CommandAreaWidth;}
// set {m_CommandAreaWidth=value;}
//}
/// <summary>
/// Sets the position and size of the node command button.
/// </summary>
/// <param name="layoutInfo">Node layout context information</param>
protected virtual void LayoutCommandPart(NodeLayoutContextInfo layoutInfo, ElementStyle nodeStyle)
{
// Command part is right-aligned just before the node border
Rectangle bounds = new Rectangle(layoutInfo.ContextNode.ContentBounds.Right - this.LayoutSettings.CommandAreaWidth -
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Border,eStyleSide.Right),layoutInfo.ContextNode.ContentBounds.Y+
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Border,eStyleSide.Top),
this.LayoutSettings.CommandAreaWidth, layoutInfo.ContextNode.ContentBounds.Height -
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Border,eStyleSide.Top)-
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Border,eStyleSide.Bottom));
// Rectangle bounds=new Rectangle(layoutInfo.ContextNode.ContentBounds.Right-this.CommandAreaWidth-
// ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Border,eStyleSide.Right),layoutInfo.ContextNode.ContentBounds.Y,
// this.CommandAreaWidth, layoutInfo.ContextNode.ContentBounds.Height);
layoutInfo.ContextNode.CommandBoundsRelative=bounds;
}
/// <summary>
/// Determines the rectangle of the +/- part of the tree node that is used to expand node.
/// </summary>
/// <param name="layoutInfo">Node layout context information</param>
protected virtual void LayoutExpandPart(NodeLayoutContextInfo layoutInfo, bool bLeftNode, int x)
{
Node node=layoutInfo.ContextNode;
Size partSize=GetExpandPartSize();
Rectangle bounds=new Rectangle(x,0,partSize.Width,partSize.Height);
if (node.ExpandPartVerticalAlignment == eVerticalAlign.Middle)
bounds.Y = (node.BoundsRelative.Height - bounds.Height) / 2;
else if (node.ExpandPartVerticalAlignment == eVerticalAlign.Top)
bounds.Y = Dpi.Height3;
else
bounds.Y = node.BoundsRelative.Height - partSize.Height - Dpi.Height3;
if (bLeftNode || layoutInfo.ExpandPartAlignedLeft && layoutInfo.LeftToRight)
bounds.X += (layoutInfo.ExpandAreaWidth - bounds.Width) / 2;
else
bounds.X = node.BoundsRelative.Right - layoutInfo.ExpandAreaWidth + (layoutInfo.ExpandAreaWidth - partSize.Width) / 2;
node.SetExpandPartRectangle(bounds);
}
/// <summary>
/// Returns the size of the node expand part.
/// </summary>
/// <returns>Size of the expand part, default 8,8.</returns>
protected virtual Size GetExpandPartSize()
{
return _CachedExpandPartSize;
//if (_LayoutSettings != null)
// return _LayoutSettings.ExpandPartSize;
//return m_ExpandPartSize;
}
///// <summary>
///// Gets or sets the size of the expand part that is expanding/collapsing the node. Default value is 8,8.
///// </summary>
//public System.Drawing.Size ExpandPartSize
//{
// get {return m_ExpandPartSize;}
// set {m_ExpandPartSize=value;}
//}
/// <summary>
/// Provides the layout for single node.
/// </summary>
/// <param name="layoutInfo">Layout information.</param>
protected virtual void LayoutNode(NodeLayoutContextInfo layoutInfo)
{
bool bHasExpandPart=this.HasExpandPart(layoutInfo);
bool bHasCommandPart=this.HasCommandPart(layoutInfo);
Node node=layoutInfo.ContextNode;
Rectangle nodeRect = new Rectangle(layoutInfo.Left, layoutInfo.Top, 0, 0);
Rectangle nodeContentRect=Rectangle.Empty; // Node content rect excludes expand rect
int height=0, width=0;
// Left node relative to the main root node...
bool bLeftNode = layoutInfo.LeftToRight; // (layoutInfo.MapPositionNear && layoutInfo.LeftToRight);
layoutInfo.LayoutNodeExpandPartWidth = 0;
if(bLeftNode && bHasExpandPart || this.ReserveExpandPartSpace)
{
layoutInfo.LayoutNodeExpandPartWidth = (layoutInfo.ExpandAreaWidth + this.GetCellLayout().CellPartSpacing);
width += (layoutInfo.ExpandAreaWidth + this.GetCellLayout().CellPartSpacing);
}
int x=width; // relative to 0,0 of the node
int y=0; // Relative to 0,0 of the node
// Apply node style
ElementStyle nodeStyle=null;
if(node.Expanded && node.StyleExpanded!=null)
nodeStyle=node.StyleExpanded;
else if(node.Style!=null)
nodeStyle=node.Style;
else
nodeStyle=layoutInfo.DefaultNodeStyle;
nodeContentRect.X=x;
if(nodeStyle!=null)
{
x+=ElementStyleLayout.LeftWhiteSpace(nodeStyle);
y+=ElementStyleLayout.TopWhiteSpace(nodeStyle);
nodeContentRect.X+=nodeStyle.MarginLeft;
nodeContentRect.Y+=nodeStyle.MarginTop;
}
Size size = this.GetCellLayout().LayoutCells(layoutInfo, x, y);
node.SetCellsBounds(new Rectangle(x, y, size.Width, size.Height));
height=size.Height;
width+=size.Width;
nodeContentRect.Width=size.Width;
nodeContentRect.Height=size.Height;
if(nodeStyle!=null)
{
nodeContentRect.Width+=(ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Padding | eSpacePart.Border,eStyleSide.Left)+
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Padding | eSpacePart.Border,eStyleSide.Right));
nodeContentRect.Height+=(ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Padding | eSpacePart.Border,eStyleSide.Top)+
ElementStyleLayout.StyleSpacing(nodeStyle,eSpacePart.Padding | eSpacePart.Border,eStyleSide.Bottom));
width+=(ElementStyleLayout.HorizontalStyleWhiteSpace(nodeStyle));
height+=(ElementStyleLayout.VerticalStyleWhiteSpace(nodeStyle));
}
if (!bLeftNode && bHasExpandPart && !this.ReserveExpandPartSpace)
width += layoutInfo.ExpandAreaWidth;
if(bHasCommandPart)
{
width += this.LayoutSettings.CommandAreaWidth;
nodeContentRect.Width += this.LayoutSettings.CommandAreaWidth;
}
nodeRect.Height=height;
nodeRect.Width=width;
node.SetBounds(nodeRect);
node.SetContentBounds(nodeContentRect);
if(bHasCommandPart)
LayoutCommandPart(layoutInfo, nodeStyle);
else
node.CommandBoundsRelative=Rectangle.Empty;
if (bHasExpandPart || this.ReserveExpandPartSpace)
LayoutExpandPart(layoutInfo,bLeftNode, 0);
else
node.SetExpandPartRectangle(Rectangle.Empty);
node.SizeChanged=false;
// Calculate size and location of node column header if any
//if(node.NodesColumnHeaderVisible)
{
//layoutInfo.Left+=this.NodeLevelOffset;
LayoutColumnHeader(layoutInfo);
//layoutInfo.Left-=this.NodeLevelOffset;
}
}
/// <summary>
/// Returns true if given node has expand part.
/// </summary>
/// <param name="layoutInfo">Layout context information.</param>
/// <returns></returns>
protected virtual bool HasExpandPart(NodeLayoutContextInfo layoutInfo)
{
Node node=layoutInfo.ContextNode;
if(node.ExpandVisibility==eNodeExpandVisibility.Auto)
{
if(IsRootNode(node) && !RootHasExpandedPart || !NodeOperations.GetAnyVisibleNodes(node))
return false;
return true;
}
else
return (node.ExpandVisibility==eNodeExpandVisibility.Visible);
}
/// <summary>
/// Returns whether given node has command part.
/// </summary>
/// <param name="layoutInfo">Layout context information.</param>
/// <returns>True if command part should be drawn otherwise false.</returns>
protected virtual bool HasCommandPart(NodeLayoutContextInfo layoutInfo)
{
return layoutInfo.ContextNode.CommandButton;
}
/// <summary>
/// Returns true if root node should have expanded part
/// </summary>
protected virtual bool RootHasExpandedPart
{
get {return true;}
}
/// <summary>
/// Returns true if expand part space should be accounted for even if they expand part is not visible or need to be displayed. Default value is false.
/// </summary>
protected virtual bool ReserveExpandPartSpace
{
get
{
return false;
}
}
/// <summary>
/// Returns class responsible for cell layout.
/// </summary>
/// <returns>Cell layout class.</returns>
protected internal virtual CellLayout GetCellLayout()
{
if (m_CellLayout == null)
m_CellLayout = new CellLayout(this.LayoutSettings);
return m_CellLayout;
}
/// <summary>
/// Offsets node location and location of it's child nodes bounds.
/// </summary>
/// <param name="node">Node to offset.</param>
/// <param name="x">Horizontal offset.</param>
/// <param name="y">Vertical offset.</param>
protected virtual void OffsetNodeLocation(Node node, int x, int y)
{
node.SetBounds(new Rectangle(node.BoundsRelative.X+x,node.BoundsRelative.Y+y,node.BoundsRelative.Width,node.BoundsRelative.Height));
if(node.Expanded)
node.ChildNodesBounds=new Rectangle(node.ChildNodesBounds.X+x,node.ChildNodesBounds.Y+y,node.ChildNodesBounds.Width,node.ChildNodesBounds.Height);
}
protected virtual NodeLayoutContextInfo GetDefaultNodeLayoutContextInfo(System.Drawing.Graphics graphics)
{
NodeLayoutContextInfo layoutInfo=new NodeLayoutContextInfo();
layoutInfo.ClientRectangle=m_ClientArea;
layoutInfo.DefaultColumns=this.GetDefaultColumnInfo();
layoutInfo.ChildColumns=null;
layoutInfo.Indent = m_Tree.Indent;
layoutInfo.Left=0;
layoutInfo.Top=0;
layoutInfo.LeftMargin = m_Tree.BackgroundStyle.PaddingLeft;
layoutInfo.DefaultFont=m_Tree.Font;
layoutInfo.LeftToRight=(this.LeftRight==System.Windows.Forms.LeftRightAlignment.Left);
layoutInfo.Graphics=graphics;
layoutInfo.Styles=m_Tree.Styles;
layoutInfo.FullRowBackgroundNodes = new ArrayList();
if(m_Tree.CellLayout!=eCellLayout.Default)
layoutInfo.CellLayout=m_Tree.CellLayout;
if(m_Tree.CellPartLayout!=eCellPartLayout.Default)
layoutInfo.CellPartLayout=m_Tree.CellPartLayout;
if(m_Tree.NodeStyle!=null)
layoutInfo.DefaultNodeStyle=m_Tree.NodeStyle;
if(m_Tree.CellStyleDefault!=null)
layoutInfo.DefaultCellStyle=m_Tree.CellStyleDefault;
else
layoutInfo.DefaultCellStyle=ElementStyle.GetDefaultCellStyle(layoutInfo.DefaultNodeStyle);
// Determine size of the default Column Header
if(m_Tree.ColumnStyleNormal!=null)
{
ElementStyleLayout.CalculateStyleSize(m_Tree.ColumnStyleNormal,layoutInfo.DefaultFont);
layoutInfo.DefaultHeaderSize=m_Tree.ColumnStyleNormal.Size;
}
if(layoutInfo.DefaultHeaderSize.IsEmpty)
layoutInfo.DefaultHeaderSize.Height=layoutInfo.DefaultFont.Height+4;
layoutInfo.ExpandPartWidth = Dpi.Width(this.Tree.ExpandWidth);
layoutInfo.View = this.Tree.View;
layoutInfo.TileSize = Dpi.Size(this.Tree.TileSize);
layoutInfo.ColumnStyle = this.Tree.ColumnStyleNormal;
layoutInfo.ExpandAreaWidth = Dpi.Width(this.LayoutSettings.ExpandAreaWidth);
return layoutInfo;
}
protected Node[] GetTopLevelNodes()
{
if(m_Tree.DisplayRootNode!=null)
return new Node[] {m_Tree.DisplayRootNode};
else
{
Node[] nodes=new Node[m_Tree.Nodes.Count];
m_Tree.Nodes.CopyTo(nodes);
return nodes;
}
}
protected bool IsRootNode(Node node)
{
return NodeOperations.IsRootNode(m_Tree,node);
}
protected virtual void EmptyBoundsUnusedNodes(Node[] topLevelNodes)
{
if(m_Tree.DisplayRootNode!=null)
{
Node node=m_Tree.DisplayRootNode.PrevVisibleNode;
while(node!=null)
{
node.SetBounds(Rectangle.Empty);
node=node.PrevVisibleNode;
}
node=m_Tree.DisplayRootNode.NextNode;
if(node==null)
{
node=m_Tree.DisplayRootNode.Parent;
while(node!=null)
{
if(node.NextNode!=null)
{
node=node.NextNode;
break;
}
else
node=node.Parent;
}
}
while(node!=null)
{
node.SetBounds(Rectangle.Empty);
node=node.NextVisibleNode;
}
}
else
{
for(int i=1;i<topLevelNodes.Length;i++)
{
topLevelNodes[i].SetBounds(Rectangle.Empty);
}
}
}
public AdvTree Tree
{
get { return m_Tree; }
}
#region Column Support
// Assumes that layoutInfo is up-to-date and that Node that is connected with
// columns is already processed and it's size and location calculated.
// layoutInfo.Top member reflects the next position below the node
// layoutInfo.LevelOffset should reflect the X offset for the child nodes.
public void LayoutColumnHeader(NodeLayoutContextInfo layoutInfo)
{
Node node=layoutInfo.ContextNode;
if (!node.HasColumns || !node.Expanded)
{
node.ColumnHeaderHeight = 0;
return;
}
int spacing = 2;
int x = layoutInfo.Left + this.NodeLevelOffset + node.NodesIndent;
int y=layoutInfo.ContextNode.BoundsRelative.Bottom + spacing;
bool bLeftNode=(layoutInfo.MapPositionNear && layoutInfo.LeftToRight);
int expandPartWidth = layoutInfo.ExpandAreaWidth;
int cellPartSpacing=GetCellLayout().CellPartSpacing;
if (!bLeftNode)
x += (expandPartWidth + cellPartSpacing);
int clientWidth = layoutInfo.ClientRectangle.Width - (layoutInfo.Left + expandPartWidth);
if (clientWidth <= 0)
clientWidth = layoutInfo.ClientRectangle.Width;
node.ColumnHeaderHeight = Layout.ColumnHeaderLayout.LayoutColumnHeader(layoutInfo, x, y, clientWidth, this.GetCellLayout().LayoutSettings.CellHorizontalSpacing) + spacing;
if (!node.NodesColumnsHeaderVisible)
node.ColumnHeaderHeight = 0;
}
private int _NodeLevelOffset = 16;
internal int NodeLevelOffset
{
get { return _NodeLevelOffset; }
set
{
_NodeLevelOffset = value;
}
}
#endregion
#region RTL Support
public virtual System.Windows.Forms.LeftRightAlignment LeftRight
{
get {return m_LeftRight;}
set {m_LeftRight=value;}
}
#endregion
}
internal class NodeColumnInfo
{
/// <summary>
/// Initializes a new instance of the NodeColumnInfo structure.
/// </summary>
/// <param name="columnInfo"></param>
/// <param name="hasAutoSizeColumn"></param>
public NodeColumnInfo(List<ColumnInfo> columnInfo, bool hasAutoSizeColumn)
{
ColumnInfo = columnInfo;
HasAutoSizeColumn = hasAutoSizeColumn;
}
/// <summary>
/// Gets or sets the list of column info object for the columns.
/// </summary>
public List<ColumnInfo> ColumnInfo;
/// <summary>
/// Gets or sets whether columns have auto-size column.
/// </summary>
public bool HasAutoSizeColumn;
}
}