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

Last change on this file since 12506 was 12506, checked in by pfleck, 7 years ago

#2025 Added ordering for categories.

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