private IEnumerable <RootNodeCategoryViewModel> CategorizeEntries(IEnumerable <NodeSearchElement> entries, bool expanded) { var tempRoot = entries.GroupByRecursive <NodeSearchElement, string, NodeCategoryViewModel>( element => element.Categories, (name, subs, es) => { var category = new NodeCategoryViewModel(name, es.OrderBy(en => en.Name).Select(MakeNodeSearchElementVM), subs); category.IsExpanded = expanded; category.RequestBitmapSource += SearchViewModelRequestBitmapSource; category.RequestReturnFocusToSearch += OnRequestFocusSearch; return(category); }, ""); var result = tempRoot.SubCategories.Select(cat => { var rootCat = new RootNodeCategoryViewModel(cat.Name, cat.Entries, cat.SubCategories) { IsExpanded = expanded }; rootCat.RequestReturnFocusToSearch += OnRequestFocusSearch; return(rootCat); }); tempRoot.Dispose(); return(result.OrderBy(cat => cat.Name)); }
private IEnumerable <RootNodeCategoryViewModel> CategorizeEntries(IEnumerable <NodeSearchElement> entries, bool expanded) { var tempRoot = entries.GroupByRecursive <NodeSearchElement, string, NodeCategoryViewModel>( element => element.Categories, (name, subs, es) => { var category = new NodeCategoryViewModel(name, es.OrderBy(en => en.Name).Select(MakeNodeSearchElementVM), subs); category.IsExpanded = expanded; category.RequestBitmapSource += SearchViewModelRequestBitmapSource; category.RequestReturnFocusToSearch += OnRequestFocusSearch; return(category); }, ""); var result = tempRoot.SubCategories.Select(cat => { var rootCat = new RootNodeCategoryViewModel(cat.Name, cat.Entries, cat.SubCategories) { IsExpanded = expanded }; rootCat.RequestReturnFocusToSearch += OnRequestFocusSearch; // Since all the root categories will be new RootNodeCategoryViewModel objects, // we should dispose the old ones. Since they are still watching for subcategories' // property changes, they will never be garbage collected. cat.Dispose(); return(rootCat); }); tempRoot.Dispose(); return(result.OrderBy(cat => cat.Name)); }
internal bool MakeOrClearSelection(NodeCategoryViewModel selectedClass) { if (currentClass != null) { if (currentClass != selectedClass) { // If 'itemIndex' is '-1', then the selection will be cleared, // otherwise the selection is set to the same as 'itemIndex'. var itemIndex = collection.IndexOf(selectedClass); classListView.SelectedIndex = itemIndex; return(true); // The call is handled. } else { // If current item is selected item, then class button was pressed second time. // Selection should be cleaned. classListView.SelectedIndex = -1; return(true); } } else { // No selection, if item is within collection, select it. var itemIndex = collection.IndexOf(selectedClass); if (itemIndex != -1) { classListView.SelectedIndex = itemIndex; return(true); // The call is handled. } } // The call is not handled. return(false); }
public void BrowserInternalElementToBoolConverterTest() { var converter = new NodeCategoryVMToBoolConverter(); var NcVM = new NodeCategoryViewModel(""); var RncVM = new RootNodeCategoryViewModel(""); var CncVM = new ClassesNodeCategoryViewModel(RncVM); object result; //1. Element is null. //2. Element is NodeCategoryViewModel. //2. Element is RootNodeCategoryViewModel. //2. Element is ClassesNodeCategoryViewModel. // 1 case result = converter.Convert(null, null, null, null); Assert.AreEqual(false, result); // 2 case result = converter.Convert(NcVM, null, null, null); Assert.AreEqual(true, result); // 3 case result = converter.Convert(RncVM, null, null, null); Assert.AreEqual(false, result); // 4 case result = converter.Convert(CncVM, null, null, null); Assert.AreEqual(false, result); }
private static NodeCategoryViewModel GetCategoryViewModel(NodeCategoryViewModel rootCategory, IEnumerable <string> categories) { var nameStack = new Stack <string>(categories.Reverse()); NodeCategoryViewModel target = rootCategory; NodeCategoryViewModel newTarget = null; bool isCheckedForClassesCategory = false; while (nameStack.Any()) { var currentCategory = nameStack.Pop(); newTarget = target.SubCategories.FirstOrDefault(c => c.Name == currentCategory); if (newTarget == null) { if (!isCheckedForClassesCategory && !target.IsClassButton && target.SubCategories[0] is ClassesNodeCategoryViewModel) { isCheckedForClassesCategory = true; nameStack.Push(currentCategory); nameStack.Push(Configurations.ClassesDefaultName); continue; } return(null); } target = newTarget; } return(target); }
// TODO: classes functionality. internal void AddClassToGroup(NodeCategoryViewModel memberNode) { // TODO: The following limit of displaying only two classes are // temporary, it should be updated whenever the design intent has been finalized. // http://adsk-oss.myjetbrains.com/youtrack/issue/MAGN-6199 //const int maxClassesCount = 2; //if (classes.Count >= maxClassesCount) // return; // Parent should be of 'BrowserInternalElement' type or derived. // Root category can't be added to classes list. // TODO(Vladimir): Implement the logic when classes are shown in search results. }
private static void PlaceInNewCategory( NodeSearchElementViewModel entry, NodeCategoryViewModel target, IEnumerable <string> categoryNames) { var newTargets = categoryNames.Select(name => new NodeCategoryViewModel(name)); foreach (var newTarget in newTargets) { target.SubCategories.Add(newTarget); target = newTarget; } target.Entries.Add(entry); }
private void AddEntryToExistingCategory(NodeCategoryViewModel category, NodeSearchElementViewModel entry) { category.RequestBitmapSource += SearchViewModelRequestBitmapSource; // Check if the category exists already. // ex : clockwork package. For clockwork // package the category names in dyf is different from what we show it // on the tree view. so when you click on the category to populate it // triggers an update to category name. on the same instance when you uninstall // and insall the clockwork package, the categories are named correctly but // every install triggers an update that gives a duplicate entry. so check if the // entry is already added (specific to browse). if (category.Entries.All(x => x.FullName != entry.FullName)) { category.Entries.Add(entry); } }
public void LibraryTreeItemsHostVisibilityConverterTest() { var converter = new LibraryTreeItemsHostVisibilityConverter(); var result = converter.Convert(null, null, null, null); Assert.AreEqual(Visibility.Visible, result); var NcVM = new NodeCategoryViewModel(""); result = converter.Convert(NcVM, null, null, null); Assert.AreEqual(Visibility.Visible, result); var RncVM = new ClassesNodeCategoryViewModel(NcVM); result = converter.Convert(RncVM, null, null, null); Assert.AreEqual(Visibility.Collapsed, result); }
private void ExpandCategory(TreeViewItem sender, NodeCategoryViewModel selectedClass) { // Get all category items. var categories = sender.Items.OfType <NodeCategoryViewModel>(); // Get all current expanded categories. List <NodeCategoryViewModel> allExpandedCategories = categories. Where(cat => (!(cat is ClassesNodeCategoryViewModel) && cat.IsExpanded == true)).ToList(); var categoryToBeExpanded = categories.Where(cat => cat == selectedClass).FirstOrDefault(); // If categoryToBeExpanded is null, that means not category button, but class button was clicked. // During loop we will find out to which category this clicked class belongs. if (categoryToBeExpanded != null) { categoryToBeExpanded.IsExpanded = !categoryToBeExpanded.IsExpanded; } // Get expanded categories that should be collapsed. var categoriesToBeCollapsed = allExpandedCategories.Remove(categoryToBeExpanded); // Close all open categories, except one, that contains class. // Or if category was clicked, also expand it and close others. foreach (var categoryToBeCollapsed in allExpandedCategories) { // If class button was clicked. if (categoryToBeExpanded == null) { var categoryClasses = categoryToBeCollapsed.Items[0] as ClassesNodeCategoryViewModel; // Ensure, that this class is not part of current category. if (categoryClasses != null) { categoryToBeCollapsed.IsExpanded = categoryClasses.Items.Contains(selectedClass); } } // If category button was clicked. else { categoryToBeCollapsed.IsExpanded = false; } } }
private void OnClassViewSelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedIndex = (sender as ListView).SelectedIndex; // As focus moves within the class details, class button gets selected which // triggers a selection change. During a selection change the items in the // wrap panel gets reordered through "OrderListItems", but this is not always // necessary. Here we determine if the "translatedIndex" is the same as // "selectedClassProspectiveIndex", if so simply returns to avoid a repainting. var translatedIndex = TranslateSelectionIndex(selectedIndex); if (selectedClassProspectiveIndex == translatedIndex) { return; } selectedClassProspectiveIndex = translatedIndex; int classInfoIndex = GetClassInformationIndex(); // If user clicks on the same item when it is expanded, then 'OnClassButtonCollapse' // is invoked to deselect the item. This causes 'OnClassViewSelectionChanged' to be // called again, with 'SelectedIndex' set to '-1', indicating that no item is selected, // in which case we need to hide the ClassInformationView. if (selectedClassProspectiveIndex == -1) { if (classInfoIndex != -1) { (collection[classInfoIndex] as ClassInformationViewModel).ClassDetailsVisibility = false; currentClass = null; } OrderListItems(); return; } else { (collection[classInfoIndex] as ClassInformationViewModel).ClassDetailsVisibility = true; } currentClass = collection[selectedIndex] as NodeCategoryViewModel; OrderListItems(); // Selection change, we may need to reorder items. }
public void FindInsertionPointByNameTest() { var elementVM = CreateCustomNodeViewModel("AMember", "TopCategory"); viewModel.InsertEntry(elementVM, elementVM.Model.Categories); elementVM = CreateCustomNodeViewModel("ZMember", "TopCategory"); viewModel.InsertEntry(elementVM, elementVM.Model.Categories); var listOfMembers = viewModel.BrowserRootCategories.First(c => c.Name == "TopCategory").Items; var index = NodeCategoryViewModel.FindInsertionPointByName(listOfMembers, "BMember"); Assert.AreEqual(1, index); elementVM = CreateCustomNodeViewModel("BMember", "TopCategory"); viewModel.InsertEntry(elementVM, elementVM.Model.Categories); index = NodeCategoryViewModel.FindInsertionPointByName(listOfMembers, "BMember"); Assert.AreEqual(1, index); }
private static IEnumerable <NodeCategoryViewModel> GetTreeBranchToNode( NodeCategoryViewModel rootNode, NodeSearchElement leafNode) { var nodesOnBranch = new Stack <NodeCategoryViewModel>(); var nameStack = new Stack <string>(leafNode.Categories.Reverse()); var target = rootNode; bool isCheckedForClassesCategory = false; while (nameStack.Any()) { var next = nameStack.Pop(); var categories = target.SubCategories; var newTarget = categories.FirstOrDefault(c => c.Name == next); if (newTarget == null) { // The last entry in categories list can be a class name. When the desired class // cannot be located with "MyAssembly.MyNamespace.ClassCandidate" pattern, try // searching with "MyAssembly.MyNamespace.Classes.ClassCandidate" instead. This // is because a class always resides under a "ClassesNodeCategoryViewModel" node. // if (!isCheckedForClassesCategory && nameStack.Count == 0) { nameStack.Push(next); nameStack.Push(Configurations.ClassesDefaultName); isCheckedForClassesCategory = true; continue; } return(Enumerable.Empty <NodeCategoryViewModel>()); } nodesOnBranch.Push(target); target = newTarget; } nodesOnBranch.Push(target); return(nodesOnBranch); }
public void ElementTypeToBoolConverterTest() { ElementTypeToBoolConverter converter = new ElementTypeToBoolConverter(); var NseVM = new NodeSearchElementViewModel( new NodeModelSearchElement(new TypeLoadData(typeof(Dynamo.Nodes.Symbol))), null); var NcVM = new NodeCategoryViewModel(""); var RncVM = new RootNodeCategoryViewModel(""); var CncVM = new ClassesNodeCategoryViewModel(RncVM); object result; //1. Element is null. //2. Element is NodeSearchElement. //3. Element is NodeCategoryViewModel. //4. Element is RootNodeCategoryViewModel. //5. Element is RootNodeCategoryViewModel with ClassesNodeCategoryViewModel. // 1 case result = converter.Convert(null, null, null, null); Assert.AreEqual(false, result); // 2 case result = converter.Convert(NseVM, null, null, null); Assert.AreEqual(false, result); // 3 case result = converter.Convert(NcVM, null, null, null); Assert.AreEqual(true, result); // 4 case result = converter.Convert(RncVM, null, null, null); Assert.AreEqual(true, result); // 5 case RncVM.SubCategories.Add(CncVM); result = converter.Convert(RncVM, null, null, null); Assert.AreEqual(false, result); }
private static IEnumerable <NodeSearchElementViewModel> GetVisibleSearchResults(NodeCategoryViewModel category) { foreach (var item in category.Items) { var sub = item as NodeCategoryViewModel; if (sub != null) { foreach (var visible in GetVisibleSearchResults(sub)) { yield return(visible); } } else { yield return((NodeSearchElementViewModel)item); } } }
internal SearchMemberGroup(string fullyQualifiedName, NodeCategoryViewModel category = null) { FullyQualifiedName = fullyQualifiedName; Category = category; members = new List <NodeSearchElementViewModel>(); }
private void InsertEntryIntoNewCategory( NodeCategoryViewModel category, NodeSearchElementViewModel entry, IEnumerable <string> categoryNames) { if (!categoryNames.Any()) { AddEntryToExistingCategory(category, entry); return; } // With the example of 'MyAssembly.MyNamespace.MyClass.Foo', 'path' would have been // set to 'MyAssembly' here. The Select statement below would store two entries into // 'newTargets' variable: // // NodeCategoryViewModel("MyAssembly.MyNamespace") // NodeCategoryViewModel("MyAssembly.MyNamespace.MyClass") // var path = category.FullCategoryName; var newTargets = categoryNames.Select(name => { path = MakeFullyQualifiedName(path, name); var cat = new NodeCategoryViewModel(name); cat.FullCategoryName = path; cat.Assembly = entry.Assembly; return(cat); }).ToList(); // The last entry 'NodeCategoryViewModel' represents a class. For our example the // entries in 'newTargets' are: // // NodeCategoryViewModel("MyAssembly.MyNamespace") // NodeCategoryViewModel("MyAssembly.MyNamespace.MyClass") // // Since all class entries are contained under a 'ClassesNodeCategoryViewModel', // we need to create a new 'ClassesNodeCategoryViewModel' instance, and insert it // right before the class entry itself to get the following list: // // NodeCategoryViewModel("MyAssembly.MyNamespace") // ClassesNodeCategoryViewModel("Classes") // NodeCategoryViewModel("MyAssembly.MyNamespace.MyClass") // int indexToInsertClass = newTargets.Count - 1; var classParent = indexToInsertClass > 0 ? newTargets[indexToInsertClass - 1] : category; var newClass = new ClassesNodeCategoryViewModel(classParent); newTargets.Insert(indexToInsertClass, newClass); // Here, all the entries in 'newTargets' are added under 'MyAssembly' recursively, // resulting in the following hierarchical structure: // // NodeCategoryViewModel("MyAssembly") // NodeCategoryViewModel("MyAssembly.MyNamespace") // ClassesNodeCategoryViewModel("Classes") // NodeCategoryViewModel("MyAssembly.MyNamespace.MyClass") // foreach (var newTarget in newTargets) { category.SubCategories.Add(newTarget); category = newTarget; } AddEntryToExistingCategory(category, entry); }
/// <summary> /// Insert a new search element under the category. /// </summary> /// <param name="entry">This could represent a function of a given /// class. For example, 'MyAssembly.MyNamespace.MyClass.Foo'.</param> /// <param name="categoryNames">A list of entries that make up the fully qualified /// class name that contains function 'Foo', e.g. 'MyAssembly.MyNamespace.MyClass'. /// </param> internal void InsertEntry(NodeSearchElementViewModel entry, IEnumerable <string> categoryNames) { var nameStack = new Stack <string>(categoryNames.Reverse()); var target = libraryRoot; string fullyQualifiedCategoryName = ""; ClassesNodeCategoryViewModel targetClass = null; while (nameStack.Any()) { var next = nameStack.Pop(); fullyQualifiedCategoryName = MakeFullyQualifiedName(fullyQualifiedCategoryName, next); var categories = target.SubCategories; NodeCategoryViewModel targetClassSuccessor = null; var newTarget = categories.FirstOrDefault(c => { // Each path has one class. We should find and save it. if (c is ClassesNodeCategoryViewModel) { targetClass = c as ClassesNodeCategoryViewModel; // As soon as ClassesNodeCategoryViewModel is found we should search // through all it classes and save result. targetClassSuccessor = c.SubCategories.FirstOrDefault(c2 => c2.Name == next); return(targetClassSuccessor != null); } return(c.Name == next); }); if (newTarget == null) { // For the first iteration, this would be 'MyAssembly', and the second iteration 'MyNamespace'. var targetIsRoot = target == libraryRoot; newTarget = targetIsRoot ? new RootNodeCategoryViewModel(next) : new NodeCategoryViewModel(next); newTarget.FullCategoryName = fullyQualifiedCategoryName; newTarget.Assembly = entry.Assembly; // Situation when we to add only one new category and item as it child. // New category should be added to existing ClassesNodeCategoryViewModel. // Make notice: ClassesNodeCategoryViewModel is always first item in // all subcategories. if (nameStack.Count == 0 && !target.IsClassButton && target.SubCategories[0] is ClassesNodeCategoryViewModel) { target.SubCategories[0].SubCategories.Add(newTarget); AddEntryToExistingCategory(newTarget, entry); return; } // We are here when target is the class. New category should be added // as child of it. So class will turn into usual category. // Here we are take class, remove it from ClassesNodeCategoryViewModel // and attach to it parrent. if (targetClass != null) { if (targetClass.SubCategories.Remove(target)) { targetClass.Parent.SubCategories.Add(target); } // Delete empty classes container. if (targetClass.IsClassButton) { targetClass.Parent.SubCategories.RemoveAt(0); } targetClass.Dispose(); } // Situation when we need to add only one new category and item. // Before adding of it we need create new ClassesNodeCategoryViewModel // as soon as new category will be a class. if (nameStack.Count == 0 && !targetIsRoot) { targetClass = new ClassesNodeCategoryViewModel(target); target.SubCategories.Insert(0, targetClass); target.SubCategories[0].SubCategories.Add(newTarget); AddEntryToExistingCategory(newTarget, entry); return; } target.InsertSubCategory(newTarget); // Proceed to insert the new entry under 'newTarget' category with the remaining // name stack. In the first iteration this would have been 'MyNamespace.MyClass'. InsertEntryIntoNewCategory(newTarget, entry, nameStack); return; } // If we meet ClassesNodecategoryViewModel during the search of newTarget, // next newTarget is specified in targetClassSuccessor. if (targetClassSuccessor != null) { target = targetClassSuccessor; } else { target = newTarget; } } AddEntryToExistingCategory(target, entry); }
private bool ExpandCategory(IEnumerable <NodeCategoryViewModel> categories, NodeCategoryViewModel selectedClass) { bool foundSelectedClass = false; // Get all current expanded categories. var allExpandedCategories = categories.Where(cat => { return(cat.IsExpanded && (!(cat is ClassesNodeCategoryViewModel))); }).ToList(); var categoryToBeExpanded = categories.FirstOrDefault(cat => cat == selectedClass); // If categoryToBeExpanded is null, that means the clicked item does not // represent a category button, but a class button. In the recursive call // the category in which this clicked class belong will be identified. if (categoryToBeExpanded != null) { categoryToBeExpanded.IsExpanded = !categoryToBeExpanded.IsExpanded; foundSelectedClass = true; } // Get expanded categories that should be collapsed. allExpandedCategories.Remove(categoryToBeExpanded); // Close all expanded categories except the one that contains the target // class button. If the clicked NodeCategoryViewModel represents a category // itself, then expand it and close out other sibling NodeCategoryViewModel. foreach (var expandedCategory in allExpandedCategories) { var searchFurtherInNextLevel = true; // If class button was clicked. if (selectedClass.IsClassButton) { var categoryClasses = expandedCategory.Items[0] as ClassesNodeCategoryViewModel; if (categoryClasses != null) // There are classes under this category... { if (expandedCategory.IsClassButton) { // If the category does not contain any sub category, // then we won't look for the selected class within it. expandedCategory.IsExpanded = false; searchFurtherInNextLevel = false; } else if (categoryClasses.Items.Contains(selectedClass)) { // If the category contains the selected class directly // within, then keep it expanded instead of collapsing it. expandedCategory.IsExpanded = true; // Found the selected class! Collapse all other sub categories. foreach (var ele in expandedCategory.SubCategories) { ele.IsExpanded = false; } searchFurtherInNextLevel = false; } } } if (searchFurtherInNextLevel) { var childCategories = expandedCategory.Items.OfType <NodeCategoryViewModel>(); expandedCategory.IsExpanded = ExpandCategory(childCategories, selectedClass); } // If the category remains expanded after this, we can // be sure that the selected class was found within it. foundSelectedClass = foundSelectedClass || expandedCategory.IsExpanded; } return(foundSelectedClass); }
private void AddEntryToExistingCategory(NodeCategoryViewModel category, NodeSearchElementViewModel entry) { category.RequestBitmapSource += SearchViewModelRequestBitmapSource; category.Entries.Add(entry); }
/// <summary> /// Performs a search and updates the observable SearchResults property. /// </summary> /// <param name="query"> The search query </param> public void SearchAndUpdateResults(string query) { if (Visible != true) { return; } // deselect the last selected item if (visibleSearchResults.Count > SelectedIndex) { visibleSearchResults[SelectedIndex].IsSelected = false; } // if the search query is empty, go back to the default treeview if (string.IsNullOrEmpty(query)) { return; } // clear visible results list visibleSearchResults.Clear(); foreach (var category in SearchRootCategories) { category.DisposeTree(); } SearchRootCategories.Clear(); if (string.IsNullOrEmpty(query)) { return; } var result = Model.Search(query).Where(r => r.IsVisibleInSearch).Take(MaxNumSearchResults).ToList(); // Add top result var firstRes = result.FirstOrDefault(); if (firstRes == null) { return; //No results } var topResultCategory = new RootNodeCategoryViewModel("Top Result"); SearchRootCategories.Add(topResultCategory); var copy = MakeNodeSearchElementVM(firstRes); var catName = MakeShortCategoryString(firstRes.FullCategoryName); var breadCrumb = new NodeCategoryViewModel(catName) { IsExpanded = true }; breadCrumb.Entries.Add(copy); topResultCategory.SubCategories.Add(breadCrumb); topResultCategory.Visibility = true; topResultCategory.IsExpanded = true; SearchRootCategories.AddRange(CategorizeEntries(result, true)); visibleSearchResults.AddRange(SearchRootCategories.SelectMany(GetVisibleSearchResults)); if (visibleSearchResults.Any()) { SelectedIndex = 0; visibleSearchResults[0].IsSelected = true; } SearchResults.Clear(); foreach (var x in visibleSearchResults) { SearchResults.Add(x); } }