diff --git a/src/ResXManager.View/Behaviors/EntityFilter.cs b/src/ResXManager.View/Behaviors/EntityFilter.cs index 0e72dc0d..94b13228 100644 --- a/src/ResXManager.View/Behaviors/EntityFilter.cs +++ b/src/ResXManager.View/Behaviors/EntityFilter.cs @@ -1,4 +1,4 @@ -namespace ResXManager.View.Behaviors; +namespace ResXManager.View.Behaviors; using System; using System.Text.RegularExpressions; @@ -8,6 +8,7 @@ using Microsoft.Xaml.Behaviors; using ResXManager.Infrastructure; +using ResXManager.Model; using Throttle; @@ -26,21 +27,49 @@ public string? FilterText /// public static readonly DependencyProperty FilterTextProperty = DependencyProperty.Register(nameof(FilterText), typeof(string), typeof(EntityFilter), - new FrameworkPropertyMetadata(null, (sender, _) => ((EntityFilter)sender).FilterText_Changed())); + new FrameworkPropertyMetadata(null, (sender, _) => ((EntityFilter)sender).Filter_Changed())); + + public string? ProjectFilter + { + get => (string)GetValue(ProjectFilterProperty); + set => SetValue(ProjectFilterProperty, value); + } + /// + /// Identifies the dependency property + /// + public static readonly DependencyProperty ProjectFilterProperty = + DependencyProperty.Register(nameof(ProjectFilter), typeof(string), typeof(EntityFilter), + new FrameworkPropertyMetadata(null, (sender, _) => ((EntityFilter)sender).Filter_Changed())); [Throttled(typeof(Throttle), 300)] - private void FilterText_Changed() + private void Filter_Changed() { var listBox = AssociatedObject; if (listBox == null) return; - var value = FilterText; + var textFilter = BuildTextFilter(FilterText); + var projectFilter = ProjectFilter; - listBox.Items.Filter = BuildFilter(value); + if (textFilter == null && projectFilter == null) + { + listBox.Items.Filter = null; + return; + } + + listBox.Items.Filter = item => + { + if (projectFilter != null && item is ResourceEntity entity && !string.Equals(entity.ProjectName, projectFilter, StringComparison.OrdinalIgnoreCase)) + return false; + + if (textFilter != null && !textFilter(item)) + return false; + + return true; + }; } - public static Predicate? BuildFilter(string? value) + public static Predicate? BuildTextFilter(string? value) { value = value?.Trim(); @@ -68,10 +97,13 @@ private void FilterText_Changed() return null; } + // Keep for backward compatibility with any external callers + public static Predicate? BuildFilter(string? value) => BuildTextFilter(value); + protected override void OnAttached() { base.OnAttached(); - FilterText_Changed(); + Filter_Changed(); } } diff --git a/src/ResXManager.View/Properties/Resources.Designer.cs b/src/ResXManager.View/Properties/Resources.Designer.cs index 64e4c85e..4ac54305 100644 --- a/src/ResXManager.View/Properties/Resources.Designer.cs +++ b/src/ResXManager.View/Properties/Resources.Designer.cs @@ -115,6 +115,24 @@ public static string AllLanguages { } } + /// + /// Looks up a localized string similar to "All Projects" + /// + public static string AllProjects { + get { + return ResourceManager.GetString("AllProjects", resourceCulture) ?? string.Empty; + } + } + + /// + /// Looks up a localized string similar to "Filter resource files by project" + /// + public static string ProjectFilterToolTip { + get { + return ResourceManager.GetString("ProjectFilterToolTip", resourceCulture) ?? string.Empty; + } + } + /// /// Looks up a localized string similar to "Append the translation prefix to" /// @@ -1170,6 +1188,14 @@ public enum StringResourceKey /// AllLanguages, /// + /// Looks up a localized string similar to All Projects. + /// + AllProjects, + /// + /// Looks up a localized string similar to Filter resource files by project. + /// + ProjectFilterToolTip, + /// /// Looks up a localized string similar to Append the translation prefix to. /// AppendPrefixValueFieldTypeLabel, diff --git a/src/ResXManager.View/Properties/Resources.resx b/src/ResXManager.View/Properties/Resources.resx index a558b589..0f6f1c7e 100644 --- a/src/ResXManager.View/Properties/Resources.resx +++ b/src/ResXManager.View/Properties/Resources.resx @@ -459,6 +459,12 @@ Your ResXManager team. All languages + + All Projects + + + Filter resource files by project + All comments diff --git a/src/ResXManager.View/Visuals/ResourceView.xaml b/src/ResXManager.View/Visuals/ResourceView.xaml index 87eb3aab..681d8875 100644 --- a/src/ResXManager.View/Visuals/ResourceView.xaml +++ b/src/ResXManager.View/Visuals/ResourceView.xaml @@ -89,6 +89,8 @@ + + @@ -113,7 +115,31 @@ - + + + + + + + + + + + + - + diff --git a/src/ResXManager.View/Visuals/ResourceViewModel.cs b/src/ResXManager.View/Visuals/ResourceViewModel.cs index cdff085c..14bfcbdf 100644 --- a/src/ResXManager.View/Visuals/ResourceViewModel.cs +++ b/src/ResXManager.View/Visuals/ResourceViewModel.cs @@ -18,6 +18,8 @@ using DataGridExtensions; +using PropertyChanged; + using ResXManager.Infrastructure; using ResXManager.Model; using ResXManager.View.ColumnHeaders; @@ -58,6 +60,7 @@ public ResourceViewModel(ResourceManager resourceManager, IConfiguration configu ResourceTableEntries.CollectionChanged += (_, __) => ResourceTableEntries_CollectionChanged(); resourceManager.LanguageChanged += ResourceManager_LanguageChanged; + resourceManager.ResourceEntities.CollectionChanged += (_, __) => UpdateProjectNames(); } internal event EventHandler? ClearFiltersRequest; @@ -70,6 +73,34 @@ public ResourceViewModel(ResourceManager resourceManager, IConfiguration configu public ObservableCollection SelectedTableEntries { get; } = []; + public ObservableCollection ProjectNames { get; } = []; + + private void UpdateProjectNames() + { + var selectedProjectName = SelectedProject?.ProjectName; + + var names = ResourceManager.ResourceEntities + .Select(e => e.ProjectName) + .Distinct() + .OrderBy(n => n, StringComparer.CurrentCultureIgnoreCase) + .ToList(); + + ProjectNames.Clear(); + ProjectNames.Add(ProjectFilterItem.AllProjects); + foreach (var name in names) + ProjectNames.Add(new ProjectFilterItem(name)); + + // Restore selection if the project still exists, otherwise reset to All Projects + SelectedProject = (selectedProjectName != null + ? ProjectNames.FirstOrDefault(p => p.ProjectName == selectedProjectName) + : null) ?? ProjectNames[0]; + } + + public ProjectFilterItem? SelectedProject { get; set; } + + [DependsOn(nameof(SelectedProject))] + public string? EffectiveProjectFilter => SelectedProject?.ProjectName; + public bool IsLoading { get; private set; } // Do not use CollectionViewSource in XAML, has huge performance impact when there are many items in the list. @@ -628,3 +659,10 @@ public void Dispose() Interlocked.Exchange(ref _loadingCancellationTokenSource, null)?.Dispose(); } } + +public sealed class ProjectFilterItem(string? projectName) +{ + public static readonly ProjectFilterItem AllProjects = new(null); + + public string? ProjectName { get; } = projectName; +}