various improvements to Framework functionality

This commit is contained in:
Michael Becker 2022-10-14 09:46:08 -04:00
parent a118fb3eef
commit ab32a58e05
No known key found for this signature in database
GPG Key ID: DA394832305DA332
10 changed files with 765 additions and 6 deletions

View File

@ -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
/// </value>
public static Application Instance { get; set; } = null;
/// <summary>
/// Searches for the file with the given <paramref name="filename" /> and
/// returns all matching fully-qualified file names.
/// </summary>
/// <returns>The files.</returns>
/// <param name="filename">Filename.</param>
/// <param name="options">Options.</param>
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)
/// <summary>
/// Searches for the file with the given <paramref name="filename" /> and
/// returns the first matching fully-qualified file name.
/// </summary>
/// <returns>The files.</returns>
/// <param name="filename">Filename.</param>
/// <param name="options">Options.</param>
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;
}
/// <summary>
/// Gets a collection of <see cref="EventFilter" />s registered for this
/// <see cref="Application" />.
/// </summary>
/// <value>The event filters.</value>
public EventFilter.EventFilterCollection EventFilters { get; } = new EventFilter.EventFilterCollection();
/// <summary>
/// Finds the command with the given <paramref name="commandID" />.
/// </summary>
protected virtual Command FindCommandInternal(string commandID)
{
return null;
@ -125,10 +148,18 @@ namespace MBS.Framework
return null;
}
/// <summary>
/// Finds the <see cref="Context" /> with the given
/// <paramref name="contextID" />.
/// </summary>
protected virtual Context FindContextInternal(Guid contextID)
{
return null;
}
/// <summary>
/// Finds the <see cref="Context" /> with the given
/// <paramref name="contextID" />.
/// </summary>
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> --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("[<global-options...>]");
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(" <command> [<command-options...>]");
Console.WriteLine();
List<CommandLineCommand> commands = new List<CommandLineCommand>(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(" <value>");
}
else if (option.Type == CommandLineOptionValueType.Multiple)
{
Console.Write(" <value1>[,<value2>,...]");
}
Console.Write(" | {0}{1}", longOptionPrefix, option.Name);
if (option.Type == CommandLineOptionValueType.Single)
{
Console.Write("=<value>");
}
else if (option.Type == CommandLineOptionValueType.Multiple)
{
Console.Write("=<value1>[,<value2>,...]");
}
}
else
{
Console.Write(longOptionPrefix ?? "--");
Console.Write("{0}", option.Name);
if (option.Type == CommandLineOptionValueType.Single)
{
Console.Write("=<value>");
}
else if (option.Type == CommandLineOptionValueType.Multiple)
{
Console.Write("=<value1>[,<value2>,...]");
}
}
if (option.Optional)
{
Console.Write(']');
}
// Console.WriteLine();
}
/// <summary>
/// Parses the option.
/// </summary>
/// <returns><c>true</c>, if option was parsed, <c>false</c> otherwise.</returns>
/// <param name="args">Arguments.</param>
/// <param name="index">Index.</param>
/// <param name="list">The list into which to add the option if it has been specified.</param>
/// <param name="optionSet">The set of available options.</param>
private bool ParseOption(string[] args, ref int index, IList<CommandLineOption> 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)

View File

@ -0,0 +1,41 @@
//
// ApplicationActivatedEvent.cs
//
// Author:
// Mike Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
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);
}

View File

