/* This file is part of ContrAlto. ContrAlto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ContrAlto 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with ContrAlto. If not, see . */ using Contralto.CPU; using Contralto.Display; using Contralto.IO; using Contralto.Properties; using Contralto.Scripting; using Contralto.UI; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Timers; using System.Windows.Forms; namespace Contralto { public partial class AltoWindow : Form, IAltoDisplay { public AltoWindow() { InitializeComponent(); InitKeymap(); _mouseCaptured = false; _currentCursorState = true; _fullScreenDisplay = false; _displayBuffer = new Bitmap(608, 808, PixelFormat.Format1bppIndexed); DisplayBox.Image = _displayBuffer; _lastBuffer = _currentBuffer = _displayData0; _frame = 0; _commitDisksAtShutdown = true; try { _frameTimer = new FrameTimer(60.0); } catch(DllNotFoundException) { // // On Mono platforms, we can't PInvoke to get what we want. // We just won't be able to synchronize to 60fps. // _frameTimer = null; } ReleaseMouse(); CreateTridentMenu(); SystemStatusLabel.Text = Resources.SystemStoppedText; DiskStatusLabel.Text = String.Empty; _diskIdleImage = Resources.DiskNoAccess; _diskReadImage = Resources.DiskRead; _diskWriteImage = Resources.DiskWrite; _diskSeekImage = Resources.DiskSeek; this.DoubleBuffered = true; _fpsTimer = new System.Timers.Timer(); _fpsTimer.AutoReset = true; _fpsTimer.Interval = 1000; _fpsTimer.Elapsed += OnFPSTimerElapsed; _fpsTimer.Start(); _diskAccessTimer = new System.Timers.Timer(); _diskAccessTimer.AutoReset = true; _diskAccessTimer.Interval = 25; _diskAccessTimer.Elapsed += OnDiskTimerElapsed; _diskAccessTimer.Start(); ScriptManager.PlaybackCompleted += OnScriptPlaybackCompleted; } public void AttachSystem(AltoSystem system) { _system = system; _system.AttachDisplay(this); _controller = new ExecutionController(_system); _controller.ErrorCallback += OnExecutionError; _controller.ShutdownCallback += OnShutdown; // Update disk image UI info // Diablo disks: Drive0ImageName.Text = _system.DiskController.Drives[0].IsLoaded ? Path.GetFileName(_system.DiskController.Drives[0].Pack.PackName) : Resources.NoImageLoadedText; Drive1ImageName.Text = _system.DiskController.Drives[1].IsLoaded ? Path.GetFileName(_system.DiskController.Drives[1].Pack.PackName) : Resources.NoImageLoadedText; // Trident disks for (int i = 0; i < _tridentImageNames.Count; i++) { TridentDrive drive = _system.TridentController.Drives[i]; _tridentImageNames[i].Text = drive.IsLoaded ? Path.GetFileName(drive.Pack.PackName) : Resources.NoImageLoadedText; } // // If a startup script was specified, start it running now -- // tell the script manager to start the script, and start the // Alto system running so that the script actually executes. // if (!string.IsNullOrWhiteSpace(StartupOptions.ScriptFile)) { StartScriptPlayback(StartupOptions.ScriptFile); _controller.StartExecution(AlternateBootType.None); } } /// /// Handler for "System->Start" menu. Start the Alto system running. /// /// /// private void OnSystemStartMenuClick(object sender, EventArgs e) { StartSystem(AlternateBootType.None); } private void OnStartWithAlternateBootClicked(object sender, EventArgs e) { if (_controller.IsRunning) { _controller.Reset(Configuration.AlternateBootType); } else { StartSystem(Configuration.AlternateBootType); } } private void OnSystemResetMenuClick(object sender, EventArgs e) { _controller.Reset(AlternateBootType.None); } private void OnSystemDrive0LoadClick(object sender, EventArgs e) { string path = ShowImageLoadDialog(0, false); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadDiabloDrive(0, path, false); Drive0ImageName.Text = System.IO.Path.GetFileName(path); Configuration.Drive0Image = path; } catch(Exception ex) { MessageBox.Show( String.Format(Resources.DiskLoadErrorText, ex.Message), Resources.DiskLoadErrorTitle, MessageBoxButtons.OK); } } private void OnSystemDrive0UnloadClick(object sender, EventArgs e) { try { _system.UnloadDiabloDrive(0); } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskSaveErrorText, "Diablo", 0, ex.Message), Resources.DiskSaveErrorTitle, MessageBoxButtons.OK); } finally { Drive1ImageName.Text = Resources.NoImageLoadedText; Configuration.Drive1Image = String.Empty; } } private void OnSystemDrive0NewClick(object sender, EventArgs e) { string path = ShowImageNewDialog(0, false); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadDiabloDrive(0, path, true); Drive0ImageName.Text = System.IO.Path.GetFileName(path); Configuration.Drive0Image = path; } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskCreateErrorText, ex.Message), Resources.DiskCreateErrorTitle, MessageBoxButtons.OK); } } private void OnSystemDrive1LoadClick(object sender, EventArgs e) { string path = ShowImageLoadDialog(1, false); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadDiabloDrive(1, path, false); Drive1ImageName.Text = System.IO.Path.GetFileName(path); Configuration.Drive1Image = path; } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskLoadErrorText, ex.Message), Resources.DiskLoadErrorTitle, MessageBoxButtons.OK); } } private void OnSystemDrive1UnloadClick(object sender, EventArgs e) { try { _system.UnloadDiabloDrive(1); } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskSaveErrorText, "Diablo", 1, ex.Message), Resources.DiskSaveErrorTitle, MessageBoxButtons.OK); } finally { Drive1ImageName.Text = Resources.NoImageLoadedText; Configuration.Drive1Image = String.Empty; } } private void OnSystemDrive1NewClick(object sender, EventArgs e) { string path = ShowImageNewDialog(1, false); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadDiabloDrive(1, path, true); Drive0ImageName.Text = System.IO.Path.GetFileName(path); Configuration.Drive0Image = path; } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskCreateErrorText, ex.Message), Resources.DiskCreateErrorTitle, MessageBoxButtons.OK); } } private void OnTridentLoadClick(object sender, EventArgs e) { int drive = (int)((ToolStripDropDownItem)sender).Tag; string path = ShowImageLoadDialog(drive, true); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadTridentDrive(drive, path, false); _tridentImageNames[drive].Text = System.IO.Path.GetFileName(path); Configuration.TridentImages[drive] = path; } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskLoadErrorText, ex.Message), Resources.DiskLoadErrorTitle, MessageBoxButtons.OK); } } private void OnTridentUnloadClick(object sender, EventArgs e) { int drive = (int)((ToolStripDropDownItem)sender).Tag; try { _system.UnloadTridentDrive(drive); } catch(Exception ex) { MessageBox.Show( String.Format(Resources.DiskSaveErrorText, "Trident", drive, ex.Message), Resources.DiskSaveErrorTitle, MessageBoxButtons.OK); } finally { _tridentImageNames[drive].Text = Resources.NoImageLoadedText; Configuration.TridentImages[drive] = String.Empty; } } private void OnTridentNewClick(object sender, EventArgs e) { int drive = (int)((ToolStripDropDownItem)sender).Tag; string path = ShowImageNewDialog(drive, true); if (String.IsNullOrEmpty(path)) { return; } try { _system.LoadTridentDrive(drive, path, true); _tridentImageNames[drive].Text = System.IO.Path.GetFileName(path); Configuration.TridentImages[drive] = path; } catch (Exception ex) { MessageBox.Show( String.Format(Resources.DiskCreateErrorText, ex.Message), Resources.DiskCreateErrorTitle, MessageBoxButtons.OK); } } private void OnAlternateBootOptionsClicked(object sender, EventArgs e) { AlternateBootOptions bootWindow = new AlternateBootOptions(); bootWindow.ShowDialog(); } private void OnSystemOptionsClick(object sender, EventArgs e) { SystemOptions optionsWindow = new SystemOptions(); optionsWindow.ShowDialog(); } private void OnFullScreenMenuClick(object sender, EventArgs e) { BeginInvoke(new DisplayDelegate(ToggleFullScreen)); } private void OnHelpAboutClick(object sender, EventArgs e) { AboutBox about = new AboutBox(); about.ShowDialog(); } private void OnDebuggerShowClick(object sender, EventArgs e) { if (_debugger == null) { _debugger = new Debugger(_system, _controller); _debugger.LoadSourceCode(MicrocodeBank.ROM0, "Disassembly\\altoIIcode3.mu"); _debugger.LoadSourceCode(MicrocodeBank.ROM1, "Disassembly\\MesaROM.mu"); _debugger.FormClosed += OnDebuggerClosed; _debugger.Show(); // Disable the Start/Reset menu items // (debugger will control those now) SystemStartMenuItem.Enabled = false; SystemResetMenuItem.Enabled = false; } else { // Debugger is already opened, just bring it to the front _debugger.BringToFront(); } } private void OnDebuggerClosed(object sender, FormClosedEventArgs e) { // Re-enable start/reset menu items depending on current execution state. SystemStartMenuItem.Enabled = !_controller.IsRunning; SystemResetMenuItem.Enabled = _controller.IsRunning; _debugger = null; } private void OnFileSaveScreenshotClick(object sender, EventArgs e) { // Pause execution while the user selects the destination for the screenshot bool wasRunning = _controller.IsRunning; _controller.StopExecution(); SaveFileDialog fileDialog = new SaveFileDialog(); fileDialog.DefaultExt = "png"; fileDialog.Filter = Resources.ScreenshotFilter; fileDialog.Title = Resources.ScreenshotTitle; fileDialog.CheckPathExists = true; fileDialog.FileName = Resources.ScreenshotDefaultFileName; DialogResult res = fileDialog.ShowDialog(); if (res == DialogResult.OK) { EncoderParameters p = new EncoderParameters(1); p.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L); try { _displayBuffer.Save(fileDialog.FileName, GetEncoderForFormat(ImageFormat.Png), p); } catch { MessageBox.Show(Resources.ScreenshotErrorText); } } if (wasRunning) { _controller.StartExecution(AlternateBootType.None); } } private void OnFileRecordScriptClick(object sender, EventArgs e) { if (!ScriptManager.IsRecording) { SaveFileDialog fileDialog = new SaveFileDialog(); fileDialog.DefaultExt = "script"; fileDialog.Filter = Resources.ScriptFilter; fileDialog.CheckFileExists = false; fileDialog.CheckPathExists = true; fileDialog.OverwritePrompt = true; fileDialog.ValidateNames = true; fileDialog.Title = Resources.ScriptRecordTitle; DialogResult res = fileDialog.ShowDialog(); if (res == DialogResult.OK) { StartScriptRecording(fileDialog.FileName); } } else { StopScriptRecording(); } } private void OnFilePlayScriptClick(object sender, EventArgs e) { if (!ScriptManager.IsPlaying) { OpenFileDialog fileDialog = new OpenFileDialog(); fileDialog.DefaultExt = "script"; fileDialog.Filter = Resources.ScriptFilter; fileDialog.Multiselect = false; fileDialog.CheckFileExists = true; fileDialog.CheckPathExists = true; fileDialog.Title = Resources.ScriptPlaybackTitle; DialogResult res = fileDialog.ShowDialog(); if (res == DialogResult.OK) { StartScriptPlayback(fileDialog.FileName); } } else { StopScriptPlayback(); } } private void OnFileExitClick(object sender, EventArgs e) { _controller.StopExecution(); this.Close(); } private void OnAltoWindowClosed(object sender, FormClosedEventArgs e) { // // Stop UI timers // _fpsTimer.Stop(); _diskAccessTimer.Stop(); // Halt the system and detach our display _controller.StopExecution(); _system.DetachDisplay(); _system.Shutdown(_commitDisksAtShutdown); // // Commit current configuration to disk // Configuration.WriteConfiguration(); DialogResult = DialogResult.OK; } private string ShowImageLoadDialog(int drive, bool trident) { OpenFileDialog fileDialog = new OpenFileDialog(); fileDialog.DefaultExt = trident ? "dsk80" : "dsk"; fileDialog.Filter = trident ? Resources.TridentFilter : Resources.DiabloFilter; fileDialog.Multiselect = false; fileDialog.CheckFileExists = true; fileDialog.CheckPathExists = true; fileDialog.Title = String.Format(Resources.DiskLoadTitle, trident ? "Trident" : "Diablo", drive); DialogResult res = fileDialog.ShowDialog(); if (res == DialogResult.OK) { return fileDialog.FileName; } else { return null; } } private string ShowImageNewDialog(int drive, bool trident) { SaveFileDialog fileDialog = new SaveFileDialog(); fileDialog.DefaultExt = trident ? "dsk80" : "dsk"; fileDialog.Filter = trident ? Resources.TridentFilter : Resources.DiabloFilter; fileDialog.CheckFileExists = false; fileDialog.CheckPathExists = true; fileDialog.OverwritePrompt = true; fileDialog.ValidateNames = true; fileDialog.Title = String.Format(Resources.DiskNewTitle, trident ? "Trident" : "Diablo", drive); DialogResult res = fileDialog.ShowDialog(); if (res == DialogResult.OK) { return fileDialog.FileName; } else { return null; } } /// /// Error handling /// /// private void OnExecutionError(Exception e) { // TODO: invoke the debugger when an error is hit //OnDebuggerShowClick(null, null); SystemStatusLabel.Text = Resources.SystemErrorText; Console.WriteLine("Execution error: {0} - {1}", e.Message, e.StackTrace); System.Diagnostics.Debugger.Break(); } /// /// Handle an internal shutdown of the emulator. /// private void OnShutdown(bool commitDisks) { BeginInvoke(new ShutdownCallbackDelegate(InternalShutdown), commitDisks); } private void StartSystem(AlternateBootType bootType) { // Disable "Start" menu item SystemStartMenuItem.Enabled = false; // Enable "Reset" menu item SystemResetMenuItem.Enabled = true; _controller.StartExecution(bootType); SystemStatusLabel.Text = Resources.SystemRunningText; } // // Display handling // public void Render() { _frame++; // Wait for the next frame if (Configuration.ThrottleSpeed && _frameTimer != null) { _frameTimer.WaitForFrame(); } if (Configuration.InterlaceDisplay) { // Flip the back-buffer if ((_frame % 2) == 0) { _currentBuffer = _displayData0; _lastBuffer = _displayData1; } else { _currentBuffer = _displayData1; _lastBuffer = _displayData0; } } else { _lastBuffer = _currentBuffer; } // Asynchronously render this frame. BeginInvoke(new DisplayDelegate(RefreshDisplayBox)); } private void RefreshDisplayBox() { DisplayBox.Invalidate(); } private void OnPaint(object sender, PaintEventArgs e) { // Update the display BitmapData data = _displayBuffer.LockBits(_displayRect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed); IntPtr ptr = data.Scan0; System.Runtime.InteropServices.Marshal.Copy(_lastBuffer, 0, ptr, _lastBuffer.Length - 4); _displayBuffer.UnlockBits(data); // Clear the buffer if we're displaying in fakey-"interlaced" mode. if (Configuration.InterlaceDisplay) { Array.Clear(_lastBuffer, 0, _lastBuffer.Length); } } /// /// Invoked by the DisplayController to put a word on the emulated screen. /// /// /// /// public void DrawDisplayWord(int scanline, int wordOffset, ushort word, bool lowRes) { // TODO: move magic numbers to constants if (lowRes) { // Low resolution; double up pixels. int address = scanline * 76 + wordOffset * 4; if (address > _currentBuffer.Length) { throw new InvalidOperationException("Display word address is out of bounds."); } UInt32 stretched = StretchWord(word); _currentBuffer[address] = (byte)(stretched >> 24); _currentBuffer[address + 1] = (byte)(stretched >> 16); _currentBuffer[address + 2] = (byte)(stretched >> 8); _currentBuffer[address + 3] = (byte)(stretched); } else { int address = scanline * 76 + wordOffset * 2; if (address > _currentBuffer.Length) { throw new InvalidOperationException("Display word address is out of bounds."); } _currentBuffer[address] = (byte)(word >> 8); _currentBuffer[address + 1] = (byte)(word); } } /// /// Invoked by the DisplayController to draw the cursor at the specified position on the given /// scanline. /// /// The scanline (Y) /// X offset (in pixels) /// The word to be drawn public void DrawCursorWord(int scanline, int xOffset, bool whiteOnBlack, ushort cursorWord) { int address = scanline * 76 + xOffset / 8; // // Grab the 32 bits straddling the cursor from the display buffer // so we can merge the 16 cursor bits in. // UInt32 displayWord = (UInt32)((_currentBuffer[address] << 24) | (_currentBuffer[address + 1] << 16) | (_currentBuffer[address + 2] << 8) | _currentBuffer[address + 3]); // Slide the cursor word to the proper X position UInt32 adjustedCursorWord = (UInt32)(cursorWord << (16 - (xOffset % 8))); if (!whiteOnBlack) { displayWord &= ~adjustedCursorWord; } else { displayWord |= adjustedCursorWord; } _currentBuffer[address] = (byte)(displayWord >> 24); _currentBuffer[address + 1] = (byte)(displayWord >> 16); _currentBuffer[address + 2] = (byte)(displayWord >> 8); _currentBuffer[address + 3] = (byte)(displayWord); } /// /// "Stretches" a 16 bit word into a 32-bit word (for low-res display purposes). /// /// /// private UInt32 StretchWord(ushort word) { UInt32 stretched = 0; for (int i = 0x8000, j = 15; j >= 0; i = i >> 1, j--) { uint bit = (uint)(word & i) >> j; stretched |= (bit << (j * 2 + 1)); stretched |= (bit << (j * 2)); } return stretched; } // // Keyboard and Mouse handling // /// /// Handle modifier keys here mostly because Windows Forms doesn't /// normally distinguish between left and right shift; the Alto keyboard does. /// CTRL is also handled here just because. /// /// /// protected override bool ProcessKeyEventArgs(ref Message m) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return base.ProcessKeyEventArgs(ref m); } // Grab the scancode from the message int scanCode = (int)((m.LParam.ToInt64() >> 16) & 0x1ff); bool down = false; const int WM_KEYDOWN = 0x100; const int WM_KEYUP = 0x101; if (m.Msg == WM_KEYDOWN) { down = true; } else if (m.Msg == WM_KEYUP) { down = false; } else { // Something else? return base.ProcessKeyEventArgs(ref m); } AltoKey modifierKey = AltoKey.None; switch(scanCode) { case 0x2a: // LShift modifierKey = AltoKey.LShift; break; case 0x36: modifierKey = AltoKey.RShift; break; case 0x1d: case 0x11d: modifierKey = AltoKey.CTRL; break; } if (modifierKey != AltoKey.None) { if (down) { _system.Keyboard.KeyDown(modifierKey); } else { _system.Keyboard.KeyUp(modifierKey); } return true; // handled } return base.ProcessKeyEventArgs(ref m); } private void OnKeyDown(object sender, KeyEventArgs e) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return; } if (!_mouseCaptured) { return; } // Handle non-modifier keys here if (_keyMap.ContainsKey(e.KeyCode)) { _system.Keyboard.KeyDown(_keyMap[e.KeyCode]); } if (_keysetMap.ContainsKey(e.KeyCode)) { _system.MouseAndKeyset.KeysetDown(_keysetMap[e.KeyCode]); } if (e.Alt) { ReleaseMouse(); e.SuppressKeyPress = true; } } private void OnKeyUp(object sender, KeyEventArgs e) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return; } if (!_mouseCaptured) { return; } // Handle non-modifier keys here if (_keyMap.ContainsKey(e.KeyCode)) { _system.Keyboard.KeyUp(_keyMap[e.KeyCode]); } if (_keysetMap.ContainsKey(e.KeyCode)) { _system.MouseAndKeyset.KeysetUp(_keysetMap[e.KeyCode]); } if (e.Alt) { ReleaseMouse(); e.SuppressKeyPress = true; } } private void OnDisplayMouseMove(object sender, MouseEventArgs e) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return; } if (!_mouseCaptured) { // We do nothing with mouse input unless we have capture. return; } if (_skipNextMouseMove) { _skipNextMouseMove = false; return; } // We implement a crude "mouse capture" behavior by forcing the mouse cursor to the // center of the display window and taking the delta of the last movement and using it // to move the Alto's mouse. // In this way the Windows cursor never leaves our window (important in order to prevent // other programs from getting clicked on while Missile Command is being played) and we // still get motion events. Point middle = new Point(DisplayBox.Width / 2, DisplayBox.Height / 2 ); int dx = e.X - middle.X; int dy = e.Y - middle.Y; if (dx != 0 || dy != 0) { _system.MouseAndKeyset.MouseMove(dx, dy); // Don't handle the very next Mouse Move event (which will just be the motion we caused in the // below line...) _skipNextMouseMove = true; Cursor.Position = DisplayBox.PointToScreen(middle); } } private void OnDisplayMouseDown(object sender, MouseEventArgs e) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return; } if (!_mouseCaptured) { return; } AltoMouseButton button = AltoMouseButton.None; switch (e.Button) { case MouseButtons.Left: button = AltoMouseButton.Left; break; case MouseButtons.Right: button = AltoMouseButton.Right; break; case MouseButtons.Middle: button = AltoMouseButton.Middle; break; } _system.MouseAndKeyset.MouseDown(button); } private void OnDisplayMouseUp(object sender, MouseEventArgs e) { // Short-circuit if a script is playing. if (ScriptManager.IsPlaying) { return; } if (!_mouseCaptured) { // On mouse-up, capture the mouse if the system is running. if (_controller.IsRunning) { CaptureMouse(); } return; } AltoMouseButton button = AltoMouseButton.None; switch (e.Button) { case MouseButtons.Left: button = AltoMouseButton.Left; break; case MouseButtons.Right: button = AltoMouseButton.Right; break; case MouseButtons.Middle: button = AltoMouseButton.Middle; break; } _system.MouseAndKeyset.MouseUp(button); } private void CaptureMouse() { // In mouse-capture mode we do the following: // - Set the mouse pointer to nothing (so we just see the Alto mouse pointer) // - Keep the mouse within our window bounds (so it doesn't escape our window, hence "capture"). // - Put some text in the Status area telling people how to leave... _mouseCaptured = true; ShowCursor(false); CaptureStatusLabel.Text = Resources.MouseCaptureActiveText; } private void ReleaseMouse() { _mouseCaptured = false; ShowCursor(true); CaptureStatusLabel.Text = Resources.MouseCaptureInactiveText; } /// /// Work around Windows Forms's bizarre refcounted cursor behavior... /// /// private void ShowCursor(bool show) { if (show == _currentCursorState) { return; } if (show) { Cursor.Show(); } else { Cursor.Hide(); } _currentCursorState = show; } private void OnWindowLeave(object sender, EventArgs e) { // We are no longer the focus, make sure to release the mouse. ReleaseMouse(); } private void OnWindowDeactivate(object sender, EventArgs e) { // We are no longer the focus, make sure to release the mouse. ReleaseMouse(); } private void OnWindowSizeChanged(object sender, EventArgs e) { // // If we've switched to a fullscreen mode, update the // Alto's display area. // if (_fullScreenDisplay) { DisplayBox.Top = 0; DisplayBox.Left = 0; DisplayBox.Width = this.Width; DisplayBox.Height = this.Height; DisplayBox.SizeMode = PictureBoxSizeMode.Zoom; DisplayBox.BackColor = Color.Black; } } private void OnFPSTimerElapsed(object sender, ElapsedEventArgs e) { string fpsMessage = String.Format("{0} fields/sec", _system.DisplayController.Fields); FPSLabel.Text = fpsMessage; _system.DisplayController.Fields = 0; } private void OnDiskTimerElapsed(object sender, ElapsedEventArgs e) { BeginInvoke(new DisplayDelegate(RefreshDiskStatus)); } private void InternalShutdown(bool commitDisks) { // Determine how to exit. _commitDisksAtShutdown = commitDisks; // Close our window and be done. this.Close(); } private void RefreshDiskStatus() { if (_lastActivity != _system.DiskController.LastDiskActivity) { _lastActivity = _system.DiskController.LastDiskActivity; switch (_lastActivity) { case DiskActivityType.Idle: DiskStatusLabel.Image = _diskIdleImage; break; case DiskActivityType.Read: DiskStatusLabel.Image = _diskReadImage; break; case DiskActivityType.Write: DiskStatusLabel.Image = _diskWriteImage; break; case DiskActivityType.Seek: DiskStatusLabel.Image = _diskSeekImage; break; } } } private void ToggleFullScreen() { _fullScreenDisplay = !_fullScreenDisplay; if (_fullScreenDisplay) { // Save the original size and location of the Alto's Display so we can // restore it when full-screen mode is exited. _windowedDisplayBoxLocation = DisplayBox.Location; _windowedDisplayBoxSize = DisplayBox.Size; // Hide window adornments and make the window full screen on the current // display. this.FormBorderStyle = FormBorderStyle.None; this.WindowState = FormWindowState.Maximized; _mainMenu.Visible = false; StatusLine.Visible = false; // Once the window repaints we will center the Alto's display and // stretch if applicable. } else { // Show everything that was hidden before. this.FormBorderStyle = FormBorderStyle.FixedSingle; this.WindowState = FormWindowState.Normal; _mainMenu.Visible = true; StatusLine.Visible = true; DisplayBox.SizeMode = PictureBoxSizeMode.Normal; DisplayBox.Location = _windowedDisplayBoxLocation; DisplayBox.Size = _windowedDisplayBoxSize; } } private void StartScriptPlayback(string fileName) { // // Start the script. We need to pause the emulation while doing so, // in order to avoid concurrency issues with the Scheduler (which is // not thread-safe). // _controller.StopExecution(); ScriptManager.StartPlayback(_system, _controller, fileName); _controller.StartExecution(AlternateBootType.None); PlayScriptMenu.Text = Resources.StopPlaybackText; RecordScriptMenu.Enabled = false; SystemStatusLabel.Text = Resources.PlaybackInProgressText; } private void StopScriptPlayback() { ScriptManager.StopPlayback(); PlayScriptMenu.Text = Resources.StartPlaybackText; RecordScriptMenu.Enabled = true; SystemStatusLabel.Text = _controller.IsRunning ? Resources.SystemRunningText : Resources.SystemStoppedText; } private void StartScriptRecording(string fileName) { ScriptManager.StartRecording(_system, fileName); RecordScriptMenu.Text = Resources.StopRecordingText; PlayScriptMenu.Enabled = false; SystemStatusLabel.Text = Resources.RecordingInProgressText; } private void StopScriptRecording() { ScriptManager.StopRecording(); RecordScriptMenu.Text = Resources.StartRecordingText; PlayScriptMenu.Enabled = true; SystemStatusLabel.Text = _controller.IsRunning ? Resources.SystemRunningText : Resources.SystemStoppedText; } private void OnScriptPlaybackCompleted(object sender, EventArgs e) { PlayScriptMenu.Text = Resources.StartPlaybackText; RecordScriptMenu.Enabled = true; SystemStatusLabel.Text = _controller.IsRunning ? Resources.SystemRunningText : Resources.SystemStoppedText; } private void InitKeymap() { _keyMap = new Dictionary(); _keyMap.Add(Keys.A, AltoKey.A); _keyMap.Add(Keys.B, AltoKey.B); _keyMap.Add(Keys.C, AltoKey.C); _keyMap.Add(Keys.D, AltoKey.D); _keyMap.Add(Keys.E, AltoKey.E); _keyMap.Add(Keys.F, AltoKey.F); _keyMap.Add(Keys.G, AltoKey.G); _keyMap.Add(Keys.H, AltoKey.H); _keyMap.Add(Keys.I, AltoKey.I); _keyMap.Add(Keys.J, AltoKey.J); _keyMap.Add(Keys.K, AltoKey.K); _keyMap.Add(Keys.L, AltoKey.L); _keyMap.Add(Keys.M, AltoKey.M); _keyMap.Add(Keys.N, AltoKey.N); _keyMap.Add(Keys.O, AltoKey.O); _keyMap.Add(Keys.P, AltoKey.P); _keyMap.Add(Keys.Q, AltoKey.Q); _keyMap.Add(Keys.R, AltoKey.R); _keyMap.Add(Keys.S, AltoKey.S); _keyMap.Add(Keys.T, AltoKey.T); _keyMap.Add(Keys.U, AltoKey.U); _keyMap.Add(Keys.V, AltoKey.V); _keyMap.Add(Keys.W, AltoKey.W); _keyMap.Add(Keys.X, AltoKey.X); _keyMap.Add(Keys.Y, AltoKey.Y); _keyMap.Add(Keys.Z, AltoKey.Z); _keyMap.Add(Keys.D0, AltoKey.D0); _keyMap.Add(Keys.D1, AltoKey.D1); _keyMap.Add(Keys.D2, AltoKey.D2); _keyMap.Add(Keys.D3, AltoKey.D3); _keyMap.Add(Keys.D4, AltoKey.D4); _keyMap.Add(Keys.D5, AltoKey.D5); _keyMap.Add(Keys.D6, AltoKey.D6); _keyMap.Add(Keys.D7, AltoKey.D7); _keyMap.Add(Keys.D8, AltoKey.D8); _keyMap.Add(Keys.D9, AltoKey.D9); _keyMap.Add(Keys.Space, AltoKey.Space); _keyMap.Add(Keys.OemPeriod, AltoKey.Period); _keyMap.Add(Keys.Oemcomma, AltoKey.Comma); _keyMap.Add(Keys.OemQuotes, AltoKey.Quote); _keyMap.Add(Keys.Oem5, AltoKey.BSlash); _keyMap.Add(Keys.OemBackslash, AltoKey.BSlash); _keyMap.Add(Keys.OemQuestion, AltoKey.FSlash); _keyMap.Add(Keys.Oemplus, AltoKey.Plus); _keyMap.Add(Keys.OemMinus, AltoKey.Minus); _keyMap.Add(Keys.Escape, AltoKey.ESC); _keyMap.Add(Keys.Delete, AltoKey.DEL); _keyMap.Add(Keys.Left, AltoKey.Arrow); _keyMap.Add(Keys.LShiftKey, AltoKey.LShift); _keyMap.Add(Keys.RShiftKey, AltoKey.RShift); _keyMap.Add(Keys.ControlKey, AltoKey.CTRL); _keyMap.Add(Keys.Return, AltoKey.Return); _keyMap.Add(Keys.F1, AltoKey.BlankTop); _keyMap.Add(Keys.F2, AltoKey.BlankMiddle); _keyMap.Add(Keys.F3, AltoKey.BlankBottom); _keyMap.Add(Keys.F4, AltoKey.Lock); _keyMap.Add(Keys.Back, AltoKey.BS); _keyMap.Add(Keys.Tab, AltoKey.TAB); _keyMap.Add(Keys.OemSemicolon, AltoKey.Semicolon); _keyMap.Add(Keys.OemOpenBrackets, AltoKey.LBracket); _keyMap.Add(Keys.OemCloseBrackets, AltoKey.RBracket); _keyMap.Add(Keys.Down, AltoKey.LF); _keysetMap = new Dictionary(); // Map the 5 keyset keys to F5-F9. _keysetMap.Add(Keys.F5, AltoKeysetKey.Keyset0); _keysetMap.Add(Keys.F6, AltoKeysetKey.Keyset1); _keysetMap.Add(Keys.F7, AltoKeysetKey.Keyset2); _keysetMap.Add(Keys.F8, AltoKeysetKey.Keyset3); _keysetMap.Add(Keys.F9, AltoKeysetKey.Keyset4); } private ImageCodecInfo GetEncoderForFormat(ImageFormat format) { ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); foreach (ImageCodecInfo codec in codecs) { if (codec.FormatID == format.Guid) { return codec; } } return null; } private void CreateTridentMenu() { // // Add eight sub-menus, one per drive. // _tridentImageNames = new List(8); for (int i=0;i<8;i++) { // Parent menu item ToolStripMenuItem tridentMenu = new ToolStripMenuItem(String.Format("Drive {0}", i)); // Children: // - Load // - Unload // - New // - Pack Name (disabled) // ToolStripMenuItem loadMenu = new ToolStripMenuItem("Load...", null, OnTridentLoadClick); loadMenu.Tag = i; ToolStripMenuItem unloadMenu = new ToolStripMenuItem("Unload", null, OnTridentUnloadClick); unloadMenu.Tag = i; ToolStripMenuItem newMenu = new ToolStripMenuItem("New...", null, OnTridentNewClick); newMenu.Tag = i; ToolStripMenuItem imageMenu = new ToolStripMenuItem(Resources.NoImageLoadedText); imageMenu.Tag = i; imageMenu.Enabled = false; _tridentImageNames.Add(imageMenu); tridentMenu.DropDownItems.Add(loadMenu); tridentMenu.DropDownItems.Add(unloadMenu); tridentMenu.DropDownItems.Add(newMenu); tridentMenu.DropDownItems.Add(imageMenu); TridentToolStripMenuItem.DropDownItems.Add(tridentMenu); } } // Display related data. // Note: display is actually 606 pixels wide, but that's not an even multiple of 8, so we round up. // Two backbuffers and references to the current / last buffer for rendering private byte[] _displayData0 = new byte[808 * 76 + 4]; // + 4 to make cursor display logic simpler. private byte[] _displayData1 = new byte[808 * 76 + 4]; // + 4 to make cursor display logic simpler. private byte[] _currentBuffer; private byte[] _lastBuffer; private int _frame; private Bitmap _displayBuffer; private Rectangle _displayRect = new Rectangle(0, 0, 608, 808); private delegate void DisplayDelegate(); // Speed throttling FrameTimer _frameTimer; // Input related data // Keyboard mapping from windows vkeys to Alto keys private Dictionary _keyMap; // Keyset mapping from windows vkeys to Keyset keys private Dictionary _keysetMap; // Mouse capture state private bool _mouseCaptured; private bool _currentCursorState; private bool _skipNextMouseMove; // Full-screen state private bool _fullScreenDisplay; private Point _windowedDisplayBoxLocation; private Size _windowedDisplayBoxSize; // The Alto system we're running private AltoSystem _system; // The controller for the system (to allow control by both debugger and main UI) private ExecutionController _controller; // The debugger, which may or may not be running. private Debugger _debugger; // Status bar things System.Timers.Timer _fpsTimer; System.Timers.Timer _diskAccessTimer; private DiskActivityType _lastActivity; private Image _diskIdleImage; private Image _diskReadImage; private Image _diskWriteImage; private Image _diskSeekImage; // Trident menu items for disk names private List _tridentImageNames; // Whether to commit disk images back to disk at shutdown private bool _commitDisksAtShutdown; } }