using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Windows.Forms;
using ReClassNET.Extensions;
using ReClassNET.Nodes;
using ReClassNET.Project;
namespace ReClassNET.Controls
{
public partial class ProjectView : UserControl
{
/// A custom tree node for class nodes with hierarchical structure.
private class ClassTreeNode : TreeNode
{
private readonly ProjectView control;
public ClassNode ClassNode { get; }
/// Constructor of the class.
/// The class node.
/// The instance this node should belong to.
public ClassTreeNode(ClassNode node, ProjectView control)
: this(node, control, null)
{
Contract.Requires(node != null);
Contract.Requires(control != null);
}
private ClassTreeNode(ClassNode node, ProjectView control, HashSet seen)
{
Contract.Requires(node != null);
Contract.Requires(control != null);
ClassNode = node;
this.control = control;
Text = node.Name;
ImageIndex = 1;
SelectedImageIndex = 1;
RebuildClassHierarchy(seen ?? new HashSet { ClassNode });
}
public void Update()
{
Text = ClassNode.Name;
RebuildClassHierarchy(new HashSet { ClassNode });
}
/// Rebuilds the class hierarchy.
/// The already seen classes.
private void RebuildClassHierarchy(HashSet seen)
{
Contract.Requires(seen != null);
if (!control.EnableClassHierarchyView)
{
return;
}
var distinctClasses = ClassNode.Nodes
.OfType()
.Select(w => w.ResolveMostInnerNode())
.OfType()
.Distinct()
.ToList();
if (distinctClasses.IsEquivalentTo(Nodes.Cast().Select(t => t.ClassNode)))
{
return;
}
Nodes.Clear();
foreach (var child in distinctClasses)
{
var childSeen = new HashSet(seen);
if (childSeen.Add(child))
{
Nodes.Add(new ClassTreeNode(child, control, childSeen));
}
}
if (control.AutoExpandClassNodes)
{
Expand();
}
}
}
public class EnumTreeNode : TreeNode
{
public EnumDescription Enum { get; }
public EnumTreeNode(EnumDescription @enum)
{
Contract.Requires(@enum != null);
Enum = @enum;
ImageIndex = 3;
SelectedImageIndex = 3;
Update();
}
public void Update()
{
Text = Enum.Name;
}
}
private class NodeSorter : IComparer
{
public int Compare(object x, object y)
{
var compare = Application.CurrentCulture.CompareInfo;
if (x is ClassTreeNode cn1 && y is ClassTreeNode cn2)
{
return compare.Compare(cn1.Text, cn2.Text);
}
if (x is EnumTreeNode en1 && y is EnumTreeNode en2)
{
return compare.Compare(en1.Text, en2.Text);
}
if (x is TreeNode tn1 && tn1.Parent == null && y is TreeNode tn2 && tn2.Parent == null)
{
return (int)tn1.Tag - (int)tn2.Tag;
}
return 0;
}
}
private readonly TreeNode enumsRootNode;
private readonly TreeNode classesRootNode;
private ClassNode selectedClass;
private bool autoExpandClassNodes;
private bool enableClassHierarchyView;
public delegate void SelectionChangedEvent(object sender, ClassNode node);
public event SelectionChangedEvent SelectionChanged;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ClassNode SelectedClass
{
get => selectedClass;
set
{
if (selectedClass != value)
{
selectedClass = value;
if (selectedClass != null)
{
projectTreeView.SelectedNode = FindMainClassTreeNode(selectedClass);
}
SelectionChanged?.Invoke(this, selectedClass);
}
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public EnumDescription SelectedEnum { get; private set; }
[DefaultValue(false)]
public bool AutoExpandClassNodes
{
get => autoExpandClassNodes;
set
{
if (autoExpandClassNodes != value)
{
autoExpandClassNodes = value;
if (autoExpandClassNodes)
{
ExpandAllClassNodes();
}
}
}
}
[DefaultValue(false)]
public bool EnableClassHierarchyView
{
get => enableClassHierarchyView;
set
{
if (enableClassHierarchyView != value)
{
enableClassHierarchyView = value;
var classes = classesRootNode.Nodes.Cast().Select(t => t.ClassNode).ToList();
classesRootNode.Nodes.Clear();
AddClasses(classes);
}
}
}
public ContextMenuStrip ClassesContextMenuStrip { get; set; }
public ContextMenuStrip ClassContextMenuStrip { get; set; }
public ContextMenuStrip EnumsContextMenuStrip { get; set; }
public ContextMenuStrip EnumContextMenuStrip { get; set; }
public ProjectView()
{
Contract.Ensures(classesRootNode != null);
Contract.Ensures(enumsRootNode != null);
InitializeComponent();
DoubleBuffered = true;
projectTreeView.TreeViewNodeSorter = new NodeSorter();
projectTreeView.ImageList = new ImageList();
projectTreeView.ImageList.Images.Add(Properties.Resources.B16x16_Text_List_Bullets);
projectTreeView.ImageList.Images.Add(Properties.Resources.B16x16_Class_Type);
projectTreeView.ImageList.Images.Add(Properties.Resources.B16x16_Category);
projectTreeView.ImageList.Images.Add(Properties.Resources.B16x16_Enum_Type);
classesRootNode = new TreeNode
{
Text = "Classes",
ImageIndex = 0,
SelectedImageIndex = 0,
Tag = 0
};
projectTreeView.Nodes.Add(classesRootNode);
enumsRootNode = new TreeNode
{
Text = "Enums",
ImageIndex = 2,
SelectedImageIndex = 2,
Tag = 1
};
projectTreeView.Nodes.Add(enumsRootNode);
}
#region Event Handler
private void projectTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Level == 0)
{
return;
}
if (e.Node is ClassTreeNode classTreeNode)
{
if (selectedClass != classTreeNode.ClassNode)
{
SelectedClass = classTreeNode.ClassNode;
}
}
else if (e.Node is EnumTreeNode enumTreeNode)
{
SelectedEnum = enumTreeNode.Enum;
}
}
private void projectTreeView_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Right)
{
return;
}
var node = projectTreeView.GetNodeAt(e.X, e.Y);
if (node == null)
{
return;
}
ContextMenuStrip cms = null;
if (node == classesRootNode)
{
cms = ClassesContextMenuStrip;
}
else if (node is ClassTreeNode)
{
cms = ClassContextMenuStrip;
projectTreeView.SelectedNode = node;
}
else if (node == enumsRootNode)
{
cms = EnumsContextMenuStrip;
}
else if (node is EnumTreeNode)
{
cms = EnumContextMenuStrip;
projectTreeView.SelectedNode = node;
}
cms?.Show(projectTreeView, e.Location);
}
private void projectTreeView_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
var isClassTreeNode = e.Node is ClassTreeNode;
var isEnumTreeNode = e.Node is EnumTreeNode;
e.CancelEdit = !isClassTreeNode && !isEnumTreeNode;
}
private void projectTreeView_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
if (!string.IsNullOrEmpty(e.Label))
{
if (e.Node is ClassTreeNode classTreeNode)
{
classTreeNode.ClassNode.Name = e.Label;
}
else if (e.Node is EnumTreeNode enumTreeNode)
{
enumTreeNode.Enum.Name = e.Label;
}
}
}
#endregion
public void ExpandAllClassNodes()
{
classesRootNode.ExpandAll();
}
public void CollapseAllClassNodes()
{
foreach (var tn in classesRootNode.Nodes.Cast())
{
tn.Collapse();
}
}
///
/// Clears all displayed nodes.
///
public void Clear()
{
Clear(true, true);
}
///
/// Clears the selected nodes.
///
/// Clears the classes if set.
/// Clears the enums if set.
public void Clear(bool clearClasses, bool clearEnums)
{
if (clearClasses)
{
classesRootNode.Nodes.Clear();
}
if (clearEnums)
{
enumsRootNode.Nodes.Clear();
}
}
/// Adds the class to the view.
/// The class to add.
public void AddClass(ClassNode @class)
{
Contract.Requires(@class != null);
AddClasses(new[] { @class });
}
///
/// Adds all classes to the view.
///
/// The classes to add.
public void AddClasses(IEnumerable classes)
{
Contract.Requires(classes != null);
projectTreeView.BeginUpdate();
foreach (var @class in classes)
{
classesRootNode.Nodes.Add(new ClassTreeNode(@class, this));
}
classesRootNode.Expand();
projectTreeView.Sort();
projectTreeView.EndUpdate();
}
/// Removes the class from the view.
/// The class to remove.
public void RemoveClass(ClassNode node)
{
Contract.Requires(node != null);
foreach (var tn in FindClassTreeNodes(node))
{
tn.Remove();
}
if (selectedClass == node)
{
if (classesRootNode.Nodes.Count > 0)
{
projectTreeView.SelectedNode = classesRootNode.Nodes[0];
}
else
{
SelectedClass = null;
}
}
}
/// Searches for the which represents the class.
/// The class to search.
/// The found class tree node.
private ClassTreeNode FindMainClassTreeNode(ClassNode node)
{
Contract.Requires(node != null);
return classesRootNode.Nodes
.Cast()
.FirstOrDefault(t => t.ClassNode == node);
}
/// Searches for the ClassTreeNode which represents the class.
/// The class to search.
/// The found class tree node.
private IEnumerable FindClassTreeNodes(ClassNode node)
{
Contract.Requires(node != null);
return classesRootNode.Nodes
.Cast()
.Traverse(t => t.Nodes.Cast())
.Where(n => n.ClassNode == node);
}
///
/// Updates the display for the given class.
///
/// The class to update.
public void UpdateClassNode(ClassNode @class)
{
Contract.Requires(@class != null);
projectTreeView.BeginUpdate();
foreach (var tn in FindClassTreeNodes(@class))
{
tn.Update();
}
projectTreeView.Sort();
projectTreeView.EndUpdate();
}
/// Adds the enum to the view.
/// The enum to add.
public void AddEnum(EnumDescription @enum)
{
Contract.Requires(@enum != null);
AddEnums(new[] { @enum });
}
/// Adds the enums to the view.
/// The enums to add.
public void AddEnums(IEnumerable enums)
{
Contract.Requires(enums != null);
projectTreeView.BeginUpdate();
foreach (var @enum in enums)
{
enumsRootNode.Nodes.Add(new EnumTreeNode(@enum));
}
enumsRootNode.ExpandAll();
projectTreeView.Sort();
projectTreeView.EndUpdate();
}
///
/// Updates the display for the given enum.
///
/// The enum to update.
public void UpdateEnumNode(EnumDescription @enum)
{
Contract.Requires(@enum != null);
projectTreeView.BeginUpdate();
var node = enumsRootNode.Nodes
.Cast()
.FirstOrDefault(n => n.Enum == @enum);
if (node != null)
{
node.Update();
}
else
{
AddEnum(@enum);
}
projectTreeView.Sort();
projectTreeView.EndUpdate();
}
}
}