diff --git a/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs b/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs index a358175e18..38d316232f 100644 --- a/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs +++ b/src/BizHawk.Client.Common/DisplayManager/FilterManager.cs @@ -267,7 +267,7 @@ namespace BizHawk.Client.Common.FilterManager if (currState.SurfaceDisposition == SurfaceDisposition.Texture) { var renderer = new Render(); - Filters.Insert(Filters.Count, renderer); + Filters.Add(renderer); Compile(channel, inSize, outsize, finalTarget); return; } diff --git a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs index 32977a366e..23358324e6 100644 --- a/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs +++ b/src/BizHawk.Client.Common/movie/bk2/Bk2Movie.IO.cs @@ -3,6 +3,7 @@ using System.IO; using BizHawk.Common; using BizHawk.Common.IOExtensions; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; namespace BizHawk.Client.Common @@ -21,8 +22,7 @@ namespace BizHawk.Client.Common return; } - var backupName = Filename; - backupName = backupName.Insert(Filename.LastIndexOf('.'), $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}"); + var backupName = Filename.InsertBeforeLast('.', insert: $".{DateTime.Now:yyyy-MM-dd HH.mm.ss}", out _); backupName = Path.Combine(Session.BackupDirectory, Path.GetFileName(backupName)); Write(backupName, isBackup: true); diff --git a/src/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs b/src/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs index 93c99f6b69..23563aab22 100644 --- a/src/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs +++ b/src/BizHawk.Client.EmuHawk/Extensions/ControlExtensions.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -12,6 +13,7 @@ using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Common; +using BizHawk.Common.CollectionExtensions; using BizHawk.Common.ReflectionExtensions; using BizHawk.Emulation.Common; @@ -158,6 +160,20 @@ namespace BizHawk.Client.EmuHawk return tabControl.TabPages.Cast(); } +#pragma warning disable CS0618 // WinForms doesn't use generics ofc + public static bool InsertAfter(this ToolStripItemCollection items, ToolStripItem needle, ToolStripItem insert) + => ((IList) items).InsertAfter(needle, insert: insert); + + public static bool InsertAfterLast(this ToolStripItemCollection items, ToolStripItem needle, ToolStripItem insert) + => ((IList) items).InsertAfterLast(needle, insert: insert); + + public static bool InsertBefore(this ToolStripItemCollection items, ToolStripItem needle, ToolStripItem insert) + => ((IList) items).InsertBefore(needle, insert: insert); + + public static bool InsertBeforeLast(this ToolStripItemCollection items, ToolStripItem needle, ToolStripItem insert) + => ((IList) items).InsertBeforeLast(needle, insert: insert); +#pragma warning restore CS0618 + public static void ReplaceDropDownItems(this ToolStripDropDownItem menu, params ToolStripItem[] items) { menu.DropDownItems.Clear(); diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 56d81a35d4..aa36d51796 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -79,9 +79,7 @@ namespace BizHawk.Client.EmuHawk #if BIZHAWKBUILD_SUPERHAWK ToolStripMenuItemEx superHawkThrottleMenuItem = new() { Text = "SUPER·HAWK" }; superHawkThrottleMenuItem.Click += (_, _) => Config.SuperHawkThrottle = !Config.SuperHawkThrottle; - SpeedSkipSubMenu.DropDownItems.Insert( - SpeedSkipSubMenu.DropDownItems.IndexOf(MinimizeSkippingMenuItem), - superHawkThrottleMenuItem); + _ = SpeedSkipSubMenu.DropDownItems.InsertBefore(MinimizeSkippingMenuItem, insert: superHawkThrottleMenuItem); ConfigSubMenu.DropDownOpened += (_, _) => superHawkThrottleMenuItem.Checked = Config.SuperHawkThrottle; #endif @@ -159,9 +157,9 @@ namespace BizHawk.Client.EmuHawk submenu.Enabled = false; } } - ConfigSubMenu.DropDownItems.Insert( - ConfigSubMenu.DropDownItems.IndexOf(CoresSubMenu) + 1, - new ToolStripMenuItemEx + _ = ConfigSubMenu.DropDownItems.InsertAfter( + CoresSubMenu, + insert: new ToolStripMenuItemEx { DropDownItems = { @@ -175,7 +173,7 @@ namespace BizHawk.Client.EmuHawk Text = "Core Settings", }); - MainformMenu.Items.Insert(MainformMenu.Items.IndexOf(ToolsSubMenu) + 1, NullHawkVSysSubmenu); + _ = MainformMenu.Items.InsertAfter(ToolsSubMenu, insert: NullHawkVSysSubmenu); // Hide Status bar icons and general StatusBar prep MainStatusBar.Padding = new Padding(MainStatusBar.Padding.Left, MainStatusBar.Padding.Top, MainStatusBar.Padding.Left, MainStatusBar.Padding.Bottom); // Workaround to remove extra padding on right diff --git a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs index 6fc67f26bf..a663dda2c4 100644 --- a/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs +++ b/src/BizHawk.Client.EmuHawk/tools/TAStudio/TAStudio.cs @@ -135,9 +135,7 @@ namespace BizHawk.Client.EmuHawk }; if (this.ShowDialogWithTempMute(dialog).IsOk()) GoToFrame(int.Parse(dialog.PromptText)); }; - EditSubMenu.DropDownItems.Insert( - EditSubMenu.DropDownItems.IndexOf(ReselectClipboardMenuItem) + 1, - goToFrameMenuItem); + _ = EditSubMenu.DropDownItems.InsertAfter(ReselectClipboardMenuItem, insert: goToFrameMenuItem); RecentSubMenu.Image = Resources.Recent; recentMacrosToolStripMenuItem.Image = Resources.Recent; diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs index ab9bcf4824..f23e6fb33d 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamWatch.cs @@ -123,7 +123,7 @@ namespace BizHawk.Client.EmuHawk i++; } }; - WatchesSubMenu.DropDownItems.Insert(11, deduperMenuItem); + _ = WatchesSubMenu.DropDownItems.InsertBefore(toolStripSeparator3, insert: deduperMenuItem); Settings = new RamWatchSettings(); diff --git a/src/BizHawk.Common/Extensions/CollectionExtensions.cs b/src/BizHawk.Common/Extensions/CollectionExtensions.cs index e59ab64be4..934e5ad109 100644 --- a/src/BizHawk.Common/Extensions/CollectionExtensions.cs +++ b/src/BizHawk.Common/Extensions/CollectionExtensions.cs @@ -10,6 +10,10 @@ namespace BizHawk.Common.CollectionExtensions public static class CollectionExtensions #pragma warning restore MA0104 { + private const string ERR_MSG_IMMUTABLE_LIST = "immutable list passed to mutating method"; + + private const string WARN_NONGENERIC = "use generic overload"; + public static IOrderedEnumerable OrderBy( this IEnumerable source, Func keySelector, @@ -247,6 +251,111 @@ namespace BizHawk.Common.CollectionExtensions return -1; } + public static bool InsertAfter(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.IndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint + 1, insert); + return true; + } + + [Obsolete(WARN_NONGENERIC)] + public static bool InsertAfter(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.IndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint + 1, insert); + return true; + } + + public static bool InsertAfterLast(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.LastIndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint + 1, insert); + return true; + } + + [Obsolete(WARN_NONGENERIC)] + public static bool InsertAfterLast(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.LastIndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint + 1, insert); + return true; + } + + public static bool InsertBefore(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.IndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint, insert); + return true; + } + + [Obsolete(WARN_NONGENERIC)] + public static bool InsertBefore(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.IndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint, insert); + return true; + } + + public static bool InsertBeforeLast(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.LastIndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint, insert); + return true; + } + + [Obsolete(WARN_NONGENERIC)] + public static bool InsertBeforeLast(this IList list, T needle, T insert) + { + Debug.Assert(!list.IsReadOnly, ERR_MSG_IMMUTABLE_LIST); + var insertPoint = list.LastIndexOf(needle); + if (insertPoint < 0) return false; + list.Insert(insertPoint, insert); + return true; + } + + public static int LastIndexOf(this IList list, T item) + { + if (list is T[] arr) return Array.LastIndexOf(arr, item); + if (list is List bclList) return bclList.LastIndexOf(item); + if (item is null) + { + for (var i = list.Count - 1; i >= 0; i--) if (list[i] is null) return i; + } + else + { + for (var i = list.Count - 1; i >= 0; i--) if (item.Equals(list[i])) return i; + } + return -1; + } + + [Obsolete(WARN_NONGENERIC)] + public static int LastIndexOf(this IList list, object? item) + { + if (item is null) + { + for (var i = list.Count - 1; i >= 0; i--) if (list[i] is null) return i; + } + else + { + for (var i = list.Count - 1; i >= 0; i--) if (item.Equals(list[i])) return i; + } + return -1; + } + public static T? FirstOrNull(this IEnumerable list, Func predicate) where T : struct { diff --git a/src/BizHawk.Common/Extensions/StringExtensions.cs b/src/BizHawk.Common/Extensions/StringExtensions.cs index 32cf1ea728..f3b02bf1ea 100644 --- a/src/BizHawk.Common/Extensions/StringExtensions.cs +++ b/src/BizHawk.Common/Extensions/StringExtensions.cs @@ -62,6 +62,34 @@ namespace BizHawk.Common.StringExtensions public static bool In(this string str, params string[] options) => options.Any(str.EqualsIgnoreCase); + public static string InsertAfter(this string str, char needle, string insert, out bool found) + { + var insertPoint = str.IndexOf(needle); + found = insertPoint >= 0; + return found ? str.Insert(insertPoint + 1, insert) : str; + } + + public static string InsertAfterLast(this string str, char needle, string insert, out bool found) + { + var insertPoint = str.LastIndexOf(needle); + found = insertPoint >= 0; + return found ? str.Insert(insertPoint + 1, insert) : str; + } + + public static string InsertBefore(this string str, char needle, string insert, out bool found) + { + var insertPoint = str.IndexOf(needle); + found = insertPoint >= 0; + return found ? str.Insert(insertPoint, insert) : str; + } + + public static string InsertBeforeLast(this string str, char needle, string insert, out bool found) + { + var insertPoint = str.LastIndexOf(needle); + found = insertPoint >= 0; + return found ? str.Insert(insertPoint, insert) : str; + } + /// a copy of with all characters outside [0-9A-Za-z] removed public static string OnlyAlphanumeric(this string raw) => string.Concat(raw.Where(static c => c is (>= '0' and <= '9') or (>= 'A' and <= 'Z') or (>= 'a' and <= 'z')));