Skip to content

Commit 97f2b88

Browse files
committed
Feature: Profile groups persist group state
1 parent edc839e commit 97f2b88

10 files changed

Lines changed: 176 additions & 607 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NETworkManager.Controls
4+
{
5+
public class GroupExpandedChangedArgs(string groupName, bool isExpanded) : EventArgs
6+
{
7+
public string GroupName { get; } = groupName;
8+
public bool IsExpanded { get; } = isExpanded;
9+
}
10+
}

‎Source/NETworkManager.Controls/GroupExpander.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class GroupExpander : Expander
88
{
99
public static readonly DependencyProperty StateStoreProperty =
1010
DependencyProperty.Register(
11-
"StateStore",
11+
nameof(StateStore),
1212
typeof(GroupExpanderStateStore),
1313
typeof(GroupExpander),
1414
new PropertyMetadata(null, OnStateStoreChanged));

‎Source/NETworkManager.Controls/GroupExpanderStateStore.cs‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.ComponentModel;
34
using System.Diagnostics;
45

@@ -8,6 +9,11 @@ public class GroupExpanderStateStore : INotifyPropertyChanged
89
{
910
public event PropertyChangedEventHandler PropertyChanged;
1011

12+
/// <summary>
13+
/// Occurs when a single group's expansion state changes via the indexer (not raised by <see cref="LoadState"/>).
14+
/// </summary>
15+
public event EventHandler<GroupExpandedChangedArgs> GroupExpandedChanged;
16+
1117
protected void OnPropertyChanged(string propertyName) =>
1218
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
1319

@@ -40,7 +46,25 @@ public bool this[string groupName]
4046

4147
_states[groupName] = value;
4248
OnPropertyChanged($"Item[{groupName}]");
49+
GroupExpandedChanged?.Invoke(this, new GroupExpandedChangedArgs(groupName, value));
4350
}
4451
}
52+
53+
/// <summary>
54+
/// Replaces all stored states with the given ones (e.g. bulk-loading previously persisted state).
55+
/// Does not raise <see cref="PropertyChanged"/> or <see cref="GroupExpandedChanged"/> - consumers
56+
/// (e.g. <see cref="GroupExpander"/>) read the current state fresh whenever they bind, so no
57+
/// notification is needed, and this avoids a load-triggers-save feedback loop.
58+
/// </summary>
59+
public void LoadState(IReadOnlyDictionary<string, bool> state)
60+
{
61+
_states.Clear();
62+
63+
if (state == null)
64+
return;
65+
66+
foreach (var kvp in state)
67+
_states[kvp.Key] = kvp.Value;
68+
}
4569
}
4670
}

‎Source/NETworkManager.Profiles/ProfileInfo.cs‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,6 @@ public ProfileInfo(ProfileInfo profile)
222222
SNMP_PrivacyProvider = profile.SNMP_PrivacyProvider;
223223
SNMP_Priv = profile.SNMP_Priv;
224224

225-
// Firewall
226-
Firewall_Enabled = profile.Firewall_Enabled;
227-
//Firewall_Rules = profile.Firewall_Rules;
228-
229225
// Wake on LAN
230226
WakeOnLAN_Enabled = profile.WakeOnLAN_Enabled;
231227
WakeOnLAN_MACAddress = profile.WakeOnLAN_MACAddress;
@@ -486,9 +482,6 @@ public ProfileInfo(ProfileInfo profile)
486482
public SNMPV3PrivacyProvider SNMP_PrivacyProvider { get; set; } = GlobalStaticConfiguration.SNMP_PrivacyProvider;
487483
[XmlIgnore] public SecureString SNMP_Priv { get; set; }
488484

489-
public bool Firewall_Enabled { get; set; }
490-
//public List<FirewallRule> Firewall_Rules { get; set; }
491-
492485
public bool WakeOnLAN_Enabled { get; set; }
493486
public string WakeOnLAN_MACAddress { get; set; }
494487
public string WakeOnLAN_Broadcast { get; set; }

