Free cookie consent management tool by TermsFeed Policy Generator

source: branches/2839_HiveProjectManagement/HeuristicLab.Optimizer/3.3/NewItemDialog.cs @ 17912

Last change on this file since 17912 was 16057, checked in by jkarder, 6 years ago

#2839:

File size: 17.6 KB
RevLine 
[2790]1#region License Information
2/* HeuristicLab
[16057]3 * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[2790]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;
[12305]23using System.Collections;
24using System.Collections.Generic;
[7201]25using System.Drawing;
[2520]26using System.Linq;
27using System.Windows.Forms;
[12512]28using HeuristicLab.Common;
[12649]29using HeuristicLab.Common.Resources;
[2790]30using HeuristicLab.Core;
[2546]31using HeuristicLab.PluginInfrastructure;
[2520]32
33namespace HeuristicLab.Optimizer {
[2546]34  internal partial class NewItemDialog : Form {
[12305]35    private bool isInitialized;
[2546]36
[12305]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
[2546]51    private IItem item;
52    public IItem Item {
53      get { return item; }
[2520]54    }
55
[2546]56    public NewItemDialog() {
[12305]57      InitializeComponent();
58      treeNodes = new List<TreeNode>();
59      currentSearchString = string.Empty;
[2546]60      item = null;
[12305]61      SelectedTypeChanged += this_SelectedTypeChanged;
[2520]62    }
[2546]63
64    private void NewItemDialog_Load(object sender, EventArgs e) {
[12305]65      if (isInitialized) return;
[2546]66
[12512]67      // Sorted by hasOrdering to create category nodes first with concrete ordering.
68      // Items with categoryname without ordering are inserted afterwards correctly
[12305]69      var categories =
70        from type in ApplicationManager.Manager.GetTypes(typeof(IItem))
[12512]71        where CreatableAttribute.IsCreatable(type)
[12305]72        let category = CreatableAttribute.GetCategory(type)
[12512]73        let hasOrdering = category.Contains(CreatableAttribute.Categories.OrderToken)
[12305]74        let name = ItemAttribute.GetName(type)
75        let priority = CreatableAttribute.GetPriority(type)
76        let version = ItemAttribute.GetVersion(type)
[12512]77        orderby category, hasOrdering descending, priority, name, version ascending
[12305]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
[13433]95    #region Create Tree
[12305]96    private TreeNode CreateCategoryTree(IEnumerable<IGrouping<string, Type>> categories) {
[12649]97      imageList.Images.Add(VSImageLibrary.Class);      // default icon
98      imageList.Images.Add(VSImageLibrary.Namespace);  // plugins
[12305]99
100      var rootNode = new TreeNode();
101
[12512]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
[12305]107      foreach (var category in categories) {
[12512]108        var rawName = category.Key;
109        string fullName = CreatableAttribute.Categories.GetFullName(rawName);
110        string name = CreatableAttribute.Categories.GetName(rawName);
[12305]111
[12512]112        // Skip categories with same full name because the raw name can still be different (missing order)
[12649]113        if (rootNode.Nodes.Find(fullName, true).Length > 0)
[12512]114          continue;
115
[12649]116        var categoryNode = new TreeNode(name, 1, 1) {
[12305]117          Name = fullName,
[12512]118          Tag = rawName
[12305]119        };
120
[12512]121        var parents = CreatableAttribute.Categories.GetParentRawNames(rawName);
[12305]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    }
[12512]131    private TreeNode FindOrCreateParentNode(TreeNode node, IEnumerable<string> rawParentNames) {
[12305]132      TreeNode parentNode = null;
[12512]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);
[12649]137        parentNode = node.Nodes.Find(fullName, false).SingleOrDefault();
[12305]138        if (parentNode == null) {
[12512]139          var name = CreatableAttribute.Categories.GetName(rawName);
[12649]140          parentNode = new TreeNode(name, 1, 1) {
[12305]141            Name = fullName,
[12512]142            Tag = rawName
[12305]143          };
144          node.Nodes.Add(parentNode);
[2546]145        }
[12305]146        node = parentNode;
[2546]147      }
[12305]148      return parentNode;
[2546]149    }
[12305]150    private void CreateItemNodes(TreeNode node, IEnumerable<IGrouping<string, Type>> categories) {
151      foreach (var category in categories) {
[12512]152        var fullName = CreatableAttribute.Categories.GetFullName(category.Key);
[12649]153        var categoryNode = node.Nodes.Find(fullName, true).Single();
[12305]154        foreach (var creatable in category) {
155          var itemNode = CreateItemNode(creatable);
[12512]156          itemNode.Name = itemNode.Name + ":" + fullName;
[12305]157          categoryNode.Nodes.Add(itemNode);
158        }
159      }
160    }
161    private TreeNode CreateItemNode(Type creatable) {
162      string name = ItemAttribute.GetName(creatable);
[2546]163
[12305]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    }
[13433]179    #endregion
[12305]180
[2546]181    private void NewItemDialog_Shown(object sender, EventArgs e) {
[13433]182      Reset();
[2546]183    }
184
[12305]185    public virtual void Filter(string searchString) {
186      if (InvokeRequired) {
187        Invoke(new Action<string>(Filter), searchString);
188      } else {
189        searchString = searchString.ToLower();
[12398]190        var selectedNode = typesTreeView.SelectedNode;
[12305]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
[12397]216        typesTreeView.BeginUpdate();
[13433]217        typesTreeView.ExpandAll();
[12305]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        }
[12397]231        typesTreeView.EndUpdate();
[12398]232
233        RestoreSelectedNode(selectedNode);
[12305]234        UpdateDescription();
235      }
[2546]236    }
237
[12305]238    private bool FilterNode(TreeNode node, string[] searchTokens) {
239      if (node.Tag is string) { // Category node
[13433]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          }
[12305]249        }
250        return node.Nodes.Count == 0;
[13433]251      }
252      if (node.Tag is Type) { // Type node
[12305]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) {
[12512]274        var node = typesTreeView.SelectedNode;
275        string category = node.Tag as string;
[12305]276        if (category != null) {
[12512]277          itemDescriptionTextBox.Text = string.Join(" - ", node.Name.Split(new[] { CreatableAttribute.Categories.SplitToken }, StringSplitOptions.RemoveEmptyEntries));
[12305]278        }
[12512]279        Type type = node.Tag as Type;
[12305]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);
[13433]309      if (string.IsNullOrWhiteSpace(searchTextBox.Text))
310        Reset();
[12305]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) {
[12649]327        var node = typesTreeView.Nodes.Find(selectedNode.Name, true).SingleOrDefault();
[12401]328        if (node != null)
329          typesTreeView.SelectedNode = node;
330        if (typesTreeView.SelectedNode == null)
331          SelectedType = null;
[12305]332      }
333    }
334    private void SetTreeNodeVisibility() {
335      TreeNode selectedNode = typesTreeView.SelectedNode;
[13433]336      if (string.IsNullOrWhiteSpace(currentSearchString) && (typesTreeView.Nodes.Count > 1)) {
[12305]337        typesTreeView.CollapseAll();
338        if (selectedNode != null) typesTreeView.SelectedNode = selectedNode;
339      } else {
340        typesTreeView.ExpandAll();
341      }
[13433]342      if (selectedNode != null) {
343        typesTreeView.Nodes[0].EnsureVisible(); // scroll top first
344        selectedNode.EnsureVisible();
345      }
[12305]346    }
[13433]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    }
[12305]361    #endregion
362
[2546]363    private void okButton_Click(object sender, EventArgs e) {
[12305]364      if (SelectedType != null) {
365        item = (IItem)Activator.CreateInstance(SelectedType);
[2546]366        DialogResult = DialogResult.OK;
367        Close();
368      }
369    }
[12305]370    private void itemTreeView_DoubleClick(object sender, EventArgs e) {
371      if (SelectedType != null) {
372        item = (IItem)Activator.CreateInstance(SelectedType);
[2546]373        DialogResult = DialogResult.OK;
374        Close();
375      }
376    }
[12305]377    private void this_SelectedTypeChanged(object sender, EventArgs e) {
378      okButton.Enabled = SelectedType != null;
379    }
380
[12649]381    private TreeNode toolStripMenuNode;
[12305]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) {
[12399]412      typesTreeView.BeginUpdate();
[12305]413      if (toolStripMenuNode != null) toolStripMenuNode.ExpandAll();
[12399]414      typesTreeView.EndUpdate();
[12305]415    }
416    private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) {
[12399]417      typesTreeView.BeginUpdate();
[12305]418      typesTreeView.ExpandAll();
[12399]419      typesTreeView.EndUpdate();
[12305]420    }
421    private void collapseToolStripMenuItem_Click(object sender, EventArgs e) {
[12399]422      typesTreeView.BeginUpdate();
[12305]423      if (toolStripMenuNode != null) toolStripMenuNode.Collapse();
[12399]424      typesTreeView.EndUpdate();
[12305]425    }
426    private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) {
[12399]427      typesTreeView.BeginUpdate();
[12305]428      typesTreeView.CollapseAll();
[12399]429      typesTreeView.EndUpdate();
[12305]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
[12393]444        if (selectedNode == null) { // nothing selected => select first
[12305]445          if (e.KeyCode == Keys.Down) typesTreeView.SelectedNode = FirstVisibleNode;
446        } else {
[12393]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;
[12305]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 {
[12512]463      private static readonly IComparer<string> Comparer = new NaturalStringComparer();
[12305]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) {
[12512]469          return Comparer.Compare((string)lhs.Tag, (string)rhs.Tag);
[12649]470        }
471        if (lhs.Tag is string) {
[12305]472          return -1;
[12649]473        }
474        return 1;
[12305]475      }
476    }
[2520]477  }
478}
Note: See TracBrowser for help on using the repository browser.