@ -0,0 +1,276 @@
//
// ActivationType.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
using System;
namespace MBS.Framework
{
/// <summary>
/// Specifies the type of activation when the application is started
/// from layout on a layout-aware operating system.
/// </summary>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// <para>
/// On non-layout-aware operating systems, such as Linux, a wrapper
/// executable (bootstrapper) must be used to invoke the app activation
/// with the desired <see cref="ApplicationActivationType" />.
/// </para>
/// </remarks>
public enum ApplicationActivationType
{
/// <summary>
/// The activation type was not specified.
/// </summary>
Unspecified = -1,
/// <summary>
/// The user wants to manage appointments that are provided by the
/// app.
/// </summary>
AppointmentsProvider,
/// <summary>
/// The app was activated as a barcode scanner provider.
/// </summary>
BarcodeScannerProvider,
/// <summary>
/// The user wants to save a file for which the app provides content
/// management.
/// </summary>
CachedFileUpdater,
/// <summary>
/// The app captures photos or video from an attached camera.
/// </summary>
CameraSettings,
/// <summary>
/// The app was launched from the command line.
/// </summary>
CommandLineLaunch,
/// <summary>
/// Reserved for system use. Introduced in Windows 10, version 1507
/// (10.0.10240).
/// </summary>
ComponentUI,
/// <summary>
/// The user wants to handle calls or messages for the phone number
/// of a contact that is provided by the app.
/// </summary>
Contact,
/// <summary>
/// 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).
/// </summary>
ContactPanel,
/// <summary>
/// The user wants to pick contacts.
/// </summary>
ContactPicker,
/// <summary>
/// The app handles AutoPlay.
/// </summary>
Device,
/// <summary>
/// This app was activated as a result of pairing a device.
/// </summary>
DevicePairing,
/// <summary>
/// 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).
/// </summary>
DialReceiver,
/// <summary>
/// An app launched a file whose file type this app is registered to
/// handle.
/// </summary>
File,
/// <summary>
/// The user wants to pick files that are provided by the app.
/// </summary>
FileOpenPicker,
/// <summary>
/// Reserved for system use. Introduced in Windows 10, version 1607
/// (10.0.14393).
/// </summary>
FilePickerExperience,
/// <summary>
/// The user wants to save a file and selected the app as the
/// location.
/// </summary>
FileSavePicker,
/// <summary>
/// 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).
/// </summary>
GameUIProvider,
/// <summary>
/// The user launched the app or tapped a content tile.
/// </summary>
Launch,
/// <summary>
/// The app was activated as the lock screen. Introduced in Windows
/// 10, version 1507 (10.0.10240).
/// </summary>
LockScreen,
/// <summary>
/// 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.
/// </summary>
LockScreenCall,
/// <summary>
/// Reserved for system use. Introduced in Windows 10, version 1703
/// (10.0.15063).
/// </summary>
LockScreenComponent,
/// <summary>
/// The app was activated in response to a phone call.
/// </summary>
PhoneCallActivation,
/// <summary>
/// Windows Phone only. The app was activated after the completion of
/// a picker.
/// </summary>
PickerReturned,
/// <summary>
/// Windows Phone only. The app was activated after the app was
/// suspended for a file picker operation.
/// </summary>
PickFileContinuation,
/// <summary>
/// Windows Phone only. The app was activated after the app was
/// suspended for a folder picker operation.
/// </summary>
PickFolderContinuation,
/// <summary>
/// Windows Phone only. The app was activated after the app was
/// suspended for a file save picker operation.
/// </summary>
PickSaveFileContinuation,
/// <summary>
/// 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).
/// </summary>
Print3DWorkflow,
/// <summary>
/// The app was activated as a print workflow job UI extension.
/// </summary>
PrintSupportJobUI,
/// <summary>
/// The app was activated as a print support settings UI extension.
/// </summary>
PrintSupportSettingsUI,
/// <summary>
/// The app handles print tasks.
/// </summary>
PrintTaskSettings,
/// <summary>
/// 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.
/// </summary>
PrintWorkflowForegroundTask,
/// <summary>
/// An app launched a URI whose scheme name this app is registered to
/// handle.
/// </summary>
Protocol,
/// <summary>
/// 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).
/// </summary>
ProtocolForResults,
/// <summary>
/// Windows Store only. The user launched the restricted app.
/// </summary>
RestrictedLaunch,
/// <summary>
/// The user wants to search with the app.
/// </summary>
Search,
/// <summary>
/// The app is activated as a target for share operations.
/// </summary>
ShareTarget,
/// <summary>
/// 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).
/// </summary>
StartupTask,
/// <summary>
/// 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).
/// </summary>
ToastNotification,
/// <summary>
/// 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).
/// </summary>
UserDataAccountsProvider,
/// <summary>
/// The app was activated as the result of a voice command.
/// </summary>
VoiceCommand,
/// <summary>
/// The app is a VPN foreground app that was activated by the plugin.
/// For more details, see VpnChannel.ActivateForeground.
/// </summary>
VpnForeground,
/// <summary>
/// The app was activated to perform a Wallet operation.
/// </summary>
WalletAction,
/// <summary>
/// The app was activated by a web account provider.
/// </summary>
WebAccountProvider,
/// <summary>
/// The app was activated after the app was suspended for a web
/// authentication broker operation.
/// </summary>
WebAuthenticationBrokerContinuation
}
}

