Free cookie consent management tool by TermsFeed Policy Generator

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

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

#2387 Fixed a bug where the automatic selection of the first element behaved differently for the NewItemDialog.

File size: 15.9 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        var selectedNode = typesTreeView.SelectedNode;
177
178        if (!searchString.Contains(currentSearchString)) {
179          typesTreeView.BeginUpdate();
180          // expand search -> restore all tree nodes
181          typesTreeView.Nodes.Clear();
182          foreach (TreeNode node in treeNodes)
183            typesTreeView.Nodes.Add((TreeNode)node.Clone());
184          typesTreeView.EndUpdate();
185        }
186
187        // remove nodes
188        typesTreeView.BeginUpdate();
189        var searchTokens = searchString.Split(' ');
190        int i = 0;
191        while (i < typesTreeView.Nodes.Count) {
192          var categoryNode = typesTreeView.Nodes[i];
193          bool remove = FilterNode(categoryNode, searchTokens);
194          if (remove)
195            typesTreeView.Nodes.RemoveAt(i);
196          else i++;
197        }
198        typesTreeView.EndUpdate();
199        currentSearchString = searchString;
200
201        // select first item
202        typesTreeView.BeginUpdate();
203        var firstNode = FirstVisibleNode;
204        while (firstNode != null && !(firstNode.Tag is Type))
205          firstNode = firstNode.NextVisibleNode;
206        if (firstNode != null)
207          typesTreeView.SelectedNode = firstNode;
208
209        if (typesTreeView.Nodes.Count == 0) {
210          SelectedType = null;
211          typesTreeView.Enabled = false;
212        } else {
213          SetTreeNodeVisibility();
214          typesTreeView.Enabled = true;
215        }
216        typesTreeView.EndUpdate();
217
218        RestoreSelectedNode(selectedNode);
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        if (node != null)
307          typesTreeView.SelectedNode = node;
308        if (typesTreeView.SelectedNode == null)
309          SelectedType = null;
310      }
311    }
312    private void SetTreeNodeVisibility() {
313      TreeNode selectedNode = typesTreeView.SelectedNode;
314      if (string.IsNullOrEmpty(currentSearchString) && (typesTreeView.Nodes.Count > 1)) {
315        typesTreeView.CollapseAll();
316        if (selectedNode != null) typesTreeView.SelectedNode = selectedNode;
317      } else {
318        typesTreeView.ExpandAll();
319      }
320      if (selectedNode != null) selectedNode.EnsureVisible();
321    }
322    #endregion
323
324    private void okButton_Click(object sender, EventArgs e) {
325      if (SelectedType != null) {
326        item = (IItem)Activator.CreateInstance(SelectedType);
327        DialogResult = DialogResult.OK;
328        Close();
329      }
330    }
331    private void itemTreeView_DoubleClick(object sender, EventArgs e) {
332      if (SelectedType != null) {
333        item = (IItem)Activator.CreateInstance(SelectedType);
334        DialogResult = DialogResult.OK;
335        Close();
336      }
337    }
338    private void this_SelectedTypeChanged(object sender, EventArgs e) {
339      okButton.Enabled = SelectedType != null;
340    }
341
342    private TreeNode toolStripMenuNode = null;
343    private void typesTreeView_MouseDown(object sender, MouseEventArgs e) {
344      if (e.Button == MouseButtons.Right) {
345        Point coordinates = typesTreeView.PointToClient(Cursor.Position);
346        toolStripMenuNode = typesTreeView.GetNodeAt(coordinates);
347
348        if (toolStripMenuNode != null && coordinates.X >= toolStripMenuNode.Bounds.Left &&
349            coordinates.X <= toolStripMenuNode.Bounds.Right) {
350          typesTreeView.SelectedNode = toolStripMenuNode;
351
352          expandToolStripMenuItem.Enabled =
353            expandToolStripMenuItem.Visible = !toolStripMenuNode.IsExpanded && toolStripMenuNode.Nodes.Count > 0;
354          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = toolStripMenuNode.IsExpanded;
355        } else {
356          expandToolStripMenuItem.Enabled = expandToolStripMenuItem.Visible = false;
357          collapseToolStripMenuItem.Enabled = collapseToolStripMenuItem.Visible = false;
358        }
359        expandAllToolStripMenuItem.Enabled =
360          expandAllToolStripMenuItem.Visible =
361            !typesTreeView.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x));
362        collapseAllToolStripMenuItem.Enabled =
363          collapseAllToolStripMenuItem.Visible = typesTreeView.Nodes.OfType<TreeNode>().Any(x => x.IsExpanded);
364        if (contextMenuStrip.Items.Cast<ToolStripMenuItem>().Any(item => item.Enabled))
365          contextMenuStrip.Show(Cursor.Position);
366      }
367    }
368    private bool TreeNodeIsFullyExpanded(TreeNode node) {
369      return (node.Nodes.Count == 0) || (node.IsExpanded && node.Nodes.OfType<TreeNode>().All(x => TreeNodeIsFullyExpanded(x)));
370    }
371
372    private void expandToolStripMenuItem_Click(object sender, EventArgs e) {
373      typesTreeView.BeginUpdate();
374      if (toolStripMenuNode != null) toolStripMenuNode.ExpandAll();
375      typesTreeView.EndUpdate();
376    }
377    private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) {
378      typesTreeView.BeginUpdate();
379      typesTreeView.ExpandAll();
380      typesTreeView.EndUpdate();
381    }
382    private void collapseToolStripMenuItem_Click(object sender, EventArgs e) {
383      typesTreeView.BeginUpdate();
384      if (toolStripMenuNode != null) toolStripMenuNode.Collapse();
385      typesTreeView.EndUpdate();
386    }
387    private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) {
388      typesTreeView.BeginUpdate();
389      typesTreeView.CollapseAll();
390      typesTreeView.EndUpdate();
391    }
392
393    private void clearSearchButton_Click(object sender, EventArgs e) {
394      searchTextBox.Text = string.Empty;
395      searchTextBox.Focus();
396    }
397
398    private void searchTextBox_KeyDown(object sender, KeyEventArgs e) {
399      if (typesTreeView.Nodes.Count == 0)
400        return;
401
402      if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down) {
403        var selectedNode = typesTreeView.SelectedNode;
404
405        if (selectedNode == null) { // nothing selected => select first
406          if (e.KeyCode == Keys.Down) typesTreeView.SelectedNode = FirstVisibleNode;
407        } else {
408          if (e.KeyCode == Keys.Down && selectedNode.NextVisibleNode != null)
409            typesTreeView.SelectedNode = selectedNode.NextVisibleNode;
410          if (e.KeyCode == Keys.Up && selectedNode.PrevVisibleNode != null)
411            typesTreeView.SelectedNode = selectedNode.PrevVisibleNode;
412        }
413        e.Handled = true;
414      }
415    }
416
417    private TreeNode FirstVisibleNode {
418      get {
419        return typesTreeView.Nodes.Count > 0 ? typesTreeView.Nodes[0] : null;
420      }
421    }
422    private TreeNode LastVisibleNode {
423      get {
424        var node = FirstVisibleNode;
425        while (node != null && node.NextVisibleNode != null) node = node.NextVisibleNode;
426        return node;
427      }
428    }
429
430    private class ItemTreeNodeComparer : IComparer {
431      public int Compare(object x, object y) {
432        var lhs = (TreeNode)x;
433        var rhs = (TreeNode)y;
434
435        if (lhs.Tag is string && rhs.Tag is string) {
436          return lhs.Name.CompareTo(rhs.Name);
437        } else if (lhs.Tag is string) {
438          return -1;
439        } else
440          return 1;
441      }
442    }
443  }
444}
Note: See TracBrowser for help on using the repository browser.