From ab32a58e0512510e86f4cc7a0b208ec1e05ec90d Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 14 Oct 2022 09:46:08 -0400 Subject: [PATCH] various improvements to Framework functionality --- MBS.Framework/Application.cs | 246 +++++++++++++++++- MBS.Framework/ApplicationActivatedEvent.cs | 41 +++ MBS.Framework/ApplicationActivationType.cs | 276 +++++++++++++++++++++ MBS.Framework/ArrayExtensions.cs | 32 ++- MBS.Framework/CommandLine.cs | 12 +- MBS.Framework/CommandLineCommand.cs | 59 +++++ MBS.Framework/CommandLineOption.cs | 6 + MBS.Framework/FindFileOptions.cs | 11 + MBS.Framework/MBS.Framework.csproj | 6 + MBS.Framework/MemorySettingsProvider.cs | 82 ++++++ 10 files changed, 765 insertions(+), 6 deletions(-) create mode 100644 MBS.Framework/ApplicationActivatedEvent.cs create mode 100644 MBS.Framework/ApplicationActivationType.cs create mode 100644 MBS.Framework/CommandLineCommand.cs create mode 100644 MBS.Framework/MemorySettingsProvider.cs diff --git a/MBS.Framework/Application.cs b/MBS.Framework/Application.cs index f7ccad6..6dae0ae 100644 --- a/MBS.Framework/Application.cs +++ b/MBS.Framework/Application.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using System.Text; +using MBS.Framework.Collections.Generic; namespace MBS.Framework { @@ -63,6 +64,13 @@ namespace MBS.Framework /// public static Application Instance { get; set; } = null; + /// + /// Searches for the file with the given and + /// returns all matching fully-qualified file names. + /// + /// The files. + /// Filename. + /// Options. public string[] FindFiles(string filename, FindFileOptions options = FindFileOptions.All) { if (filename.StartsWith("~/")) @@ -82,9 +90,16 @@ namespace MBS.Framework } return files.ToArray(); } - public string FindFile(string fileName, FindFileOptions options = FindFileOptions.All) + /// + /// Searches for the file with the given and + /// returns the first matching fully-qualified file name. + /// + /// The files. + /// Filename. + /// Options. + public string FindFile(string filename, FindFileOptions options = FindFileOptions.All) { - string[] files = FindFiles(fileName, options); + string[] files = FindFiles(filename, options); if (files.Length > 0) { return files[0]; @@ -92,8 +107,16 @@ namespace MBS.Framework return null; } + /// + /// Gets a collection of s registered for this + /// . + /// + /// The event filters. public EventFilter.EventFilterCollection EventFilters { get; } = new EventFilter.EventFilterCollection(); + /// + /// Finds the command with the given . + /// protected virtual Command FindCommandInternal(string commandID) { return null; @@ -125,10 +148,18 @@ namespace MBS.Framework return null; } + /// + /// Finds the with the given + /// . + /// protected virtual Context FindContextInternal(Guid contextID) { return null; } + /// + /// Finds the with the given + /// . + /// public Context FindContext(Guid contextID) { Context ctx = FindContextInternal(contextID); @@ -303,10 +334,221 @@ namespace MBS.Framework Initialized = true; } + public event ApplicationActivatedEventHandler Activated; + protected virtual void OnActivated(ApplicationActivatedEventArgs e) + { + Activated?.Invoke(this, e); + } + protected virtual int StartInternal() { + CommandLine cline = new CommandLine(); + string[] args = CommandLine.Arguments; + + if (args.Length > 0) + { + int i = 0; + for (i = 0; i < args.Length; i++) + { + if (ParseOption(args, ref i, cline.Options, CommandLine.Options)) + break; + } + + // we have finished parsing the first set of options ("global" options) + // now we see if we have commands + if (CommandLine.Commands.Count > 0 && i < args.Length) + { + // we support commands like git and apt, "appname --app-global-options --command-options" + CommandLineCommand cmd = CommandLine.Commands[args[i]]; + if (cmd != null) + { + for (i++; i < args.Length; i++) + { + if (ParseOption(args, ref i, null, cmd.Options)) + break; + } + + cline.Command = cmd; + } + else + { + // assume filename + } + } + + for (/* intentionally left blank */; i < args.Length; i++) + { + cline.FileNames.Add(args[i]); + } + } + + OnActivated(new ApplicationActivatedEventArgs(true, ApplicationActivationType.CommandLineLaunch, cline)); return 0; } + + + protected void PrintUsageStatement(CommandLineCommand command = null) + { + Console.Write("usage: {0} ", ShortName); + foreach (CommandLineOption option in CommandLine.Options) + { + PrintUsageStatementOption(option); + } + Console.WriteLine(); + // Console.Write("[]"); + + if (command != null) + { + Console.WriteLine(" {0}{1}", command.Name, command.Description == null ? null : String.Format(" - {0}", command.Description)); + foreach (CommandLineOption option in command.Options) + { + PrintUsageStatementOption(option); + } + } + else + { + if (CommandLine.Commands.Count > 0) + { + Console.WriteLine(" []"); + Console.WriteLine(); + + List commands = new List(CommandLine.Commands); + commands.Sort((x, y) => + { + return x.Name.CompareTo(y.Name); + }); + foreach (CommandLineCommand command1 in commands) + { + Console.WriteLine(" {0}{1}", command1.Name, command1.Description == null ? null : String.Format(" - {0}", command1.Description)); + } + } + } + } + + private void PrintUsageStatementOption(CommandLineOption option) + { + Console.Write(" "); + if (option.Optional) + { + Console.Write('['); + } + + string shortOptionPrefix = CommandLine.ShortOptionPrefix ?? "-"; + string longOptionPrefix = CommandLine.LongOptionPrefix ?? "--"; + + if (option.Abbreviation != '\0') + { + Console.Write("{0}{1}", shortOptionPrefix, option.Abbreviation); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write(" "); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write(" [,,...]"); + } + + Console.Write(" | {0}{1}", longOptionPrefix, option.Name); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write("="); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write("=[,,...]"); + } + } + else + { + Console.Write(longOptionPrefix ?? "--"); + Console.Write("{0}", option.Name); + if (option.Type == CommandLineOptionValueType.Single) + { + Console.Write("="); + } + else if (option.Type == CommandLineOptionValueType.Multiple) + { + Console.Write("=[,,...]"); + } + } + if (option.Optional) + { + Console.Write(']'); + } + // Console.WriteLine(); + } + + /// + /// Parses the option. + /// + /// true, if option was parsed, false otherwise. + /// Arguments. + /// Index. + /// The list into which to add the option if it has been specified. + /// The set of available options. + private bool ParseOption(string[] args, ref int index, IList list, CommandLineOption.CommandLineOptionCollection optionSet) + { + string longOptionPrefix = "--", shortOptionPrefix = "-"; + + bool breakout = false; + if (args[index].StartsWith(shortOptionPrefix) && args[index].Length == (shortOptionPrefix.Length + 1)) + { + char shortOptionChar = args[index][args[index].Length - 1]; + CommandLineOption option = optionSet[shortOptionChar]; + if (option != null) + { + if (option.Abbreviation == shortOptionChar) + { + if (option.Type != CommandLineOptionValueType.None) + { + index++; + option.Value = args[index]; + } + + if (list != null) + list.Add(option); + } + } + else + { + list.Add(new CommandLineOption() { Abbreviation = shortOptionChar }); + } + } + else if (args[index].StartsWith(longOptionPrefix)) + { + // long option format is --name[=value] + string name = args[index].Substring(longOptionPrefix.Length); + string value = null; + if (name.Contains("=")) + { + int idx = name.IndexOf('='); + value = name.Substring(idx + 1); + name = name.Substring(0, idx); + } + CommandLineOption option = optionSet[name]; + if (option != null) + { + if (option.Type != CommandLineOptionValueType.None) + { + // index++; + // option.Value = args[index]; + option.Value = value; + } + list.Add(option); + } + else + { + list.Add(new CommandLineOption() { Name = name }); + } + } + else + { + // we have reached a non-option + return true; + } + return false; + } + public int Start() { if (Application.Instance == null) diff --git a/MBS.Framework/ApplicationActivatedEvent.cs b/MBS.Framework/ApplicationActivatedEvent.cs new file mode 100644 index 0000000..8dbfa1e --- /dev/null +++ b/MBS.Framework/ApplicationActivatedEvent.cs @@ -0,0 +1,41 @@ +// +// ApplicationActivatedEvent.cs +// +// Author: +// Mike Becker +// +// Copyright (c) 2019 Mike Becker +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Collections.Generic; + +namespace MBS.Framework +{ + public class ApplicationActivatedEventArgs : EventArgs + { + public bool FirstRun { get; } = true; + public CommandLine CommandLine { get; } = null; + public int ExitCode { get; set; } = 0; + public ApplicationActivationType ActivationType { get; } = ApplicationActivationType.Launch; + + public ApplicationActivatedEventArgs(bool firstRun, ApplicationActivationType activationType, CommandLine commandLine) + { + FirstRun = firstRun; + ActivationType = activationType; + CommandLine = commandLine; + } + } + public delegate void ApplicationActivatedEventHandler(object sender, ApplicationActivatedEventArgs e); +} diff --git a/MBS.Framework/ApplicationActivationType.cs b/MBS.Framework/ApplicationActivationType.cs new file mode 100644 index 0000000..75a13a8 --- /dev/null +++ b/MBS.Framework/ApplicationActivationType.cs @@ -0,0 +1,276 @@ +// +// ActivationType.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2022 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +namespace MBS.Framework +{ + /// + /// Specifies the type of activation when the application is started + /// from layout on a layout-aware operating system. + /// + /// + /// + /// Values are mutually exclusive and cannot be combined. Each one + /// relates to a different type of activation, and an app instance can + /// be activated in only one way at a time. + /// + /// + /// The activation types are based on those supported by Windows 11, + /// however the enum values are operating system agnostic. Translation + /// (e.g. by an Engine) must be done before calls to operating system + /// specific methods are made. + /// + /// + /// On non-layout-aware operating systems, such as Linux, a wrapper + /// executable (bootstrapper) must be used to invoke the app activation + /// with the desired . + /// + /// + public enum ApplicationActivationType + { + /// + /// The activation type was not specified. + /// + Unspecified = -1, + + /// + /// The user wants to manage appointments that are provided by the + /// app. + /// + AppointmentsProvider, + /// + /// The app was activated as a barcode scanner provider. + /// + BarcodeScannerProvider, + /// + /// The user wants to save a file for which the app provides content + /// management. + /// + CachedFileUpdater, + /// + /// The app captures photos or video from an attached camera. + /// + CameraSettings, + /// + /// The app was launched from the command line. + /// + CommandLineLaunch, + /// + /// Reserved for system use. Introduced in Windows 10, version 1507 + /// (10.0.10240). + /// + ComponentUI, + /// + /// The user wants to handle calls or messages for the phone number + /// of a contact that is provided by the app. + /// + Contact, + /// + /// The app was launched from the My People UI. Note: introduced in + /// Windows 10, version 1703 (10.0.15063), but not used. Now used + /// starting with Windows 10, version 1709 (10.0.16299). + /// + ContactPanel, + /// + /// The user wants to pick contacts. + /// + ContactPicker, + /// + /// The app handles AutoPlay. + /// + Device, + /// + /// This app was activated as a result of pairing a device. + /// + DevicePairing, + /// + /// This app was launched by another app on a different device by + /// using the DIAL protocol.Introduced in Windows 10, version 1507 + /// (10.0.10240). + /// + DialReceiver, + /// + /// An app launched a file whose file type this app is registered to + /// handle. + /// + File, + /// + /// The user wants to pick files that are provided by the app. + /// + FileOpenPicker, + /// + /// Reserved for system use. Introduced in Windows 10, version 1607 + /// (10.0.14393). + /// + FilePickerExperience, + /// + /// The user wants to save a file and selected the app as the + /// location. + /// + FileSavePicker, + /// + /// The app was activated because it was launched by the OS due to a + /// game's request for Xbox-specific UI. Introduced in Windows 10, + /// version 1703 (10.0.15063). + /// + GameUIProvider, + /// + /// The user launched the app or tapped a content tile. + /// + Launch, + /// + /// The app was activated as the lock screen. Introduced in Windows + /// 10, version 1507 (10.0.10240). + /// + LockScreen, + /// + /// Windows Store only. The app launches a call from the lock screen. + /// If the user wants to accept the call, the app displays its call + /// UI directly on the lock screen without requiring the user to + /// unlock. A lock-screen call is a special type of launch + /// activation. + /// + LockScreenCall, + /// + /// Reserved for system use. Introduced in Windows 10, version 1703 + /// (10.0.15063). + /// + LockScreenComponent, + /// + /// The app was activated in response to a phone call. + /// + PhoneCallActivation, + /// + /// Windows Phone only. The app was activated after the completion of + /// a picker. + /// + PickerReturned, + /// + /// Windows Phone only. The app was activated after the app was + /// suspended for a file picker operation. + /// + PickFileContinuation, + /// + /// Windows Phone only. The app was activated after the app was + /// suspended for a folder picker operation. + /// + PickFolderContinuation, + /// + /// Windows Phone only. The app was activated after the app was + /// suspended for a file save picker operation. + /// + PickSaveFileContinuation, + /// + /// This app was launched by another app to provide a customized + /// printing experience for a 3D printer. Introduced in Windows 10, + /// version 1507 (10.0.10240). + /// + Print3DWorkflow, + /// + /// The app was activated as a print workflow job UI extension. + /// + PrintSupportJobUI, + /// + /// The app was activated as a print support settings UI extension. + /// + PrintSupportSettingsUI, + /// + /// The app handles print tasks. + /// + PrintTaskSettings, + /// + /// The app was activated because the user is printing to a printer + /// that has a Print Workflow Application associated with it which + /// has requested user input. + /// + PrintWorkflowForegroundTask, + /// + /// An app launched a URI whose scheme name this app is registered to + /// handle. + /// + Protocol, + /// + /// The app was launched by another app with the expectation that it + /// will return a result back to the caller. Introduced in Windows + /// 10, version 1507 (10.0.10240). + /// + ProtocolForResults, + /// + /// Windows Store only. The user launched the restricted app. + /// + RestrictedLaunch, + /// + /// The user wants to search with the app. + /// + Search, + /// + /// The app is activated as a target for share operations. + /// + ShareTarget, + /// + /// The app was activated because the app is specified to launch at + /// system startup or user log-in. Introduced in Windows 10, version + /// 1703 (10.0.15063). + /// + StartupTask, + /// + /// The app was activated when a user tapped on the body of a toast + /// notification or performed an action inside a toast notification. + /// Introduced in Windows 10, version 1507 (10.0.10240). + /// + ToastNotification, + /// + /// The app was launched to handle the user interface for account + /// management. In circumstances where the system would have shown + /// the default system user interface, it instead has invoked your + /// app with the UserDataAccountProvider contract. The activation + /// payload contains information about the type of operation being + /// requested and all the information necessary to replicate the + /// system-provided user interface. This activation kind is limited + /// to 1st party apps. To use this field, you must add the + /// userDataAccountsProvider capability in your app's package + /// manifest. For more info see App capability declarations. + /// Introduced in Windows 10, version 1607 (10.0.14393). + /// + UserDataAccountsProvider, + /// + /// The app was activated as the result of a voice command. + /// + VoiceCommand, + /// + /// The app is a VPN foreground app that was activated by the plugin. + /// For more details, see VpnChannel.ActivateForeground. + /// + VpnForeground, + /// + /// The app was activated to perform a Wallet operation. + /// + WalletAction, + /// + /// The app was activated by a web account provider. + /// + WebAccountProvider, + /// + /// The app was activated after the app was suspended for a web + /// authentication broker operation. + /// + WebAuthenticationBrokerContinuation + } +} diff --git a/MBS.Framework/ArrayExtensions.cs b/MBS.Framework/ArrayExtensions.cs index b6e54b6..1ffcbfa 100644 --- a/MBS.Framework/ArrayExtensions.cs +++ b/MBS.Framework/ArrayExtensions.cs @@ -19,10 +19,23 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; +using System.Collections.Generic; + namespace MBS.Framework { public static class ArrayExtensions { + /// + /// Splits the into two arrays at the specified + /// , where contains the + /// elements before and + /// contains the elements after . + /// + /// The array to split. + /// The index at which to split the array. + /// The array containing elements before the split point. + /// The array containing elements after the split point. + /// The of elements in the array. public static void Bisect(this T[] array, int index, out T[] left, out T[] right) { left = new T[index]; @@ -55,12 +68,25 @@ namespace MBS.Framework Array.Copy(sourceArray, 0, destinationArray, start, sourceArray.Length); } - public static bool Matches(this T[] array1, T[] array2) + /// + /// Determines if and + /// contain the same elements. + /// + /// + /// if both arrays contain the same elements, + /// otherwise. + /// The first array to search. + /// The second array to search. + /// The 1st type parameter. + public static bool Matches(this IList array1, IList array2) { - if (array1.Length != array2.Length) + if (array1.Count != array2.Count) + { + // short-circuit if arrays have different lengths return false; + } - for (int i = 0; i < array1.Length; i++) + for (int i = 0; i < array1.Count; i++) { if (!array1[i].Equals(array2[i])) return false; diff --git a/MBS.Framework/CommandLine.cs b/MBS.Framework/CommandLine.cs index 3405149..1705c68 100644 --- a/MBS.Framework/CommandLine.cs +++ b/MBS.Framework/CommandLine.cs @@ -40,10 +40,20 @@ namespace MBS.Framework public CommandLineParser Parser { get; set; } = null; public CommandLineOption.CommandLineOptionCollection Options { get; } = new CommandLineOption.CommandLineOptionCollection(); + public CommandLineCommand.CommandLineCommandCollection Commands { get; } = new CommandLineCommand.CommandLineCommandCollection(); + public CommandLineCommand Command { get; internal set; } = null; + public string ShortOptionPrefix { get; set; } = null; + public string LongOptionPrefix { get; set; } = null; public CommandLine() { - Options.Add(new CommandLineOption() { Name = "activation-type", Description = "The type of activation for this app", Type = CommandLineOptionValueType.Single }); + string[] args = Environment.GetCommandLineArgs(); + string[] args2 = new string[args.Length - 1]; + Array.Copy(args, 1, args2, 0, args2.Length); + Arguments = args2; + + Options.Add(new CommandLineOption() { Abbreviation = 'A', Name = "activation-type", Description = "The type of activation for this app", Type = CommandLineOptionValueType.Single, Optional = true }); + Options.Add(new CommandLineOption() { Name = "help", Description = "Displays help", Type = CommandLineOptionValueType.None, Optional = true }); } public override string ToString() diff --git a/MBS.Framework/CommandLineCommand.cs b/MBS.Framework/CommandLineCommand.cs new file mode 100644 index 0000000..a8ddbe9 --- /dev/null +++ b/MBS.Framework/CommandLineCommand.cs @@ -0,0 +1,59 @@ +// +// CommandLineCommand.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2022 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using MBS.Framework.Collections.Generic; + +namespace MBS.Framework +{ + public class CommandLineCommand + { + public class CommandLineCommandCollection + : System.Collections.ObjectModel.Collection + { + public CommandLineCommand this[string name] + { + get + { + foreach (CommandLineCommand item in this) + { + if (item.Name == name) + return item; + } + return null; + } + } + } + + public string Name { get; set; } = null; + public string Description { get; set; } = null; + + public CommandLineOption.CommandLineOptionCollection Options { get; } = new CommandLineOption.CommandLineOptionCollection(); + + public CommandLineCommand(string command, CommandLineOption[] options = null) + { + Name = command; + if (options != null) + { + Options.AddRange(options); + } + } + } +} diff --git a/MBS.Framework/CommandLineOption.cs b/MBS.Framework/CommandLineOption.cs index 58e68e8..51148fa 100644 --- a/MBS.Framework/CommandLineOption.cs +++ b/MBS.Framework/CommandLineOption.cs @@ -117,6 +117,12 @@ namespace MBS.Framework public object DefaultValue { get; set; } = null; + /// + /// Gets or sets a value indicating whether this is optional. + /// + /// true if optional; otherwise, false. + public bool Optional { get; set; } = false; + /// /// The description displayed in the help text. /// diff --git a/MBS.Framework/FindFileOptions.cs b/MBS.Framework/FindFileOptions.cs index be964f2..e08bc94 100644 --- a/MBS.Framework/FindFileOptions.cs +++ b/MBS.Framework/FindFileOptions.cs @@ -21,9 +21,20 @@ using System; namespace MBS.Framework { + /// + /// Controls the behavior of relative file resolution. + /// public enum FindFileOptions { + /// + /// Returns all matching fully-qualified file paths across all global, + /// application, and user directories. + /// All = 0, + /// + /// Returns only file paths that are writable by the user (i.e., in the + /// user's local or roaming data directory). + /// UserWritable = 1 } } diff --git a/MBS.Framework/MBS.Framework.csproj b/MBS.Framework/MBS.Framework.csproj index 3f47052..8eac4d0 100644 --- a/MBS.Framework/MBS.Framework.csproj +++ b/MBS.Framework/MBS.Framework.csproj @@ -135,6 +135,9 @@ + + + @@ -146,5 +149,8 @@ + + + diff --git a/MBS.Framework/MemorySettingsProvider.cs b/MBS.Framework/MemorySettingsProvider.cs new file mode 100644 index 0000000..4271ae3 --- /dev/null +++ b/MBS.Framework/MemorySettingsProvider.cs @@ -0,0 +1,82 @@ +// +// MemorySettingsProvider.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2022 Mike Becker's Software +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +using System; +using System.Collections.Generic; +using MBS.Framework.Settings; + +namespace MBS.Framework +{ + public class MemorySettingsProvider : SettingsProvider + { + private Dictionary _values = new Dictionary(); + protected override void LoadSettingsInternal() + { + base.LoadSettingsInternal(); + foreach (SettingsGroup sg in SettingsGroups) + { + foreach (Setting s in sg.Settings) + { + LoadSetting(s); + } + } + } + protected override void SaveSettingsInternal() + { + base.SaveSettingsInternal(); + foreach (SettingsGroup sg in SettingsGroups) + { + foreach (Setting s in sg.Settings) + { + SaveSetting(s); + } + } + } + + private void LoadSetting(Setting s) + { + if (s is GroupSetting) + { + foreach (Setting s2 in ((GroupSetting)s).Options) + { + LoadSetting(s2); + } + } + else + { + s.SetValue(_values[s.ID]); + } + } + private void SaveSetting(Setting s) + { + if (s is GroupSetting) + { + foreach (Setting s2 in ((GroupSetting)s).Options) + { + SaveSetting(s2); + } + } + else + { + _values[s.ID] = s.GetValue(); + } + } + } +}