public void Convert_ToLowerCase_ConvertGivenStringToLowerCase() { var sorting = new Sorting.Sorting(InputString); var lowerCaseString = sorting.InputString.ToLower(); Assert.AreEqual(ExpectedOutputStringLowerCase, lowerCaseString); }
public virtual void InitMediaNavigation(out string mediaNavigationMode, out NavigationData navigationData) { Prepare(); string nextScreenName; AbstractScreenData nextScreen = null; // Try to load the prefered next screen from settings. if (NavigationData.LoadScreenHierarchy(_viewName, out nextScreenName)) { // Support for browsing mode. if (nextScreenName == Consts.USE_BROWSE_MODE) { SetBrowseMode(); } if (_availableScreens != null) { nextScreen = _availableScreens.FirstOrDefault(s => s.GetType().ToString() == nextScreenName); } } IEnumerable <Guid> optionalMIATypeIDs = MediaNavigationModel.GetMediaSkinOptionalMIATypes(MediaNavigationMode); if (_optionalMias != null) { optionalMIATypeIDs = optionalMIATypeIDs.Union(_optionalMias); optionalMIATypeIDs = optionalMIATypeIDs.Except(_necessaryMias); } // Prefer custom view specification. ViewSpecification rootViewSpecification = _customRootViewSpecification ?? new MediaLibraryQueryViewSpecification(_viewName, _filter, _necessaryMias, optionalMIATypeIDs, true, _necessaryMias) { MaxNumItems = Consts.MAX_NUM_ITEMS_VISIBLE }; if (nextScreen == null) { nextScreen = _defaultScreen; } ScreenConfig nextScreenConfig; NavigationData.LoadLayoutSettings(nextScreen.GetType().ToString(), out nextScreenConfig); Sorting.Sorting nextSortingMode = _availableSortings.FirstOrDefault(sorting => sorting.GetType().ToString() == nextScreenConfig.Sorting) ?? _defaultSorting; Sorting.Sorting nextGroupingMode = _availableGroupings == null || String.IsNullOrEmpty(nextScreenConfig.Grouping) ? null : _availableGroupings.FirstOrDefault(grouping => grouping.GetType().ToString() == nextScreenConfig.Grouping) ?? _defaultGrouping; navigationData = new NavigationData(null, _viewName, MediaNavigationRootState, MediaNavigationRootState, rootViewSpecification, nextScreen, _availableScreens, nextSortingMode, nextGroupingMode) { AvailableSortings = _availableSortings, AvailableGroupings = _availableGroupings, LayoutType = nextScreenConfig.LayoutType, LayoutSize = nextScreenConfig.LayoutSize }; mediaNavigationMode = MediaNavigationMode; }
public void FilterOut_NonLetters_RemovesAnyNonLetters() { var sorting = new Sorting.Sorting(InputString); var regexFilter = @"[^A-Za-z]"; sorting.FilteredString = sorting.FilterOut(regexFilter); Assert.AreEqual(ExpectedOutputStringOnlyLetters, sorting.FilteredString); }
public void FilterOut_Numbers_RemoveNumbersOnly() { var sorting = new Sorting.Sorting(InputString); var regexFilter = @"[^A-Za-z\p{P}\p{S}]"; sorting.FilteredString = sorting.FilterOut(regexFilter); Assert.AreEqual(ExpectedOutputStringNoNumbers, sorting.FilteredString); }
public void FilterOut_SpecialCharacters_RemoveSpecialCharactersOnly() { var sorting = new Sorting.Sorting(InputString); var regexFilter = @"[^A-Za-z0-9]"; sorting.FilteredString = sorting.FilterOut(regexFilter); Assert.AreEqual(ExpectedOutputStringNoSpecialCharacters, sorting.FilteredString); }
public void Sort_Alphabetically_SortGivenStringAlphabeticallyAndReturnSortedArray() { var sorting = new Sorting.Sorting("thisisatest"); string[] expectedSortedStringArray = { "a", "e", "h", "i", "i", "s", "s", "s", "t", "t", "t" }; var actualSortedStringArray = sorting.SortLowerCaseFilteredString(sorting.InputString); CollectionAssert.AreEqual(expectedSortedStringArray, actualSortedStringArray); }
/// <summary> /// Returns all media items of the current screen and all sub-screens recursively. /// </summary> /// <returns>Enumeration of media items.</returns> public IEnumerable <MediaItem> GetAllMediaItems() { List <MediaItem> result = new List <MediaItem>(GetAllMediaItemsOverride()); Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { result.Sort(sorting); } return(result); }
public void Concatenate_StringArrayIntoToString_ConcatenateTheGivenStringArrayIntoSingleString() { var sorting = new Sorting.Sorting(); string[] sortedStringArray = { "a", "e", "h", "i", "i", "s", "s", "s", "t", "t", "t" }; var expectedConcatenatedString = "aehiisssttt"; var actualConcatenatedString = sorting.ConcatenateGivenString(sortedStringArray); Assert.AreEqual(expectedConcatenatedString, actualConcatenatedString); }
public void Sort_FilteredLowerCaseString_SortsLowerCaseStringInAlphabeticOrder() { var sorting = new Sorting.Sorting(InputString); var regexFilter = @"[^A-Za-z]"; sorting.FilteredString = sorting.FilterOut(regexFilter); sorting.FilteredString = sorting.FilteredString.ToLower(); var sortedStringArray = sorting.SortLowerCaseFilteredString(sorting.FilteredString); sorting.OutputString = sorting.ConcatenateGivenString(sortedStringArray); Assert.AreEqual(ExpectedSortedLowerCaseOutput, sorting.OutputString); }
protected void SortFilterValuesList() { Sorting.Sorting sorting = CurrentSorting; if (sorting == null) { return; } lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } if (!_sortable) { return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items = _items; List <FilterItem> itemsList = items.Select(li => li as FilterItem).ToList(); itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, false); return; } items.Clear(); CollectionUtils.AddAll(items, itemsList); UpdateOrRebuildView(items, false); } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a filter values selection screen which reflects the available filter values for /// the current base view specification of our <see cref="AbstractScreenData._navigationData"/>. /// </summary> protected async Task ReloadFilterValuesList(bool createNewList) { MediaLibraryQueryViewSpecification currentVS = _navigationData.BaseViewSpecification as MediaLibraryQueryViewSpecification; if (currentVS == null) { // Should never happen ServiceRegistration.Get <ILogger>().Error("FilterScreenData: Wrong type of media library view '{0}'", _navigationData.BaseViewSpecification); return; } // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { Display_ListBeingBuilt(); IFilter filter = currentVS.FilterTree.BuildFilter(_filterPath); ICollection <Guid> necessaryMIAs = _necessaryFilteredMIATypeIds ?? currentVS.NecessaryMIATypeIds; // If the number of values to create exceeds MAX_NUM_ITEMS_VISIBLE we need to try and group the items. // We request all values first, rather than groups, on the assumption that most of the time the limit // shouldn't be reached given that we are filtering the values. bool grouping = false; ICollection <FilterValue> fv = await _filterCriterion.GetAvailableValuesAsync(necessaryMIAs, _clusterFilter, filter).ConfigureAwait(false); if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE && _clusterFilter == null) { ICollection <FilterValue> groupValues = await _filterCriterion.GroupValuesAsync(necessaryMIAs, _clusterFilter, filter).ConfigureAwait(false); if (groupValues != null && groupValues.Count > 0) { fv = groupValues; grouping = true; } } if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(fv.Count); } else { bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, createNewList); return; } _sortable = true; int totalNumItems = 0; List <FilterItem> itemsList = new List <FilterItem>(); // Build collection of available (filter/display) screens which will remain in the next view - that is all currently // available screens without the screen which equals this current screen. But we cannot simply remove "this" // from the collection, because "this" could be a derived screen (in case our base screen showed groups). // So we need an equality criterion when the screen to be removed is equal to this screen in terms of its // filter criterion. But with the given data, we actually cannot derive that equality. // So we simply use the MenuItemLabel, which should be the same in this and the base screen of the same filter. foreach (FilterValue f in fv) { //Used for enclosure FilterValue filterValue = f; _sortable &= filterValue.Item != null; string filterTitle = filterValue.Title; IFilter selectAttributeFilter = filterValue.SelectAttributeFilter; MediaLibraryQueryViewSpecification subVS = currentVS.CreateSubViewSpecification(filterTitle, FilterTreePath.Combine(_filterPath, filterValue.RelativeFilterPath), filterValue.Filter, filterValue.LinkedId); T filterValueItem = new T { // Support non-playable MediaItems (i.e. Series, Seasons) MediaItem = filterValue.Item, SimpleTitle = filterTitle, NumItems = filterValue.NumItems, Id = filterValue.Id, Command = new MethodDelegateCommand(() => { if (grouping) { NavigateToGroup(subVS, selectAttributeFilter); } else { NavigateToSubView(subVS); } }), View = subVS.BuildView() }; itemsList.Add(filterValueItem); if (filterValue.NumItems.HasValue) { totalNumItems += filterValue.NumItems.Value; } } if (_sortable) { Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } } // Derived classes can implement special initial selection handling here, // e.g. the first unwatched episode could be selected from a list of episodes SetSelectedItem(itemsList); CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractFiltersScreenData: Error creating filter values list", e); Display_ItemsInvalid(); } UpdateOrRebuildView(items, createNewList); } finally { lock (_syncObj) _buildingList = false; } }
// If the suppressActions parameter is set to <c>true</c>, no actions will be built. Instead, they will be inherited from // the parent navigation step. That is used for subview navigation where the navigation step doesn't produce own // workflow actions. protected NavigationData(NavigationData parent, string navigationContextName, Guid parentWorkflowStateId, Guid currentWorkflowStateId, ViewSpecification baseViewSpecification, AbstractScreenData defaultScreen, ICollection<AbstractScreenData> availableScreens, Sorting.Sorting currentSorting, bool suppressActions) { _parent = parent; _navigationContextName = navigationContextName; _currentWorkflowStateId = currentWorkflowStateId; _baseWorkflowStateId = parentWorkflowStateId; _baseViewSpecification = baseViewSpecification; _currentScreenData = defaultScreen; _availableScreens = availableScreens ?? new List<AbstractScreenData>(); _currentSorting = currentSorting; if (suppressActions) _dynamicWorkflowActions = null; else BuildWorkflowActions(); }
/// <summary> /// Updates the GUI data for a media items view screen which reflects the data of the <see cref="CurrentView"/>. /// </summary> /// <param name="createNewList">If set to <c>true</c>, this method will re-create the /// <see cref="AbstractScreenData.Items"/> list, else it will reuse it.</param> protected void UpdateMediaItems(bool createNewList) { View view; // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built view = _view; InstallViewChangeNotificator(_view.CreateViewChangeNotificator()); _buildingList = true; _listDirty = false; } try { Display_ListBeingBuilt(); ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { // TODO: Add the items in a separate job while the UI already shows the new screen if (view.IsValid) { // Add items for sub views IList <View> subViews = view.SubViews; IList <MediaItem> mediaItems = view.MediaItems; lock (_syncObj) if (_listDirty) { goto RebuildView; } if (subViews == null || mediaItems == null) { Display_ItemsInvalid(); } else { if (subViews.Count + mediaItems.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(subViews.Count + mediaItems.Count); } else { int totalNumItems = 0; bool subViewsPreSorted = false; List <NavigationItem> viewsList = new List <NavigationItem>(); foreach (View sv in subViews) { if (sv.Specification.SortedSubViews) { subViewsPreSorted = true; } ViewItem item = new ViewItem(sv, null, sv.AbsNumItems); View subView = sv; item.Command = new MethodDelegateCommand(() => NavigateToView(subView.Specification)); viewsList.Add(item); if (sv.AbsNumItems.HasValue) { totalNumItems += sv.AbsNumItems.Value; } } // Morpheus_xx, 2014-05-03: Only sort the subviews here, if they are not pre-sorted by the ViewSpecification if (!subViewsPreSorted) { viewsList.Sort((v1, v2) => string.Compare(v1.SortString, v2.SortString)); } CollectionUtils.AddAll(items, viewsList); lock (_syncObj) if (_listDirty) { goto RebuildView; } PlayableItemCreatorDelegate picd = PlayableItemCreator; List <PlayableMediaItem> itemsList = mediaItems.Select(childItem => picd(childItem)).Where(item => item != null).ToList(); Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } else { // Default sorting: Use SortString itemsList.Sort((i1, i2) => string.Compare(i1.SortString, i2.SortString)); } // Derived classes can implement special initial selection handling here, // e.g. the first unwatched episode could be selected from a list of episodes SetSelectedItem(itemsList); CollectionUtils.AddAll(items, itemsList); // Support custom sorting logic by view specification. At this time it can work on both MediaItems and SubViews. if (view.Specification.CustomItemsListSorting != null) { view.Specification.CustomItemsListSorting(items, sorting); } _currentTotalNumItems = totalNumItems == 0 ? new int?() : totalNumItems; Display_Normal(items.Count, _currentTotalNumItems); } } } else { Display_ItemsInvalid(); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractItemsScreenData: Error creating items list", e); Display_ItemsInvalid(); } RebuildView: bool dirty; lock (_syncObj) if (_listDirty) { dirty = true; _buildingList = false; } else { dirty = false; } if (dirty) { UpdateMediaItems(createNewList); } else { _items = items; _items.FireChange(); } } finally { lock (_syncObj) _buildingList = false; } }
/// <summary> /// Updates the GUI data for a media items view screen which reflects the data of the <see cref="CurrentView"/>. /// </summary> /// <param name="createNewList">If set to <c>true</c>, this method will re-create the /// <see cref="AbstractScreenData.Items"/> list, else it will reuse it.</param> protected void UpdateMediaItems(bool createNewList) { View view; // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built view = _view; InstallViewChangeNotificator(_view.CreateViewChangeNotificator()); _buildingList = true; _listDirty = false; } try { Display_ListBeingBuilt(); ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { // TODO: Add the items in a separate job while the UI already shows the new screen if (view.IsValid) { // Add items for sub views IList <View> subViews = view.SubViews; IList <MediaItem> mediaItems = view.MediaItems; lock (_syncObj) if (_listDirty) { goto RebuildView; } if (subViews == null || mediaItems == null) { Display_ItemsInvalid(); } else { if (subViews.Count + mediaItems.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(subViews.Count + mediaItems.Count); } else { int totalNumItems = 0; List <NavigationItem> viewsList = new List <NavigationItem>(); foreach (View sv in subViews) { ViewItem item = new ViewItem(sv, null, sv.AbsNumItems); View subView = sv; item.Command = new MethodDelegateCommand(() => NavigateToView(subView.Specification)); viewsList.Add(item); if (sv.AbsNumItems.HasValue) { totalNumItems += sv.AbsNumItems.Value; } } viewsList.Sort((v1, v2) => string.Compare(v1.SortString, v2.SortString)); CollectionUtils.AddAll(items, viewsList); lock (_syncObj) if (_listDirty) { goto RebuildView; } PlayableItemCreatorDelegate picd = PlayableItemCreator; List <PlayableMediaItem> itemsList = mediaItems.Select(childItem => picd(childItem)).Where(item => item != null).ToList(); Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } else { // Default sorting: Use SortString itemsList.Sort((i1, i2) => string.Compare(i1.SortString, i2.SortString)); } CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } } else { Display_ItemsInvalid(); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractItemsScreenData: Error creating items list", e); Display_ItemsInvalid(); } RebuildView: bool dirty; lock (_syncObj) if (_listDirty) { dirty = true; _buildingList = false; } else { dirty = false; } if (dirty) { UpdateMediaItems(createNewList); } else { _items = items; _items.FireChange(); } } finally { lock (_syncObj) _buildingList = false; } }
public virtual void InitMediaNavigation(MediaNavigationConfig config, out string mediaNavigationMode, out NavigationData navigationData) { PrepareAsync().Wait(); IFilterTree filterTree = _customFilterTree ?? (_rootRole.HasValue ? new RelationshipFilterTree(_rootRole.Value) : (IFilterTree) new SimpleFilterTree()); if (_filter != null) { filterTree.AddFilter(_filter); } //Default configuration string viewName = _viewName; AbstractScreenData nextScreen = null; //Apply any custom configuration if (config != null) { if (_availableScreens != null) { //Use the configured root screen to load the next screen from the hierarchy //and remove it from the list of available screens AbstractScreenData configRoot = config.RootScreenType != null?_availableScreens.FirstOrDefault(s => s.GetType() == config.RootScreenType) : null; if (configRoot != null) { viewName = configRoot.GetType().ToString(); _availableScreens.Remove(configRoot); } //Use the configured default screen if there is no saved screen hierarchy AbstractScreenData configDefault = config.DefaultScreenType != null?_availableScreens.FirstOrDefault(s => s.GetType() == config.DefaultScreenType) : null; if (configDefault != null) { _defaultScreen = configDefault; // If we want to force the default screen to be shown, set the next screen // here to avoid loading it from the screen hierarchy below. if (config.AlwaysUseDefaultScreen) { nextScreen = configDefault; } } } //Apply any additional filters if (config.LinkedId.HasValue) { filterTree.AddLinkedId(config.LinkedId.Value, config.FilterPath); } if (config.Filter != null) { filterTree.AddFilter(config.Filter, config.FilterPath); } } IEnumerable <Guid> optionalMIATypeIDs = MediaNavigationModel.GetMediaSkinOptionalMIATypes(MediaNavigationMode); if (_optionalMias != null) { optionalMIATypeIDs = optionalMIATypeIDs.Union(_optionalMias).Except(_necessaryMias); } // Try to load the prefered next screen from settings if not already set. if (nextScreen == null && NavigationData.LoadScreenHierarchy(viewName, out string nextScreenName)) { // Support for browsing mode. if (nextScreenName == Consts.USE_BROWSE_MODE) { SetBrowseMode(optionalMIATypeIDs); } if (_availableScreens != null) { nextScreen = _availableScreens.FirstOrDefault(s => s.GetType().ToString() == nextScreenName); } } if (_applyUserFilter) { var userFilter = UserHelper.GetUserRestrictionFilter(_necessaryMias.ToList()); if (userFilter != null) { filterTree.AddFilter(userFilter); } } // Prefer custom view specification. ViewSpecification rootViewSpecification = _customRootViewSpecification ?? // Always use the default view name for the root view specification, not any custom name that may // have been specified in a navigation config, otherwise toggling browse mode won't work correctly. // To switch to browse mode we update the root screen hierarchy to point to the browse screen and // then navigate to the root wf state. The name of the root screen hierarchy is determined by the // name specified here so it should always point to the actual root view name. new MediaLibraryQueryViewSpecification(_viewName, filterTree, _necessaryMias, optionalMIATypeIDs, true) { MaxNumItems = Consts.MAX_NUM_ITEMS_VISIBLE, }; if (nextScreen == null) { nextScreen = _defaultScreen; } ScreenConfig nextScreenConfig; NavigationData.LoadLayoutSettings(nextScreen.GetType().ToString(), out nextScreenConfig); Sorting.Sorting nextSortingMode = _availableSortings.FirstOrDefault(sorting => sorting.GetType().ToString() == nextScreenConfig.Sorting) ?? _defaultSorting; Sorting.Sorting nextGroupingMode = _availableGroupings == null || String.IsNullOrEmpty(nextScreenConfig.Grouping) ? null : _availableGroupings.FirstOrDefault(grouping => grouping.GetType().ToString() == nextScreenConfig.Grouping) ?? _defaultGrouping; navigationData = new NavigationData(null, viewName, MediaNavigationRootState, MediaNavigationRootState, rootViewSpecification, nextScreen, _availableScreens, nextSortingMode, nextGroupingMode) { AvailableSortings = _availableSortings, AvailableGroupings = _availableGroupings, LayoutType = nextScreenConfig.LayoutType, LayoutSize = nextScreenConfig.LayoutSize }; mediaNavigationMode = MediaNavigationMode; }
/// <summary> /// Updates the GUI data for a filter values selection screen which reflects the available filter values for /// the current base view specification of our <see cref="AbstractScreenData._navigationData"/>. /// </summary> protected void ReloadFilterValuesList(bool createNewList) { MediaLibraryQueryViewSpecification currentVS = _navigationData.BaseViewSpecification as MediaLibraryQueryViewSpecification; if (currentVS == null) { // Should never happen ServiceRegistration.Get <ILogger>().Error("FilterScreenData: Wrong type of media library view '{0}'", _navigationData.BaseViewSpecification); return; } // Control other threads reentering this method lock (_syncObj) { if (_buildingList) { // Another thread is already building the items list - mark it as dirty and let the other thread // rebuild it. _listDirty = true; return; } // Mark the list as being built _buildingList = true; _listDirty = false; } try { ItemsList items; if (createNewList) { items = new ItemsList(); } else { items = _items; items.Clear(); } try { Display_ListBeingBuilt(); bool grouping = true; //If currentVS is the base view it's possible that it has a filter that is incompatible with _filterCriterion. //This is the case if a plugin has added a base filter to exclude certain items, e.g. TV excludes recordings //and the new filter filters by a different media type, e.g. series'. Ignore the base filter in this case. IFilter currentFilter = (_navigationData.Parent != null && CanFilter(_navigationData.Parent.CurrentScreenData)) || currentVS.CanCombineFilters(_filteredMias) ? currentVS.Filter : null; ICollection <FilterValue> fv = _clusterFilter == null? _filterCriterion.GroupValues(currentVS.NecessaryMIATypeIds, _clusterFilter, currentFilter) : null; if (fv == null || fv.Count <= Consts.MAX_NUM_ITEMS_VISIBLE) { fv = _filterCriterion.GetAvailableValues(currentVS.NecessaryMIATypeIds, _clusterFilter, currentFilter); grouping = false; } if (fv.Count > Consts.MAX_NUM_ITEMS_VISIBLE) { Display_TooManyItems(fv.Count); } else { bool dirty; lock (_syncObj) dirty = _listDirty; if (dirty) { UpdateOrRebuildView(items, createNewList); return; } _sortable = true; int totalNumItems = 0; List <FilterItem> itemsList = new List <FilterItem>(); // Build collection of available (filter/display) screens which will remain in the next view - that is all currently // available screens without the screen which equals this current screen. But we cannot simply remove "this" // from the collection, because "this" could be a derived screen (in case our base screen showed groups). // So we need an equality criterion when the screen to be removed is equal to this screen in terms of its // filter criterion. But with the given data, we actually cannot derive that equality. // So we simply use the MenuItemLabel, which should be the same in this and the base screen of the same filter. foreach (FilterValue filterValue in fv) { _sortable &= filterValue.Item != null; string filterTitle = filterValue.Title; IFilter selectAttributeFilter = filterValue.SelectAttributeFilter; MediaLibraryQueryViewSpecification subVS = currentVS.CreateSubViewSpecification(filterTitle, filterValue.Filter, _filteredMias); T filterValueItem = new T { // Support non-playable MediaItems (i.e. Series, Seasons) MediaItem = filterValue.Item, SimpleTitle = filterTitle, NumItems = filterValue.NumItems, Id = filterValue.Id, Command = grouping ? new MethodDelegateCommand(() => NavigateToGroup(subVS, selectAttributeFilter)) : new MethodDelegateCommand(() => NavigateToSubView(subVS)) }; itemsList.Add(filterValueItem); if (filterValue.NumItems.HasValue) { totalNumItems += filterValue.NumItems.Value; } } if (_sortable) { Sorting.Sorting sorting = CurrentSorting; if (sorting != null) { itemsList.Sort((i1, i2) => sorting.Compare(i1.MediaItem, i2.MediaItem)); } } CollectionUtils.AddAll(items, itemsList); Display_Normal(items.Count, totalNumItems == 0 ? new int?() : totalNumItems); } } catch (Exception e) { ServiceRegistration.Get <ILogger>().Warn("AbstractFiltersScreenData: Error creating filter values list", e); Display_ItemsInvalid(); } UpdateOrRebuildView(items, createNewList); } finally { lock (_syncObj) _buildingList = false; } }