44using System ;
55using System . Collections ;
66using System . Collections . Generic ;
7+ using System . Collections . ObjectModel ;
78using System . ComponentModel ;
89using System . Linq ;
910using System . Windows ;
@@ -19,8 +20,12 @@ public class ProfilesViewModel : ViewModelBase, IProfileManager
1920
2021 public ProfilesViewModel ( )
2122 {
22- SetGroupsView ( ) ;
23+ ProfileFilterTagsView = CollectionViewSource . GetDefaultView ( ProfileFilterTags ) ;
24+ ProfileFilterTagsView . SortDescriptions . Add ( new SortDescription ( nameof ( ProfileFilterTagsInfo . Name ) ,
25+ ListSortDirection . Ascending ) ) ;
2326
27+ SetGroupsView ( ) ;
28+
2429 ProfileManager . OnProfilesUpdated += ProfileManager_OnProfilesUpdated ;
2530
2631 _searchDispatcherTimer . Interval = GlobalStaticConfiguration . SearchDispatcherTimerTimeSpan ;
@@ -32,6 +37,7 @@ public ProfilesViewModel()
3237 #region Variables
3338
3439 private readonly DispatcherTimer _searchDispatcherTimer = new ( ) ;
40+ private bool _searchDisabled ;
3541
3642 private bool _isViewActive = true ;
3743
@@ -66,14 +72,30 @@ public GroupInfo SelectedGroup
6672
6773 // NullReferenceException occurs if profile file is changed
6874 if ( value != null )
69- SetProfilesView ( value , _lastSelectedProfileOnRefresh ) ;
75+ {
76+ // Set/update tags based on current group
77+ CreateTags ( ) ;
78+
79+ var filter = new ProfileFilterInfo
80+ {
81+ Search = Search ,
82+ Tags = [ .. ProfileFilterTags . Where ( x => x . IsSelected ) . Select ( x => x . Name ) ] ,
83+ TagsFilterMatch = ProfileFilterTagsMatchAny ? ProfileFilterTagsMatch . Any : ProfileFilterTagsMatch . All
84+ } ;
85+
86+ SetProfilesView ( filter , value , _lastSelectedProfileOnRefresh ) ;
87+
88+ IsProfileFilterSet = ! string . IsNullOrEmpty ( filter . Search ) || filter . Tags . Any ( ) ;
89+ }
7090 else
91+ {
7192 Profiles = null ;
93+ }
7294
7395 OnPropertyChanged ( ) ;
7496 }
7597 }
76-
98+
7799 private ICollectionView _profiles ;
78100
79101 public ICollectionView Profiles
@@ -132,8 +154,11 @@ public string Search
132154 _search = value ;
133155
134156 // Start searching...
135- IsSearching = true ;
136- _searchDispatcherTimer . Start ( ) ;
157+ if ( ! _searchDisabled )
158+ {
159+ IsSearching = true ;
160+ _searchDispatcherTimer . Start ( ) ;
161+ }
137162
138163 OnPropertyChanged ( ) ;
139164 }
@@ -154,11 +179,94 @@ public bool IsSearching
154179 }
155180 }
156181
157-
182+ private bool _profileFilterIsOpen ;
183+
184+ public bool ProfileFilterIsOpen
185+ {
186+ get => _profileFilterIsOpen ;
187+ set
188+ {
189+ if ( value == _profileFilterIsOpen )
190+ return ;
191+
192+ _profileFilterIsOpen = value ;
193+ OnPropertyChanged ( ) ;
194+ }
195+ }
196+
197+ public ICollectionView ProfileFilterTagsView { get ; }
198+
199+ private ObservableCollection < ProfileFilterTagsInfo > ProfileFilterTags { get ; } = [ ] ;
200+
201+ private bool _profileFilterTagsMatchAny = GlobalStaticConfiguration . Profile_TagsMatchAny ;
202+
203+ public bool ProfileFilterTagsMatchAny
204+ {
205+ get => _profileFilterTagsMatchAny ;
206+ set
207+ {
208+ if ( value == _profileFilterTagsMatchAny )
209+ return ;
210+
211+ _profileFilterTagsMatchAny = value ;
212+ OnPropertyChanged ( ) ;
213+ }
214+ }
215+
216+ private bool _profileFilterTagsMatchAll ;
217+
218+ public bool ProfileFilterTagsMatchAll
219+ {
220+ get => _profileFilterTagsMatchAll ;
221+ set
222+ {
223+ if ( value == _profileFilterTagsMatchAll )
224+ return ;
225+
226+ _profileFilterTagsMatchAll = value ;
227+ OnPropertyChanged ( ) ;
228+ }
229+ }
230+
231+ private bool _isProfileFilterSet ;
232+
233+ public bool IsProfileFilterSet
234+ {
235+ get => _isProfileFilterSet ;
236+ set
237+ {
238+ if ( value == _isProfileFilterSet )
239+ return ;
240+
241+ _isProfileFilterSet = value ;
242+ OnPropertyChanged ( ) ;
243+ }
244+ }
158245 #endregion
159246
160247 #region Commands & Actions
161248
249+ public ICommand AddGroupCommand => new RelayCommand ( _ => AddGroupAction ( ) ) ;
250+
251+ private void AddGroupAction ( )
252+ {
253+ ProfileDialogManager . ShowAddGroupDialog ( Application . Current . MainWindow , this ) . ConfigureAwait ( false ) ;
254+ }
255+
256+ public ICommand EditGroupCommand => new RelayCommand ( _ => EditGroupAction ( ) ) ;
257+
258+ private void EditGroupAction ( )
259+ {
260+ ProfileDialogManager . ShowEditGroupDialog ( Application . Current . MainWindow , this , SelectedGroup ) . ConfigureAwait ( false ) ;
261+ }
262+
263+ public ICommand DeleteGroupCommand => new RelayCommand ( _ => DeleteGroupAction ( ) ) ;
264+
265+ private void DeleteGroupAction ( )
266+ {
267+ ProfileDialogManager . ShowDeleteGroupDialog ( Application . Current . MainWindow , this , SelectedGroup ) . ConfigureAwait ( false ) ;
268+ }
269+
162270 public ICommand AddProfileCommand => new RelayCommand ( _ => AddProfileAction ( ) ) ;
163271
164272 private void AddProfileAction ( )
@@ -200,27 +308,38 @@ private void DeleteProfileAction()
200308 . ConfigureAwait ( false ) ;
201309 }
202310
203- public ICommand AddGroupCommand => new RelayCommand ( _ => AddGroupAction ( ) ) ;
311+ public ICommand OpenProfileFilterCommand => new RelayCommand ( _ => OpenProfileFilterAction ( ) ) ;
204312
205- private void AddGroupAction ( )
313+ private void OpenProfileFilterAction ( )
206314 {
207- ProfileDialogManager . ShowAddGroupDialog ( Application . Current . MainWindow , this ) . ConfigureAwait ( false ) ;
315+ ProfileFilterIsOpen = true ;
208316 }
209317
210- public ICommand EditGroupCommand => new RelayCommand ( _ => EditGroupAction ( ) ) ;
318+ public ICommand ApplyProfileFilterCommand => new RelayCommand ( _ => ApplyProfileFilterAction ( ) ) ;
211319
212- private void EditGroupAction ( )
320+ private void ApplyProfileFilterAction ( )
213321 {
214- ProfileDialogManager . ShowEditGroupDialog ( Application . Current . MainWindow , this , SelectedGroup ) . ConfigureAwait ( false ) ;
322+ RefreshProfiles ( ) ;
323+
324+ ProfileFilterIsOpen = false ;
215325 }
216326
217- public ICommand DeleteGroupCommand => new RelayCommand ( _ => DeleteGroupAction ( ) ) ;
327+ public ICommand ClearProfileFilterCommand => new RelayCommand ( _ => ClearProfileFilterAction ( ) ) ;
218328
219- private void DeleteGroupAction ( )
329+ private void ClearProfileFilterAction ( )
220330 {
221- ProfileDialogManager . ShowDeleteGroupDialog ( Application . Current . MainWindow , this , SelectedGroup ) . ConfigureAwait ( false ) ;
331+ _searchDisabled = true ;
332+ Search = string . Empty ;
333+ _searchDisabled = false ;
334+
335+ foreach ( var tag in ProfileFilterTags )
336+ tag . IsSelected = false ;
337+
338+ RefreshProfiles ( ) ;
339+
340+ IsProfileFilterSet = false ;
341+ ProfileFilterIsOpen = false ;
222342 }
223-
224343 #endregion
225344
226345 #region Methods
@@ -240,7 +359,9 @@ public void OnViewHide()
240359 private void SetGroupsView ( GroupInfo group = null )
241360 {
242361 Groups = new CollectionViewSource
243- { Source = ProfileManager . Groups . Where ( x => ! x . IsDynamic ) . OrderBy ( x => x . Name ) } . View ;
362+ {
363+ Source = ProfileManager . Groups . Where ( x => ! x . IsDynamic ) . OrderBy ( x => x . Name )
364+ } . View ;
244365
245366 // Set specific group or first if null
246367 SelectedGroup = null ;
@@ -252,34 +373,44 @@ private void SetGroupsView(GroupInfo group = null)
252373 SelectedGroup = Groups . SourceCollection . Cast < GroupInfo > ( ) . MinBy ( x => x . Name ) ;
253374 }
254375
255- private void SetProfilesView ( GroupInfo group , ProfileInfo profile = null )
376+ private void CreateTags ( )
256377 {
257- Profiles = new CollectionViewSource
258- {
259- Source = ProfileManager . Groups . FirstOrDefault ( x => x . Equals ( group ) ) ? . Profiles . Where ( x => ! x . IsDynamic )
260- . OrderBy ( x => x . Name )
261- } . View ;
378+ // Get all tags from profiles in the selected group
379+ var tags = ProfileManager . Groups . First ( x => x . Name == SelectedGroup . Name ) . Profiles
380+ . SelectMany ( x => x . TagsCollection ) . Distinct ( ) . ToList ( ) ;
381+
382+ var tagSet = new HashSet < string > ( tags ) ;
262383
263- Profiles . Filter = o =>
384+ for ( var i = ProfileFilterTags . Count - 1 ; i >= 0 ; i -- )
264385 {
265- if ( string . IsNullOrEmpty ( Search ) )
266- return true ;
267-
268- if ( o is not ProfileInfo info )
269- return false ;
270-
271- var search = Search . Trim ( ) ;
386+ if ( ! tagSet . Contains ( ProfileFilterTags [ i ] . Name ) )
387+ ProfileFilterTags . RemoveAt ( i ) ;
388+ }
272389
273- // Search by: Tag=xxx (exact match, ignore case)
274- /*
275- if (search.StartsWith(ProfileManager.TagIdentifier, StringComparison.OrdinalIgnoreCase))
276- return !string.IsNullOrEmpty(info.Tags) && info.Tags.Replace(" ", "").Split(';').Any(str => search.Substring(ProfileManager.TagIdentifier.Length, search.Length - ProfileManager.TagIdentifier.Length).Equals(str, StringComparison.OrdinalIgnoreCase));
277- */
390+ var existingTagNames = new HashSet < string > ( ProfileFilterTags . Select ( ft => ft . Name ) ) ;
278391
279- // Search by: Name, Host
280- return info . Name . IndexOf ( search , StringComparison . OrdinalIgnoreCase ) > - 1 ||
281- info . Host . IndexOf ( search , StringComparison . OrdinalIgnoreCase ) > - 1 ;
282- } ;
392+ foreach ( var tag in tags . Where ( tag => ! existingTagNames . Contains ( tag ) ) )
393+ {
394+ ProfileFilterTags . Add ( new ProfileFilterTagsInfo ( false , tag ) ) ;
395+ }
396+ }
397+
398+ private void SetProfilesView ( ProfileFilterInfo filter , GroupInfo group , ProfileInfo profile = null )
399+ {
400+ Profiles = new CollectionViewSource
401+ {
402+ Source = ProfileManager . Groups . FirstOrDefault ( x => x . Equals ( group ) ) ? . Profiles . Where ( x => ! x . IsDynamic && (
403+ string . IsNullOrEmpty ( Search ) || x . Name . IndexOf ( filter . Search , StringComparison . OrdinalIgnoreCase ) > - 1 ) && (
404+ // If no tags are selected, show all profiles
405+ ( ! filter . Tags . Any ( ) ) ||
406+ // Any tag can match
407+ ( filter . TagsFilterMatch == ProfileFilterTagsMatch . Any &&
408+ filter . Tags . Any ( tag => x . TagsCollection . Contains ( tag ) ) ) ||
409+ // All tags must match
410+ ( filter . TagsFilterMatch == ProfileFilterTagsMatch . All &&
411+ filter . Tags . All ( tag => x . TagsCollection . Contains ( tag ) ) ) )
412+ ) . OrderBy ( x => x . Name )
413+ } . View ;
283414
284415 // Set specific profile or first if null
285416 SelectedProfile = null ;
0 commit comments