Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.Optimizer/3.3/NewItemDialog.cs @ 16474

Last change on this file since 16474 was 15583, checked in by swagner, 7 years ago

#2640: Updated year of copyrights in license headers

File size: 17.6 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2018 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    #region Create Tree
96    private TreeNode CreateCategoryTree(IEnumerable<IGrouping<string, Type>> categories) {
97      imageList.Images.Add(VSImageLibrary.Class);      // default icon
98      imageList.Images.Add(VSImageLibrary.Namespace);  // plugins
99
100      var rootNode = new TreeNode();
101
102      // CategoryNode
103      // Tag: raw string, used for sorting, e.g. 1$$$Algorithms###2$$$Single Solution
104      // Name: full name = combined category name with parent categories, used for finding nodes in tree, e.g. Algorithms###Single Solution
105      // Text: category name, used for displaying on node itself, e.g. Single Solution
106
107      foreach (var category in categories) {
108        var rawName = category.Key;
109        string fullName = CreatableAttribute.Categories.GetFullName(rawName);
110        string name = CreatableAttribute.Categories.GetName(rawName);
111
112        // Skip categories with same full name because the raw name can still be different (missing order)
113        if (rootNode.Nodes.Find(fullName, true).Length > 0)
114          continue;
115
116        var categoryNode = new TreeNode(name, 1, 1) {
117          Name = fullName,
118          Tag = rawName
119        };
120
121        var parents = CreatableAttribute.Categories.GetParentRawNames(rawName);
122        var parentNode = FindOrCreateParentNode(rootNode, parents);
123        if (parentNode != null)
124          parentNode.Nodes.Add(categoryNode);
125        else
126          rootNode.Nodes.Add(categoryNode);
127      }
128
129      return rootNode;
130    }
131    private TreeNode FindOrCreateParentNode(TreeNode node, IEnumerable<string> rawParentNames) {
132      TreeNode parentNode = null;
133      string rawName = null;
134      foreach (string rawParentName in rawParentNames) {
135        rawName = rawName == null ? rawParentName : rawName + CreatableAttribute.Categories.SplitToken + rawParentName;
136        var fullName = CreatableAttribute.Categories.GetFullName(rawName);
137        parentNode = node.Nodes.Find(fullName, false).SingleOrDefault();
138        if (parentNode == null) {
139          var name = CreatableAttribute.Categories.GetName(rawName);
140          parentNode = new TreeNode(name, 1, 1) {
141            Name = fullName,
142            Tag = rawName
143          };
144          node.Nodes.Add(parentNode);
145        }
146        node = parentNode;
147      }
148      return parentNode;
149    }
150    private void CreateItemNodes(TreeNode node, IEnumerable<IGrouping<string, Type>> categories) {
151      foreach (var category in categories) {
152        var fullName = CreatableAttribute.Categories.GetFullName(category.Key);
153        var categoryNode = node.Nodes.Find(fullName, true).Single();
154        foreach (var creatable in category) {
155          var itemNode = CreateItemNode(creatable);
156          itemNode.Name = itemNode.Name + ":" + fullName;
157          categoryNode.Nodes.Add(itemNode);
158        }
159      }
160    }
161    private TreeNode CreateItemNode(Type creatable) {
162      string name = ItemAttribute.GetName(creatable);
163
164      var itemNode = new TreeNode(name) {
165        ImageIndex = 0,
166        Tag = creatable,
167        Name = name
168      };
169
170      var image = ItemAttribute.GetImage(creatable);
171      if (image != null) {
172        imageList.Images.Add(image);
173        itemNode.ImageIndex = imageList.Images.Count - 1;
174      }
175      itemNode.SelectedImageIndex = itemNode.ImageIndex;
176
177      return itemNode;
178    }
179    #endregion
180
181    private void NewItemDialog_Shown(object sender, EventArgs e) {
182      Reset();
183    }
184
185    public virtual void Filter(string searchString) {
186      if (InvokeRequired) {
187        Invoke(new Action<string>(Filter), searchString);
188      } else {
189        searchString = searchString.ToLower();
190        var selectedNode = typesTreeView.SelectedNode;
191
192        if (!searchString.Contains(currentSearchString)) {
193          typesTreeView.BeginUpdate();
194          // expand search -> restore all tree nodes
195          typesTreeView.Nodes.Clear();
196          foreach (TreeNode node in treeNodes)
197            typesTreeView.Nodes.Add((TreeNode)node.Clone());
198          typesTreeView.EndUpdate();
199        }
200
201        // remove nodes
202        typesTreeView.BeginUpdate();
203        var searchTokens = searchString.Split(' ');
204        int i = 0;
205        while (i < typesTreeView.Nodes.Count) {
206          var categoryNode = typesTreeView.Nodes[i];
207          bool remove = FilterNode(categoryNode, searchTokens);
208          if (remove)
209            typesTreeView.Nodes.RemoveAt(i);
210          else i++;
211        }
212        typesTreeView.EndUpdate();
213        currentSearchString = searchString;
214
215        // select first item
216        typesTreeView.BeginUpdate();
217        typesTreeView.ExpandAll();
218        var firstNode = FirstVisibleNode;
219        while (firstNode != null && !(firstNode.Tag is Type))
220          firstNode = firstNode.NextVisibleNode;
221        if (firstNode != null)
222          typesTreeView.SelectedNode = firstNode;
223
224        if (typesTreeView.Nodes.Count == 0) {
225          SelectedType = null;
226          typesTreeView.Enabled = false;
227        } else {
228          SetTreeNodeVisibility();
229          typesTreeView.Enabled = true;
230        }
231        typesTreeView.EndUpdate();
232
233        RestoreSelectedNode(selectedNode);
234        UpdateDescription();
235      }
236    }
237
238    private bool FilterNode(TreeNode node, string[] searchTokens) {
239      if (node.Tag is string) { // Category node
240        var text = node.Text;
241        if (searchTokens.Any(token => !text.ToLower().Contains(token))) {
242          int i = 0;
243          while (i < node.Nodes.Count) {
244            bool remove = FilterNode(node.Nodes[i], searchTokens);
245            if (remove)
246              node.Nodes.RemoveAt(i);
247            else i++;
248          }
249        }
250        return node.Nodes.Count == 0;
251      }
252      if (node.Tag is Type) { // Type node
253        var text = node.Text;
254        if (searchTokens.Any(searchToken => !text.ToLower().Contains(searchToken))) {
255          var typeTag = (Type)node.Tag;
256          if (typeTag == SelectedType) {
257            SelectedType = null;
258            typesTreeView.SelectedNode = null;
259          }
260          return true;
261        }
262        return false;
263      }
264      throw new InvalidOperationException("Encountered neither a category nor a creatable node during tree traversal.");
265    }
266
267    protected virtual void UpdateDescription() {
268      itemDescriptionTextBox.Text = string.Empty;
269      pluginDescriptionTextBox.Text = string.Empty;
270      pluginTextBox.Text = string.Empty;
271      versionTextBox.Text = string.Empty;
272
273      if (typesTreeView.SelectedNode != null) {
274        var node = typesTreeView.SelectedNode;
275        string category = node.Tag as string;
276        if (category != null) {
277          itemDescriptionTextBox.Text = string.Join(" - ", node.Name.Split(new[] { CreatableAttribute.Categories.SplitToken }, StringSplitOptions.RemoveEmptyEntries));
278        }
279        Type type = node.Tag as Type;
280        if (type != null) {
281          string description = ItemAttribute.GetDescription(type);
282          var version = ItemAttribute.GetVersion(type);
283          var plugin = ApplicationManager.Manager.GetDeclaringPlugin(type);
284          if (description != null)
285            itemDescriptionTextBox.Text = description;
286          if (plugin != null) {
287            pluginTextBox.Text = plugin.Name;
288            pluginDescriptionTextBox.Text = plugin.Description;
289          }
290          if (version != null)
291            versionTextBox.Text = version.ToString();
292        }
293      } else if (typesTreeView.Nodes.Count == 0) {
294        itemDescriptionTextBox.Text = "No types found";
295      }
296    }
297
298    #region Events
299    public event EventHandler SelectedTypeChanged;
300    protected virtual void OnSelectedTypeChanged() {
301      if (SelectedTypeChanged != null)
302        SelectedTypeChanged(this, EventArgs.Empty);
303    }
304    #endregion
305
306    #region Control Events
307    protected virtual void searchTextBox_TextChanged(object sender, EventArgs e) {
308      Filter(searchTextBox.Text);
309      if (string.IsNullOrWhiteSpace(searchTextBox.Text))
310        Reset();
311    }
312
313    protected virtual void itemsTreeView_AfterSelect(object sender, TreeViewEventArgs e) {
314      if (typesTreeView.SelectedNode == null) SelectedType = null;
315      else SelectedType = typesTreeView.SelectedNode.Tag as Type;
316      UpdateDescription();
317    }
318
319    protected virtual void itemsTreeView_VisibleChanged(object sender, EventArgs e) {
320      if (Visible) SetTreeNodeVisibility();
321    }
322    #endregion
323
324    #region Helpers
325    private void RestoreSelectedNode(TreeNode selectedNode) {
326      if (selectedNode != null) {
327        var node = typesTreeView.Nodes.Find(selectedNode.Name, true).SingleOrDefault();
328        if (node != null)
329          typesTreeView.SelectedNode = node;
330        if (typesTreeView.SelectedNode == null)
331          SelectedType = null;
332      }
333    }
334    private void SetTreeNodeVisibility() {
335      TreeNode selectedNode = typesTreeView.SelectedNode;
336      if (string.IsNullOrWhiteSpace(currentSearchString) && (typesTreeView.Nodes.Count > 1)) {
337        typesTreeView.CollapseAll();
338        if (selectedNode != null) typesTreeView.SelectedNode = selectedNode;
339      } else {
340        typesTreeView.ExpandAll();
341      }
342      if (selectedNode != null) {
343        typesTreeView.Nodes[0].EnsureVisible(); // scroll top first
344        selectedNode.EnsureVisible();
345      }
346    }
347    private void Reset() {
348      searchTextBox.Text = string.Empty;
349      searchTextBox.Focus();
350      SelectedType = null;
351      typesTreeView.SelectedNode = null;
352      UpdateDescription();
353
354      typesTreeView.BeginUpdate();
355      typesTreeView.CollapseAll();
356      foreach (TreeNode node in typesTreeView.Nodes)
357        node.Expand();
358      typesTreeView.Nodes[0].EnsureVisible();
359      typesTreeView.EndUpdate();
360    }
361    #endregion
362
363    private void okButton_Click(object sender, EventArgs e) {
364      if (SelectedType != null) {
365        item = (IItem)Activator.CreateInstance(SelectedType);
366        DialogResult = DialogResult.OK;
367        Close();
368      }
369    }
370    private void itemTreeView_DoubleClick(object sender, EventArgs e) {
371      if (SelectedType != null) {
372        item = (IItem)Activator.CreateInstance(SelectedType);
373        DialogResult = DialogResult.OK;
374        Close();
375      }
376    }
377    private void this_SelectedTypeChanged(object sender, EventArgs e) {
378      okButton.Enabled = SelectedType != null;
379    }
380
381    private TreeNode toolStripMenuNode;
382    private void typesTreeView_MouseDown(object sender, MouseEventArgs e) {
383      if (e.Button == MouseButtons.Right) {
384        Point coordinates = typesTreeView.PointToClient(Cursor.Position);
385        toolStripMenuNode = typesTreeView.GetNodeAt(coordinates);
386
387        if (toolStripMenuNode != null && coordinates.X >= toolStripMenuNode.Bounds.Left &&
388            coordinates.X <= toolStripMenuNode.Bounds.Right) {
389          typesTreeView.SelectedNode = toolStripMenuNode;
390
391          expandToolStripMenuItem.Enabled =
392            expandToolStripMenuItem.Visible = !toolStripMenuNode.IsExpanded && toolStripMenuNode.Nodes.Count > 0;
393          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = toolStripMenuNode.IsExpanded;
394        } else {
395          expandToolStripMenuItem.Enabled = expandToolStripMenuItem.Visible = false;
396          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = false;
397        }
398        expandAllToolStripMenuItem.Enabled =
399          expandAllToolStripMenuItem.Visible =
400            !typesTreeView.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x));
401        collapseAllToolStripMenuItem.Enabled =
402          collapseAllToolStripMenuItem.Visible = typesTreeView.Nodes.OfType<TreeNode>().Any(x => x.IsExpanded);
403        if (contextMenuStrip.Items.Cast<ToolStripMenuItem>().Any(item => item.Enabled))
404          contextMenuStrip.Show(Cursor.Position);
405      }
406    }
407    private bool TreeNodeIsFullyExpanded(TreeNode node) {
408      return (node.Nodes.Count == 0) || (node.IsExpanded && node.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x)));
409    }
410
411    private void expandToolStripMenuItem_Click(object sender, EventArgs e) {
412      typesTreeView.BeginUpdate();
413      if (toolStripMenuNode != null) toolStripMenuNode.ExpandAll();
414      typesTreeView.EndUpdate();
415    }
416    private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) {
417      typesTreeView.BeginUpdate();
418      typesTreeView.ExpandAll();
419      typesTreeView.EndUpdate();
420    }
421    private void collapseToolStripMenuItem_Click(object sender, EventArgs e) {
422      typesTreeView.BeginUpdate();
423      if (toolStripMenuNode != null) toolStripMenuNode.Collapse();
424      typesTreeView.EndUpdate();
425    }
426    private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) {
427      typesTreeView.BeginUpdate();
428      typesTreeView.CollapseAll();
429      typesTreeView.EndUpdate();
430    }
431
432    private void clearSearchButton_Click(object sender, EventArgs e) {
433      searchTextBox.Text = string.Empty;
434      searchTextBox.Focus();
435    }
436
437    private void searchTextBox_KeyDown(object sender, KeyEventArgs e) {
438      if (typesTreeView.Nodes.Count == 0)
439        return;
440
441      if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) {
442        var selectedNode = typesTreeView.SelectedNode;
443
444        if (selectedNode == null) { // nothing selected => select first
445          if (e.KeyCode == Keys.Down) typesTreeView.SelectedNode = FirstVisibleNode;
446        } else {
447          if (e.KeyCode == Keys.Down && selectedNode.NextVisibleNode != null)
448            typesTreeView.SelectedNode = selectedNode.NextVisibleNode;
449          if (e.KeyCode == Keys.Up && selectedNode.PrevVisibleNode != null)
450            typesTreeView.SelectedNode = selectedNode.PrevVisibleNode;
451        }
452        e.Handled = true;
453      }
454    }
455
456    private TreeNode FirstVisibleNode {
457      get {
458        return typesTreeView.Nodes.Count > 0 ? typesTreeView.Nodes[0] : null;
459      }
460    }
461
462    private class ItemTreeNodeComparer : IComparer {
463      private static readonly IComparer<string> Comparer = new NaturalStringComparer();
464      public int Compare(object x, object y) {
465        var lhs = (TreeNode)x;
466        var rhs = (TreeNode)y;
467
468        if (lhs.Tag is string && rhs.Tag is string) {
469          return Comparer.Compare((string)lhs.Tag, (string)rhs.Tag);
470        }
471        if (lhs.Tag is string) {
472          return -1;
473        }
474        return 1;
475      }
476    }
477  }
478}
Note: See TracBrowser for help on using the repository browser.