‎Source/NETworkManager.Profiles/ProfileManager.cs‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ public static void RenameProfileFile(ProfileFileInfo profileFileInfo, string new
381381
File.Copy(profileFileInfo.Path, newProfileFileInfo.Path);
382382
ProfileFiles.Add(newProfileFileInfo);
383383

384+
MigrateGroupExpandState(profileFileInfo.Path, newProfileFileInfo.Path);
385+
384386
// Switch profile, if it was previously loaded
385387
if (switchProfile)
386388
{
@@ -407,6 +409,8 @@ public static void DeleteProfileFile(ProfileFileInfo profileFileInfo)
407409
if (LoadedProfileFile != null && LoadedProfileFile.Equals(profileFileInfo))
408410
LoadedProfileFileChanged(ProfileFiles.FirstOrDefault(x => !x.Equals(profileFileInfo)));
409411

412+
RemoveGroupExpandState(profileFileInfo.Path);
413+
410414
File.Delete(profileFileInfo.Path);
411415
ProfileFiles.Remove(profileFileInfo);
412416
}
@@ -478,6 +482,8 @@ public static void EnableEncryption(ProfileFileInfo profileFileInfo, SecureStrin
478482
// Add the new profile
479483
ProfileFiles.Add(newProfileFileInfo);
480484

485+
MigrateGroupExpandState(profileFileInfo.Path, newProfileFileInfo.Path);
486+
481487
// Switch profile, if it was previously loaded
482488
if (switchProfile)
483489
{
@@ -563,6 +569,8 @@ public static void ChangeMasterPassword(ProfileFileInfo profileFileInfo, SecureS
563569
// Add the new profile
564570
ProfileFiles.Add(newProfileFileInfo);
565571

572+
MigrateGroupExpandState(profileFileInfo.Path, newProfileFileInfo.Path);
573+
566574
// Switch profile, if it was previously loaded
567575
if (switchProfile)
568576
{
@@ -632,6 +640,8 @@ public static void DisableEncryption(ProfileFileInfo profileFileInfo, SecureStri
632640
// Add the new profile
633641
ProfileFiles.Add(newProfileFileInfo);
634642

643+
MigrateGroupExpandState(profileFileInfo.Path, newProfileFileInfo.Path);
644+
635645
// Switch profile, if it was previously loaded
636646
if (switchProfile)
637647
{
@@ -739,6 +749,8 @@ private static void Load(ProfileFileInfo profileFileInfo)
739749
// Add the new profile
740750
ProfileFiles.Add(newProfileFileInfo);
741751

752+
MigrateGroupExpandState(profileFileInfo.Path, newProfileFileInfo.Path);
753+
742754
// Switch profile
743755
Log.Info($"Switching to migrated profile file: {newProfileFileInfo.Path}.");
744756
Switch(newProfileFileInfo, false);
@@ -849,6 +861,70 @@ public static void Switch(ProfileFileInfo info, bool saveLoadedProfiles = true)
849861

850862
#endregion
851863

864+
#region Group expand state persistence
865+
866+
/// <summary>
867+
/// Moves the persisted group-expand state (see <see cref="SettingsInfo.Profiles_GroupExpandState" />)
868+
/// from the old to the new profile file path. No-op if the paths are equal or there is no state for
869+
/// the old path.
870+
/// </summary>
871+
private static void MigrateGroupExpandState(string oldPath, string newPath)
872+
{
873+
if (oldPath == newPath)
874+
return;
875+
876+
if (SettingsManager.Current.Profiles_GroupExpandState.Remove(oldPath, out var state))
877+
{
878+
SettingsManager.Current.Profiles_GroupExpandState[newPath] = state;
879+
SettingsManager.Current.SettingsChanged = true;
880+
}
881+
}
882+
883+
/// <summary>
884+
/// Removes the persisted group-expand state for a profile file that no longer exists.
885+
/// </summary>
886+
private static void RemoveGroupExpandState(string path)
887+
{
888+
if (SettingsManager.Current.Profiles_GroupExpandState.Remove(path))
889+
SettingsManager.Current.SettingsChanged = true;
890+
}
891+
892+
/// <summary>
893+
/// Renames the group key inside the persisted group-expand state for the currently loaded profile file.
894+
/// </summary>
895+
private static void MigrateGroupExpandStateKey(string oldGroupName, string newGroupName)
896+
{
897+
if (oldGroupName == newGroupName)
898+
return;
899+
900+
if (LoadedProfileFile == null)
901+
return;
902+
903+
if (!SettingsManager.Current.Profiles_GroupExpandState.TryGetValue(LoadedProfileFile.Path, out var state))
904+
return;
905+
906+
if (!state.Remove(oldGroupName, out var isExpanded))
907+
return;
908+
909+
state[newGroupName] = isExpanded;
910+
SettingsManager.Current.SettingsChanged = true;
911+
}
912+
913+
/// <summary>
914+
/// Removes the group key from the persisted group-expand state for the currently loaded profile file.
915+
/// </summary>
916+
private static void RemoveGroupExpandStateKey(string groupName)
917+
{
918+
if (LoadedProfileFile == null)
919+
return;
920+
921+
if (SettingsManager.Current.Profiles_GroupExpandState.TryGetValue(LoadedProfileFile.Path, out var state)
922+
&& state.Remove(groupName))
923+
SettingsManager.Current.SettingsChanged = true;
924+
}
925+
926+
#endregion
927+
852928
#region Serialize and deserialize
853929

854930
/// <summary>
@@ -1085,6 +1161,8 @@ public static void ReplaceGroup(GroupInfo oldGroup, GroupInfo newGroup)
10851161
LoadedProfileFileData.Groups.Remove(oldGroup);
10861162
LoadedProfileFileData.Groups.Add(newGroup);
10871163

1164+
MigrateGroupExpandStateKey(oldGroup.Name, newGroup.Name);
1165+
10881166
ProfilesUpdated();
10891167
}
10901168

@@ -1099,6 +1177,8 @@ public static void RemoveGroup(GroupInfo group)
10991177

11001178
LoadedProfileFileData.Groups.Remove(group);
11011179

1180+
RemoveGroupExpandStateKey(group.Name);
1181+
11021182
ProfilesUpdated();
11031183
}
11041184

‎Source/NETworkManager.Settings/SettingsInfo.cs‎

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using NETworkManager.Utilities;
1111
using NETworkManager.Utilities.ActiveDirectory;
1212
using System;
13+
using System.Collections.Generic;
1314
using System.Collections.ObjectModel;
1415
using System.Collections.Specialized;
1516
using System.ComponentModel;
@@ -566,6 +567,25 @@ public string Profiles_LastSelected
566567
}
567568
}
568569

570+
/// <summary>
571+
/// Persisted collapsed/expanded state of profile groups, shared across all tools. Outer key is the
572+
/// full profile file path, inner key is the group name. Mutated in place by callers - since nested
573+
/// dictionary mutations don't raise <see cref="PropertyChanged" />, callers must explicitly set
574+
/// <see cref="SettingsChanged" /> themselves.
575+
/// </summary>
576+
public Dictionary<string, Dictionary<string, bool>> Profiles_GroupExpandState
577+
{
578+
get;
579+
set
580+
{
581+
if (value == field)
582+
return;
583+
584+
field = value;
585+
OnPropertyChanged();
586+
}
587+
} = [];
588+
569589
public bool Profiles_IsDailyBackupEnabled
570590
{
571591
get;
@@ -3198,32 +3218,6 @@ public ExportFileType HostsFileEditor_ExportFileType
31983218
#endregion
31993219

32003220
#region Firewall
3201-
public bool Firewall_ExpandProfileView
3202-
{
3203-
get;
3204-
set
3205-
{
3206-
if (value == field)
3207-
return;
3208-
3209-
field = value;
3210-
OnPropertyChanged();
3211-
}
3212-
} = GlobalStaticConfiguration.Profile_ExpandProfileView;
3213-
3214-
public double Firewall_ProfileWidth
3215-
{
3216-
get;
3217-
set
3218-
{
3219-
if (Math.Abs(value - field) < GlobalStaticConfiguration.FloatPointFix)
3220-
return;
3221-
3222-
field = value;
3223-
OnPropertyChanged();
3224-
}
3225-
} = GlobalStaticConfiguration.Profile_DefaultWidthExpanded;
3226-
32273221
public string Firewall_ExportFilePath
32283222
{
32293223
get;

‎Source/NETworkManager/ProfileDialogManager.cs‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,6 @@ private static ProfileInfo ParseProfileInfo(ProfileViewModel instance)
256256
? instance.SNMP_Priv
257257
: new SecureString(),
258258

259-
// Firewall
260-
Firewall_Enabled = instance.Firewall_Enabled,
261-
262259
// Wake on LAN
263260
WakeOnLAN_Enabled = instance.WakeOnLAN_Enabled,
264261
WakeOnLAN_MACAddress = instance.WakeOnLAN_MACAddress?.Trim(),

0 commit comments

Comments
 (0)