#region License Information
/* HeuristicLab
* Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HeuristicLab is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using HeuristicLab.Common;
using HeuristicLab.Common.Resources;
using HeuristicLab.Core;
using HeuristicLab.PluginInfrastructure;
namespace HeuristicLab.Optimizer {
internal partial class NewItemDialog : Form {
private bool isInitialized;
private readonly List treeNodes;
private string currentSearchString;
private Type selectedType;
public Type SelectedType {
get { return selectedType; }
private set {
if (value != selectedType) {
selectedType = value;
OnSelectedTypeChanged();
}
}
}
private IItem item;
public IItem Item {
get { return item; }
}
public NewItemDialog() {
InitializeComponent();
treeNodes = new List();
currentSearchString = string.Empty;
item = null;
SelectedTypeChanged += this_SelectedTypeChanged;
}
private void NewItemDialog_Load(object sender, EventArgs e) {
if (isInitialized) return;
// Sorted by hasOrdering to create category nodes first with concrete ordering.
// Items with categoryname without ordering are inserted afterwards correctly
var categories =
from type in ApplicationManager.Manager.GetTypes(typeof(IItem))
where CreatableAttribute.IsCreatable(type)
let category = CreatableAttribute.GetCategory(type)
let hasOrdering = category.Contains(CreatableAttribute.Categories.OrderToken)
let name = ItemAttribute.GetName(type)
let priority = CreatableAttribute.GetPriority(type)
let version = ItemAttribute.GetVersion(type)
orderby category, hasOrdering descending, priority, name, version ascending
group type by category into categoryGroup
select categoryGroup;
var rootNode = CreateCategoryTree(categories);
CreateItemNodes(rootNode, categories);
foreach (TreeNode topNode in rootNode.Nodes)
treeNodes.Add(topNode);
foreach (var node in treeNodes)
typesTreeView.Nodes.Add((TreeNode)node.Clone());
typesTreeView.TreeViewNodeSorter = new ItemTreeNodeComparer();
typesTreeView.Sort();
isInitialized = true;
}
#region Create Tree
private TreeNode CreateCategoryTree(IEnumerable> categories) {
imageList.Images.Add(VSImageLibrary.Class); // default icon
imageList.Images.Add(VSImageLibrary.Namespace); // plugins
var rootNode = new TreeNode();
// CategoryNode
// Tag: raw string, used for sorting, e.g. 1$$$Algorithms###2$$$Single Solution
// Name: full name = combined category name with parent categories, used for finding nodes in tree, e.g. Algorithms###Single Solution
// Text: category name, used for displaying on node itself, e.g. Single Solution
foreach (var category in categories) {
var rawName = category.Key;
string fullName = CreatableAttribute.Categories.GetFullName(rawName);
string name = CreatableAttribute.Categories.GetName(rawName);
// Skip categories with same full name because the raw name can still be different (missing order)
if (rootNode.Nodes.Find(fullName, true).Length > 0)
continue;
var categoryNode = new TreeNode(name, 1, 1) {
Name = fullName,
Tag = rawName
};
var parents = CreatableAttribute.Categories.GetParentRawNames(rawName);
var parentNode = FindOrCreateParentNode(rootNode, parents);
if (parentNode != null)
parentNode.Nodes.Add(categoryNode);
else
rootNode.Nodes.Add(categoryNode);
}
return rootNode;
}
private TreeNode FindOrCreateParentNode(TreeNode node, IEnumerable rawParentNames) {
TreeNode parentNode = null;
string rawName = null;
foreach (string rawParentName in rawParentNames) {
rawName = rawName == null ? rawParentName : rawName + CreatableAttribute.Categories.SplitToken + rawParentName;
var fullName = CreatableAttribute.Categories.GetFullName(rawName);
parentNode = node.Nodes.Find(fullName, false).SingleOrDefault();
if (parentNode == null) {
var name = CreatableAttribute.Categories.GetName(rawName);
parentNode = new TreeNode(name, 1, 1) {
Name = fullName,
Tag = rawName
};
node.Nodes.Add(parentNode);
}
node = parentNode;
}
return parentNode;
}
private void CreateItemNodes(TreeNode node, IEnumerable> categories) {
foreach (var category in categories) {
var fullName = CreatableAttribute.Categories.GetFullName(category.Key);
var categoryNode = node.Nodes.Find(fullName, true).Single();
foreach (var creatable in category) {
var itemNode = CreateItemNode(creatable);
itemNode.Name = itemNode.Name + ":" + fullName;
categoryNode.Nodes.Add(itemNode);
}
}
}
private TreeNode CreateItemNode(Type creatable) {
string name = ItemAttribute.GetName(creatable);
var itemNode = new TreeNode(name) {
ImageIndex = 0,
Tag = creatable,
Name = name
};
var image = ItemAttribute.GetImage(creatable);
if (image != null) {
imageList.Images.Add(image);
itemNode.ImageIndex = imageList.Images.Count - 1;
}
itemNode.SelectedImageIndex = itemNode.ImageIndex;
return itemNode;
}
#endregion
private void NewItemDialog_Shown(object sender, EventArgs e) {
Reset();
}
public virtual void Filter(string searchString) {
if (InvokeRequired) {
Invoke(new Action(Filter), searchString);
} else {
searchString = searchString.ToLower();
var selectedNode = typesTreeView.SelectedNode;
if (!searchString.Contains(currentSearchString)) {
typesTreeView.BeginUpdate();
// expand search -> restore all tree nodes
typesTreeView.Nodes.Clear();
foreach (TreeNode node in treeNodes)
typesTreeView.Nodes.Add((TreeNode)node.Clone());
typesTreeView.EndUpdate();
}
// remove nodes
typesTreeView.BeginUpdate();
var searchTokens = searchString.Split(' ');
int i = 0;
while (i < typesTreeView.Nodes.Count) {
var categoryNode = typesTreeView.Nodes[i];
bool remove = FilterNode(categoryNode, searchTokens);
if (remove)
typesTreeView.Nodes.RemoveAt(i);
else i++;
}
typesTreeView.EndUpdate();
currentSearchString = searchString;
// select first item
typesTreeView.BeginUpdate();
typesTreeView.ExpandAll();
var firstNode = FirstVisibleNode;
while (firstNode != null && !(firstNode.Tag is Type))
firstNode = firstNode.NextVisibleNode;
if (firstNode != null)
typesTreeView.SelectedNode = firstNode;
if (typesTreeView.Nodes.Count == 0) {
SelectedType = null;
typesTreeView.Enabled = false;
} else {
SetTreeNodeVisibility();
typesTreeView.Enabled = true;
}
typesTreeView.EndUpdate();
RestoreSelectedNode(selectedNode);
UpdateDescription();
}
}
private bool FilterNode(TreeNode node, string[] searchTokens) {
if (node.Tag is string) { // Category node
var text = node.Text;
if (searchTokens.Any(token => !text.ToLower().Contains(token))) {
int i = 0;
while (i < node.Nodes.Count) {
bool remove = FilterNode(node.Nodes[i], searchTokens);
if (remove)
node.Nodes.RemoveAt(i);
else i++;
}
}
return node.Nodes.Count == 0;
}
if (node.Tag is Type) { // Type node
var text = node.Text;
if (searchTokens.Any(searchToken => !text.ToLower().Contains(searchToken))) {
var typeTag = (Type)node.Tag;
if (typeTag == SelectedType) {
SelectedType = null;
typesTreeView.SelectedNode = null;
}
return true;
}
return false;
}
throw new InvalidOperationException("Encountered neither a category nor a creatable node during tree traversal.");
}
protected virtual void UpdateDescription() {
itemDescriptionTextBox.Text = string.Empty;
pluginDescriptionTextBox.Text = string.Empty;
pluginTextBox.Text = string.Empty;
versionTextBox.Text = string.Empty;
if (typesTreeView.SelectedNode != null) {
var node = typesTreeView.SelectedNode;
string category = node.Tag as string;
if (category != null) {
itemDescriptionTextBox.Text = string.Join(" - ", node.Name.Split(new[] { CreatableAttribute.Categories.SplitToken }, StringSplitOptions.RemoveEmptyEntries));
}
Type type = node.Tag as Type;
if (type != null) {
string description = ItemAttribute.GetDescription(type);
var version = ItemAttribute.GetVersion(type);
var plugin = ApplicationManager.Manager.GetDeclaringPlugin(type);
if (description != null)
itemDescriptionTextBox.Text = description;
if (plugin != null) {
pluginTextBox.Text = plugin.Name;
pluginDescriptionTextBox.Text = plugin.Description;
}
if (version != null)
versionTextBox.Text = version.ToString();
}
} else if (typesTreeView.Nodes.Count == 0) {
itemDescriptionTextBox.Text = "No types found";
}
}
#region Events
public event EventHandler SelectedTypeChanged;
protected virtual void OnSelectedTypeChanged() {
if (SelectedTypeChanged != null)
SelectedTypeChanged(this, EventArgs.Empty);
}
#endregion
#region Control Events
protected virtual void searchTextBox_TextChanged(object sender, EventArgs e) {
Filter(searchTextBox.Text);
if (string.IsNullOrWhiteSpace(searchTextBox.Text))
Reset();
}
protected virtual void itemsTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
if (typesTreeView.SelectedNode == null) SelectedType = null;
else SelectedType = typesTreeView.SelectedNode.Tag as Type;
UpdateDescription();
}
protected virtual void itemsTreeView_VisibleChanged(object sender, EventArgs e) {
if (Visible) SetTreeNodeVisibility();
}
#endregion
#region Helpers
private void RestoreSelectedNode(TreeNode selectedNode) {
if (selectedNode != null) {
var node = typesTreeView.Nodes.Find(selectedNode.Name, true).SingleOrDefault();
if (node != null)
typesTreeView.SelectedNode = node;
if (typesTreeView.SelectedNode == null)
SelectedType = null;
}
}
private void SetTreeNodeVisibility() {
TreeNode selectedNode = typesTreeView.SelectedNode;
if (string.IsNullOrWhiteSpace(currentSearchString) && (typesTreeView.Nodes.Count > 1)) {
typesTreeView.CollapseAll();
if (selectedNode != null) typesTreeView.SelectedNode = selectedNode;
} else {
typesTreeView.ExpandAll();
}
if (selectedNode != null) {
typesTreeView.Nodes[0].EnsureVisible(); // scroll top first
selectedNode.EnsureVisible();
}
}
private void Reset() {
searchTextBox.Text = string.Empty;
searchTextBox.Focus();
SelectedType = null;
typesTreeView.SelectedNode = null;
UpdateDescription();
typesTreeView.BeginUpdate();
typesTreeView.CollapseAll();
foreach (TreeNode node in typesTreeView.Nodes)
node.Expand();
typesTreeView.Nodes[0].EnsureVisible();
typesTreeView.EndUpdate();
}
#endregion
private void okButton_Click(object sender, EventArgs e) {
if (SelectedType != null) {
item = (IItem)Activator.CreateInstance(SelectedType);
DialogResult = DialogResult.OK;
Close();
}
}
private void itemTreeView_DoubleClick(object sender, EventArgs e) {
if (SelectedType != null) {
item = (IItem)Activator.CreateInstance(SelectedType);
DialogResult = DialogResult.OK;
Close();
}
}
private void this_SelectedTypeChanged(object sender, EventArgs e) {
okButton.Enabled = SelectedType != null;
}
private TreeNode toolStripMenuNode;
private void typesTreeView_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Right) {
Point coordinates = typesTreeView.PointToClient(Cursor.Position);
toolStripMenuNode = typesTreeView.GetNodeAt(coordinates);
if (toolStripMenuNode != null && coordinates.X >= toolStripMenuNode.Bounds.Left &&
coordinates.X <= toolStripMenuNode.Bounds.Right) {
typesTreeView.SelectedNode = toolStripMenuNode;
expandToolStripMenuItem.Enabled =
expandToolStripMenuItem.Visible = !toolStripMenuNode.IsExpanded && toolStripMenuNode.Nodes.Count > 0;
collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = toolStripMenuNode.IsExpanded;
} else {
expandToolStripMenuItem.Enabled = expandToolStripMenuItem.Visible = false;
collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = false;
}
expandAllToolStripMenuItem.Enabled =
expandAllToolStripMenuItem.Visible =
!typesTreeView.Nodes.OfType().All(x => TreeNodeIsFullyExpanded(x));
collapseAllToolStripMenuItem.Enabled =
collapseAllToolStripMenuItem.Visible = typesTreeView.Nodes.OfType().Any(x => x.IsExpanded);
if (contextMenuStrip.Items.Cast().Any(item => item.Enabled))
contextMenuStrip.Show(Cursor.Position);
}
}
private bool TreeNodeIsFullyExpanded(TreeNode node) {
return (node.Nodes.Count == 0) || (node.IsExpanded && node.Nodes.OfType().All(x => TreeNodeIsFullyExpanded(x)));
}
private void expandToolStripMenuItem_Click(object sender, EventArgs e) {
typesTreeView.BeginUpdate();
if (toolStripMenuNode != null) toolStripMenuNode.ExpandAll();
typesTreeView.EndUpdate();
}
private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) {
typesTreeView.BeginUpdate();
typesTreeView.ExpandAll();
typesTreeView.EndUpdate();
}
private void collapseToolStripMenuItem_Click(object sender, EventArgs e) {
typesTreeView.BeginUpdate();
if (toolStripMenuNode != null) toolStripMenuNode.Collapse();
typesTreeView.EndUpdate();
}
private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) {
typesTreeView.BeginUpdate();
typesTreeView.CollapseAll();
typesTreeView.EndUpdate();
}
private void clearSearchButton_Click(object sender, EventArgs e) {
searchTextBox.Text = string.Empty;
searchTextBox.Focus();
}
private void searchTextBox_KeyDown(object sender, KeyEventArgs e) {
if (typesTreeView.Nodes.Count == 0)
return;
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) {
var selectedNode = typesTreeView.SelectedNode;
if (selectedNode == null) { // nothing selected => select first
if (e.KeyCode == Keys.Down) typesTreeView.SelectedNode = FirstVisibleNode;
} else {
if (e.KeyCode == Keys.Down && selectedNode.NextVisibleNode != null)
typesTreeView.SelectedNode = selectedNode.NextVisibleNode;
if (e.KeyCode == Keys.Up && selectedNode.PrevVisibleNode != null)
typesTreeView.SelectedNode = selectedNode.PrevVisibleNode;
}
e.Handled = true;
}
}
private TreeNode FirstVisibleNode {
get {
return typesTreeView.Nodes.Count > 0 ? typesTreeView.Nodes[0] : null;
}
}
private class ItemTreeNodeComparer : IComparer {
private static readonly IComparer Comparer = new NaturalStringComparer();
public int Compare(object x, object y) {
var lhs = (TreeNode)x;
var rhs = (TreeNode)y;
if (lhs.Tag is string && rhs.Tag is string) {
return Comparer.Compare((string)lhs.Tag, (string)rhs.Tag);
}
if (lhs.Tag is string) {
return -1;
}
return 1;
}
}
}
}