@@ -516,6 +516,70 @@ public bool IsSearching
516516 }
517517 }
518518
519+ private bool _profileFilterIsOpen ;
520+
521+ public bool ProfileFilterIsOpen
522+ {
523+ get => _profileFilterIsOpen ;
524+ set
525+ {
526+ if ( value == _profileFilterIsOpen )
527+ return ;
528+
529+ _profileFilterIsOpen = value ;
530+ OnPropertyChanged ( ) ;
531+ }
532+ }
533+
534+ public ICollectionView ProfileFilterTagsView { get ; set ; }
535+
536+ public ObservableCollection < ProfileFilterTagsInfo > ProfileFilterTags { get ; set ; } = [ ] ;
537+
538+ private bool _profileFilterTagsMatchAny = GlobalStaticConfiguration . Profile_TagsMatchAny ;
539+
540+ public bool ProfileFilterTagsMatchAny
541+ {
542+ get => _profileFilterTagsMatchAny ;
543+ set
544+ {
545+ if ( value == _profileFilterTagsMatchAny )
546+ return ;
547+
548+ _profileFilterTagsMatchAny = value ;
549+ OnPropertyChanged ( ) ;
550+ }
551+ }
552+
553+ private bool _profileFilterTagsMatchAll ;
554+
555+ public bool ProfileFilterTagsMatchAll
556+ {
557+ get => _profileFilterTagsMatchAll ;
558+ set
559+ {
560+ if ( value == _profileFilterTagsMatchAll )
561+ return ;
562+
563+ _profileFilterTagsMatchAll = value ;
564+ OnPropertyChanged ( ) ;
565+ }
566+ }
567+
568+ private bool _isProfileFilterSet ;
569+
570+ public bool IsProfileFilterSet
571+ {
572+ get => _isProfileFilterSet ;
573+ set
574+ {
575+ if ( value == _isProfileFilterSet )
576+ return ;
577+
578+ _isProfileFilterSet = value ;
579+ OnPropertyChanged ( ) ;
580+ }
581+ }
582+
519583 private bool _canProfileWidthChange = true ;
520584 private double _tempProfileWidth ;
521585
@@ -581,7 +645,12 @@ public NetworkInterfaceViewModel(IDialogCoordinator instance)
581645 InitialBandwidthChart ( ) ;
582646
583647 // Profiles
584- SetProfilesView ( ) ;
648+ CreateTags ( ) ;
649+
650+ ProfileFilterTagsView = CollectionViewSource . GetDefaultView ( ProfileFilterTags ) ;
651+ ProfileFilterTagsView . SortDescriptions . Add ( new SortDescription ( nameof ( ProfileFilterTagsInfo . Name ) , ListSortDirection . Ascending ) ) ;
652+
653+ SetProfilesView ( new ProfileFilterInfo ( ) ) ;
585654
586655 ProfileManager . OnProfilesUpdated += ProfileManager_OnProfilesUpdated ;
587656
@@ -801,6 +870,36 @@ private void ClearSearchAction()
801870 Search = string . Empty ;
802871 }
803872
873+ public ICommand OpenProfileFilterCommand => new RelayCommand ( _ => OpenProfileFilterAction ( ) ) ;
874+
875+ private void OpenProfileFilterAction ( )
876+ {
877+ ProfileFilterIsOpen = true ;
878+ }
879+
880+ public ICommand ApplyProfileFilterCommand => new RelayCommand ( _ => ApplyProfileFilterAction ( ) ) ;
881+
882+ private void ApplyProfileFilterAction ( )
883+ {
884+ RefreshProfiles ( ) ;
885+
886+ IsProfileFilterSet = true ;
887+ ProfileFilterIsOpen = false ;
888+ }
889+
890+ public ICommand ClearProfileFilterCommand => new RelayCommand ( _ => ClearProfileFilterAction ( ) ) ;
891+
892+ private void ClearProfileFilterAction ( )
893+ {
894+ foreach ( var tag in ProfileFilterTags )
895+ tag . IsSelected = false ;
896+
897+ RefreshProfiles ( ) ;
898+
899+ IsProfileFilterSet = false ;
900+ ProfileFilterIsOpen = false ;
901+ }
902+
804903 #region Additional commands
805904
806905 private bool AdditionalCommands_CanExecute ( object parameter )
@@ -1326,35 +1425,43 @@ public void OnViewHide()
13261425 _isViewActive = false ;
13271426 }
13281427
1329- private void SetProfilesView ( ProfileInfo profile = null )
1428+ private void CreateTags ( )
13301429 {
1331- Profiles = new CollectionViewSource
1332- {
1333- Source = ProfileManager . Groups . SelectMany ( x => x . Profiles ) . Where ( x => x . NetworkInterface_Enabled )
1334- . OrderBy ( x => x . Group ) . ThenBy ( x => x . Name )
1335- } . View ;
1430+ var tags = ProfileManager . Groups . SelectMany ( x => x . Profiles ) . Where ( x => x . NetworkInterface_Enabled ) . SelectMany ( x => x . TagsCollection ) . Distinct ( ) . ToList ( ) ;
13361431
1337- Profiles . GroupDescriptions . Add ( new PropertyGroupDescription ( nameof ( ProfileInfo . Group ) ) ) ;
1432+ var tagSet = new HashSet < string > ( tags ) ;
13381433
1339- Profiles . Filter = o =>
1434+ for ( var i = ProfileFilterTags . Count - 1 ; i >= 0 ; i -- )
13401435 {
1341- if ( string . IsNullOrEmpty ( Search ) )
1342- return true ;
1436+ if ( ! tagSet . Contains ( ProfileFilterTags [ i ] . Name ) )
1437+ ProfileFilterTags . RemoveAt ( i ) ;
1438+ }
13431439
1344- if ( o is not ProfileInfo info )
1345- return false ;
1440+ var existingTagNames = new HashSet < string > ( ProfileFilterTags . Select ( ft => ft . Name ) ) ;
13461441
1347- var search = Search . Trim ( ) ;
1442+ foreach ( var tag in tags )
1443+ {
1444+ if ( ! existingTagNames . Contains ( tag ) )
1445+ ProfileFilterTags . Add ( new ProfileFilterTagsInfo ( false , tag ) ) ;
1446+ }
1447+ }
13481448
1349- // Search by: Tag=xxx (exact match, ignore case)
1350- /*
1351- if (search.StartsWith(ProfileManager.TagIdentifier, StringComparison.OrdinalIgnoreCase))
1352- return !string.IsNullOrEmpty(info.Tags) && info.PingMonitor_Enabled && info.Tags.Replace(" ", "").Split(';').Any(str => search.Substring(ProfileManager.TagIdentifier.Length, search.Length - ProfileManager.TagIdentifier.Length).Equals(str, StringComparison.OrdinalIgnoreCase));
1353- */
1449+ private void SetProfilesView ( ProfileFilterInfo filter , ProfileInfo profile = null )
1450+ {
1451+ Profiles = new CollectionViewSource
1452+ {
1453+ Source = ProfileManager . Groups . SelectMany ( x => x . Profiles ) . Where ( x => x . NetworkInterface_Enabled && (
1454+ string . IsNullOrEmpty ( filter . Search ) || x . Name . IndexOf ( filter . Search ) > - 1 ) && (
1455+ // If no tags are selected, show all profiles
1456+ ( ! filter . Tags . Any ( ) ) ||
1457+ // Any tag can match
1458+ ( filter . TagsFilterMatch == ProfileFilterTagsMatch . Any && filter . Tags . Any ( tag => x . TagsCollection . Contains ( tag ) ) ) ||
1459+ // All tags must match
1460+ ( filter . TagsFilterMatch == ProfileFilterTagsMatch . All && filter . Tags . All ( tag => x . TagsCollection . Contains ( tag ) ) ) )
1461+ ) . OrderBy ( x => x . Group ) . ThenBy ( x => x . Name )
1462+ } . View ;
13541463
1355- // Search by: Name
1356- return info . Name . IndexOf ( search , StringComparison . OrdinalIgnoreCase ) > - 1 ;
1357- } ;
1464+ Profiles . GroupDescriptions . Add ( new PropertyGroupDescription ( nameof ( ProfileInfo . Group ) ) ) ;
13581465
13591466 // Set specific profile or first if null
13601467 SelectedProfile = null ;
@@ -1371,7 +1478,12 @@ private void RefreshProfiles()
13711478 if ( ! _isViewActive )
13721479 return ;
13731480
1374- SetProfilesView ( SelectedProfile ) ;
1481+ SetProfilesView ( new ProfileFilterInfo
1482+ {
1483+ Search = Search ,
1484+ Tags = [ .. ProfileFilterTags . Where ( x => x . IsSelected ) . Select ( x => x . Name ) ] ,
1485+ TagsFilterMatch = ProfileFilterTagsMatchAny ? ProfileFilterTagsMatch . Any : ProfileFilterTagsMatch . All
1486+ } , SelectedProfile ) ;
13751487 }
13761488
13771489 #endregion
0 commit comments