Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2025

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