diff --git a/PKHeX.WinForms/MainWindow/PluginLoader.cs b/PKHeX.WinForms/MainWindow/PluginLoader.cs index f899fc49b..d40d531f8 100644 --- a/PKHeX.WinForms/MainWindow/PluginLoader.cs +++ b/PKHeX.WinForms/MainWindow/PluginLoader.cs @@ -1,101 +1,104 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; +using static PKHeX.WinForms.PluginLoadSetting; -namespace PKHeX.WinForms +namespace PKHeX.WinForms; + +public static class PluginLoader { - public static class PluginLoader + public static IEnumerable LoadPlugins(string pluginPath, PluginLoadSetting loadSetting) where T : class { - public static IEnumerable LoadPlugins(string pluginPath, PluginLoadSetting loadSetting) where T : class - { - var dllFileNames = !Directory.Exists(pluginPath) - ? Enumerable.Empty() - : Directory.EnumerateFiles(pluginPath, "*.dll", SearchOption.AllDirectories); - var assemblies = GetAssemblies(dllFileNames, loadSetting); - var pluginTypes = GetPluginsOfType(assemblies); - return LoadPlugins(pluginTypes); - } + var dllFileNames = !Directory.Exists(pluginPath) + ? Array.Empty() // Don't immediately return, as we may be loading plugins merged with this .exe + : Directory.EnumerateFiles(pluginPath, "*.dll", SearchOption.AllDirectories); + var assemblies = GetAssemblies(dllFileNames, loadSetting); + var pluginTypes = GetPluginsOfType(assemblies); + return LoadPlugins(pluginTypes); + } - private static IEnumerable LoadPlugins(IEnumerable pluginTypes) where T : class + private static IEnumerable LoadPlugins(IEnumerable pluginTypes) where T : class + { + foreach (var t in pluginTypes) { - foreach (var t in pluginTypes) - { - T? activate; - try { activate = (T?)Activator.CreateInstance(t); } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"Unable to load plugin [{t.Name}]: {t.FullName}"); - System.Diagnostics.Debug.WriteLine(ex.Message); - continue; - } - if (activate != null) - yield return activate; - } - } - - private static IEnumerable GetAssemblies(IEnumerable dllFileNames, PluginLoadSetting loadSetting) - { - var assemblies = dllFileNames.Select(GetPluginLoadMethod(loadSetting)); - if (loadSetting is PluginLoadSetting.LoadFromMerged or PluginLoadSetting.LoadFileMerged or PluginLoadSetting.UnsafeMerged) - assemblies = assemblies.Concat(new[] { Assembly.GetExecutingAssembly() }); // load merged too - return assemblies; - } - - private static Func GetPluginLoadMethod(PluginLoadSetting pls) => pls switch - { - PluginLoadSetting.LoadFrom or PluginLoadSetting.LoadFromMerged => Assembly.LoadFrom, - PluginLoadSetting.LoadFile or PluginLoadSetting.LoadFileMerged => Assembly.LoadFile, - PluginLoadSetting.UnsafeLoadFrom or PluginLoadSetting.UnsafeMerged => Assembly.UnsafeLoadFrom, - _ => throw new NotImplementedException($"PluginLoadSetting: {pls} method not defined."), - }; - - private static IEnumerable GetPluginsOfType(IEnumerable assemblies) - { - var pluginType = typeof(T); - return assemblies.SelectMany(z => GetPluginTypes(z, pluginType)); - } - - private static IEnumerable GetPluginTypes(Assembly z, Type pluginType) - { - try - { - // Handle Costura merged plugin dll's; need to Attach for them to correctly retrieve their dependencies. - var assemblyLoaderType = z.GetType("Costura.AssemblyLoader", false); - var attachMethod = assemblyLoaderType?.GetMethod("Attach", BindingFlags.Static | BindingFlags.Public); - attachMethod?.Invoke(null, Array.Empty()); - - var types = z.GetTypes(); - return types.Where(type => IsTypePlugin(type, pluginType)); - } - // User plugins can be out of date, with mismatching API surfaces. + T? activate; + try { activate = (T?)Activator.CreateInstance(t); } catch (Exception ex) { - System.Diagnostics.Debug.WriteLine($"Unable to load plugin [{pluginType.Name}]: {z.FullName}"); - System.Diagnostics.Debug.WriteLine(ex.Message); - if (ex is ReflectionTypeLoadException rtle) - { - foreach (var le in rtle.LoaderExceptions) - { - if (le is not null) - System.Diagnostics.Debug.WriteLine(le.Message); - } - } - return Array.Empty(); + Debug.WriteLine($"Unable to load plugin [{t.Name}]: {t.FullName}"); + Debug.WriteLine(ex.Message); + continue; } - } - - private static bool IsTypePlugin(Type type, Type pluginType) - { - if (type.IsInterface || type.IsAbstract) - return false; - var name = pluginType.FullName; - if (name == null) - return false; - if (type.GetInterface(name) == null) - return false; - return true; + if (activate != null) + yield return activate; } } + + private static IEnumerable GetAssemblies(IEnumerable dllFileNames, PluginLoadSetting loadSetting) + { + var loadMethod = GetPluginLoadMethod(loadSetting); + var assemblies = dllFileNames.Select(loadMethod); + if (loadSetting.IsMerged()) + assemblies = assemblies.Concat(new[] { Assembly.GetExecutingAssembly() }); // load merged too + return assemblies; + } + + private static Func GetPluginLoadMethod(PluginLoadSetting pls) => pls switch + { + LoadFrom or LoadFromMerged => Assembly.LoadFrom, + LoadFile or LoadFileMerged => Assembly.LoadFile, + UnsafeLoadFrom or UnsafeMerged => Assembly.UnsafeLoadFrom, + _ => throw new IndexOutOfRangeException($"PluginLoadSetting: {pls} method not defined."), + }; + + public static bool IsMerged(this PluginLoadSetting loadSetting) => loadSetting is LoadFromMerged or LoadFileMerged or UnsafeMerged; + + private static IEnumerable GetPluginsOfType(IEnumerable assemblies) + { + var interfaceTypeName = typeof(T).FullName; + if (interfaceTypeName is null) + return Array.Empty(); + return assemblies.SelectMany(z => GetPluginTypes(z, interfaceTypeName)); + } + + private static IEnumerable GetPluginTypes(Assembly z, string interfaceTypeName) + { + try + { + // Handle Costura merged plugin dll's; need to Attach for them to correctly retrieve their dependencies. + var assemblyLoaderType = z.GetType("Costura.AssemblyLoader", false); + var attachMethod = assemblyLoaderType?.GetMethod("Attach", BindingFlags.Static | BindingFlags.Public); + attachMethod?.Invoke(null, Array.Empty()); + + var types = z.GetTypes(); + return types.Where(type => IsTypePlugin(type, interfaceTypeName)); + } + // User plugins can be out of date, with mismatching API surfaces. + catch (Exception ex) + { + Debug.WriteLine($"Unable to load plugin [{interfaceTypeName}]: {z.FullName}"); + Debug.WriteLine(ex.Message); + if (ex is not ReflectionTypeLoadException rtle) + return Array.Empty(); + + foreach (var le in rtle.LoaderExceptions) + { + if (le is not null) + Debug.WriteLine(le.Message); + } + return Array.Empty(); + } + } + + private static bool IsTypePlugin(Type type, string interfaceTypeName) + { + if (type.IsInterface || type.IsAbstract) + return false; + if (type.GetInterface(interfaceTypeName) == null) + return false; + return true; + } } diff --git a/PKHeX.WinForms/Subforms/ReportGrid.cs b/PKHeX.WinForms/Subforms/ReportGrid.cs index 2bdbdf15c..9d631b5c2 100644 --- a/PKHeX.WinForms/Subforms/ReportGrid.cs +++ b/PKHeX.WinForms/Subforms/ReportGrid.cs @@ -124,7 +124,7 @@ namespace PKHeX.WinForms var headers = dgData.Columns.Cast(); await s.WriteLineAsync(string.Join(",", headers.Skip(1).Select(column => $"\"{column.HeaderText}\""))).ConfigureAwait(false); - foreach (var cells in from DataGridViewRow row in dgData.Rows select row.Cells.Cast()) + foreach (var cells in dgData.Rows.Cast().Select(row => row.Cells.Cast())) await s.WriteLineAsync(string.Join(",", cells.Skip(1).Select(cell => $"\"{cell.Value}\""))).ConfigureAwait(false); } diff --git a/PKHeX.WinForms/Util/DevUtil.cs b/PKHeX.WinForms/Util/DevUtil.cs index cfed19aec..aa78f11e9 100644 --- a/PKHeX.WinForms/Util/DevUtil.cs +++ b/PKHeX.WinForms/Util/DevUtil.cs @@ -46,17 +46,19 @@ namespace PKHeX.WinForms private static void UpdateTranslations() { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + var types = assembly.GetTypes(); // add mode WinFormsTranslator.SetRemovalMode(false); WinFormsTranslator.LoadSettings(DefaultLanguage); - WinFormsTranslator.LoadAllForms(LoadBanlist); // populate with every possible control + WinFormsTranslator.LoadAllForms(types, LoadBanlist); // populate with every possible control WinFormsTranslator.UpdateAll(DefaultLanguage, Languages); // propagate to others WinFormsTranslator.DumpAll(Banlist); // dump current to file // de-populate WinFormsTranslator.SetRemovalMode(); // remove used keys, don't add any WinFormsTranslator.LoadSettings(DefaultLanguage, false); - WinFormsTranslator.LoadAllForms(LoadBanlist); + WinFormsTranslator.LoadAllForms(types, LoadBanlist); WinFormsTranslator.RemoveAll(DefaultLanguage, PurgeBanlist); // remove all lines from above generated files that still remain // Move translated files from the debug exe loc to their project location diff --git a/PKHeX.WinForms/Util/WinFormsTranslator.cs b/PKHeX.WinForms/Util/WinFormsTranslator.cs index 9811f17da..f0584fc5c 100644 --- a/PKHeX.WinForms/Util/WinFormsTranslator.cs +++ b/PKHeX.WinForms/Util/WinFormsTranslator.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Windows.Forms; @@ -31,30 +30,37 @@ namespace PKHeX.WinForms private static void TranslateForm(Control form, TranslationContext context) { form.SuspendLayout(); - var formname = form.Name; + // Translate Title - form.Text = context.GetTranslatedText(formname, form.Text); + var formName = form.Name; + form.Text = context.GetTranslatedText(formName, form.Text); + + // Translate Controls var translatable = GetTranslatableControls(form); foreach (var c in translatable) - { - if (c is Control r) - { - var current = r.Text; - var updated = context.GetTranslatedText($"{formname}.{r.Name}", current); - if (!ReferenceEquals(current, updated)) - r.Text = updated; - } - else if (c is ToolStripItem t) - { - var current = t.Text; - var updated = context.GetTranslatedText($"{formname}.{t.Name}", current); - if (!ReferenceEquals(current, updated)) - t.Text = updated; - } - } + TranslateControl(c, context, formName); + form.ResumeLayout(); } + private static void TranslateControl(object c, TranslationContext context, string formname) + { + if (c is Control r) + { + var current = r.Text; + var updated = context.GetTranslatedText($"{formname}.{r.Name}", current); + if (!ReferenceEquals(current, updated)) + r.Text = updated; + } + else if (c is ToolStripItem t) + { + var current = t.Text; + var updated = context.GetTranslatedText($"{formname}.{t.Name}", current); + if (!ReferenceEquals(current, updated)) + t.Text = updated; + } + } + private static IEnumerable GetTranslationFile(string lang) { var file = GetTranslationFileNameInternal(lang); @@ -132,12 +138,14 @@ namespace PKHeX.WinForms foreach (var dropDownItem in item.DropDownItems.OfType()) { yield return dropDownItem; - if (!dropDownItem.HasDropDownItems) continue; + if (!dropDownItem.HasDropDownItems) + continue; foreach (ToolStripMenuItem subItem in GetToolsStripDropDownItems(dropDownItem)) yield return subItem; } } +#if DEBUG public static void UpdateAll(string baseLanguage, IEnumerable others) { var baseContext = GetContext(baseLanguage); @@ -161,16 +169,14 @@ namespace PKHeX.WinForms } } - public static void LoadAllForms(params string[] banlist) + public static void LoadAllForms(IEnumerable types, params string[] banlist) { - var q = from t in System.Reflection.Assembly.GetExecutingAssembly().GetTypes() - where t.BaseType == typeof(Form) && !banlist.Contains(t.Name) - select t; - foreach (var t in q) + types = types.Where(t => t.BaseType == typeof(Form) && !banlist.Contains(t.Name)); + foreach (var t in types) { var constructors = t.GetConstructors(); if (constructors.Length == 0) - { Debug.WriteLine($"No constructors: {t.Name}"); continue; } + { System.Diagnostics.Debug.WriteLine($"No constructors: {t.Name}"); continue; } var argCount = constructors[0].GetParameters().Length; try { @@ -179,7 +185,7 @@ namespace PKHeX.WinForms // This is a debug utility method, will always be logging. Shouldn't ever fail. catch { - Debug.Write($"Failed to create a new form {t}"); + System.Diagnostics.Debug.Write($"Failed to create a new form {t}"); } } } @@ -235,6 +241,7 @@ namespace PKHeX.WinForms } } } +#endif } public sealed class TranslationContext @@ -278,11 +285,5 @@ namespace PKHeX.WinForms GetTranslatedText(kvp.Key, kvp.Value); AddNew = oldAdd; } - - public void RemoveKeys(TranslationContext other) - { - foreach (var kvp in other.Translation) - Translation.Remove(kvp.Key); - } } }