From 604901d6735f1a3d694d8b874389ea696dc0233b Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sat, 20 Jul 2024 23:43:02 -0400 Subject: [PATCH 01/16] port MBS.Framework .NET legacy to .NET Core --- .../src/lib/MBS.Core/Application.cs | 448 +++++++++++++++++- .../Collections/Generic/ExtensionMethods.cs | 2 +- .../src/lib/MBS.Core/FindFileOptions.cs | 45 ++ framework-dotnet/src/lib/MBS.Core/NanoId.cs | 2 + .../src/lib/MBS.Core/Reflection/TypeLoader.cs | 19 +- .../src/lib/MBS.Core/StreamExtensions.cs | 25 + 6 files changed, 536 insertions(+), 5 deletions(-) create mode 100644 framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 024c65c..9c1bcb3 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -1,4 +1,6 @@ -namespace MBS.Core; +using System.ComponentModel; + +namespace MBS.Core; public class Application { @@ -8,12 +10,224 @@ public class Application protected virtual void OnStartup(EventArgs e) { } + public event ApplicationActivatedEventHandler BeforeActivated; + protected virtual void OnBeforeActivated(ApplicationActivatedEventArgs e) + { + BeforeActivated?.Invoke(this, e); + } + + public event ApplicationActivatedEventHandler Activated; protected virtual void OnActivated(ApplicationActivatedEventArgs e) + { + Activated?.Invoke(this, e); + } + + + public event ApplicationActivatedEventHandler AfterActivated; + protected virtual void OnAfterActivated(ApplicationActivatedEventArgs e) + { + AfterActivated?.Invoke(this, e); + } + + /// + /// 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]; + } + else + { + option.Value = true; + } + + 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]; + if (!name.Contains("=")) + { + // already taken care of the true case above + if (index + 1 < args.Length) + { + index++; + value = args[index]; + } + } + option.Value = value; + } + else + { + option.Value = true; + } + list.Add(option); + } + else + { + list.Add(new CommandLineOption() { Name = name }); + } + } + else + { + // we have reached a non-option + return true; + } + return false; + } + + protected virtual void OnBeforeStartInternal(CancelEventArgs e) { } protected virtual int StartInternal() { + CancelEventArgs ce = new CancelEventArgs(); + OnBeforeStartInternal(ce); + if (ce.Cancel) + return 2; + + 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, cline.Options, cmd.Options)) + break; + } + + cline.Command = cmd; + } + else + { + // assume filename + } + } + + for (/* intentionally left blank */; i < args.Length; i++) + { + cline.FileNames.Add(args[i]); + } + } + + ApplicationActivatedEventArgs e = new ApplicationActivatedEventArgs(true, ApplicationActivationType.CommandLineLaunch, cline); + + if (CommandLine.Options["help"]?.Value is bool && ((bool)CommandLine.Options["help"]?.Value) == true) + { + if (ShowCommandLineHelp(out int resultCode)) + { + return resultCode; + } + } + + OnStartup(EventArgs.Empty); + if (cline.Command != null && cline.Command.ActivationDelegate != null) + { + OnBeforeActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + // use the activation delegate instead of calling OnActivated + cline.Command.ActivationDelegate(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnAfterActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + } + else + { + OnBeforeActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + + OnAfterActivated(e); + if (!e.Success) + { + Console.WriteLine(String.Format("Try '{0} --help' for more information.", ShortName)); + return e.ExitCode; + } + } + return e.ExitCode; + /* OnStartup(EventArgs.Empty); CommandLine cline = new CommandLine(); @@ -23,6 +237,7 @@ public class Application OnActivated(e); return e.ExitCode; + */ } public int Start() @@ -43,4 +258,235 @@ public class Application StopInternal(exitCode); } + private string _UniqueName = null; + public string UniqueName + { + get + { + if (_UniqueName == null) + { + return ShortName; + } + return _UniqueName; + } + set + { + _UniqueName = value; + } + } + + private string mvarBasePath = null; + public string BasePath + { + get + { + if (mvarBasePath == null) + { + // Set up the base path for the current application. Should this be able to be + // overridden with a switch (/basepath:...) ? + mvarBasePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + } + return mvarBasePath; + } + } + + protected virtual string DataDirectoryName => ShortName; + public string[] EnumerateDataPaths() => EnumerateDataPaths(FindFileOptions.All); + public string[] EnumerateDataPaths(FindFileOptions options) + { + List list = new List(); + + string[] dataDirectoryNames = null; + if (AdditionalShortNames != null) + { + dataDirectoryNames = new string[AdditionalShortNames.Length + 1]; + dataDirectoryNames[0] = DataDirectoryName; + for (int i = 0; i < AdditionalShortNames.Length; i++) + { + dataDirectoryNames[i + 1] = AdditionalShortNames[i]; + } + } + else + { + dataDirectoryNames = new string[] { DataDirectoryName }; + } + + if ((options & FindFileOptions.All) == FindFileOptions.All) + { + // first look in the application root directory since this will override everything else + list.Add(BasePath); + + foreach (string dataDirName in dataDirectoryNames) + { + if (Environment.OSVersion.Platform == PlatformID.Unix) + { + // if we are on Unix or Mac OS X, look in /etc/... + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + String.Empty, // *nix root directory + "etc", + dataDirName + })); + } + + // then look in /usr/share/universal-editor or C:\ProgramData\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData), + dataDirName + })); + + // then look in ~/.local/share/universal-editor or C:\Users\USERNAME\AppData\Local\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), + dataDirName + })); + } + } + + + + //fixme: addd finddfileoption.userconfig, localdata, etc. + // now for the user-writable locations... + + foreach (string dataDirName in dataDirectoryNames) + { + // then look in ~/.universal-editor or C:\Users\USERNAME\AppData\Roaming\Mike Becker's Software\Universal Editor + list.Add(String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] + { + System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), + dataDirName + })); + } + return list.ToArray(); + } + + public string ShortName { get; set; } + public virtual string[] AdditionalShortNames => null; + + protected void PrintUsageStatement(CommandLineCommand command = null) + { + string shortOptionPrefix = CommandLine.ShortOptionPrefix ?? "-"; + string longOptionPrefix = CommandLine.LongOptionPrefix ?? "--"; + + Console.Write("usage: {0} ", ShortName); + foreach (CommandLineOption option in CommandLine.Options) + { + PrintUsageStatementOption(option); + } + + if (CommandLine.HelpTextPrefix != null) + { + Console.WriteLine(); + Console.WriteLine(); + Console.WriteLine(CommandLine.HelpTextPrefix); + } + + Console.WriteLine(); + + bool printDescriptions = true; + if (printDescriptions) + { + foreach (CommandLineOption option in CommandLine.Options) + { + Console.WriteLine(" {0}{1}", option.Abbreviation != '\0' ? String.Format("{0}{1}, {2}{3}", shortOptionPrefix, option.Abbreviation, longOptionPrefix, option.Name) : String.Format("{0}{1}", longOptionPrefix, option.Name), option.Description == null ? null : String.Format(" - {0}", option.Description)); + } + } + // 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)); + } + } + } + + if (CommandLine.HelpTextSuffix != null) + { + Console.WriteLine(); + Console.WriteLine(CommandLine.HelpTextSuffix); + } + } + + 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(); + } + protected virtual bool ShowCommandLineHelp(out int resultCode) + { + // bash: cd returns 2 if --help is specified OR invalid option selected, 1 if file not found + PrintUsageStatement(); + resultCode = 2; + return true; + } + } diff --git a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs index db03cbf..0b58403 100755 --- a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs +++ b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs @@ -55,7 +55,7 @@ namespace MBS.Core.Collections.Generic return ((List)(cacheOfT[obj][typeof(T)])).ToArray(); } - public static void AddRange(this IList list, IEnumerable items) + public static void AddRange(this ICollection list, IEnumerable items) { foreach (T item in items) { diff --git a/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs b/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs new file mode 100644 index 0000000..473678d --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/FindFileOptions.cs @@ -0,0 +1,45 @@ +// +// FindFileOptions.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.Core; + +/// +/// Controls the behavior of relative file resolution. +/// +[Flags()] +public enum FindFileOptions +{ + /// + /// Returns all matching fully-qualified file paths across all global, + /// application, and user directories. + /// + All = 1, + /// + /// Returns only file paths that are writable by the user (i.e., in the + /// user's local or roaming data directory). + /// + UserWritable = 2, + /// + /// Allows the user to create a file if it does not exist (i.e., returns + /// a file name even if it does not exist) + /// + Create = 4 +} diff --git a/framework-dotnet/src/lib/MBS.Core/NanoId.cs b/framework-dotnet/src/lib/MBS.Core/NanoId.cs index 6259def..8349238 100755 --- a/framework-dotnet/src/lib/MBS.Core/NanoId.cs +++ b/framework-dotnet/src/lib/MBS.Core/NanoId.cs @@ -163,6 +163,8 @@ namespace MBS.Core public const string DefaultAlphabet = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public const string DefaultAlphabetNoSpecialChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphabetNoSpecialChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphanumericNoSpecialChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly CryptoRandom Random = new CryptoRandom(); /// diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs index 29e999e..eeab03f 100644 --- a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs @@ -197,16 +197,29 @@ public class TypeLoader } } mvarAvailableTypes = types.ToArray(); + /* + foreach (Type t in mvarAvailableTypes) + { + Console.WriteLine("Type load: {0}", t.FullName); + } + */ } if (inheritsFrom != null) { List retval = new List(); - for (int iTyp = 0; iTyp < mvarAvailableTypes.Length; iTyp++) + foreach (Type t in mvarAvailableTypes) { - for (int jInh = 0; jInh < inheritsFrom.Length; jInh++) + foreach (Type inheritsFromType in inheritsFrom) { - if (mvarAvailableTypes[iTyp].IsSubclassOf(inheritsFrom[jInh])) retval.Add(mvarAvailableTypes[iTyp]); + if (t.FullName.Contains("Mini.")) + { + Console.WriteLine(inheritsFromType.FullName + " ? inherits ? " + t.FullName); + } + if (t.IsSubclassOf(inheritsFromType)) + { + retval.Add(t); + } } } return retval.ToArray(); diff --git a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs index b1a5d14..9568243 100644 --- a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs +++ b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs @@ -19,6 +19,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.Net.Http.Headers; + namespace MBS.Core; public static class StreamExtensions @@ -96,4 +98,27 @@ public static class StreamExtensions } _streamPositions[st].Push(st.Position); } + + public static byte[] ReadToEnd(this Stream st) + { + byte[] retval = new byte[0]; + byte[] buffer = new byte[4096]; + + long j = 0; + bool remaining = true; + while (remaining) + { + int count = st.Read(buffer, 0, buffer.Length); + if (count < buffer.Length) + { + Array.Resize(ref buffer, count); + remaining = false; + } + + Array.Resize(ref retval, retval.Length + buffer.Length); + Array.Copy(buffer, 0, retval, j, buffer.Length); + j += buffer.Length; + } + return retval; + } } \ No newline at end of file From 30bde5d2cd3938e5949b5d688196cc9a88b80d46 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 21 Jul 2024 22:32:37 -0400 Subject: [PATCH 02/16] add capital alphanumeric alphabets for convenience --- framework-dotnet/src/lib/MBS.Core/NanoId.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework-dotnet/src/lib/MBS.Core/NanoId.cs b/framework-dotnet/src/lib/MBS.Core/NanoId.cs index 6259def..5f58995 100755 --- a/framework-dotnet/src/lib/MBS.Core/NanoId.cs +++ b/framework-dotnet/src/lib/MBS.Core/NanoId.cs @@ -163,6 +163,8 @@ namespace MBS.Core public const string DefaultAlphabet = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public const string DefaultAlphabetNoSpecialChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphanumeric = "_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string CapitalAlphanumericNoSpecialChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static readonly CryptoRandom Random = new CryptoRandom(); /// From c275c017c14c66399ad03386f2e8f52faf4d6fd1 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 21 Jul 2024 22:35:30 -0400 Subject: [PATCH 03/16] add BeforeStartInternal event --- framework-dotnet/src/lib/MBS.Core/Application.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 024c65c..2a84844 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -1,4 +1,6 @@ -namespace MBS.Core; +using System.ComponentModel; + +namespace MBS.Core; public class Application { @@ -25,9 +27,21 @@ public class Application return e.ExitCode; } + public event EventHandler BeforeStartInternal; + protected virtual void OnBeforeStartInternal(CancelEventArgs e) + { + BeforeStartInternal?.Invoke(this, e); + } + public int Start() { Instance = this; + + CancelEventArgs e = new CancelEventArgs(); + OnBeforeStartInternal(e); + if (e.Cancel) + return 2; + int exitCode = StartInternal(); Instance = null; From 9df55b9c082a37da070fe351fbb91d0aa3a114d9 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 21 Jul 2024 22:36:57 -0400 Subject: [PATCH 04/16] add ReadToEnd function --- .../src/lib/MBS.Core/StreamExtensions.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs index b1a5d14..3d0e9c9 100644 --- a/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs +++ b/framework-dotnet/src/lib/MBS.Core/StreamExtensions.cs @@ -96,4 +96,33 @@ public static class StreamExtensions } _streamPositions[st].Push(st.Position); } + + private const long BUFFER_SIZE=4096; + public static byte[] ReadToEnd(this Stream st) + { + byte[] buffer = new byte[BUFFER_SIZE]; + byte[] output = new byte[0]; + int i = 0; + bool done = false; + while (!done) + { + int length = st.Read(buffer, 0, buffer.Length); + if (length == 0) + { + done = true; + break; + } + + if (length < BUFFER_SIZE) + { + Array.Resize(ref buffer, length); + done = true; + } + + Array.Resize(ref output, output.Length + buffer.Length); + Array.Copy(buffer, 0, output, i, buffer.Length); + i += buffer.Length; + } + return output; + } } \ No newline at end of file From 623402805b12b11c6a32ef1db6848e7f5587b48c Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 21 Jul 2024 22:37:57 -0400 Subject: [PATCH 05/16] fix AddRange to allow any type of Collection<>, not just List<> --- .../src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs index db03cbf..0b58403 100755 --- a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs +++ b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs @@ -55,7 +55,7 @@ namespace MBS.Core.Collections.Generic return ((List)(cacheOfT[obj][typeof(T)])).ToArray(); } - public static void AddRange(this IList list, IEnumerable items) + public static void AddRange(this ICollection list, IEnumerable items) { foreach (T item in items) { From 6ee1135732e56641877d3f5b21b5870553301051 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 21 Jul 2024 22:38:38 -0400 Subject: [PATCH 06/16] add TypeExtensions to support Type.IsSubclassOfGeneric(...) --- .../lib/MBS.Core/Reflection/TypeExtensions.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs new file mode 100644 index 0000000..b7930ed --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs @@ -0,0 +1,32 @@ +namespace MBS.Core.Reflection; + +public static class TypeExtensions +{ + public static bool IsSubclassOfGeneric(this Type toCheck, Type generic) + { + // thanks https://stackoverflow.com/a/457708 + + while (toCheck != null && toCheck != typeof(object)) + { + var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck; + if (generic == cur) + { + return true; + } + + Type[] intfs = toCheck.GetInterfaces(); + foreach (Type intf in intfs) + { + // !!! HACK HACK HACK !!! + bool hack = intf.Namespace.Equals(generic.Namespace) && intf.Name.Equals(generic.Name); + if (hack) + { + return true; + } + } + toCheck = toCheck.BaseType; + } + return false; + } + +} \ No newline at end of file From 8e41ff2f82448dba81c97470f7b2cd191430fd52 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Mon, 22 Jul 2024 23:40:16 -0400 Subject: [PATCH 07/16] remove duplicate definition of OnBeforeStartInternal (how'd that get there?) --- framework-dotnet/src/lib/MBS.Core/Application.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index a7b0ca0..255002d 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -117,10 +117,6 @@ public class Application return false; } - protected virtual void OnBeforeStartInternal(CancelEventArgs e) - { - } - protected virtual int StartInternal() { CancelEventArgs ce = new CancelEventArgs(); From 2431ffd45a856c6c53d9fc35e0f5fb8e216b0a5d Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 28 Jul 2024 14:32:48 -0400 Subject: [PATCH 08/16] add GetProperty function to return the Property associated with a get_... or set_... special method --- .../lib/MBS.Core/Reflection/TypeExtensions.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs index b7930ed..5275b44 100644 --- a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeExtensions.cs @@ -1,3 +1,5 @@ +using System.Reflection; + namespace MBS.Core.Reflection; public static class TypeExtensions @@ -29,4 +31,18 @@ public static class TypeExtensions return false; } + public static PropertyInfo? GetProperty(this MethodBase methodBase) + { + Type declType = methodBase.DeclaringType; + foreach (PropertyInfo pi in declType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (pi.GetMethod == methodBase + || pi.SetMethod == methodBase) + { + return pi; + } + } + return null; + } + } \ No newline at end of file From 27f32852392251695a011a405d3546ab94d37920 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 1 Aug 2024 22:53:03 -0400 Subject: [PATCH 09/16] comment out temporary debugging prints --- framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs index eeab03f..4aa052e 100644 --- a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs @@ -214,7 +214,7 @@ public class TypeLoader { if (t.FullName.Contains("Mini.")) { - Console.WriteLine(inheritsFromType.FullName + " ? inherits ? " + t.FullName); + // Console.WriteLine(inheritsFromType.FullName + " ? inherits ? " + t.FullName); } if (t.IsSubclassOf(inheritsFromType)) { From 833c378b1d3c00f419580318d585c8d83e7d9da2 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Thu, 1 Aug 2024 22:53:27 -0400 Subject: [PATCH 10/16] make ContainsAny even more generic --- .../src/lib/MBS.Core/Collections/ExtensionMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Collections/ExtensionMethods.cs b/framework-dotnet/src/lib/MBS.Core/Collections/ExtensionMethods.cs index fdd29c9..e76176e 100755 --- a/framework-dotnet/src/lib/MBS.Core/Collections/ExtensionMethods.cs +++ b/framework-dotnet/src/lib/MBS.Core/Collections/ExtensionMethods.cs @@ -27,7 +27,7 @@ namespace MBS.Core.Collections { public static class ExtensionMethods { - public static bool ContainsAny(this IEnumerable enumerable, T[] item) + public static bool ContainsAny(this IEnumerable enumerable, IEnumerable item) { foreach (object item1 in enumerable) { From 7a884cfd214b4312304bb8c32e75102550740645 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 4 Aug 2024 14:26:01 -0400 Subject: [PATCH 11/16] remove duplicate call to OnBeforeStartInternal --- framework-dotnet/src/lib/MBS.Core/Application.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 255002d..6773ebe 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -119,11 +119,6 @@ public class Application protected virtual int StartInternal() { - CancelEventArgs ce = new CancelEventArgs(); - OnBeforeStartInternal(ce); - if (ce.Cancel) - return 2; - CommandLine cline = new CommandLine(); string[] args = CommandLine.Arguments; From a5028581a029b57994333f7c90549d1b1da77dd1 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Fri, 9 Aug 2024 22:21:52 -0400 Subject: [PATCH 12/16] import more stuff from MBS.Framework --- .../src/lib/MBS.Core/Application.cs | 9 + framework-dotnet/src/lib/MBS.Core/Command.cs | 149 ++++++++++++++ .../src/lib/MBS.Core/CommandEventArgs.cs | 50 +++++ .../src/lib/MBS.Core/CommandItem.cs | 93 +++++++++ framework-dotnet/src/lib/MBS.Core/Context.cs | 115 +++++++++++ .../src/lib/MBS.Core/Extensibility/Feature.cs | 61 ++++++ .../src/lib/MBS.Core/Extensibility/Plugin.cs | 175 ++++++++++++++++ .../Extensibility/PluginPropertyBag.cs | 49 +++++ .../lib/MBS.Core/Extensibility/PropertyBag.cs | 187 ++++++++++++++++++ .../lib/MBS.Core/IO/DirectoryExtensions.cs | 35 ++++ .../src/lib/MBS.Core/StockType.cs | 137 +++++++++++++ 11 files changed, 1060 insertions(+) create mode 100644 framework-dotnet/src/lib/MBS.Core/Command.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/CommandEventArgs.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/CommandItem.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Context.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Extensibility/Feature.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Extensibility/PluginPropertyBag.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/Extensibility/PropertyBag.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/IO/DirectoryExtensions.cs create mode 100644 framework-dotnet/src/lib/MBS.Core/StockType.cs diff --git a/framework-dotnet/src/lib/MBS.Core/Application.cs b/framework-dotnet/src/lib/MBS.Core/Application.cs index 6773ebe..85822ed 100644 --- a/framework-dotnet/src/lib/MBS.Core/Application.cs +++ b/framework-dotnet/src/lib/MBS.Core/Application.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using MBS.Core.Extensibility; namespace MBS.Core; @@ -492,4 +493,12 @@ public class Application return true; } + internal void _EnableDisableCommand(Command command, bool enable) + { + } + + protected internal virtual Plugin[] GetAdditionalPlugins() + { + return new Plugin[0]; + } } diff --git a/framework-dotnet/src/lib/MBS.Core/Command.cs b/framework-dotnet/src/lib/MBS.Core/Command.cs new file mode 100644 index 0000000..c838887 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Command.cs @@ -0,0 +1,149 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core; + +public class Command +{ + public class CommandCollection + : System.Collections.ObjectModel.Collection + { + public Command this[string ID] + { + get + { + foreach (Command command in this) + { + if (command.ID == ID) return command; + } + return null; + } + } + } + + public Command() + { + } + public Command(string id, string title, CommandItem[] items = null) + { + ID = id; + Title = title; + if (items != null) + { + for (int i = 0; i < items.Length; i++) + { + Items.Add(items[i]); + } + } + } + /// + /// Determines whether this command displays as checked. + /// + public bool Checked { get; set; } = false; + /// + /// The ID of the command, used to reference it in . + /// + public string ID { get; set; } = String.Empty; + /// + /// The title of the command (including mnemonic prefix, if applicable). + /// + public string Title { get; set; } = String.Empty; + + private string mvarDefaultCommandID = String.Empty; + public string DefaultCommandID { get { return mvarDefaultCommandID; } set { mvarDefaultCommandID = value; } } + + /// + /// A that represents a predefined, platform-themed command. + /// + public StockType StockType { get; set; } = StockType.None; + + private string mvarImageFileName = String.Empty; + /// + /// The file name of the image to be displayed on the command. + /// + public string ImageFileName { get { return mvarImageFileName; } set { mvarImageFileName = value; } } + + + /// + /// The child s that are contained within this . + /// + public CommandItem.CommandItemCollection Items { get; } = new CommandItem.CommandItemCollection(); + + /// + /// The event that is fired when the command is executed. + /// + public event EventHandler Executed; + + /// + /// Determines whether this is enabled in all s and s + /// that reference it. + /// + /// true if visible; otherwise, false. + private bool _Enabled = true; + public bool Enabled { get { return _Enabled; } set { _Enabled = value; Application.Instance._EnableDisableCommand(this, value); } } + + /// + /// Determines whether this is visible in all s and s + /// that reference it. + /// + /// true if visible; otherwise, false. + public bool Visible { get; set; } + + /// + /// Executes this . + /// + [Obsolete("Please use Application.ExecuteCommand. Command.Execute does not always work and will be removed in a future release.")] + public void Execute() + { + if (Executed != null) Executed(this, EventArgs.Empty); + } + + public override string ToString() + { + return String.Format("{0} [{1}]", ID, Title); + } + + private Dictionary _extraData = new Dictionary(); + public T GetExtraData(string key, T defaultValue = default(T)) + { + if (_extraData.ContainsKey(key)) + { + if (_extraData[key] is T) + { + return (T)_extraData[key]; + } + } + return defaultValue; + } + + public void SetExtraData(string key, T value) + { + _extraData[key] = value; + } + + public object GetExtraData(string key, object defaultValue = null) + { + if (_extraData.ContainsKey(key)) + return _extraData[key]; + return defaultValue; + } + + public void SetExtraData(string key, object value) + { + _extraData[key] = value; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/CommandEventArgs.cs b/framework-dotnet/src/lib/MBS.Core/CommandEventArgs.cs new file mode 100644 index 0000000..a1c1b27 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CommandEventArgs.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core; + +public class CommandEventArgs : EventArgs +{ + public Command Command { get; private set; } + + private Dictionary _NamedParameters = new Dictionary(); + public T GetNamedParameter(string key, T defaultValue = default(T)) + { + if (_NamedParameters.ContainsKey(key)) + return (T)_NamedParameters[key]; + return defaultValue; + } + public object GetNamedParameter(string key, object defaultValue = null) + { + if (_NamedParameters.ContainsKey(key)) + return _NamedParameters[key]; + return defaultValue; + } + + + public CommandEventArgs(Command command, KeyValuePair[] namedParameters = null) + { + Command = command; + if (namedParameters != null) + { + for (int i = 0; i < namedParameters.Length; i++) + { + _NamedParameters[namedParameters[i].Key] = namedParameters[i].Value; + } + } + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/CommandItem.cs b/framework-dotnet/src/lib/MBS.Core/CommandItem.cs new file mode 100644 index 0000000..a259a2f --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CommandItem.cs @@ -0,0 +1,93 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET 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. +// +// Mocha.NET 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 Mocha.NET. If not, see . + +namespace MBS.Core; + +public abstract class CommandItem +{ + public string InsertAfterID { get; set; } = null; + public string InsertBeforeID { get; set; } = null; + + public class CommandItemCollection + : System.Collections.ObjectModel.Collection + { + public int IndexOf(string value) + { + for (int i = 0; i < Count; i++) + { + if (this[i] is CommandReferenceCommandItem) + { + if ((this[i] as CommandReferenceCommandItem).CommandID.Equals(value)) + return i; + } + } + return -1; + } + } +} +public class ActionCommandItem : CommandItem +{ + public string ID { get; } + public string Title { get; } + + public event EventHandler Executed; + + public void Execute() + { + Executed?.Invoke(this, EventArgs.Empty); + } + + public ActionCommandItem(string id, string title, EventHandler execute = null) + { + ID = id; + Title = title; + if (execute != null) + { + Executed += execute; + } + } +} +public class CommandReferenceCommandItem : CommandItem +{ + private string mvarCommandID = String.Empty; + public string CommandID { get { return mvarCommandID; } set { mvarCommandID = value; } } + + public CommandReferenceCommandItem() + { + } + public CommandReferenceCommandItem(string commandID) + { + mvarCommandID = commandID; + } +} +public class CommandPlaceholderCommandItem : CommandItem +{ + private string mvarPlaceholderID = String.Empty; + public string PlaceholderID { get { return mvarPlaceholderID; } set { mvarPlaceholderID = value; } } + + public CommandPlaceholderCommandItem(string placeholderID) + { + mvarPlaceholderID = placeholderID; + } +} +public class SeparatorCommandItem : CommandItem +{ +} +public class GroupCommandItem : CommandItem +{ + public CommandItemCollection Items { get; } = new CommandItemCollection(); +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Context.cs b/framework-dotnet/src/lib/MBS.Core/Context.cs new file mode 100644 index 0000000..4394f02 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Context.cs @@ -0,0 +1,115 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET 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. +// +// Mocha.NET 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 Mocha.NET. If not, see . + +namespace MBS.Core; + +public class Context +{ + public class ContextCollection + : System.Collections.ObjectModel.Collection + { + private Dictionary _ItemsByID = new Dictionary(); + + public bool Contains(Guid contextID) + { + return _ItemsByID.ContainsKey(contextID); + } + + public Context this[Guid id] + { + get + { + if (_ItemsByID.ContainsKey(id)) + return _ItemsByID[id]; + return null; + } + } + + protected override void ClearItems() + { + base.ClearItems(); + _ItemsByID.Clear(); + } + protected override void InsertItem(int index, Context item) + { + base.InsertItem(index, item); + _ItemsByID[item.ID] = item; + } + protected override void RemoveItem(int index) + { + _ItemsByID.Remove(this[index].ID); + base.RemoveItem(index); + } + } + + public Guid ID { get; private set; } = Guid.Empty; + public string Name { get; private set; } = String.Empty; + + // public MenuBar MenuBar { get; } = new MenuBar(); + public Command.CommandCollection Commands { get; } = new Command.CommandCollection(); + + public Context(Guid id, string name) + { + ID = id; + Name = name; + } + + public override string ToString() + { + return String.Format("{0} {1}", Name, ID); + } + + private Dictionary> _CommandEventHandlers = new Dictionary>(); + /// + /// Attachs an event handler for the command with the ID specified by . + /// + /// true, if command event handler was attached, false otherwise. + /// The ID of the for which to attach an event handler. + /// The event handler to invoke when the is executed. + public bool AttachCommandEventHandler(string commandID, EventHandler handler) + { + // handle command event handlers attached without a Command instance + if (!_CommandEventHandlers.ContainsKey(commandID)) + { + _CommandEventHandlers.Add(commandID, new List()); + } + if (!_CommandEventHandlers[commandID].Contains(handler)) + { + _CommandEventHandlers[commandID].Add(handler); + return true; + } + return false; + } + /// + /// Executes the command (i.e., calls all attached s for the ) with + /// the given , passing in the given parameters in an instance of . + /// + /// true, if command was executed, false otherwise. + /// Command identifier. + /// Named parameters. + public bool ExecuteCommand(string commandID, KeyValuePair[] namedParameters = null) + { + if (_CommandEventHandlers.ContainsKey(commandID)) + { + for (int i = 0; i < _CommandEventHandlers[commandID].Count; i++) + { + _CommandEventHandlers[commandID][i](this, new CommandEventArgs(Commands[commandID], namedParameters)); + } + } + return false; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/Feature.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/Feature.cs new file mode 100644 index 0000000..50c802b --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/Feature.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core.Extensibility; + +public class Feature +{ + public class FeatureCollection + : System.Collections.ObjectModel.Collection + { + } + + public Guid ID { get; private set; } = Guid.Empty; + public string Title { get; private set; } = null; + + public Feature(Guid id, string title) + { + ID = id; + Title = title; + } + + public override bool Equals(object obj) + { + if (!(this is null) && (obj is null)) return false; + if (this is null && !(obj is null)) return false; + + if (obj is Feature) + { + Feature feat = (obj as Feature); + return feat.ID.Equals(ID); + } + return base.Equals(obj); + } + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static bool operator ==(Feature left, Feature right) + { + return (left.ID == right.ID); + } + public static bool operator !=(Feature left, Feature right) + { + return (left.ID != right.ID); + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs new file mode 100644 index 0000000..c82f5cb --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs @@ -0,0 +1,175 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core.Extensibility; + +public class Plugin +{ + public Context Context { get; protected set; } + public virtual string Title { get; set; } = null; + public Feature.FeatureCollection ProvidedFeatures { get; } = new Feature.FeatureCollection(); + + /// + /// Gets a containing the plugin-specific + /// settings for this . + /// + /// The settings. + public PropertyBag Settings { get; } + + public Plugin() + { + Settings = new PluginPropertyBag(this); + } + + public bool Initialized { get; private set; } = false; + public void Initialize() + { + if (Initialized) + return; + + InitializeInternal(); + + Settings.Initialize(); + Initialized = true; + } + + public Guid ID { get; set; } = Guid.Empty; + + protected virtual void InitializeInternal() + { + // this method intentionally left blank + } + + protected virtual bool AutoRegister => true; + + protected virtual bool IsSupportedInternal() + { + return true; + } + public bool IsSupported() + { + return IsSupportedInternal(); + } + private static Plugin[] _plugins = null; + public static TPlugin[] Get(bool resetCache = false) where TPlugin : Plugin + { + Plugin[] plugins = Get(resetCache); + return plugins.OfType().ToArray(); + } + public static Plugin[] Get(bool resetCache = false) + { + if (resetCache) + { + _plugins = null; // should not be cached? // actually, yes it should... + + // 2020-12-12 20:54 by beckermj + // ACTUALLY, it depends on whether the configuration needs to be persisted across calls to Get() + // [it does] and whether the list of plugins needs to be reloaded when CustomPlugins is modified + // [it shouldn't, but it does] . + // + // The safest way we can handle this RIGHT NOW is to prevent any plugin from being loaded until after CustomPlugins + // is initialized by UIApplication. + // + // We call Plugins.Get(new Feature[] { KnownFeatures.UWTPlatform }) to retrieve the available User Interface plugins + // that supply the UWT Platform implementation (e.g. GTK, Windows Forms, etc.) - and this causes CustomPlugins to not + // load properly since it loads AFTER this initial call to Plugins.Get(). + // + // So I add ed a resetCache parameter that when specified TRUE will clear the cache and then return the appropriate + // plugin. This way we can continue caching future calls to Get() without missing out on CustomPlugins. + } + + if (_plugins == null) + { + Type[] types = Reflection.TypeLoader.GetAvailableTypes(new Type[] { typeof(Plugin) }); + System.Collections.Generic.List plugins = new System.Collections.Generic.List(); + for (int i = 0; i < types.Length; i++) + { + try + { + /* + if (types[i].IsSubclassOf(typeof(ICustomPlugin))) + { + continue; + } + */ + + Plugin plg = (Plugin)types[i].Assembly.CreateInstance(types[i].FullName); + plugins.Add(plg); + } + catch (Exception ex) + { + } + } + + Plugin[] plugins2 = Application.Instance.GetAdditionalPlugins(); + for (int i = 0; i < plugins2.Length; i++) + { + plugins.Add(plugins2[i]); + } + _plugins = plugins.ToArray(); + + if (resetCache) + { + _plugins = null; + return plugins.ToArray(); + } + } + return _plugins; + } + + public static TPlugin[] Get(Feature[] providedFeatures, bool resetCache = false) where TPlugin : Plugin + { + Plugin[] plugins = Get(providedFeatures, resetCache); + return plugins.OfType().ToArray(); + } + public static Plugin[] Get(Feature[] providedFeatures, bool resetCache = false) + { + System.Collections.Generic.List list = new System.Collections.Generic.List(); + Plugin[] plugins = Get(resetCache); + for (int i = 0; i < plugins.Length; i++) + { + if (!plugins[i].AutoRegister) + continue; + + if (!plugins[i].IsSupported()) + continue; + + for (int j = 0; j < providedFeatures.Length; j++) + { + if (plugins[i].ProvidedFeatures.Contains(providedFeatures[j])) + list.Add(plugins[i]); + } + } + return list.ToArray(); + } + + public static TPlugin Get(Guid id) where TPlugin : Plugin + { + Plugin plugin = Get(id); + return (TPlugin)plugin; + } + public static Plugin Get(Guid id) + { + Plugin[] plugins = Get(); + for (int i = 0; i < plugins.Length; i++) + { + if (plugins[i].ID == id) + return plugins[i]; + } + return null; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/PluginPropertyBag.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/PluginPropertyBag.cs new file mode 100644 index 0000000..c4a8b11 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/PluginPropertyBag.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core.Extensibility; + + +public class PluginPropertyBag : PropertyBag +{ + private Plugin Plugin { get; } + + protected override void OnInitialized(EventArgs e) + { + base.OnInitialized(e); + + string[] paths = Application.Instance.EnumerateDataPaths(); + foreach (string datapath in paths) + { + string path = String.Format("{0}/plugins/{1}/config.xml", datapath, this.Plugin.ID.ToString("b")); + if (System.IO.File.Exists(path)) + { + Console.WriteLine("found config in {0}", path); + } + } + } + + internal PluginPropertyBag(Plugin plugin) + { + Plugin = plugin; + } + + protected override void OnPropertyValueRequested(PropertyValueRequestedEventArgs e) + { + base.OnPropertyValueRequested(e); + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/PropertyBag.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/PropertyBag.cs new file mode 100644 index 0000000..db7ae3e --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/PropertyBag.cs @@ -0,0 +1,187 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core.Extensibility; + +public class PropertyValueRequestedEventArgs : EventArgs +{ + public Guid ID { get; } + public object Value { get; set; } + + public bool Cache { get; set; } = true; + public bool Handled { get; set; } = false; + + public PropertyValueRequestedEventArgs(Guid id, object value) + { + ID = id; + Value = value; + } +} +public class PropertyValueRequestedEventArgs : PropertyValueRequestedEventArgs +{ + public new T Value { get { return (T)base.Value; } set { base.Value = value; } } + + public PropertyValueRequestedEventArgs(Guid id, T value) : base(id, value) + { + } +} + +public class PropertyValueChangingEventArgs : System.ComponentModel.CancelEventArgs +{ + public Guid ID { get; } + public object OldValue { get; } + public object NewValue { get; set; } + + public PropertyValueChangingEventArgs(Guid id, object oldValue, object newValue) + { + ID = id; + OldValue = oldValue; + NewValue = newValue; + } +} +public class PropertyValueChangedEventArgs : EventArgs +{ + public Guid ID { get; } + public object OldValue { get; } + public object NewValue { get; } + + public PropertyValueChangedEventArgs(Guid id, object oldValue, object newValue) + { + ID = id; + OldValue = oldValue; + NewValue = newValue; + } +} + +public class PropertyBag +{ + private Dictionary _settings = new Dictionary(); + private Dictionary _names = new Dictionary(); + public string GetName(Guid id) + { + if (_names.ContainsKey(id)) + return _names[id]; + return null; + } + public void SetName(Guid id, string name) + { + _names[id] = name; + } + + private Dictionary _PropertyValues = new Dictionary(); + public bool Contains(Guid id) + { + return _PropertyValues.ContainsKey(id); + } + public bool SetValue(Guid id, T value) + { + bool changed = false; + lock (_PropertyValues) + { + object oldValue = null; + if (!_PropertyValues.ContainsKey(id) || (!( + (_PropertyValues[id] == null && (value as object) == null) || + (_PropertyValues[id] != null && _PropertyValues[id].Equals(value))))) + { + changed = true; + } + if (_PropertyValues.ContainsKey(id)) + { + oldValue = _PropertyValues[id]; + } + + if (changed) + { + PropertyValueChangingEventArgs e = new PropertyValueChangingEventArgs(id, oldValue, value); + OnPropertyValueChanging(e); + if (e.Cancel) + { + return false; + } + } + _PropertyValues[id] = value; + if (changed) + { + OnPropertyValueChanged(new PropertyValueChangedEventArgs(id, oldValue, value)); + } + } + return changed; + } + + public event EventHandler PropertyValueChanging; + protected virtual void OnPropertyValueChanging(PropertyValueChangingEventArgs e) + { + PropertyValueChanging?.Invoke(this, e); + } + public event EventHandler PropertyValueChanged; + protected virtual void OnPropertyValueChanged(PropertyValueChangedEventArgs e) + { + PropertyValueChanged?.Invoke(this, e); + } + + public T GetValue(Guid id, T defaultValue = default(T)) + { + lock (_PropertyValues) + { + if (_PropertyValues.ContainsKey(id)) + { + if (_PropertyValues[id] is T val) + { + return val; + } + } + else + { + PropertyValueRequestedEventArgs e = new PropertyValueRequestedEventArgs(id, defaultValue); + OnPropertyValueRequested(e); + if (e.Handled) + { + if (e.Cache) + { + _PropertyValues[id] = e.Value; + } + return e.Value; + } + } + } + return defaultValue; + } + + public event EventHandler PropertyValueRequested; + protected virtual void OnPropertyValueRequested(PropertyValueRequestedEventArgs e) + { + PropertyValueRequested?.Invoke(this, e); + } + + public IEnumerable> GetAll() + { + return _PropertyValues; + } + + public bool Initialized { get; private set; } = false; + protected virtual void OnInitialized(EventArgs e) + { + } + public void Initialize() + { + if (Initialized) + return; + + OnInitialized(EventArgs.Empty); + Initialized = true; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/IO/DirectoryExtensions.cs b/framework-dotnet/src/lib/MBS.Core/IO/DirectoryExtensions.cs new file mode 100644 index 0000000..fdc496b --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/IO/DirectoryExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of Mocha.NET. +// +// Mocha.NET 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. +// +// Mocha.NET 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 Mocha.NET. If not, see . + +namespace MBS.Core.IO; + +public static class DirectoryExtensions +{ + public static string[] GetFiles(string path, string[] searchPatterns, SearchOption options) + { + string[] result = new string[0]; + int j = 0; + foreach (string ext in searchPatterns) + { + string[] filenames = Directory.GetFiles(path, ext, options); + Array.Resize(ref result, result.Length + filenames.Length); + Array.Copy(filenames, 0, result, j, filenames.Length); + j += filenames.Length; + } + return result; + } +} \ No newline at end of file diff --git a/framework-dotnet/src/lib/MBS.Core/StockType.cs b/framework-dotnet/src/lib/MBS.Core/StockType.cs new file mode 100644 index 0000000..9f36272 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/StockType.cs @@ -0,0 +1,137 @@ +// Copyright (C) 2024 Michael Becker +// +// This file is part of MBS Framework for .NET Core. +// +// MBS Framework for .NET Core 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. +// +// MBS Framework for .NET Core 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 MBS Framework for .NET Core. If not, see . + +namespace MBS.Core; + +public enum StockType +{ + None, + About, + Add, + Apply, + + // these four do not have gtk-stock equivalents + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + + Bold, + Bookmarks, + Cancel, + CapsLockWarning, // not for buttons + CDROM, + Clear, + Close, + ColorPicker, // not for buttons + Connect, + Convert, + Copy, + Cut, + Delete, + DialogAuthentication, // not for buttons + DialogInfo, + DialogWarning, + DialogError, + DialogQuestion, + Directory, // not for buttons + Discard, + Disconnect, + DragAndDrop, // not for buttons + DragAndDropMultiple, // not for buttons + Edit, + Execute, + File, + Find, + FindAndReplace, + Floppy, + Folder, + Fullscreen, + GotoBottom, + GotoFirst, + GotoLast, + GotoTop, + GoBack, + GoDown, + GoForward, + GoUp, + HardDisk, + Help, + Home, + Index, + Indent, + Info, + Italic, + JumpTo, + JustifyCenter, + JustifyFill, + JustifyLeft, + JustifyRight, + LeaveFullscreen, + MissingImage, // not for buttons + MediaForward, + MediaNext, + MediaPause, + MediaPlay, + MediaPrevious, + MediaRecord, + MediaRewind, + MediaStop, + Network, + New, + No, + OK, + Open, + OrientationPortrait, + OrientationLandscape, + OrientationReverseLandscape, + OrientationReversePortrait, + PageSetup, + Paste, + Preferences, + Print, + PrintError, // not for buttons + PrintPaused, // not for buttons + PrintPreview, + PrintReport, // not for buttons + PrintWarning, // not for buttons + Properties, + Quit, + Redo, + Refresh, + Remove, + RevertToSaved, + Save, + SaveAs, + SelectAll, + SelectColor, + SelectFont, + SortAscending, + SortDescending, + SpellCheck, + Stop, + Strikethrough, + Undelete, + Underline, + Undo, + Unindent, + Yes, + Zoom100, + ZoomFit, + ZoomIn, + ZoomOut +} \ No newline at end of file From d6287b07f360678f5c2a75f1dc4ab4515a4ce8d9 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Mon, 19 Aug 2024 15:33:10 -0400 Subject: [PATCH 13/16] import CardinalDirection from MBS.Framework --- .../src/lib/MBS.Core/CardinalDirection.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 framework-dotnet/src/lib/MBS.Core/CardinalDirection.cs diff --git a/framework-dotnet/src/lib/MBS.Core/CardinalDirection.cs b/framework-dotnet/src/lib/MBS.Core/CardinalDirection.cs new file mode 100755 index 0000000..ed24932 --- /dev/null +++ b/framework-dotnet/src/lib/MBS.Core/CardinalDirection.cs @@ -0,0 +1,31 @@ +// +// CardinalDirection.cs +// +// Author: +// Michael Becker +// +// Copyright (c) 2021 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.Core +{ + public enum CardinalDirection + { + Top, + Left, + Bottom, + Right + } +} From 83b68d16fa170d6b505b2db8b811d9274159b8ca Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 25 Aug 2024 09:46:42 -0400 Subject: [PATCH 14/16] ignore types which are abstract --- framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs index c82f5cb..43f58bb 100644 --- a/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs @@ -98,6 +98,9 @@ public class Plugin System.Collections.Generic.List plugins = new System.Collections.Generic.List(); for (int i = 0; i < types.Length; i++) { + if (types[i].IsAbstract) + continue; + try { /* From 466ce7e5b20c0c2cc47ed4dcb948994cdb74679e Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sun, 25 Aug 2024 14:12:10 -0400 Subject: [PATCH 15/16] don't crash if we don't have an Application.Instance; like if we're loading plugins outside of MBS Core framework --- .../src/lib/MBS.Core/Extensibility/Plugin.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs index 43f58bb..1bc062b 100644 --- a/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs +++ b/framework-dotnet/src/lib/MBS.Core/Extensibility/Plugin.cs @@ -118,10 +118,13 @@ public class Plugin } } - Plugin[] plugins2 = Application.Instance.GetAdditionalPlugins(); - for (int i = 0; i < plugins2.Length; i++) + if (Application.Instance != null) { - plugins.Add(plugins2[i]); + Plugin[] plugins2 = Application.Instance.GetAdditionalPlugins(); + for (int i = 0; i < plugins2.Length; i++) + { + plugins.Add(plugins2[i]); + } } _plugins = plugins.ToArray(); From 93f8790653a013ad7f6e822056b4cb40ccd5fa53 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Tue, 19 Nov 2024 22:36:37 -0500 Subject: [PATCH 16/16] updates --- .../MBS.Core/Collections/Generic/ExtensionMethods.cs | 7 +++++++ .../src/lib/MBS.Core/Reflection/TypeLoader.cs | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs index 0b58403..fbe1f43 100755 --- a/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs +++ b/framework-dotnet/src/lib/MBS.Core/Collections/Generic/ExtensionMethods.cs @@ -62,6 +62,13 @@ namespace MBS.Core.Collections.Generic list.Add(item); } } + public static void AddRange(this ICollection> list, IEnumerable> items) + { + foreach (KeyValuePair kvp in items) + { + list.Add(kvp); + } + } public static T[] ToNullTerminatedArray(this IEnumerable enumerable) where T : class { diff --git a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs index 4aa052e..acdfefa 100644 --- a/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs +++ b/framework-dotnet/src/lib/MBS.Core/Reflection/TypeLoader.cs @@ -129,12 +129,12 @@ public class TypeLoader } private static Type[] mvarAvailableTypes = null; - public static T[] GetAvailableTypes(Assembly[] additionalAssemblies = null) where T : class + public static T[] GetAvailableTypes(Assembly[] additionalAssemblies = null, bool resetCache = false) where T : class { Type[] ts = null; try { - ts = GetAvailableTypes(new Type[] { typeof(T) }, additionalAssemblies); + ts = GetAvailableTypes(new Type[] { typeof(T) }, additionalAssemblies, resetCache); } catch (ReflectionTypeLoadException ex) { @@ -156,8 +156,12 @@ public class TypeLoader } return list.ToArray(); } - public static Type[] GetAvailableTypes(Type[] inheritsFrom = null, Assembly[] additionalAssemblies = null) + public static Type[] GetAvailableTypes(Type[] inheritsFrom = null, Assembly[] additionalAssemblies = null, bool resetCache = false) { + if (resetCache) + { + mvarAvailableTypes = null; + } if (mvarAvailableTypes == null) { List types = new List(); @@ -210,6 +214,7 @@ public class TypeLoader List retval = new List(); foreach (Type t in mvarAvailableTypes) { + string typeFullName = t.FullName; foreach (Type inheritsFromType in inheritsFrom) { if (t.FullName.Contains("Mini."))