/* * GamePanel.cs - Displays text and graphics provided by the Level 9 * interpreter and handles user interaction. * * Copyright (C) 2004 - 2011 Andreas Scherrer * * This 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 2 of the License, or * (at your option) any later version. * * This software 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 software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Threading; using System.Windows.Forms; namespace Level9 { /// /// Displays text and graphics provided by the Level 9 interpreter and /// handles user interaction. /// public class GamePanel : Panel { // -- Constants ------------------------------------------------------ /// /// The width of the borders around a title bar or status bar. /// const int BAR_XY_SPACING = 2; /// /// The distance between adjacent elements in the title bar or /// status bar. /// const int BAR_ELEMENT_SPACING = 10; /// /// The width of the upper and lower borders around a text area. /// const int TEXT_Y_SPACING = 5; /// /// The width of the left and right borders around a text area. /// const int TEXT_X_SPACING = 2; /// /// The title info displayed on game file errors. /// const string TITLE_ERROR = "*ERROR*"; /// /// The update region identifier for all regions. /// const int REGION_ALL = 0x001F; /// /// The update region identifier for the title bar. /// const int REGION_TITLE_BAR = 0x0001; /// /// The update region identifier for the timer. /// const int REGION_TIMER = 0x0002; /// /// The update region identifier for the picture area. /// const int REGION_PICTURE = 0x0004; /// /// The update region identifier for the text area. /// const int REGION_TEXT = 0x0008; /// /// The update region identifier for the status bar. /// const int REGION_STATUS = 0x0010; // -- Delegates ------------------------------------------------------ /// /// Represents the method that fires an InputMode event. /// delegate void FireInputModeEventDelegate(bool active); // -- Attributes ----------------------------------------------------- /// /// A handler for text provided by the Level 9 interpreter. /// GameText gameText = null; /// /// A handler for graphics provided by the Level 9 interpreter. /// GameGraphics gameGraphics = null; /// /// The current game title. /// string title = null; /// /// This property stores the starting time of a new game. /// DateTime gameStart; /// /// A textual representation of the game timer. /// string timerString = null; /// /// The currently displayed picture. /// Bitmap picture = null; /// /// A timer to control the in-game time display. /// System.Windows.Forms.Timer gameTimer = new System.Windows.Forms.Timer(); /// /// The event data of the last OsInput event. /// OsInputEventArgs inputArgs = null; /// /// The event data of the last OsReadchar event. /// OsReadcharEventArgs readcharArgs = null; /// /// The event data of the last InputMode event. /// InputModeEventArgs inputModeArgs = null; /// /// This property indicates whether the text area is too small to /// display all the available lines provided by the Level 9 interpreter. /// bool moreLines = false; /// /// This property indicates whether the Level 9 interpreter waits for /// user input. /// bool inputMode = false; /// /// A bitmap that contains the rendered game title. /// Bitmap titleBitmap = null; /// /// A bitmap that contains the rendered game timer. /// Bitmap timerBitmap = null; /// /// A bitmap that contains the rendered in-game text. /// Bitmap textBitmap = null; /// /// A bitmap that contains the rendered status information. /// Bitmap statusBitmap = null; /// /// A bitmap that contains the rendered client area size. /// Bitmap sizeBitmap = null; /// /// A bitmap that contains the client area. /// Bitmap screenBitmap = null; /// /// A renderer that draws the game title with a bitmap font. /// BitmapFontRenderer titleRenderer = new BitmapFontRenderer(); /// /// A renderer that draws the game timer with a bitmap font. /// BitmapFontRenderer timerRenderer = new BitmapFontRenderer(); /// /// A renderer that draws in-game text with a bitmap font. /// BitmapFontRenderer textRenderer = new BitmapFontRenderer(); /// /// A renderer that draws status information with a bitmap font. /// BitmapFontRenderer statusRenderer = new BitmapFontRenderer(); /// /// A renderer that draws the client area size with a bitmap font. /// BitmapFontRenderer sizeRenderer = new BitmapFontRenderer(); // -- Events --------------------------------------------------------- /// /// Occurs when the Level 9 interpreter waits for user input. /// public event InputModeEventHandler InputMode; // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the GamePanel class. /// public GamePanel() { this.SetStyle(ControlStyles.DoubleBuffer, false); this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); this.SetStyle(ControlStyles.ResizeRedraw, false); // // Timer initialization // gameTimer.Tick += new EventHandler(TimerTick); gameTimer.Interval = 1000; // // Text and graphics handlers // gameText = new GameText(); gameGraphics = new GameGraphics(); // // Event listeners // Interpreter.NewGameFile += new NewGameFileEventHandler(InterpreterNewGameFile); Interpreter.GameFileError += new GameFileErrorEventHandler(InterpreterGameFileError); Interpreter.OsInput += new OsInputEventHandler(InterpreterOsInput); Interpreter.OsReadchar += new OsReadcharEventHandler(InterpreterOsReadchar); Interpreter.OsFlush += new OsFlushEventHandler(InterpreterOsFlush); Config.Instance.ConfigChanged += new ConfigChangedEventHandler(ConfigChanged); gameGraphics.GraphicsOff += new GraphicsOffEventHandler(GameGraphicsGraphicsOff); gameGraphics.PictureCreated += new PictureCreatedEventHandler(GameGraphicsPictureCreated); } // -- Event handling ------------------------------------------------- /// /// Releases the unmanaged resources used by the Control and its /// child controls and optionally releases the managed resources. /// /// /// true to release both managed and unmanaged resources; false to /// release only unmanaged resources /// protected override void Dispose(bool disposing) { base.Dispose(disposing); if (titleBitmap != null) { titleBitmap.Dispose(); titleBitmap = null; } if (timerBitmap != null) { timerBitmap.Dispose(); timerBitmap = null; } if (textBitmap != null) { textBitmap.Dispose(); textBitmap = null; } if (statusBitmap != null) { statusBitmap.Dispose(); statusBitmap = null; } if (sizeBitmap != null) { sizeBitmap.Dispose(); sizeBitmap = null; } if (screenBitmap != null) { screenBitmap.Dispose(); screenBitmap = null; } } /// /// Handles the LostFocus event. /// /// an EventArgs that contains the event data protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); if (inputMode) { gameText.HideCaret(); } UpdateScreen(REGION_TEXT); } /// /// Handles the GotFocus event. /// /// an EventArgs that contains the event data protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); if (inputMode) { gameText.ShowCaret(); } UpdateScreen(REGION_TEXT); } /// /// Handles the KeyDown event. /// /// a KeyEventArgs that contains the event data protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); if (!inputMode || moreLines) { return; } switch(e.KeyCode) { case Keys.Up: gameText.HistoryUp(); break; case Keys.Down: gameText.HistoryDown(); break; case Keys.Left: gameText.CaretLeft(); break; case Keys.Right: gameText.CaretRight(); break; case Keys.Back: gameText.CaretBack(); break; case Keys.Delete: gameText.CaretDelete(); break; case Keys.Home: gameText.CaretHome(); break; case Keys.End: gameText.CaretEnd(); break; case Keys.Return: inputArgs.Input = gameText.GetInput(); GameThread.Resume(); break; case Keys.V: if (e.Modifiers == Keys.Control) { string text = GetClipboardText(); gameText.CaretIns(text); } break; default: break; } // // Handle text area invalidation for non-control keys // switch(e.KeyCode) { case Keys.Up: case Keys.Down: case Keys.Left: case Keys.Right: case Keys.Delete: case Keys.Home: case Keys.End: gameText.SetTextOffset(); UpdateScreen(REGION_TEXT); break; } } /// /// Handles the KeyPress event. /// /// /// a KeyPressEventArgs that contains the event data /// protected override void OnKeyPress(KeyPressEventArgs e) { if (!moreLines) { if (readcharArgs != null) { readcharArgs.Character = Convert.ToByte(e.KeyChar); } if (!Char.IsControl(e.KeyChar) && inputMode) { gameText.CaretIns(e.KeyChar); } } gameText.SetTextOffset(); UpdateScreen(REGION_TEXT | REGION_STATUS); } /// /// Handles the SizeChanged event. /// /// a EventArgs that contains the event data protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); if (screenBitmap != null) { screenBitmap.Dispose(); screenBitmap = null; } if (this.ClientSize.Width > 0 && this.ClientSize.Height > 0) { screenBitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height, this.CreateGraphics()); UpdateScreen(REGION_ALL); } } /// /// Handles the PaintBackground event. /// /// /// a PaintEventArgs that contains the event data /// protected override void OnPaintBackground(PaintEventArgs e) { if (title == null) { base.OnPaintBackground(e); } } /// /// Handles the Paint event. /// /// /// a PaintEventArgs that contains the event data /// protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (screenBitmap != null && title != null) { Graphics g = e.Graphics; g.InterpolationMode = InterpolationMode.NearestNeighbor; if (this.ClientRectangle.Equals(e.ClipRectangle)) { g.DrawImageUnscaled(screenBitmap, 0, 0); } else { Rectangle rect = e.ClipRectangle; g.DrawImage(screenBitmap, rect, rect.X, rect.Y, rect.Width, rect.Height, GraphicsUnit.Pixel); } } } /// /// Updates screen regions. /// /// the screen region identifier void UpdateScreen(int region) { if (title == null || screenBitmap == null || this.IsDisposed) { return; } Graphics g = Graphics.FromImage(screenBitmap); g.InterpolationMode = InterpolationMode.NearestNeighbor; Rectangle clientRect = this.ClientRectangle; int clientWidth = this.ClientSize.Width; int clientHeight = this.ClientSize.Height; BitmapFont font = Config.Instance.Font; int fontHeight = font.Height; int fontWidth = font.Width; int barHeight = fontHeight + (BAR_XY_SPACING * 2); Color backColor = Config.Instance.BackColor; Color textColor = Config.Instance.TextColor; Color barFgColor = Config.Instance.BarTextColor; Color barBgColor = Config.Instance.BarBackColor; SolidBrush bgBrush = new SolidBrush(backColor); SolidBrush barBgBrush = new SolidBrush(barBgColor); Rectangle rect; int screenY = 0; List updateRegions = new List(); // // Title bar // if ((region & REGION_TITLE_BAR) > 0 && Config.Instance.ShowTitleBar) { rect = new Rectangle(0, 0, clientWidth, barHeight); if (rect.IntersectsWith(clientRect)) { // // Title bar background // g.FillRectangle(barBgBrush, rect); // // Game title // titleBitmap = titleRenderer.RenderText(title, font, barBgColor, barFgColor); if (titleBitmap != null) { g.DrawImageUnscaled(titleBitmap, BAR_XY_SPACING, BAR_XY_SPACING); } updateRegions.Add(rect); } } // // Timer // string timer; if ((region & (REGION_TIMER | REGION_TITLE_BAR)) > 0 && Config.Instance.ShowTitleBar && Config.Instance.ShowTimer && !title.Equals(TITLE_ERROR) && (timer = GetTimerString()) != null) { int timerWidth = fontWidth * timer.Length; int timerX = clientWidth - BAR_XY_SPACING - timerWidth; int timerMinX = BAR_XY_SPACING + BAR_ELEMENT_SPACING + fontWidth * title.Length; if (timerX < timerMinX) { timerX = timerMinX; } rect = new Rectangle(timerX, BAR_XY_SPACING, timerWidth, fontHeight); if (rect.IntersectsWith(clientRect)) { timerBitmap = timerRenderer.RenderText(timer, font, barBgColor, barFgColor); if (timerBitmap != null) { g.DrawImageUnscaled(timerBitmap, timerX, BAR_XY_SPACING); } updateRegions.Add(rect); } } if (Config.Instance.ShowTitleBar) { screenY += barHeight; } // // Picture area // if (picture != null) { int picHeight = picture.Height; if ((region & REGION_PICTURE) > 0) { int picWidth = picture.Width; int picX = (clientWidth - picWidth) / 2; if (picX < 0) { picX = 0; } // // Left border // if (picX > 0) { rect = new Rectangle(0, screenY, picX, picHeight); if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } } // // Picture // rect = new Rectangle(picX, screenY, picWidth, picHeight); if (rect.IntersectsWith(clientRect)) { g.DrawImageUnscaled(picture, rect); updateRegions.Add(rect); } // // Right border // if (picX + picWidth < clientWidth) { rect = new Rectangle(picX + picWidth, screenY, clientWidth - picWidth - picX, picHeight); if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } } } screenY += picHeight; } // // Text spacing // if ((region & (REGION_TEXT | REGION_PICTURE)) > 0) { int spacingHeight = clientHeight - screenY; if (Config.Instance.ShowStatusBar) { spacingHeight -= barHeight; } if (spacingHeight < 0) { spacingHeight = 0; } else if (spacingHeight > TEXT_Y_SPACING) { spacingHeight = TEXT_Y_SPACING; } rect = new Rectangle(0, screenY, clientWidth, spacingHeight); if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } } screenY += TEXT_Y_SPACING; // // Text area // if ((region & REGION_TEXT) > 0) { // // Text // int textWidth = clientWidth - TEXT_X_SPACING * 2; int textHeight = clientHeight - screenY - TEXT_Y_SPACING; if (Config.Instance.ShowStatusBar) { textHeight -= barHeight; } int lineSpacing = Config.Instance.LineSpacing; int lines = textHeight / (fontHeight + lineSpacing); rect = new Rectangle(TEXT_X_SPACING, screenY, textWidth, textHeight); textBitmap = null; if (gameText.GetParagraphCount() > 0 && lines > 0 && rect.IntersectsWith(clientRect)) { int changeOffset; int columns = textWidth / fontWidth; List output = gameText.GetOutput(lines, columns, out moreLines, out changeOffset); textBitmap = textRenderer.RenderText(output, font, lineSpacing, changeOffset, backColor, textColor, Config.Instance.InputColor); if (inputMode) { FireInputModeEvent(!moreLines); } if (textBitmap != null) { int textOffset = region != REGION_ALL ? (fontHeight + lineSpacing) * changeOffset : 0; rect = new Rectangle(TEXT_X_SPACING, screenY + textOffset, textBitmap.Width, textBitmap.Height - textOffset); g.DrawImage(textBitmap, rect, 0, textOffset, textBitmap.Width, textBitmap.Height - textOffset, GraphicsUnit.Pixel); updateRegions.Add(rect); } } if (textBitmap != null && region == REGION_ALL) { // // Left border // rect = new Rectangle(0, screenY, TEXT_X_SPACING, textBitmap.Height); if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } // // Right border // rect = new Rectangle( TEXT_X_SPACING + textBitmap.Width, screenY, clientWidth - textBitmap.Width - TEXT_X_SPACING, textBitmap.Height); if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } } if (textBitmap != null) { screenY += textBitmap.Height; } // // Bottom border // int borderHeight = clientHeight - screenY; int lineHeight = fontHeight + lineSpacing; if (Config.Instance.ShowStatusBar) { borderHeight -= barHeight; } if (borderHeight < 0) { borderHeight = 0; } if (region == REGION_ALL) { rect = new Rectangle(0, screenY, clientWidth, borderHeight); } else { if (borderHeight > lineHeight) { borderHeight = lineHeight; } rect = new Rectangle(0, screenY, clientWidth, borderHeight); } if (rect.IntersectsWith(clientRect)) { g.FillRectangle(bgBrush, rect); updateRegions.Add(rect); } } // // Status bar // if ((region & REGION_STATUS) > 0 && Config.Instance.ShowStatusBar) { int statusBarY = clientHeight - barHeight; if (statusBarY < barHeight) { statusBarY = barHeight; } rect = new Rectangle(0, statusBarY, clientWidth, barHeight); if (rect.IntersectsWith(clientRect)) { // // Status bar background // g.FillRectangle(barBgBrush, rect); // // Status message // int statusWidth = 0; string status = GetStatusString(); if (status != null && status.Length > 0) { statusWidth = fontWidth * status.Length; statusBitmap = statusRenderer.RenderText(status, font, barBgColor, barFgColor); if (statusBitmap != null) { g.DrawImageUnscaled(statusBitmap, BAR_XY_SPACING, statusBarY + BAR_XY_SPACING); } } // // Client size // string size = GetClientSizeString(); if (size != null && size.Length > 0) { int sizeX = clientWidth - BAR_XY_SPACING - fontWidth * size.Length; int sizeMinX = BAR_XY_SPACING + statusWidth + BAR_ELEMENT_SPACING; if (sizeX < sizeMinX) { sizeX = sizeMinX; } sizeBitmap = sizeRenderer.RenderText(size, font, barBgColor, barFgColor); if (sizeBitmap != null) { g.DrawImageUnscaled(sizeBitmap, sizeX, statusBarY + BAR_XY_SPACING); } } updateRegions.Add(rect); } } // // Cleanup and screen refresh // barBgBrush.Dispose(); bgBrush.Dispose(); g.Dispose(); if (region == REGION_ALL) { this.Invalidate(); } else { for (int i = 0; i < updateRegions.Count; i++) { this.Invalidate(updateRegions[i]); } } this.Update(); } /// /// Invoked when an interval of the game timer has expired. /// /// the object that originated the event /// an EventArgs that contains the event data void TimerTick(Object sender, EventArgs e) { if (Config.Instance.ShowTimer && title != null) { UpdateScreen(REGION_TIMER); } } /// /// Invoked when a running game waits for user input. /// /// /// an OsInputEventArgs that contains the event data /// void InterpreterOsInput(OsInputEventArgs e) { if (!this.InvokeRequired) { inputArgs = e; inputMode = true; gameText.SetInputPosition(); UpdateScreen(REGION_TEXT | REGION_STATUS); } else if (!GameThread.StopRequest) { OsInputEventHandler osInputHandler = new OsInputEventHandler(InterpreterOsInput); this.Invoke(osInputHandler, new object[] { e }); GameThread.Sleep(); inputMode = false; FireInputModeEvent(false); } } /// /// Invoked when a running game waits for a key press. /// /// /// an OsInputEventArgs that contains the event data /// void InterpreterOsReadchar(OsReadcharEventArgs e) { readcharArgs = e; Thread.Sleep(e.Millis); } /// /// Invoked when the interpreter flushes a portion of text. /// /// /// an OsFlushEventArgs that contains the event data /// void InterpreterOsFlush(OsFlushEventArgs e) { if (!this.InvokeRequired) { if (e.Command != Interpreter.Commands.Dictionary) { gameText.Append(e.Text); UpdateScreen(REGION_TEXT | REGION_STATUS); } } else if (!GameThread.StopRequest) { OsFlushEventHandler osFlushHandler = new OsFlushEventHandler(InterpreterOsFlush); this.Invoke(osFlushHandler, new object[] { e }); } } /// /// Invoked when a new game has been loaded. /// /// /// a NewGameFileEventArgs that contains the event data /// void InterpreterNewGameFile(NewGameFileEventArgs e) { if (!this.InvokeRequired) { Logger.Log(this, "InterpreterNewGameFile: " + Interpreter.GameFilePath); picture = null; title = Interpreter.GameTitle; gameStart = DateTime.Now; inputMode = false; inputModeArgs = null; FireInputModeEvent(false); gameText.Clear(); gameTimer.Start(); UpdateScreen(REGION_ALL); } else if (!GameThread.StopRequest) { NewGameFileEventHandler newGameFileHandler = new NewGameFileEventHandler(InterpreterNewGameFile); this.Invoke(newGameFileHandler, new object[] { e }); } } /// /// Invoked when the interpreter detects an error. /// /// /// a GameFileErrorEventArgs that contains the event data /// void InterpreterGameFileError(GameFileErrorEventArgs e) { if (!this.InvokeRequired) { Logger.Log(this, "InterpreterGameFileError"); picture = null; title = TITLE_ERROR; inputMode = false; inputModeArgs = null; FireInputModeEvent(false); gameText.Clear(); UpdateScreen(REGION_ALL); } else if (!GameThread.StopRequest) { GameFileErrorEventHandler gameFileErrorHandler = new GameFileErrorEventHandler(InterpreterGameFileError); this.Invoke(gameFileErrorHandler, new object[] { e }); } } /// /// Invoked when a picture is ready for display. /// /// /// a GameGraphicsPictureCreated that contains the event data /// void GameGraphicsPictureCreated(PictureCreatedEventArgs e) { if (!this.InvokeRequired) { Logger.Log(this, "GameGraphicsPictureCreated: " + e.Picture.Size); Size oldPictureSize = Size.Empty; if (picture != null) { oldPictureSize = picture.Size; picture.Dispose(); } picture = e.Picture; if (picture.Size.Equals(oldPictureSize)) { UpdateScreen(REGION_PICTURE); } else { UpdateScreen(REGION_ALL); } } else if (!GameThread.StopRequest) { PictureCreatedEventHandler pictureCreatedHandler = new PictureCreatedEventHandler(GameGraphicsPictureCreated); this.Invoke(pictureCreatedHandler, new object[] { e }); } } /// /// Invoked when game graphics have been turned off. /// /// /// a GraphicsOffEventArgs that contains the event data /// void GameGraphicsGraphicsOff(GraphicsOffEventArgs e) { if (!this.InvokeRequired) { Logger.Log(this, "GameGraphicsGraphicsOff"); picture = null; UpdateScreen(REGION_ALL); } else if (!GameThread.StopRequest) { GraphicsOffEventHandler graphicsOffHandler = new GraphicsOffEventHandler(GameGraphicsGraphicsOff); this.Invoke(graphicsOffHandler, new object[] { e }); } } /// /// Invoked when the configuration has been changed. /// /// /// a ConfigChangedEventArgs that contains the event data /// void ConfigChanged(ConfigChangedEventArgs e) { this.BackColor = Config.Instance.BackColor; this.ForeColor = Config.Instance.TextColor; UpdateScreen(REGION_ALL); } /// /// Notifies the input mode event listeners. /// /// /// true if the Level 9 interpreter waits for user input, else false /// void FireInputModeEvent(bool active) { bool fireEvent = false; if (InputMode != null) { if (inputModeArgs == null) { inputModeArgs = new InputModeEventArgs(active, moreLines); fireEvent = true; } else if (inputModeArgs.Active != active || inputModeArgs.MoreLines != moreLines) { inputModeArgs.Active = active; inputModeArgs.MoreLines = moreLines; fireEvent = true; } if (fireEvent) { InputMode(inputModeArgs); Logger.Log(this, "FireInputModeEvent: " + active + ", More lines: " + moreLines); } } } // -- Methods -------------------------------------------------------- /// /// Simulates key presses as if they have been done with the keyboard. /// /// the keys to 'press' public void SimulateKeys(string keys) { if (keys == null || (keys != null && keys.Length == 0)) { return; } for (int i = 0; i < keys.Length; i++) { SimulateKey(keys[i]); } } /// /// Simulates a key press as if it has been done with the keyboard. /// /// the key to 'press' public void SimulateKey(char key) { if (key == '\n') { KeyEventArgs args = new KeyEventArgs(Keys.Return); OnKeyDown(args); } else { KeyPressEventArgs args = new KeyPressEventArgs(key); OnKeyPress(args); } } /// /// Provides the timer string. /// /// the timer string, or null if no game is running string GetTimerString() { if (gameStart != null && GameThread.IsRunning) { DateTime now = DateTime.Now; DateTime diff = new DateTime(now.Ticks - gameStart.Ticks); timerString = diff.ToString("HH:mm:ss"); } return timerString; } /// /// Provides the status message string. /// /// the status message string string GetStatusString() { string status = ""; if (title != null) { if (title.Equals(TITLE_ERROR)) { status = "Game file error."; } else if (!GameThread.IsRunning) { status = "Game stopped."; } else { status = "Game running."; } } if (moreLines) { status = "Press a key for more..."; } if (gameText.GetParagraphCount() > 0 && gameText.GetOutputLinesCount() < 2) { status = "Please enlarge the game area."; } return status; } /// /// Provides the client size string. /// /// the client size string string GetClientSizeString() { return "Size: " + this.ClientSize.Width + "x" + this.ClientSize.Height; } /// /// Gets text from the system clipboard. /// /// the clipboard text, or null if none exists string GetClipboardText() { string text = null; IDataObject iData = Clipboard.GetDataObject(); if (iData != null && iData.GetDataPresent(DataFormats.Text)) { text = (string)iData.GetData(DataFormats.Text); } return text; } /// /// Displays the scrollback window. /// public void ShowScrollback() { gameText.ShowScrollback(this.Parent); } /// /// Saves a screenshot of the game area. /// public void SaveScreenshot() { if (screenBitmap == null) { return; } string filename = FileUtils.SFDPngFiles("Save Screenshot..."); if (filename != null) { try { screenBitmap.Save(filename, ImageFormat.Png); } catch (Exception ex) { ErrorHandler.Handle(this, "Error writing screenshot file.", ex); } } } } /// /// Represents the method that handles an InputMode event. /// public delegate void InputModeEventHandler(InputModeEventArgs e); /// /// Provides data for the InputMode event. /// public class InputModeEventArgs : EventArgs { // -- Attributes ----------------------------------------------------- /// /// This property indicates whether the Level 9 interpreter waits for /// user input. /// bool active = false; /// /// This property indicates whether the text area is too small to /// display all the available lines provided by the Level 9 interpreter. /// bool moreLines = false; /// /// Timestamp of the InputMode event. /// DateTime timestamp; // -- Constructors --------------------------------------------------- /// /// Initializes a new instance of the InputModeEventArgs class. /// /// /// true if the Level 9 interpreter waits for user input, else false /// /// /// true if the text area is too small to display all the available /// lines provided by the Level 9 interpreter /// public InputModeEventArgs(bool active, bool moreLines) { this.active = active; this.moreLines = moreLines; this.timestamp = DateTime.Now; } // -- Accessors ------------------------------------------------------ /// /// Gets or sets a value that indicates whether the Level 9 interpreter /// waits for user input. /// public bool Active { get { return active; } set { active = value; timestamp = DateTime.Now; } } /// /// Gets or sets a value that indicates whether the text area is too /// small to display all the available lines provided by the Level 9 /// interpreter. /// public bool MoreLines { get { return moreLines; } set { moreLines = value; timestamp = DateTime.Now; } } /// /// Gets the timestamp of the InputMode event. /// public DateTime Timestamp { get { return timestamp; } } } }