Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Optimizer/3.3/NewItemDialog.cs @ 12397

Last change on this file since 12397 was 12397, checked in by pfleck, 9 years ago

#2387

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