diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index 882f27e5dc..47428f4f55 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -443,6 +443,7 @@ Component + @@ -605,6 +606,7 @@ TI83KeyPad.cs + Form diff --git a/BizHawk.MultiClient/GlobalWinF.cs b/BizHawk.MultiClient/GlobalWinF.cs index cb75010815..9d584d17f3 100644 --- a/BizHawk.MultiClient/GlobalWinF.cs +++ b/BizHawk.MultiClient/GlobalWinF.cs @@ -8,6 +8,7 @@ namespace BizHawk.MultiClient public static class GlobalWinF { public static MainForm MainForm; + public static ToolManager Tools; #if WINDOWS public static DirectSound DSound; public static Direct3D Direct3D; diff --git a/BizHawk.MultiClient/MainForm.Events.cs b/BizHawk.MultiClient/MainForm.Events.cs index 67731ae57d..5fec45ec28 100644 --- a/BizHawk.MultiClient/MainForm.Events.cs +++ b/BizHawk.MultiClient/MainForm.Events.cs @@ -504,7 +504,7 @@ namespace BizHawk.MultiClient private void ExitMenuItem_Click(object sender, EventArgs e) { - if (RamWatch1.AskSave()) + if (GlobalWinF.Tools.AskSave()) { Close(); } @@ -2179,7 +2179,7 @@ namespace BizHawk.MultiClient else if (ext.ToUpper() == ".WCH") { LoadRamWatch(true); - RamWatch1.LoadWatchFile(new FileInfo(filePaths[0]), false); + (GlobalWinF.Tools.Get() as RamWatch).LoadWatchFile(new FileInfo(filePaths[0]), false); } else if (MovieImport.IsValidMovieExtension(Path.GetExtension(filePaths[0]))) diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index ed8df9da47..c5beeae170 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -86,36 +86,35 @@ namespace BizHawk.MultiClient //tool dialogs - private RamSearch _ramsearch = null; + private RamSearch _ramsearch; - private HexEditor _hexeditor = null; - private TraceLogger _tracelogger = null; - private SNESGraphicsDebugger _snesgraphicsdebugger = null; - private NESNameTableViewer _nesnametableview = null; - private NESPPU _nesppu = null; - private NESDebugger _nesdebugger = null; - private GBtools.GBGPUView _gbgpuview = null; - private GBAtools.GBAGPUView _gbagpuview = null; - private PCEBGViewer _pcebgviewer = null; - private Cheats _cheats = null; - private ToolBox _toolbox = null; - private TI83KeyPad _ti83pad = null; - private TAStudio _tastudio = null; - private VirtualPadForm _vpad = null; - private NESGameGenie _ngg = null; - private SNESGameGenie _sgg = null; - private GBGameGenie _gbgg = null; - private GenGameGenie _gengg = null; - private NESSoundConfig _nessound = null; - private RamWatch _ramwatch = null; + private HexEditor _hexeditor; + private TraceLogger _tracelogger; + private SNESGraphicsDebugger _snesgraphicsdebugger; + private NESNameTableViewer _nesnametableview; + private NESPPU _nesppu; + private NESDebugger _nesdebugger; + private GBtools.GBGPUView _gbgpuview; + private GBAtools.GBAGPUView _gbagpuview; + private PCEBGViewer _pcebgviewer; + private Cheats _cheats; + private ToolBox _toolbox; + private TI83KeyPad _ti83pad; + private TAStudio _tastudio; + private VirtualPadForm _vpad; + private NESGameGenie _ngg; + private SNESGameGenie _sgg; + private GBGameGenie _gbgg; + private GenGameGenie _gengg; + private NESSoundConfig _nessound; //TODO: this is a lazy way to refactor things, but works for now. The point is to not have these objects created until needed, without refactoring a lot of code public RamSearch RamSearch1 { get { if (_ramsearch == null) _ramsearch = new RamSearch(); return _ramsearch; } set { _ramsearch = value; } } public HexEditor HexEditor1 { get { if (_hexeditor == null) _hexeditor = new HexEditor(); return _hexeditor; } set { _hexeditor = value; } } public TraceLogger TraceLogger1 { get { if (_tracelogger == null) _tracelogger = new TraceLogger(); return _tracelogger; } set { _tracelogger = value; } } public SNESGraphicsDebugger SNESGraphicsDebugger1 { get { if (_snesgraphicsdebugger == null) _snesgraphicsdebugger = new SNESGraphicsDebugger(); return _snesgraphicsdebugger; } set { _snesgraphicsdebugger = value; } } - public NESNameTableViewer NESNameTableViewer1 { get { if (_nesnametableview == null) _nesnametableview = new NESNameTableViewer(); return _nesnametableview; } set { _nesnametableview = value; } } - public NESPPU NESPPU1 { get { if (_nesppu == null) _nesppu = new NESPPU(); return _nesppu; } set { _nesppu = value; } } + public NESNameTableViewer NESNameTableViewer1 { get { return _nesnametableview ?? (_nesnametableview = new NESNameTableViewer()); } set { _nesnametableview = value; } } + public NESPPU NESPPU1 { get { return _nesppu ?? (_nesppu = new NESPPU()); } set { _nesppu = value; } } public NESDebugger NESDebug1 { get { if (_nesdebugger == null) _nesdebugger = new NESDebugger(); return _nesdebugger; } set { _nesdebugger = value; } } public GBtools.GBGPUView GBGPUView1 { get { if (_gbgpuview == null) _gbgpuview = new GBtools.GBGPUView(); return _gbgpuview; } set { _gbgpuview = value; } } public GBAtools.GBAGPUView GBAGPUView1 { get { if (_gbagpuview == null) _gbagpuview = new GBAtools.GBAGPUView(); return _gbagpuview; } set { _gbagpuview = value; } } @@ -130,8 +129,6 @@ namespace BizHawk.MultiClient public GenGameGenie Gengg { get { if (_gengg == null) _gengg = new GenGameGenie(); return _gengg; } set { _gengg = value; } } public NESSoundConfig NesSound { get { if (_nessound == null) _nessound = new NESSoundConfig(); return _nessound; } set { _nessound = value; } } - public RamWatch RamWatch1 { get { if (_ramwatch == null) _ramwatch = new RamWatch(); return _ramwatch; } set { _ramwatch = value; } } - //TODO: eventually start doing this, rather than tools attempting to talk to tools public void Cheats_UpdateValues() { if (_cheats != null) { _cheats.UpdateValues(); } } public void Cheats_Restart() @@ -229,7 +226,7 @@ namespace BizHawk.MultiClient Global.CheatList.SaveOnClose(); CloseGame(); Global.MovieSession.Movie.Stop(); - CloseTools(); + GlobalWinF.Tools.Close(); SaveConfig(); }; @@ -259,6 +256,7 @@ namespace BizHawk.MultiClient #endif GlobalWinF.Sound.StartSound(); RewireInputChain(); + GlobalWinF.Tools = new ToolManager(); //TODO - replace this with some kind of standard dictionary-yielding parser in a separate component string cmdRom = null; string cmdLoadState = null; @@ -351,14 +349,7 @@ namespace BizHawk.MultiClient if (Global.Config.RecentWatches.AutoLoad) { - if (Global.Config.DisplayRamWatch) - { - LoadRamWatch(false); - } - else - { - LoadRamWatch(true); - } + LoadRamWatch(!Global.Config.DisplayRamWatch); } if (Global.Config.RecentSearches.AutoLoad) { @@ -779,8 +770,8 @@ namespace BizHawk.MultiClient private void InitControls() { var controls = new Controller( - new ControllerDefinition() - { + new ControllerDefinition + { Name = "Emulator Frontend Controls", BoolButtons = Global.Config.HotkeyBindings.Select(x => x.DisplayName).ToList() }); @@ -984,7 +975,7 @@ namespace BizHawk.MultiClient } } - static Controller BindToDefinition(ControllerDefinition def, Dictionary> allbinds, Dictionary> analogbinds) + static Controller BindToDefinition(ControllerDefinition def, Dictionary> allbinds, Dictionary> analogbinds) { var ret = new Controller(def); Dictionary binds; @@ -1389,7 +1380,7 @@ namespace BizHawk.MultiClient else { string sgbromPath = Global.FirmwareManager.Request("SNES", "Rom_SGB"); - byte[] sgbrom = null; + byte[] sgbrom; try { if (File.Exists(sgbromPath)) @@ -1514,7 +1505,7 @@ namespace BizHawk.MultiClient if (INTERIM) { string gbabiospath = Global.FirmwareManager.Request("GBA", "Bios"); - byte[] gbabios = null; + byte[] gbabios; if (File.Exists(gbabiospath)) { @@ -1616,8 +1607,11 @@ namespace BizHawk.MultiClient // throttle.SetCoreFps(Global.Emulator.CoreComm.VsyncRate); // SyncThrottle(); //} + + GlobalWinF.Tools.Restart(); + if (_ramsearch != null) RamSearch1.Restart(); - if (_ramwatch != null) RamWatch1.Restart(); + if (_hexeditor != null) HexEditor1.Restart(); if (_nesppu != null) NESPPU1.Restart(); if (_nesnametableview != null) NESNameTableViewer1.Restart(); @@ -2350,6 +2344,9 @@ namespace BizHawk.MultiClient LuaConsole1.LuaImp.CallFrameBeforeEvent(); } #endif + + GlobalWinF.Tools.UpdateBefore(); + if (_nesnametableview != null) NESNameTableViewer1.UpdateValues(); if (_nesppu != null) NESPPU1.UpdateValues(); if (_pcebgviewer != null) PCEBGViewer1.UpdateValues(); @@ -2374,7 +2371,7 @@ namespace BizHawk.MultiClient } #endif - if (_ramwatch != null) RamWatch1.UpdateValues(); + GlobalWinF.Tools.UpdateAfter(); if (_ramsearch != null) RamSearch1.UpdateValues(); if (_hexeditor != null) HexEditor1.UpdateValues(); //The other tool updates are earlier, TAStudio needs to be later so it can display the latest @@ -3208,11 +3205,12 @@ namespace BizHawk.MultiClient CoreFileProvider.SyncCoreCommInputSignals(); Global.Emulator = new NullEmulator(Global.CoreComm); Global.Game = GameInfo.GetNullGame(); - + + GlobalWinF.Tools.Restart(); + RewireSound(); ResetRewindBuffer(); RamSearch1.Restart(); - RamWatch1.Restart(); HexEditor1.Restart(); NESPPU1.Restart(); NESNameTableViewer1.Restart(); @@ -3253,7 +3251,6 @@ namespace BizHawk.MultiClient public void CloseTools() { - CloseForm(RamWatch1); CloseForm(RamSearch1); CloseForm(HexEditor1); CloseForm(NESNameTableViewer1); @@ -4146,21 +4143,13 @@ namespace BizHawk.MultiClient public void LoadRamWatch(bool load_dialog) { - if (!RamWatch1.IsHandleCreated || RamWatch1.IsDisposed) + if (Global.Config.RecentWatches.AutoLoad && !Global.Config.RecentWatches.Empty) { - RamWatch1 = new RamWatch(); - if (Global.Config.RecentWatches.AutoLoad && !Global.Config.RecentWatches.Empty) - { - RamWatch1.LoadFileFromRecent(Global.Config.RecentWatches[0]); - } - if (load_dialog) - { - RamWatch1.Show(); - } + GlobalWinF.Tools.RamWatch.LoadFileFromRecent(Global.Config.RecentWatches[0]); } - else + if (load_dialog) { - RamWatch1.Focus(); + GlobalWinF.Tools.Load(); } } diff --git a/BizHawk.MultiClient/tools/HexEditor/HexEditor.cs b/BizHawk.MultiClient/tools/HexEditor/HexEditor.cs index 491ea8d133..62fdbc771e 100644 --- a/BizHawk.MultiClient/tools/HexEditor/HexEditor.cs +++ b/BizHawk.MultiClient/tools/HexEditor/HexEditor.cs @@ -712,11 +712,11 @@ namespace BizHawk.MultiClient if (HighlightedAddress.HasValue) { - GlobalWinF.MainForm.RamWatch1.AddWatch(MakeWatch(HighlightedAddress.Value)); + GlobalWinF.Tools.RamWatch.AddWatch(MakeWatch(HighlightedAddress.Value)); } foreach (int i in SecondaryHighlightedAddresses) { - GlobalWinF.MainForm.RamWatch1.AddWatch(MakeWatch(i)); + GlobalWinF.Tools.RamWatch.AddWatch(MakeWatch(i)); } } @@ -897,7 +897,7 @@ namespace BizHawk.MultiClient { GlobalWinF.MainForm.UpdateCheatStatus(); GlobalWinF.MainForm.RamSearch1.UpdateValues(); - GlobalWinF.MainForm.RamWatch1.UpdateValues(); + GlobalWinF.Tools.UpdateValues(); GlobalWinF.MainForm.Cheats_UpdateValues(); UpdateValues(); } diff --git a/BizHawk.MultiClient/tools/IToolForm.cs b/BizHawk.MultiClient/tools/IToolForm.cs new file mode 100644 index 0000000000..e8af01f8e9 --- /dev/null +++ b/BizHawk.MultiClient/tools/IToolForm.cs @@ -0,0 +1,37 @@ +namespace BizHawk.MultiClient +{ + public interface IToolForm + { + /// + /// Will be called by the client anytime an Update needs to occur, such as after an emulated frame, a loadstate, or a related dialog has made a relevant change + /// + void UpdateValues(); + + /// + /// Will be called anytime the dialog needs to be restarted, such as when a new ROM is loaded + /// The tool implementing this needs to account for a Game and Core change + /// + void Restart(); + + /// + /// This gives the opportunity for the tool dialog to ask the user to save changes (such is necessary when + /// This tool dialog edits a file. Returning false will tell the client the user wants to cancel the given action, + /// Return false to tell the client to back out of an action (such as closing the emulator) + /// + /// + bool AskSave(); + + + /// + /// Indicates whether the tool should be updated before a frame loop or after. + /// In general, tools that draw graphics from the core should update before the loop, + /// Information tools such as those that display core ram values should be after. + /// + bool UpdateBefore { get; } + + //Necessary winform calls + bool Focus(); + void Show(); + void Close(); + } +} diff --git a/BizHawk.MultiClient/tools/ToolHelpers.cs b/BizHawk.MultiClient/tools/ToolHelpers.cs index 903c8ab1c7..c3df6e2d91 100644 --- a/BizHawk.MultiClient/tools/ToolHelpers.cs +++ b/BizHawk.MultiClient/tools/ToolHelpers.cs @@ -206,7 +206,7 @@ namespace BizHawk.MultiClient public static void UpdateCheatRelatedTools() { - GlobalWinF.MainForm.RamWatch1.UpdateValues(); + GlobalWinF.Tools.UpdateValues(); GlobalWinF.MainForm.HexEditor1.UpdateValues(); GlobalWinF.MainForm.Cheats_UpdateValues(); GlobalWinF.MainForm.RamSearch1.UpdateValues(); diff --git a/BizHawk.MultiClient/tools/ToolManager.cs b/BizHawk.MultiClient/tools/ToolManager.cs new file mode 100644 index 0000000000..70db529b4e --- /dev/null +++ b/BizHawk.MultiClient/tools/ToolManager.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BizHawk.MultiClient +{ + public class ToolManager + { + //TODO: merge ToolHelper code where logical + //For instance, add an IToolForm property called UsesCheats, so that a UpdateCheatRelatedTools() method can update all tools of this type + //Also a UsesRam, and similar method + + private List _tools = new List(); + + /// + /// Loads the tool dialog T, if it does not exist it will be created, if it is already open, it will be focused + /// + /// + /// + public IToolForm Load() where T : IToolForm + { + var existingTool = _tools.FirstOrDefault(x => x is T); + if (existingTool != null) + { + existingTool.Show(); + existingTool.Focus(); + return existingTool; + } + else + { + var result = Get(); + result.Show(); + return result; + } + } + + /// + /// Returns true if an instance of T exists + /// + /// + /// + public bool Has() where T : IToolForm + { + return _tools.Any(x => x is T); + } + + /// + /// Gets the instance of T, or creates and returns a new instance + /// + /// + /// + public IToolForm Get() where T : IToolForm + { + var existingTool = _tools.FirstOrDefault(x => x is T); + if (existingTool != null) + { + return existingTool; + } + else + { + var tool = Activator.CreateInstance(typeof(T)); + + //Add to the list and extract it, so it will be strongly typed as T + _tools.Add(tool as IToolForm); + return _tools.FirstOrDefault(x => x is T); + } + + } + + public void UpdateBefore() + { + var beforeList = _tools.Where(x => x.UpdateBefore); + foreach (var tool in beforeList) + { + tool.UpdateValues(); + } + } + + public void UpdateAfter() + { + var afterList = _tools.Where(x => !x.UpdateBefore); + foreach (var tool in afterList) + { + tool.UpdateValues(); + } + } + + /// + /// Calls UpdateValues() on an instance of T, if it exists + /// + public void UpdateValues() where T : IToolForm + { + var tool = _tools.FirstOrDefault(x => x is T); + if (tool != null) + { + tool.UpdateValues(); + } + } + + public void Restart() + { + _tools.ForEach(x => x.Restart()); + } + + /// + /// Calls Restart() on an instance of T, if it exists + /// + /// + public void Restart() where T : IToolForm + { + var tool = _tools.FirstOrDefault(x => x is T); + if (tool != null) + { + tool.Restart(); + } + } + + /// + /// Runs AskSave on every tool dialog, false is returned if any tool returns false + /// + /// + public bool AskSave() + { + foreach (var tool in _tools) + { + var result = tool.AskSave(); + if (!result) + { + return false; + } + } + + return true; + } + + /// + /// Calls AskSave() on an instance of T, if it exists, else returns true + /// The caller should interpret false as cancel and will back out of the action that invokes this call + /// + /// + /// + public bool AskSave() where T : IToolForm + { + var tool = _tools.FirstOrDefault(x => x is T); + if (tool != null) + { + return tool.AskSave(); + } + else + { + return false; + } + } + + /// + /// If T exists, this call will close the tool, and remove it from memory + /// + /// + public void Close() where T : IToolForm + { + var tool = _tools.FirstOrDefault(x => x is T); + if (tool != null) + { + tool.Close(); + _tools.Remove(tool); + } + } + + public void Close() + { + _tools.ForEach(x => x.Close()); + _tools.Clear(); + } + + //Note: Referencing these properties creates an instance of the tool and persists it. They should be referenced by type if this is not desired + #region Tools + + public RamWatch RamWatch + { + get + { + var tool = _tools.FirstOrDefault(x => x is RamWatch); + if (tool != null) + { + return tool as RamWatch; + } + else + { + var ramWatch = new RamWatch(); + _tools.Add(ramWatch); + return ramWatch; + } + } + } + + + #endregion + } +} diff --git a/BizHawk.MultiClient/tools/Watch/RamSearch.cs b/BizHawk.MultiClient/tools/Watch/RamSearch.cs index 335e5c5ae0..b4c9cddf8a 100644 --- a/BizHawk.MultiClient/tools/Watch/RamSearch.cs +++ b/BizHawk.MultiClient/tools/Watch/RamSearch.cs @@ -816,7 +816,7 @@ namespace BizHawk.MultiClient GlobalWinF.MainForm.LoadRamWatch(true); for (int x = 0; x < SelectedIndices.Count; x++) { - GlobalWinF.MainForm.RamWatch1.AddWatch(Searches[SelectedIndices[x]]); + GlobalWinF.Tools.RamWatch.AddWatch(Searches[SelectedIndices[x]]); } if (Global.Config.RamSearchAlwaysExcludeRamWatch) @@ -854,9 +854,12 @@ namespace BizHawk.MultiClient private void RemoveRamWatchesFromList() { - Searches.RemoveRange(GlobalWinF.MainForm.RamWatch1.AddressList); - WatchListView.ItemCount = Searches.Count; - SetTotal(); + if (GlobalWinF.Tools.Has()) + { + Searches.RemoveRange(GlobalWinF.Tools.RamWatch.AddressList); + WatchListView.ItemCount = Searches.Count; + SetTotal(); + } } private void UpdateUndoToolBarButtons() diff --git a/BizHawk.MultiClient/tools/Watch/RamWatch.cs b/BizHawk.MultiClient/tools/Watch/RamWatch.cs index 2cab1aa448..8240d8995b 100644 --- a/BizHawk.MultiClient/tools/Watch/RamWatch.cs +++ b/BizHawk.MultiClient/tools/Watch/RamWatch.cs @@ -11,7 +11,7 @@ using BizHawk.Client.Common; namespace BizHawk.MultiClient { - public partial class RamWatch : Form + public partial class RamWatch : Form, IToolForm { private readonly Dictionary DefaultColumnWidths = new Dictionary { @@ -30,6 +30,8 @@ namespace BizHawk.MultiClient private string _sortedColumn = ""; private bool _sortReverse = false; + public bool UpdateBefore { get { return true; } } + public RamWatch() { InitializeComponent();