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

Last change on this file since 12504 was 12504, checked in by mkommend, 7 years ago

#2025: Changed categories for all creatables.

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