source: stable/HeuristicLab.Optimizer/3.3/NewItemDialog.cs @ 12708

Last change on this file since 12708 was 12708, checked in by mkommend, 6 years ago

#2025:Merged all changes regarding the new item dialog into stable.
#2387: Merged all changes regarding the type selector into stable.

File size: 17.0 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab 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 * HeuristicLab 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 HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections;
24using System.Collections.Generic;
25using System.Drawing;
26using System.Linq;
27using System.Windows.Forms;
28using HeuristicLab.Common;
29using HeuristicLab.Common.Resources;
30using HeuristicLab.Core;
31using HeuristicLab.PluginInfrastructure;
32
33namespace HeuristicLab.Optimizer {
34  internal partial class NewItemDialog : Form {
35    private bool isInitialized;
36
37    private readonly List<TreeNode> treeNodes;
38    private string currentSearchString;
39
40    private Type selectedType;
41    public Type SelectedType {
42      get { return selectedType; }
43      private set {
44        if (value != selectedType) {
45          selectedType = value;
46          OnSelectedTypeChanged();
47        }
48      }
49    }
50
51    private IItem item;
52    public IItem Item {
53      get { return item; }
54    }
55
56    public NewItemDialog() {
57      InitializeComponent();
58      treeNodes = new List<TreeNode>();
59      currentSearchString = string.Empty;
60      item = null;
61      SelectedTypeChanged += this_SelectedTypeChanged;
62    }
63
64    private void NewItemDialog_Load(object sender, EventArgs e) {
65      if (isInitialized) return;
66
67      // Sorted by hasOrdering to create category nodes first with concrete ordering.
68      // Items with categoryname without ordering are inserted afterwards correctly
69      var categories =
70        from type in ApplicationManager.Manager.GetTypes(typeof(IItem))
71        where CreatableAttribute.IsCreatable(type)
72        let category = CreatableAttribute.GetCategory(type)
73        let hasOrdering = category.Contains(CreatableAttribute.Categories.OrderToken)
74        let name = ItemAttribute.GetName(type)
75        let priority = CreatableAttribute.GetPriority(type)
76        let version = ItemAttribute.GetVersion(type)
77        orderby category, hasOrdering descending, priority, name, version ascending
78        group type by category into categoryGroup
79        select categoryGroup;
80
81      var rootNode = CreateCategoryTree(categories);
82      CreateItemNodes(rootNode, categories);
83
84      foreach (TreeNode topNode in rootNode.Nodes)
85        treeNodes.Add(topNode);
86      foreach (var node in treeNodes)
87        typesTreeView.Nodes.Add((TreeNode)node.Clone());
88
89      typesTreeView.TreeViewNodeSorter = new ItemTreeNodeComparer();
90      typesTreeView.Sort();
91
92      isInitialized = true;
93    }
94
95    private TreeNode CreateCategoryTree(IEnumerable<IGrouping<string, Type>> categories) {
96      imageList.Images.Add(VSImageLibrary.Class);      // default icon
97      imageList.Images.Add(VSImageLibrary.Namespace);  // plugins
98
99      var rootNode = new TreeNode();
100
101      // CategoryNode
102      // Tag: raw string, used for sorting, e.g. 1$$$Algorithms###2$$$Single Solution
103      // Name: full name = combined category name with parent categories, used for finding nodes in tree, e.g. Algorithms###Single Solution
104      // Text: category name, used for displaying on node itself, e.g. Single Solution
105
106      foreach (var category in categories) {
107        var rawName = category.Key;
108        string fullName = CreatableAttribute.Categories.GetFullName(rawName);
109        string name = CreatableAttribute.Categories.GetName(rawName);
110
111        // Skip categories with same full name because the raw name can still be different (missing order)
112        if (rootNode.Nodes.Find(fullName, true).Length > 0)
113          continue;
114
115        var categoryNode = new TreeNode(name, 1, 1) {
116          Name = fullName,
117          Tag = rawName
118        };
119
120        var parents = CreatableAttribute.Categories.GetParentRawNames(rawName);
121        var parentNode = FindOrCreateParentNode(rootNode, parents);
122        if (parentNode != null)
123          parentNode.Nodes.Add(categoryNode);
124        else
125          rootNode.Nodes.Add(categoryNode);
126      }
127
128      return rootNode;
129    }
130    private TreeNode FindOrCreateParentNode(TreeNode node, IEnumerable<string> rawParentNames) {
131      TreeNode parentNode = null;
132      string rawName = null;
133      foreach (string rawParentName in rawParentNames) {
134        rawName = rawName == null ? rawParentName : rawName + CreatableAttribute.Categories.SplitToken + rawParentName;
135        var fullName = CreatableAttribute.Categories.GetFullName(rawName);
136        parentNode = node.Nodes.Find(fullName, false).SingleOrDefault();
137        if (parentNode == null) {
138          var name = CreatableAttribute.Categories.GetName(rawName);
139          parentNode = new TreeNode(name, 1, 1) {
140            Name = fullName,
141            Tag = rawName
142          };
143          node.Nodes.Add(parentNode);
144        }
145        node = parentNode;
146      }
147      return parentNode;
148    }
149    private void CreateItemNodes(TreeNode node, IEnumerable<IGrouping<string, Type>> categories) {
150      foreach (var category in categories) {
151        var fullName = CreatableAttribute.Categories.GetFullName(category.Key);
152        var categoryNode = node.Nodes.Find(fullName, true).Single();
153        foreach (var creatable in category) {
154          var itemNode = CreateItemNode(creatable);
155          itemNode.Name = itemNode.Name + ":" + fullName;
156          categoryNode.Nodes.Add(itemNode);
157        }
158      }
159    }
160    private TreeNode CreateItemNode(Type creatable) {
161      string name = ItemAttribute.GetName(creatable);
162
163      var itemNode = new TreeNode(name) {
164        ImageIndex = 0,
165        Tag = creatable,
166        Name = name
167      };
168
169      var image = ItemAttribute.GetImage(creatable);
170      if (image != null) {
171        imageList.Images.Add(image);
172        itemNode.ImageIndex = imageList.Images.Count - 1;
173      }
174      itemNode.SelectedImageIndex = itemNode.ImageIndex;
175
176      return itemNode;
177    }
178
179    private void NewItemDialog_Shown(object sender, EventArgs e) {
180      searchTextBox.Text = string.Empty;
181      searchTextBox.Focus();
182      SelectedType = null;
183      typesTreeView.SelectedNode = null;
184      UpdateDescription();
185
186      foreach (TreeNode node in typesTreeView.Nodes)
187        node.Expand();
188      typesTreeView.Nodes[0].EnsureVisible();
189    }
190
191    public virtual void Filter(string searchString) {
192      if (InvokeRequired) {
193        Invoke(new Action<string>(Filter), searchString);
194      } else {
195        searchString = searchString.ToLower();
196        var selectedNode = typesTreeView.SelectedNode;
197
198        if (!searchString.Contains(currentSearchString)) {
199          typesTreeView.BeginUpdate();
200          // expand search -> restore all tree nodes
201          typesTreeView.Nodes.Clear();
202          foreach (TreeNode node in treeNodes)
203            typesTreeView.Nodes.Add((TreeNode)node.Clone());
204          typesTreeView.EndUpdate();
205        }
206
207        // remove nodes
208        typesTreeView.BeginUpdate();
209        var searchTokens = searchString.Split(' ');
210        int i = 0;
211        while (i < typesTreeView.Nodes.Count) {
212          var categoryNode = typesTreeView.Nodes[i];
213          bool remove = FilterNode(categoryNode, searchTokens);
214          if (remove)
215            typesTreeView.Nodes.RemoveAt(i);
216          else i++;
217        }
218        typesTreeView.EndUpdate();
219        currentSearchString = searchString;
220
221        // select first item
222        typesTreeView.BeginUpdate();
223        var firstNode = FirstVisibleNode;
224        while (firstNode != null && !(firstNode.Tag is Type))
225          firstNode = firstNode.NextVisibleNode;
226        if (firstNode != null)
227          typesTreeView.SelectedNode = firstNode;
228
229        if (typesTreeView.Nodes.Count == 0) {
230          SelectedType = null;
231          typesTreeView.Enabled = false;
232        } else {
233          SetTreeNodeVisibility();
234          typesTreeView.Enabled = true;
235        }
236        typesTreeView.EndUpdate();
237
238        RestoreSelectedNode(selectedNode);
239        UpdateDescription();
240      }
241    }
242
243    private bool FilterNode(TreeNode node, string[] searchTokens) {
244      if (node.Tag is string) { // Category node
245        int i = 0;
246        while (i < node.Nodes.Count) {
247          bool remove = FilterNode(node.Nodes[i], searchTokens);
248          if (remove)
249            node.Nodes.RemoveAt(i);
250          else i++;
251        }
252        return node.Nodes.Count == 0;
253      } if (node.Tag is Type) { // Type node
254        var text = node.Text;
255        if (searchTokens.Any(searchToken => !text.ToLower().Contains(searchToken))) {
256          var typeTag = (Type)node.Tag;
257          if (typeTag == SelectedType) {
258            SelectedType = null;
259            typesTreeView.SelectedNode = null;
260          }
261          return true;
262        }
263        return false;
264      }
265      throw new InvalidOperationException("Encountered neither a category nor a creatable node during tree traversal.");
266    }
267
268    protected virtual void UpdateDescription() {
269      itemDescriptionTextBox.Text = string.Empty;
270      pluginDescriptionTextBox.Text = string.Empty;
271      pluginTextBox.Text = string.Empty;
272      versionTextBox.Text = string.Empty;
273
274      if (typesTreeView.SelectedNode != null) {
275        var node = typesTreeView.SelectedNode;
276        string category = node.Tag as string;
277        if (category != null) {
278          itemDescriptionTextBox.Text = string.Join(" - ", node.Name.Split(new[] { CreatableAttribute.Categories.SplitToken }, StringSplitOptions.RemoveEmptyEntries));
279        }
280        Type type = node.Tag as Type;
281        if (type != null) {
282          string description = ItemAttribute.GetDescription(type);
283          var version = ItemAttribute.GetVersion(type);
284          var plugin = ApplicationManager.Manager.GetDeclaringPlugin(type);
285          if (description != null)
286            itemDescriptionTextBox.Text = description;
287          if (plugin != null) {
288            pluginTextBox.Text = plugin.Name;
289            pluginDescriptionTextBox.Text = plugin.Description;
290          }
291          if (version != null)
292            versionTextBox.Text = version.ToString();
293        }
294      } else if (typesTreeView.Nodes.Count == 0) {
295        itemDescriptionTextBox.Text = "No types found";
296      }
297    }
298
299    #region Events
300    public event EventHandler SelectedTypeChanged;
301    protected virtual void OnSelectedTypeChanged() {
302      if (SelectedTypeChanged != null)
303        SelectedTypeChanged(this, EventArgs.Empty);
304    }
305    #endregion
306
307    #region Control Events
308    protected virtual void searchTextBox_TextChanged(object sender, EventArgs e) {
309      Filter(searchTextBox.Text);
310    }
311
312    protected virtual void itemsTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
313      if (typesTreeView.SelectedNode == null) SelectedType = null;
314      else SelectedType = typesTreeView.SelectedNode.Tag as Type;
315      UpdateDescription();
316    }
317
318    protected virtual void itemsTreeView_VisibleChanged(object sender, EventArgs e) {
319      if (Visible) SetTreeNodeVisibility();
320    }
321    #endregion
322
323    #region Helpers
324    private void RestoreSelectedNode(TreeNode selectedNode) {
325      if (selectedNode != null) {
326        var node = typesTreeView.Nodes.Find(selectedNode.Name, true).SingleOrDefault();
327        if (node != null)
328          typesTreeView.SelectedNode = node;
329        if (typesTreeView.SelectedNode == null)
330          SelectedType = null;
331      }
332    }
333    private void SetTreeNodeVisibility() {
334      TreeNode selectedNode = typesTreeView.SelectedNode;
335      if (string.IsNullOrEmpty(currentSearchString) && (typesTreeView.Nodes.Count > 1)) {
336        typesTreeView.CollapseAll();
337        if (selectedNode != null) typesTreeView.SelectedNode = selectedNode;
338      } else {
339        typesTreeView.ExpandAll();
340      }
341      if (selectedNode != null) selectedNode.EnsureVisible();
342    }
343    #endregion
344
345    private void okButton_Click(object sender, EventArgs e) {
346      if (SelectedType != null) {
347        item = (IItem)Activator.CreateInstance(SelectedType);
348        DialogResult = DialogResult.OK;
349        Close();
350      }
351    }
352    private void itemTreeView_DoubleClick(object sender, EventArgs e) {
353      if (SelectedType != null) {
354        item = (IItem)Activator.CreateInstance(SelectedType);
355        DialogResult = DialogResult.OK;
356        Close();
357      }
358    }
359    private void this_SelectedTypeChanged(object sender, EventArgs e) {
360      okButton.Enabled = SelectedType != null;
361    }
362
363    private TreeNode toolStripMenuNode;
364    private void typesTreeView_MouseDown(object sender, MouseEventArgs e) {
365      if (e.Button == MouseButtons.Right) {
366        Point coordinates = typesTreeView.PointToClient(Cursor.Position);
367        toolStripMenuNode = typesTreeView.GetNodeAt(coordinates);
368
369        if (toolStripMenuNode != null && coordinates.X >= toolStripMenuNode.Bounds.Left &&
370            coordinates.X <= toolStripMenuNode.Bounds.Right) {
371          typesTreeView.SelectedNode = toolStripMenuNode;
372
373          expandToolStripMenuItem.Enabled =
374            expandToolStripMenuItem.Visible = !toolStripMenuNode.IsExpanded && toolStripMenuNode.Nodes.Count > 0;
375          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = toolStripMenuNode.IsExpanded;
376        } else {
377          expandToolStripMenuItem.Enabled = expandToolStripMenuItem.Visible = false;
378          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = false;
379        }
380        expandAllToolStripMenuItem.Enabled =
381          expandAllToolStripMenuItem.Visible =
382            !typesTreeView.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x));
383        collapseAllToolStripMenuItem.Enabled =
384          collapseAllToolStripMenuItem.Visible = typesTreeView.Nodes.OfType<TreeNode>().Any(x => x.IsExpanded);
385        if (contextMenuStrip.Items.Cast<ToolStripMenuItem>().Any(item => item.Enabled))
386          contextMenuStrip.Show(Cursor.Position);
387      }
388    }
389    private bool TreeNodeIsFullyExpanded(TreeNode node) {
390      return (node.Nodes.Count == 0) || (node.IsExpanded && node.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x)));
391    }
392
393    private void expandToolStripMenuItem_Click(object sender, EventArgs e) {
394      typesTreeView.BeginUpdate();
395      if (toolStripMenuNode != null) toolStripMenuNode.ExpandAll();
396      typesTreeView.EndUpdate();
397    }
398    private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) {
399      typesTreeView.BeginUpdate();
400      typesTreeView.ExpandAll();
401      typesTreeView.EndUpdate();
402    }
403    private void collapseToolStripMenuItem_Click(object sender, EventArgs e) {
404      typesTreeView.BeginUpdate();
405      if (toolStripMenuNode != null) toolStripMenuNode.Collapse();
406      typesTreeView.EndUpdate();
407    }
408    private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) {
409      typesTreeView.BeginUpdate();
410      typesTreeView.CollapseAll();
411      typesTreeView.EndUpdate();
412    }
413
414    private void clearSearchButton_Click(object sender, EventArgs e) {
415      searchTextBox.Text = string.Empty;
416      searchTextBox.Focus();
417    }
418
419    private void searchTextBox_KeyDown(object sender, KeyEventArgs e) {
420      if (typesTreeView.Nodes.Count == 0)
421        return;
422
423      if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) {
424        var selectedNode = typesTreeView.SelectedNode;
425
426        if (selectedNode == null) { // nothing selected => select first
427          if (e.KeyCode == Keys.Down) typesTreeView.SelectedNode = FirstVisibleNode;
428        } else {
429          if (e.KeyCode == Keys.Down && selectedNode.NextVisibleNode != null)
430            typesTreeView.SelectedNode = selectedNode.NextVisibleNode;
431          if (e.KeyCode == Keys.Up && selectedNode.PrevVisibleNode != null)
432            typesTreeView.SelectedNode = selectedNode.PrevVisibleNode;
433        }
434        e.Handled = true;
435      }
436    }
437
438    private TreeNode FirstVisibleNode {
439      get {
440        return typesTreeView.Nodes.Count > 0 ? typesTreeView.Nodes[0] : null;
441      }
442    }
443
444    private class ItemTreeNodeComparer : IComparer {
445      private static readonly IComparer<string> Comparer = new NaturalStringComparer();
446      public int Compare(object x, object y) {
447        var lhs = (TreeNode)x;
448        var rhs = (TreeNode)y;
449
450        if (lhs.Tag is string && rhs.Tag is string) {
451          return Comparer.Compare((string)lhs.Tag, (string)rhs.Tag);
452        }
453        if (lhs.Tag is string) {
454          return -1;
455        }
456        return 1;
457      }
458    }
459  }
460}
Note: See TracBrowser for help on using the repository browser.