View File

@ -19,10 +19,23 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
namespace MBS.Framework
{
public static class ArrayExtensions
{
/// <summary>
/// Splits the <paramref name="array" /> into two arrays at the specified
/// <paramref name="index" />, where <paramref name="left" /> contains the
/// elements before <paramref name="index" /> and <paramref name="right" />
/// contains the elements after <paramref name="index" />.
/// </summary>
/// <param name="array">The array to split.</param>
/// <param name="index">The index at which to split the array.</param>
/// <param name="left">The array containing elements before the split point.</param>
/// <param name="right">The array containing elements after the split point.</param>
/// <typeparam name="T">The <see cref="Type" /> of elements in the array.</typeparam>
public static void Bisect<T>(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<T>(this T[] array1, T[] array2)
/// <summary>
/// Determines if <paramref name="array1" /> and <paramref name="array2" />
/// contain the same elements.
/// </summary>
/// <returns>
/// <see langword="true" /> if both arrays contain the same elements,
/// <see langword="false" /> otherwise.</returns>
/// <param name="array1">The first array to search.</param>
/// <param name="array2">The second array to search.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static bool Matches<T>(this IList<T> array1, IList<T> 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;

View File

@ -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()

View File

@ -0,0 +1,59 @@
//
// CommandLineCommand.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
using System;
using MBS.Framework.Collections.Generic;
namespace MBS.Framework
{
public class CommandLineCommand
{
public class CommandLineCommandCollection
: System.Collections.ObjectModel.Collection<CommandLineCommand>
{
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);
}
}
}
}

View File

@ -117,6 +117,12 @@ namespace MBS.Framework
public object DefaultValue { get; set; } = null;
/// <summary>
/// Gets or sets a value indicating whether this <see cref="T:MBS.Framework.CommandLineOption"/> is optional.
/// </summary>
/// <value><c>true</c> if optional; otherwise, <c>false</c>.</value>
public bool Optional { get; set; } = false;
/// <summary>
/// The description displayed in the help text.
/// </summary>

View File

@ -21,9 +21,20 @@
using System;
namespace MBS.Framework
{
/// <summary>
/// Controls the behavior of relative file resolution.
/// </summary>
public enum FindFileOptions
{
/// <summary>
/// Returns all matching fully-qualified file paths across all global,
/// application, and user directories.
/// </summary>
All = 0,
/// <summary>
/// Returns only file paths that are writable by the user (i.e., in the
/// user's local or roaming data directory).
/// </summary>
UserWritable = 1
}
}

View File

@ -135,6 +135,9 @@
<Compile Include="ConsoleExtensions.cs" />
<Compile Include="MessageSeverity.cs" />
<Compile Include="Language.cs" />
<Compile Include="ApplicationActivatedEvent.cs" />
<Compile Include="ApplicationActivationType.cs" />
<Compile Include="CommandLineCommand.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Logic\" />
@ -146,5 +149,8 @@
<Folder Include="Settings\" />
<Folder Include="UserInterface\" />
</ItemGroup>
<ItemGroup>
<None Include="MemorySettingsProvider.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,82 @@
//
// MemorySettingsProvider.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using MBS.Framework.Settings;
namespace MBS.Framework
{
public class MemorySettingsProvider : SettingsProvider
{
private Dictionary<Guid, object> _values = new Dictionary<Guid, object>();
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();
}
}
}
}