{*_* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Program:      EMULVT.PAS
Description:  Delphi component which does Ansi terminal emulation
              Not every escape sequence is implemented, but a large subset.
Author:       François PIETTE
Creation:     May, 1996
Version:      2.23
EMail:        http://www.overbyte.be       francois.piette@overbyte.be
Support:      Use the mailing list twsocket@elists.org
              Follow "support" link at http://www.overbyte.be for subscription.
Legal issues: Copyright (C) 1996-2007 by François PIETTE
              Rue de Grady 24, 4053 Embourg, Belgium. Fax: +32-4-365.74.56
              <francois.piette@overbyte.be>

              This software is provided 'as-is', without any express or
              implied warranty.  In no event will the author be held liable
              for any  damages arising from the use of this software.

              Permission is granted to anyone to use this software for any
              purpose, including commercial applications, and to alter it
              and redistribute it freely, subject to the following
              restrictions:

              1. The origin of this software must not be misrepresented,
                 you must not claim that you wrote the original software.
                 If you use this software in a product, an acknowledgment
                 in the product documentation would be appreciated but is
                 not required.

              2. Altered source versions must be plainly marked as such, and
                 must not be misrepresented as being the original software.

              3. This notice may not be removed or altered from any source
                 distribution.

              4. You must register this software by sending a picture postcard
                 to the author. Use a nice stamp and mention your name, street
                 address, EMail address and any comment you like to say.

Updates:
Jul 22, 1997  Some optimization
              Adapted to Delphi 3
Sep 05, 1997  Version 2.01
Dec 16, 1997  V2.02 Corrected a bug int the paint routine which caused GDI
                    resource leak when color was used.
Feb 24, 1998  V2.03 Added AddFKey function
Jul 15, 1998  V2.04 Adapted to Delphi 4 (moved DoKeyBuffer to protected section)
Dec 04, 1998  V2.05 Added 'single char paint' and 'char zoom' features.
Dec 09, 1998  V2.10 Added graphic char drawing using graphic primitives
                    Added (with permission) scroll back code developed by Steve
                    Endicott <s_endicott@compuserve.com>
Dec 21, 1998  V2.11 Corrected some screen update problems related to scrollback.
                    Added fixes from Steve Endicott.
                    Beautified code.
Mar 14, 1999  V2.12 Added OnKeyDown event.
                    Corrected a missing band at right of screen when painting.
Aug 15, 1999  V2.13 Moved KeyPress procedure to public section for BCB4 compat.
Aug 20, 1999  V2.14 Added compile time options. Revised for BCB4.
Nov 12, 1999  V2.15 Corrected display attribute error in delete line.
                    Checked for range in SetLines/GetLine
Aug 09, 2000  V2.16 Wilfried Mestdagh" <wilfried_sonal@compuserve.com> and
                    Steve Endicott <s_endicott@compuserve.com> corrected a
                    bug related to scroll back buffer. See WM + SE 09/08/00
                    tags in code.
Jul 28, 2001  V2.17 Made FCharPos and FLinePos member variables instead of
                    global to avoid conflict when sevaral components are used
                    simultaneously. Suggested by Jeroen Cranendonk
                    <j.p.cranendonk@student.utwente.nl>
Jan 03, 2002  V2.19 Don't adjust scroll bar if not visible
                    Make properties with TopMargin, LeftMargin, RightMargin,
                    BottomMargin.
Jan 10, 2002  V2.19 Fixed SetLineHeight to fill FLinePos up to last item.
May 31, 2004  V2.20 Used ICSDEFS.INC
Jan 10, 2005  V2.21 Fixed TCustomEmulVT.SetCaret so that caret is updated
                    only when component has the focus. Thanks to Xie, Qi-Ming
                    <xieqm@axp1.csie.ncu.edu.tw> for finding an easy test case.
Jan 13, 2005 V2.22 Replaced symbol "Debug" by "DEBUG_OUTPUT"
Mar 26, 2006 V2.23 Added key #$1C (Enter key) in all 3 FKeys tables so that
                   using the enter key is the same as sending CRLF.


 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
unit EmulVT;

{$B-}           { Enable partial boolean evaluation   }
{$T-}           { Untyped pointers                    }
{$X+}           { Enable extended syntax              }
{$I ICSDEFS.INC}
{$IFDEF DELPHI6_UP}
    {$WARN SYMBOL_PLATFORM   OFF}
    {$WARN SYMBOL_LIBRARY    OFF}
    {$WARN SYMBOL_DEPRECATED OFF}
{$ENDIF}
{$IFNDEF VER80}   { Not for Delphi 1                    }
    {$H+}         { Use long strings                    }
    {$J+}         { Allow typed constant to be modified }
{$ENDIF}
{$IFDEF BCB3_UP}
    {$ObjExportAll On}
{$ENDIF}

interface

{$DEFINE SINGLE_CHAR_PAINT}
{$DEFINE CHAR_ZOOM}

uses
    Messages,
{$IFDEF USEWINDOWS}
    Windows,
{$ELSE}
    WinTypes, WinProcs,
{$ENDIF}
    SysUtils, Classes, Graphics, Controls, Forms, StdCtrls, ClipBrd;

const
  EmulVTVersion      = 223;
  CopyRight : String = ' TEmulVT (c) 1996-2007 F. Piette V2.23 ';
  MAX_ROW            = 50;
  MAX_COL            = 160;
  NumPaletteEntries  = 16;

type
  TBackColors     = (vtsBlack, vtsRed,     vtsGreen, vtsYellow,
                     vtsBlue,  vtsMagenta, vtsCyan,  vtsWhite);

  TScreenOption   = (vtoBackColor, vtoCopyBackOnClear);
  TScreenOptions  = set of TScreenOption;
  TXlatTable      = array [0..255] of char;
  PXlatTable      = ^TXlatTable;
  TFuncKeyValue   = String[50];
  PFuncKeyValue   = ^TFuncKeyValue;
  TFuncKey        = record
                        ScanCode : Char;
                        Shift    : TShiftState;
                        Ext      : Boolean;
                        Value    : TFuncKeyValue;
                    end;
  TFuncKeysTable  = array [0..63] of TFuncKey;
  PFuncKeysTable  = ^TFuncKeysTable;
  TKeyBufferEvent = procedure (Sender : TObject; Buffer : PChar; Len : Integer) of object;
  TKeyDownEvent   = procedure (Sender        : TObject;
                               var VirtKey   : Integer;
                               var Shift     : TShiftState;
                               var ShiftLock : Boolean;
                               var ScanCode  : Char;
                               var Ext       : Boolean) of object;


type
  { TLine is an object used to hold one line of text on screen }
  TLine = class(TObject)
  public
    Txt : array [0..MAX_COL] of Char;
    Att : array [0..MAX_COL] of Byte;
    constructor Create;
    procedure   Clear(Attr : Byte);
  end;
  TLineArray      = array [0..16382] of TLine;
  PLineArray      = ^TLineArray;

  { TScreen is an object to hold an entire screen of line and handle }
  { Ansi escape sequences to update this virtual screen              }
  TScreen = class(TObject)
  public
    FLines           : PLineArray;
    FRow             : Integer;
    FCol             : Integer;
    FRowSaved        : Integer;
    FColSaved        : Integer;
    FScrollRowTop    : Integer;
    FScrollRowBottom : Integer;
    FAttribute       : Byte;
    FForceHighBit    : Boolean;
    FReverseVideo    : Boolean;
    FUnderLine       : Boolean;
    FRowCount        : Integer;
    FColCount        : Integer;
    FBackRowCount    : Integer;
    FBackEndRow      : Integer;
    FBackColor       : TBackColors;
    FOptions         : TScreenOptions;
    FEscBuffer       : String[80];
    FEscFlag         : Boolean;
    Focused          : Boolean;
    FAutoLF          : Boolean;
    FAutoCR          : Boolean;
    FAutoWrap        : Boolean;
    FCursorOff       : Boolean;
    FCKeyMode        : Boolean;
    FNoXlat          : Boolean;
    FNoXlatInitial   : Boolean;
    FCntLiteral      : Integer;
    FCarbonMode      : Boolean;
    FXlatInputTable  : PXlatTable;
    FXlatOutputTable : PXlatTable;
    FCharSetG0       : Char;
    FCharSetG1       : Char;
    FCharSetG2       : Char;
    FCharSetG3       : Char;
    FAllInvalid      : Boolean;
    FInvRect         : TRect;
    FOnCursorVisible : TNotifyEvent;
    constructor Create;
    destructor  Destroy; override;
    procedure   AdjustFLines(NewCount : Integer);
    procedure   CopyScreenToBack;
    procedure   SetRowCount(NewCount : Integer);
    procedure   SetBackRowCount(NewCount : Integer);
    procedure   InvRect(nRow, nCol : Integer);
    procedure   InvClear;
    procedure   SetLines(I : Integer; Value : TLine);
    function    GetLines(I : Integer) : TLine;
    procedure   WriteChar(Ch : Char);
    procedure   WriteStr(Str : String);
    function    ReadStr : String;
    procedure   GotoXY(X, Y : Integer);
    procedure   WriteLiteralChar(Ch : Char);
    procedure   ProcessEscape(EscCmd : Char);
    procedure   SetAttr(Att : Char);
    procedure   CursorRight;
    procedure   CursorLeft;
    procedure   CursorDown;
    procedure   CursorUp;
    procedure   CarriageReturn;
    procedure   ScrollUp;
    procedure   ScrollDown;
    procedure   ClearScreen;
    procedure   BackSpace;
    procedure   Eol;
    procedure   Eop;
    procedure   ProcessESC_D;                { Index                   }
    procedure   ProcessESC_M;                { Reverse index           }
    procedure   ProcessESC_E;                { Next line               }
    procedure   ProcessCSI_u;                { Restore Cursor          }
    procedure   ProcessCSI_I;                { Select IBM char set     }
    procedure   ProcessCSI_J;                { Clear the screen        }
    procedure   ProcessCSI_K;                { Erase to End of Line    }
    procedure   ProcessCSI_L;                { Insert Line             }
    procedure   ProcessCSI_M;                { Delete Line             }
    procedure   ProcessCSI_m_lc;             { Select Attributes       }
    procedure   ProcessCSI_n_lc;             { Cursor position report  }
    procedure   ProcessCSI_at;               { Insert character        }
    procedure   ProcessCSI_r_lc;             { Scrolling margins       }
    procedure   ProcessCSI_s_lc;             { Save cursor location    }
    procedure   ProcessCSI_u_lc;             { Restore cursor location }
    procedure   ProcessCSI_7;                { Save cursor location    }
    procedure   ProcessCSI_8;                { Restore cursor location }
    procedure   ProcessCSI_H;                { Set Cursor Position     }
    procedure   ProcessCSI_h_lc;             { Terminal mode set       }
    procedure   ProcessCSI_l_lc;             { Terminal mode reset     }
    procedure   ProcessCSI_A;                { Cursor Up               }
    procedure   ProcessCSI_B;                { Cursor Down             }
    procedure   ProcessCSI_C;                { Cursor Right            }
    procedure   ProcessCSI_D;                { Cursor Left             }
    procedure   ProcessCSI_P;                { Delete Character        }
    procedure   ProcessCSI_S;                { Scroll up               }
    procedure   ProcessCSI_T;                { Scroll down             }
    procedure   process_charset_G0(EscCmd : Char);{ G0 character set   }
    procedure   process_charset_G1(EscCmd : Char);{ G1 character set   }
    procedure   process_charset_G2(EscCmd : Char);{ G2 character set   }
    procedure   process_charset_G3(EscCmd : Char);{ G3 character set   }
    procedure   UnimplementedEscape(EscCmd : Char);
    procedure   InvalidEscape(EscCmd : Char);
    function    GetEscapeParam(From : Integer; var Value : Integer) : Integer;
    property    OnCursorVisible : TNotifyEvent read  FonCursorVisible
                                               write FOnCursorVisible;
    property    Lines[I : Integer] : TLine read GetLines write SetLines;
  end;

  { TCustomEmulVT is an visual component wich does the actual display }
  { of a TScreen object wich is the virtual screen                    }
  { No property is published. See TEmulVT class                       }
  TCustomEmulVT = class(TCustomControl)
  private
    FCharPos         : array [0..MAX_COL + 1] of integer;
    FLinePos         : array [0..MAX_ROW + 1] of integer;
    FFileHandle      : TextFile;
    FCursorVisible   : Boolean;
    FCaretShown      : Boolean;
    FCaretCreated    : Boolean;
    FLineHeight      : Integer;
    FLineZoom        : Single;
    FCharWidth       : Integer;
    FCharZoom        : Single;
    FGraphicDraw     : Boolean;
    FInternalLeading : Integer;
    FBorderStyle     : TBorderStyle;
    FBorderWidth     : Integer;
    FAutoRepaint     : Boolean;
    FFont            : TFont;
    FVScrollBar      : TScrollBar;
    FTopLine         : Integer;
    FLocalEcho       : Boolean;
    FOnKeyBuffer     : TKeyBufferEvent;
    FOnKeyDown       : TKeyDownEvent;
    FFKeys           : Integer;
    FMonoChrome      : Boolean;
    FLog             : Boolean;
    FAppOnMessage    : TMessageEvent;
    FFlagCirconflexe : Boolean;
    FFlagTrema       : Boolean;
    FSelectRect      : TRect;
    FTopMargin       : Integer;
    FLeftMargin      : Integer;
    FRightMargin     : Integer;
    FBottomMargin    : Integer;
    FPal             : HPalette;
    FPaletteEntries  : array[0..NumPaletteEntries - 1] of TPaletteEntry;
    FMarginColor     : Integer;
    procedure   WMPaint(var Message: TWMPaint); message WM_PAINT;
    procedure   WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
    procedure   WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
    procedure   WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
    procedure   WMPaletteChanged(var Message : TMessage); message WM_PALETTECHANGED;
    procedure   VScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
    procedure   SetCaret;
    procedure   AdjustScrollBar;
    function    ProcessFKeys(ScanCode: Char; Shift: TShiftState; Ext: Boolean) : Boolean;
    function    FindFKeys(ScanCode: Char; Shift: TShiftState;
                          Ext: Boolean) : PFuncKeyValue;
    procedure   CursorVisibleEvent(Sender : TObject);
    procedure   SetFont(Value : TFont);
    procedure   SetAutoLF(Value : Boolean);
    procedure   SetAutoCR(Value : Boolean);
    procedure   SetXlat(Value : Boolean);
    procedure   SetLog(Value : Boolean);
    procedure   SetRows(Value : Integer);
    procedure   SetCols(Value : Integer);
    procedure   SetBackRows(Value : Integer);
    procedure   SetTopLine(Value : Integer);
    procedure   SetBackColor(Value : TBackColors);
    procedure   SetOptions(Value : TScreenOptions);
    procedure   SetLineHeight(Value : Integer);
    function    GetAutoLF : Boolean;
    function    GetAutoCR : Boolean;
    function    GetXlat : Boolean;
    function    GetRows : Integer;
    function    GetCols : Integer;
    function    GetBackRows : Integer;
    function    GetBackColor : TBackColors;
    function    GetOptions : TScreenOptions;
    procedure SetMarginColor(const Value: Integer);
    procedure SetLeftMargin(const Value: Integer);
    procedure SetBottomMargin(const Value: Integer);
    procedure SetRightMargin(const Value: Integer);
    procedure SetTopMargin(const Value: Integer);
  protected
    FScreen          : TScreen;
    procedure   AppMessageHandler(var Msg: TMsg; var Handled: Boolean);
    procedure   DoKeyBuffer(Buffer : PChar; Len : Integer); virtual;
    procedure   PaintGraphicChar(DC   : HDC;
                                 X, Y : Integer;
                                 rc   : PRect;
                                 ch   : Char);
  public
    constructor Create(AOwner: TComponent); override;
    destructor  Destroy; override;
    procedure   ShowCursor;
    procedure   SetCursor(Row, Col : Integer);
    procedure   WriteChar(Ch : Char);
    procedure   WriteStr(Str : String);
    procedure   WriteBuffer(Buffer : Pointer; Len : Integer);
    function    ReadStr : String;
    procedure   CopyHostScreen;
    procedure   Clear;
    procedure   UpdateScreen;
    function    SnapPixelToRow(Y : Integer) : Integer;
    function    SnapPixelToCol(X : Integer) : Integer;
    function    PixelToRow(Y : Integer) : Integer;
    function    PixelToCol(X : Integer) : Integer;
    procedure   MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
    procedure   SetLineZoom(newValue : Single);
    procedure   SetCharWidth(newValue : Integer);
    procedure   SetCharZoom(newValue : Single);
    procedure   KeyPress(var Key: Char); override;
    property    LineZoom  : Single        read FLineZoom     write SetLineZoom;
    property    CharWidth : Integer       read FCharWidth    write SetCharWidth;
    property    CharZoom  : Single        read FCharZoom     write SetCharZoom;
    property    GraphicDraw : Boolean     read FGraphicDraw  write FGraphicDraw;
    property    TopLine     : Integer     read FTopLine      write SetTopLine;
    property    VScrollBar  : TScrollBar  read FVScrollBar;
    property    TopMargin       : Integer read FTopMargin    write SetTopMargin;
    property    LeftMargin      : Integer read FLeftMargin   write SetLeftMargin;
    property    RightMargin     : Integer read FRightMargin  write SetRightMargin;
    property    BottomMargin    : Integer read FBottomMargin write SetBottomMargin;
    property    MarginColor     : Integer read FMarginColor  write SetMarginColor;
  private
    procedure   PaintOneLine(DC: HDC; Y, Y1 : Integer; const Line : TLine;
                             nColFrom : Integer; nColTo : Integer; Blank : Boolean);
    procedure   SetupFont;
    property Text : String read ReadStr write WriteStr;
    property OnMouseMove;
    property OnMouseDown;
    property OnMouseUp;
    property OnClick;
    property OnKeyPress;
    property OnKeyBuffer : TKeyBufferEvent read FOnKeyBuffer write FOnKeyBuffer;
    property OnKeyDown   : TKeyDownEvent   read FOnKeyDown   write FOnKeyDown;
    property Ctl3D;
    property Align;
    property TabStop;
    property TabOrder;
    property BorderStyle: TBorderStyle read FBorderStyle write FBorderStyle;
    property AutoRepaint : Boolean     read FAutoRepaint write FAutoRepaint;
    property Font : TFont              read FFont        write SetFont;
    property LocalEcho : Boolean       read FLocalEcho   write FLocalEcho;
    property AutoLF : Boolean          read GetAutoLF    write SetAutoLF;
    property AutoCR : Boolean          read GetAutoCR    write SetAutoCR;
    property Xlat : Boolean            read GetXlat      write SetXlat;
    property MonoChrome : Boolean      read FMonoChrome  write FMonoChrome;
    property Log : Boolean             read FLog         write SetLog;
    property Rows : Integer            read GetRows      write SetRows;
    property Cols : Integer            read GetCols      write SetCols;
    property LineHeight : Integer      read FLineHeight  write SetLineHeight;
    property FKeys : Integer           read FFKeys       write FFKeys;
    property SelectRect : TRect        read FSelectRect  write FSelectRect;
    property BackRows : Integer        read GetBackRows  write SetBackRows;
    property BackColor : TBackColors   read GetBackColor write SetBackColor;
    property Options : TScreenOptions  read GetOptions   write SetOptions;
  end;

  { Same as TCustomEmulVT, but with published properties }
  TEmulVT = class(TCustomEmulVT)
  public
    property Screen : TScreen read FScreen;
    property SelectRect;
    property Text;
  published
    property OnMouseMove;
    property OnMouseDown;
    property OnMouseUp;
    property OnClick;
    property OnKeyPress;
    property OnKeyDown;
    property OnKeyBuffer;
    property Ctl3D;
    property Align;
    property BorderStyle;
    property AutoRepaint;
    property Font;
    property LocalEcho;
    property AutoLF;
    property AutoCR;
    property Xlat;
    property MonoChrome;
    property Log;
    property Rows;
    property Cols;
    property BackRows;
    property BackColor;
    property Options;
    property LineHeight;
    property CharWidth;
    property TabStop;
    property TabOrder;
    property FKeys;
    property TopMargin;
    property LeftMargin;
    property RightMargin;
    property BottomMargin;
    property MarginColor;
  end;

const
  F_BLACK   = $00;
  F_BLUE    = $01;
  F_GREEN   = $02;
  F_CYAN    = $03;
  F_RED     = $04;
  F_MAGENTA = $05;
  F_BROWN   = $06;
  F_WHITE   = $07;

  B_BLACK   = $00;
  B_BLUE    = $01;
  B_GREEN   = $02;
  B_CYAN    = $03;
  B_RED     = $04;
  B_MAGENTA = $05;
  B_BROWN   = $06;
  B_WHITE   = $07;

  F_INTENSE = $08;
  B_BLINK   = $80;

  { Function keys (SCO Console) }
  FKeys1 : TFuncKeysTable = (
      (ScanCode: #$48; Shift: []; Ext: TRUE ; Value: #$1B + '[A'),   { UP    }
      (ScanCode: #$50; Shift: []; Ext: TRUE ; Value: #$1B + '[B'),   { DOWN  }
      (ScanCode: #$4D; Shift: []; Ext: TRUE ; Value: #$1B + '[C'),   { RIGHT }
      (ScanCode: #$4B; Shift: []; Ext: TRUE ; Value: #$1B + '[D'),   { LEFT  }
      (ScanCode: #$49; Shift: []; Ext: TRUE ; Value: #$1B + '[I'),   { PREV  }
      (ScanCode: #$51; Shift: []; Ext: TRUE ; Value: #$1B + '[G'),   { NEXT  }
      (ScanCode: #$47; Shift: []; Ext: TRUE ; Value: #$1B + '[H'),   { HOME  }
      (ScanCode: #$4F; Shift: []; Ext: TRUE ; Value: #$1B + '[F'),   { END   }
      (ScanCode: #$52; Shift: []; Ext: TRUE ; Value: #$1B + '[L'),   { INS   }
      (ScanCode: #$0F; Shift: []; Ext: FALSE; Value: #$1B + '[Z'),   { RTAB  }
      (ScanCode: #$53; Shift: []; Ext: TRUE ; Value: #$7F       ),   { DEL   }
      (ScanCode: #$3B; Shift: []; Ext: FALSE; Value: #$1B + '[M'),   { F1    }
      (ScanCode: #$3C; Shift: []; Ext: FALSE; Value: #$1B + '[N'),
      (ScanCode: #$3D; Shift: []; Ext: FALSE; Value: #$1B + '[O'),
      (ScanCode: #$3E; Shift: []; Ext: FALSE; Value: #$1B + '[P'),
      (ScanCode: #$3F; Shift: []; Ext: FALSE; Value: #$1B + '[Q'),
      (ScanCode: #$40; Shift: []; Ext: FALSE; Value: #$1B + '[R'),
      (ScanCode: #$41; Shift: []; Ext: FALSE; Value: #$1B + '[S'),
      (ScanCode: #$42; Shift: []; Ext: FALSE; Value: #$1B + '[T'),
      (ScanCode: #$43; Shift: []; Ext: FALSE; Value: #$1B + '[U'),
      (ScanCode: #$44; Shift: []; Ext: FALSE; Value: #$1B + '[V'),   { F10   }
      (ScanCode: #$85; Shift: []; Ext: FALSE; Value: #$1B + '[W'),   { F11   }
      (ScanCode: #$86; Shift: []; Ext: FALSE; Value: #$1B + '[X'),   { F12   }
      (ScanCode: #$3B; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[V'),{ SF1 should be 'Y' }
      (ScanCode: #$3C; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[Z'),
      (ScanCode: #$3D; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[a'),
      (ScanCode: #$3E; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[b'),
      (ScanCode: #$3F; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[c'),
      (ScanCode: #$40; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[d'),
      (ScanCode: #$41; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[e'),
      (ScanCode: #$42; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[f'),
      (ScanCode: #$43; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[g'),
      (ScanCode: #$44; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[h'),
      (ScanCode: #$85; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[i'),
      (ScanCode: #$86; Shift: [ssShift]; Ext: FALSE; Value: #$1B + '[j'),{ SF10 }
      (ScanCode: #$3B; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[k'), { CF1  }
      (ScanCode: #$3C; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[l'),
      (ScanCode: #$3D; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[m'),
      (ScanCode: #$3E; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[n'),
      (ScanCode: #$3F; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[o'),
      (ScanCode: #$40; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[p'),
      (ScanCode: #$41; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[q'),
      (ScanCode: #$42; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[r'),
      (ScanCode: #$43; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[s'),
      (ScanCode: #$44; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[t'),
      (ScanCode: #$85; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[u'),
      (ScanCode: #$86; Shift: [ssCtrl]; Ext: FALSE; Value: #$1B + '[v'),   { CF12 }
      (ScanCode: #$1C; Shift: []; Ext: FALSE; Value: #13#10           ),   { Enter key }
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         )
      );

{ Alternate function keys (ordinary VT keys) }
  FKeys2 : TFuncKeysTable = (
      (ScanCode: #$48; Shift: []; Ext: TRUE ; Value: #$1B + '[A'),   { UP      }
      (ScanCode: #$50; Shift: []; Ext: TRUE ; Value: #$1B + '[B'),   { DOWN    }
      (ScanCode: #$4D; Shift: []; Ext: TRUE ; Value: #$1B + '[C'),   { RIGHT   }
      (ScanCode: #$4B; Shift: []; Ext: TRUE ; Value: #$1B + '[D'),   { LEFT    }
      (ScanCode: #$49; Shift: []; Ext: TRUE ; Value: #$1B + '[5~'),  { PREV    }
      (ScanCode: #$51; Shift: []; Ext: TRUE ; Value: #$1B + '[6~'),  { NEXT    }
      (ScanCode: #$52; Shift: []; Ext: TRUE ; Value: #$1B + '[2~'),  { INSERT  }
      (ScanCode: #$53; Shift: []; Ext: TRUE ; Value: #$7F       ),   { DELETE  }
      (ScanCode: #$3B; Shift: []; Ext: FALSE; Value: #$1B + 'OP'),   { F1->PF1 }
      (ScanCode: #$3C; Shift: []; Ext: FALSE; Value: #$1B + 'OQ'),   { F2->PF2 }
      (ScanCode: #$3D; Shift: []; Ext: FALSE; Value: #$1B + 'OR'),   { F3->PF3 }
      (ScanCode: #$3E; Shift: []; Ext: FALSE; Value: #$1B + 'OS'),   { F4->PF4 }
      (ScanCode: #$57; Shift: []; Ext: FALSE; Value: #$1B + '[28~'), { F11->Aide }
      (ScanCode: #$58; Shift: []; Ext: FALSE; Value: #$1B + '[29~'), { F12->Exécuter }
      (ScanCode: #$1C; Shift: []; Ext: FALSE; Value: #13#10       ), { Enter key }
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         )
      );

{ A-Series Telnet function keys (ordinary VT100 keys + specials) }
  FKeys3 : TFuncKeysTable = (
      (ScanCode: #$48; Shift: []; Ext: TRUE ; Value: #$1B + '[A'),   { UP      }
      (ScanCode: #$50; Shift: []; Ext: TRUE ; Value: #$1B + '[B'),   { DOWN    }
      (ScanCode: #$4D; Shift: []; Ext: TRUE ; Value: #$1B + '[C'),   { RIGHT   }
      (ScanCode: #$4B; Shift: []; Ext: TRUE ; Value: #$1B + '[D'),   { LEFT    }
      (ScanCode: #$49; Shift: []; Ext: TRUE ; Value: #$1B + '-'),    { PREV    }
      (ScanCode: #$51; Shift: []; Ext: TRUE ; Value: #$1B + '+'),    { NEXT    }
      (ScanCode: #$47; Shift: []; Ext: TRUE ; Value: #$1B + 'H'),    { HOME    }
      (ScanCode: #$47; Shift: [ssCtrl]; Ext: TRUE ; Value: #$1B + 'C'),{ HOME  }
      (ScanCode: #$4F; Shift: []; Ext: TRUE ; Value: #$1B + 'R'),    { END     }
      (ScanCode: #$52; Shift: []; Ext: TRUE ; Value: #$1B + 'I'),    { INSERT  }
      (ScanCode: #$53; Shift: []; Ext: TRUE ; Value: #$7F       ),   { DELETE  }
      (ScanCode: #$3B; Shift: []; Ext: FALSE; Value: #$1B + 'OP'),   { F1->PF1 }
      (ScanCode: #$3C; Shift: []; Ext: FALSE; Value: #$1B + 'OQ'),   { F2->PF2 }
      (ScanCode: #$3D; Shift: []; Ext: FALSE; Value: #$1B + 'OR'),   { F3->PF3 }
      (ScanCode: #$3E; Shift: []; Ext: FALSE; Value: #$1B + 'OS'),   { F4->PF4 }
      (ScanCode: #$43; Shift: []; Ext: FALSE; Value: #$1B + 'OP'),   { F9      }
      (ScanCode: #$44; Shift: []; Ext: FALSE; Value: ''),            { F10     }
      (ScanCode: #$57; Shift: []; Ext: FALSE; Value: #$1B + 'OQ'),   { F11     }
      (ScanCode: #$58; Shift: []; Ext: FALSE; Value: #$1B + 'OS'),   { F12     }
      (ScanCode: #$0F; Shift: []; Ext: FALSE; Value: #$1B + 'Z'),    { RTAB    }
      (ScanCode: #$40; Shift: []; Ext: FALSE; Value: #$1B + 'K'),    { F6      }
      (ScanCode: #$53; Shift: [ssCtrl]; Ext: TRUE ; Value: #$1B + 'D'), { CDEL }
      (ScanCode: #$52; Shift: [ssCtrl]; Ext: TRUE ; Value: #$1B + 'L'), { CINS }
      (ScanCode: #$1C; Shift: []; Ext: FALSE; Value: #13#10          ), { Enter key }
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         ),
      (ScanCode: #$00; Shift: []; Ext: FALSE; Value: ''         )
      );

  { Ethernet to screen }
  ibm_iso8859_1_G0 : TXlatTable = (
      #$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07,   { 00 - 07 }
      #$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F,   { 08 - 0F }
      #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17,   { 10 - 17 }
      #$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F,   { 18 - 1F }
      #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27,   { 20 - 27 }
      #$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F,   { 28 - 2F }
      #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37,   { 30 - 37 }
      #$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F,   { 38 - 3F }
      #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47,   { 40 - 47 }
      #$48, #$49, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F,   { 48 - 4F }
      #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57,   { 50 - 57 }
      #$58, #$59, #$5A, #$5B, #$5C, #$5D, #$5E, #$5F,   { 58 - 5F }
      #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67,   { 60 - 67 }
      #$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F,   { 68 - 6F }
      #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77,   { 70 - 77 }
      #$78, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F,   { 78 - 7F }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 80 - 87 }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 88 - 8F }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 90 - 97 }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 98 - 9F }
      #$B1, #$AD, #$9B, #$9C, #$0F, #$9D, #$B3, #$15,   { A0 - A7 }
      #$20, #$43, #$A6, #$AE, #$AA, #$C4, #$52, #$C4,   { A8 - AF }
      #$F8, #$F1, #$FD, #$20, #$27, #$E6, #$14, #$FA,   { B0 - B7 }
      #$2C, #$20, #$A7, #$AF, #$AC, #$AB, #$20, #$A8,   { B8 - BF }
      #$41, #$41, #$41, #$41, #$8E, #$8F, #$92, #$80,   { C0 - C7 }
      #$45, #$45, #$45, #$45, #$45, #$49, #$49, #$49,   { C8 - CF }
      #$44, #$A5, #$4F, #$4F, #$4F, #$4F, #$4F, #$78,   { D0 - D7 }
      #$ED, #$55, #$55, #$55, #$55, #$59, #$70, #$E1,   { D8 - DF }
      #$85, #$A0, #$83, #$61, #$84, #$86, #$91, #$87,   { E0 - E7 }
      #$8A, #$82, #$88, #$89, #$8D, #$A1, #$8C, #$49,   { E8 - EF }
      #$64, #$A4, #$95, #$A2, #$93, #$6F, #$94, #$F6,   { F0 - F7 }
      #$ED, #$97, #$A3, #$96, #$9A, #$79, #$70, #$98);  { F8 - FF }

{ Ethernet to screen }
  ibm_iso8859_1_G1 : TXlatTable = (
      #$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07,   { 00 - 07 }
      #$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F,   { 08 - 0F }
      #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17,   { 10 - 17 }
      #$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F,   { 18 - 1F }
      #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27,   { 20 - 27 }
      #$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F,   { 28 - 2F }
      #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37,   { 30 - 37 }
      #$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F,   { 38 - 3F }
      #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47,   { 40 - 47 }
      #$48, #$49, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F,   { 48 - 4F }
      #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57,   { 50 - 57 }
      #$58, #$59, #$5A, #$5B, #$5C, #$5D, #$5E, #$5F,   { 58 - 5F }
      #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67,   { 60 - 67 }
      #$68, #$69, #$D9, #$BF, #$DA, #$C0, #$C5, #$6F,   { 68 - 6F }
      #$70, #$C4, #$72, #$73, #$C3, #$B4, #$C1, #$C2,   { 70 - 77 }
      #$B3, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F,   { 78 - 7F }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 80 - 87 }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 88 - 8F }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 90 - 97 }
      #$20, #$20, #$20, #$20, #$20, #$20, #$20, #$20,   { 98 - 9F }
      #$B1, #$AD, #$9B, #$9C, #$0F, #$9D, #$B3, #$15,   { A0 - A7 }
      #$20, #$43, #$A6, #$AE, #$AA, #$C4, #$52, #$C4,   { A8 - AF }
      #$F8, #$F1, #$FD, #$20, #$27, #$E6, #$14, #$FA,   { B0 - B7 }
      #$2C, #$20, #$A7, #$AF, #$AC, #$AB, #$20, #$A8,   { B8 - BF }
      #$41, #$41, #$41, #$41, #$8E, #$8F, #$92, #$80,   { C0 - C7 }
      #$45, #$45, #$45, #$45, #$45, #$49, #$49, #$49,   { C8 - CF }
      #$44, #$A5, #$4F, #$4F, #$4F, #$4F, #$4F, #$78,   { D0 - D7 }
      #$ED, #$55, #$55, #$55, #$55, #$59, #$70, #$E1,   { D8 - DF }
      #$85, #$A0, #$83, #$61, #$84, #$86, #$91, #$87,   { E0 - E7 }
      #$8A, #$82, #$88, #$89, #$8D, #$A1, #$8C, #$49,   { E8 - EF }
      #$64, #$A4, #$95, #$A2, #$93, #$6F, #$94, #$F6,   { F0 - F7 }
      #$ED, #$97, #$A3, #$96, #$9A, #$79, #$70, #$98);  { F8 - FF }

{ Keyboard to Ethernet }
  Output : TXlatTable = (
      #$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07,   { 00 - 07 }
      #$08, #$09, #$0A, #$0B, #$0C, #$0D, #$0E, #$0F,   { 08 - 0F }
      #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17,   { 10 - 17 }
      #$18, #$19, #$1A, #$1B, #$1C, #$1D, #$1E, #$1F,   { 18 - 1F }
      #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27,   { 20 - 27 }
      #$28, #$29, #$2A, #$2B, #$2C, #$2D, #$2E, #$2F,   { 28 - 2F }
      #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37,   { 30 - 37 }
      #$38, #$39, #$3A, #$3B, #$3C, #$3D, #$3E, #$3F,   { 38 - 3F }
      #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47,   { 40 - 47 }
      #$48, #$49, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F,   { 48 - 4F }
      #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57,   { 50 - 57 }
      #$58, #$59, #$5A, #$5B, #$5C, #$5D, #$5E, #$5F,   { 58 - 5F }
      #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67,   { 60 - 67 }
      #$68, #$69, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F,   { 68 - 6F }
      #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77,   { 70 - 77 }
      #$78, #$79, #$7A, #$7B, #$7C, #$7D, #$7E, #$7F,   { 78 - 7F }
      #$C7, #$FC, #$E9, #$E2, #$E4, #$E0, #$E5, #$E7,   { 80 - 87 }
      #$EA, #$EB, #$E8, #$EF, #$EE, #$EC, #$C4, #$C5,   { 88 - 8F }
      #$C9, #$E6, #$C6, #$F4, #$F6, #$F2, #$FB, #$F9,   { 90 - 97 }
      #$FF, #$F6, #$FC, #$A2, #$A3, #$A5, #$DE, #$20,   { 98 - 9F }
      #$E1, #$ED, #$F3, #$FA, #$F1, #$D1, #$AA, #$BA,   { A0 - A7 }
      #$BF, #$20, #$AC, #$BD, #$BC, #$A1, #$AB, #$BB,   { A8 - AF }
      #$A0, #$A0, #$A0, #$A6, #$A6, #$A6, #$A6, #$AD,   { B0 - B7 }
      #$2B, #$A6, #$A6, #$2B, #$2B, #$2B, #$2B, #$2B,   { B8 - BF }
      #$2B, #$AD, #$AD, #$AD, #$A6, #$AD, #$2B, #$A6,   { C0 - C7 }
      #$2B, #$2B, #$AD, #$AD, #$A6, #$AD, #$2B, #$AD,   { C8 - CF }
      #$AD, #$AD, #$AD, #$2B, #$2B, #$2B, #$2B, #$2B,   { D0 - D7 }
      #$2B, #$2B, #$2B, #$A0, #$A0, #$A0, #$A0, #$A0,   { D8 - DF }
      #$20, #$20, #$20, #$AD, #$20, #$20, #$B5, #$20,   { E0 - E7 }
      #$20, #$20, #$20, #$20, #$20, #$F8, #$20, #$20,   { E8 - EF }
      #$A0, #$B1, #$20, #$20, #$20, #$20, #$F7, #$20,   { F0 - F7 }
      #$B0, #$B0, #$B0, #$20, #$20, #$B2, #$A0, #$20);  { F8 - FF }

procedure Register;
procedure FKeysToFile(var FKeys : TFuncKeysTable; FName : String);
procedure FileToFKeys(var FKeys : TFuncKeysTable; FName : String);
function  AddFKey(var FKeys : TFuncKeysTable;
                  ScanCode  : Char;
                  Shift     : TShiftState;
                  Ext       : Boolean;
                  Value     : TFuncKeyValue) : Boolean;



implementation
{-$DEFINE DEBUG_OUTPUT}   { Add or remove minus sign before dollar sign to }
                          { generate code for debug message output         }

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure Register;
begin
    RegisterComponents('FPiette', [TEmulVT]);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function ShiftStateToString(var State : TShiftState) : String;
begin
    Result := '';
    if ssShift in State then
        Result := Result + 'ssShift ';
    if ssAlt in State then
        Result := Result + 'ssAlt ';
    if ssCtrl in State then
        Result := Result + 'ssCtrl ';
    if ssLeft in State then
        Result := Result + 'ssLeft ';
    if ssRight in State then
        Result := Result + 'ssRight ';
    if ssMiddle in State then
        Result := Result + 'ssMiddle ';
    if ssDouble in State then
        Result := Result + 'ssDouble ';
    if Result = '' then
        Result := 'ssNormal';
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function StringToShiftState(var S : String) : TShiftState;
begin
    Result := [];
    if Pos('ssShift', S) <> 0 then
        Result := Result + [ssShift];
    if Pos('ssAlt', S) <> 0 then
        Result := Result + [ssAlt];
    if Pos('ssCtrl', S) <> 0 then
        Result := Result + [ssCtrl];
    if Pos('ssLeft', S) <> 0 then
        Result := Result + [ssLeft];
    if Pos('ssRight', S) <> 0 then
        Result := Result + [ssRight];
    if Pos('ssMiddle', S) <> 0 then
        Result := Result + [ssMiddle];
    if Pos('ssDouble', S) <> 0 then
        Result := Result + [ssDouble];
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function xdigit(Ch : char) : integer;
begin
    if ch in ['0'..'9'] then
        Result := Ord(ch) - ord('0')
    else if ch in ['A'..'Z'] then
        Result := Ord(ch) - Ord('A') + 10
    else if ch in ['a'..'z'] then
        Result := Ord(ch) - Ord('a') + 10
    else
        Result := 0;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function xdigit2(S : PChar) : integer;
begin
    Result := 16 * xdigit(S[0]) + xdigit(S[1]);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function FuncKeyValueToString(var S : TFuncKeyValue) : String;
var
    I : Integer;
begin
    Result := '';
    for I := 1 to Length(S) do begin
        if (Ord(S[I]) < 32) or (Ord(S[I]) >= 127) or
           (S[I] = '''') or (S[I] = '\') then
            Result := Result + '\x' + IntToHex(Ord(S[I]), 2)
        else
            Result := Result + S[I];
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function StringToFuncKeyValue(var S : String) : TFuncKeyValue;
var
    I : Integer;
begin
    Result := '';
    I := 1;
    while I <= Length(S) do begin
        if (S[I] = '\') and
           ((I + 3) <= Length(S)) and
           (S[I + 1] = 'x') then begin
            Result := Result + chr(xdigit2(@S[I + 2]));
            I := I + 3;
        end
        else
            Result := Result + S[I];
        Inc(I);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function AddFKey(var FKeys : TFuncKeysTable;
                  ScanCode  : Char;
                  Shift     : TShiftState;
                  Ext       : Boolean;
                  Value     : TFuncKeyValue) : Boolean;
var
    I : Integer;
begin
    { Search for existing key definition to replace it }
    for I := Low(FKeys) to High(FKeys) do begin
        if (FKeys[I].ScanCode = ScanCode) and
           (FKeys[I].Shift    = Shift) and
           (FKeys[I].Ext      = Ext) then begin
            FKeys[I].Value := Value;
            Result         := TRUE;     { Success}
            Exit;
        end;
    end;

    { Key not existing, add in an empty space }
    for I := Low(FKeys) to High(FKeys) do begin
        if FKeys[I].ScanCode = #0 then begin
            FKeys[I].ScanCode := ScanCode;
            FKeys[I].Shift    := Shift;
            FKeys[I].Ext      := Ext;
            FKeys[I].Value    := Value;
            Result            := TRUE;     { Success}
            Exit;
        end;
    end;

    { Failure, no more space available }
    Result := FALSE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure FKeysToFile(var FKeys : TFuncKeysTable; FName : String);
var
    I : Integer;
    F : TextFile;
begin
    AssignFile(F, FName);
    Rewrite(F);
    for I := Low(FKeys) to High(FKeys) do begin
        with FKeys[I] do begin
            if ScanCode <> chr(0) then
                WriteLn(F, IntToHex(Ord(ScanCode), 2), ', ',
                           ShiftStateToString(Shift), ', ',
                           Ext, ', ''',
                           FuncKeyValueToString(Value), '''');
        end;
    end;
    CloseFile(F);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function GetToken(var S : String; var I : Integer; Delim : Char) : String;
begin
    Result := '';
    while (I <= Length(S)) and (S[I] = ' ') do
        Inc(I);
    while (I <= Length(S)) and (S[I] <> Delim) do begin
        Result := Result + S[I];
        Inc(I);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure FileToFKeys(var FKeys : TFuncKeysTable; FName : String);
var
    I, J : Integer;
    F : TextFile;
    S, T : String;
    sc   : Integer;
begin
    AssignFile(F, FName);
 {$I-}
    Reset(F);
    if IOResult <> 0 then begin
        { File do not exist, create default one }
        FKeysToFile(FKeys, FName);
        Exit;
    end;

    for I := Low(FKeys) to High(FKeys) do begin
        with FKeys[I] do begin
            ScanCode := chr(0);
            Shift    := [];
            Ext      := FALSE;
            Value    := '';
            if not Eof(F) then begin
                { 71, ssNormal, TRUE, '\x1B[H' }
                ReadLn(F, S);
                J  := 1;
                T  := GetToken(S, J, ',');
                if (Length(T) > 0) and (T[1] <> ';') then begin
                    sc := xdigit2(@T[1]);
                    if sc <> 0 then begin
                        ScanCode := chr(sc);
                        Inc(J);
                        T := GetToken(S, J, ',');
                        Shift := StringToShiftState(T);
                        Inc(J);
                        T := GetToken(S, J, ',');
                        Ext := UpperCase(T) = 'TRUE';
                        Inc(J);
                        T := GetToken(S, J, '''');
                        Inc(J);
                        T := GetToken(S, J, '''');
                        Value := StringToFuncKeyValue(T);
                    end;
                end;
            end;
        end;
    end;
    CloseFile(F);
{$I+}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure DebugString(Msg : String);
const
    Cnt : Integer = 0;
{$IFDEF DEBUG_OUTPUT}
var
    Buf : String[20];
{$ENDIF}
begin
{$IFDEF DEBUG_OUTPUT}
    Cnt := Cnt + 1;
    Buf := IntToHex(Cnt, 4) + ' ' + #0;
    OutputDebugString(@Buf[1]);

{$IFNDEF WIN32}
    if Length(Msg) < High(Msg) then
        Msg[Length(Msg) + 1] := #0;
{$ENDIF}

    OutputDebugString(@Msg[1]);
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{$IFNDEF WIN32}
procedure SetLength(var S: string; NewLength: Integer);
begin
    S[0] := chr(NewLength);
end;
{$ENDIF}


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
constructor TLine.Create;
begin
    inherited Create;
    FillChar(Txt, SizeOf(Txt), ' ');
    FillChar(Att, SizeOf(Att), Chr(F_WHITE));
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TLine.Clear(Attr : Byte);
begin
    FillChar(Txt, SizeOF(Txt), ' ');
    FillChar(Att, SizeOf(Att), Attr);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
constructor TScreen.Create;
begin
    inherited Create;
    FRowCount        := 0;
    FBackRowCount    := 0;
    FBackEndRow      := 0;
    FBackColor       := vtsWhite;
    FOptions         := [vtoBackColor];
    SetRowCount(25);
    FColCount        := 80;
    FRowSaved        := -1;
    FColSaved        := -1;
    FScrollRowTop    := 0;
    {FScrollRowBottom := FRowCount - 1; // WM + SE 09/08/00 }
    FAttribute       := F_WHITE;
    InvClear;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TScreen.Destroy;
var
    nRow : Integer;
begin
    for nRow := 0 to FRowCount + FBackRowCount - 1 do
        FLines^[nRow].Free;
    FreeMem (FLines, (FRowCount + FBackRowCount) * SizeOf(TObject));
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.AdjustFLines(NewCount : Integer);
var
    NewLines        : PLineArray;
    CurrCount       : Integer;
    nRow            : Integer;
begin
    CurrCount := FRowCount + FBackRowCount;
    if  (NewCount <> CurrCount) and (NewCount > 0) then begin
        GetMem(NewLines, NewCount * SizeOf(TObject));
        if NewCount > CurrCount then begin
            if CurrCount <> 0 then
                Move(FLines^, NewLines^, CurrCount * SizeOf(TObject));
            for nRow := CurrCount to NewCount - 1 do
                NewLines^[nRow] := TLine.Create;
            if CurrCount <> 0 then
                FreeMem(FLines, CurrCount * SizeOf(TObject));
        end
        else begin
            Move (FLines^, NewLines^, NewCount * SizeOf(TObject));
            for nRow := NewCount to CurrCount - 1 do
                FLines^[nRow].Free;
            FreeMem(FLines, CurrCount * SizeOf(TObject));
        end;
        FLines := NewLines;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.SetRowCount(NewCount : Integer);
begin
    if NewCount <> FRowCount then begin
        AdjustFLines(NewCount + FBackRowCount);
        FRowCount := NewCount;
        FScrollRowBottom := FRowCount - 1; { WM + SE 09/08/00 }
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.SetBackRowCount(NewCount : Integer);
begin
    if NewCount <> FBackRowCount then begin
        AdjustFLines(FRowCount + NewCount);
        FBackRowCount := NewCount;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CopyScreenToBack;
{ Copies the current host screen into the scrollback buffer. }
var
    Temp : TLine;
    Row  : Integer;
    Pass : Integer;
    nCol : Integer;
begin
    if FBackRowCount >= FRowCount then begin
        Dec (FBackEndRow, FRowCount);
        if (0 - FBackEndRow) >= FBackRowCount then
            FBackEndRow := 1 - FBackRowCount;
        { We have to make  FRowCount  lines available at the head of the
          scrollback buffer.  These will come from the end of the scrollback
          buffer.  We'll make  FRowCount  passes through the scrollback buffer
          moving the available lines up to the top and the existing lines
          down a page at a time.
          Net result is that we only move each line once. }
        For Pass := 0 To FRowCount - 1 Do begin
            Row := FBackEndRow + Pass;
            Temp := Lines[Row];
            Inc (Row, FRowCount);
            While Row < 0 Do begin
                Lines[Row - FRowCount] := Lines[Row];
                Inc (Row, FRowCount);
            end;
            Lines[Row - FRowCount] := Temp;
        end;

        { Now, copy the host screen lines to the ones we made available. }
        For Row := 0 To FRowCount - 1 Do begin
            Move (Lines[Row].Txt, Lines[Row - FRowCount].Txt, FColCount);
            Move (Lines[Row].Att, Lines[Row - FRowCount].Att, FColCount);
            if vtoBackColor in FOptions then begin
                with Lines[Row - FRowCount] do begin
                    for nCol := 0 to FColCount - 1 do begin
                        Att[nCol] := Att[nCol] And $8F Or (Ord (FBackColor) shl 4);
                    end;
                end;
            end;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ScrollUp;
var
    Temp : TLine;
    Row  : Integer;
    nCol : Integer;
begin
    if FBackRowCount > 0 then begin
        if (0 - FBackEndRow) < (FBackRowCount - 1) then
            Dec (FBackEndRow);
        Temp := Lines[FBackEndRow];
        For Row := FBackEndRow + 1 To -1 Do begin
            Lines[Row - 1] := Lines[Row];
        end;
        Lines[-1] := Lines[FScrollRowTop];
            if vtoBackColor in FOptions then begin
                with Lines[-1] do begin
                    for nCol := 0 to FColCount - 1 do begin
                        Att[nCol] := Att[nCol] And $8F Or (Ord (FBackColor) shl 4);
                    end;
                end;
            end;

    end
    else
        Temp := Lines[FScrollRowTop];

    for Row := FScrollRowTop + 1 to FScrollRowBottom do
        Lines[Row - 1] := Lines[Row];
    Lines[FScrollRowBottom] := Temp;
    Temp.Clear(F_WHITE {FAttribute});
    FAllInvalid := TRUE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ScrollDown;
var
    Temp : TLine;
    Row  : Integer;
begin
    Temp := Lines[FScrollRowBottom];
    for Row := FScrollRowBottom DownTo FScrollRowTop + 1 do
        Lines[Row] := Lines[Row - 1];
    Lines[FScrollRowTop] := Temp;
    Temp.Clear(F_WHITE {FAttribute});
    FAllInvalid := TRUE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CursorDown;
begin
    Inc(FRow);
    if FRow > FScrollRowBottom then begin
        FRow := FScrollRowBottom;
        ScrollUp;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CursorUp;
begin
    Dec(FRow);
    if FRow < 0 then begin
        Inc(FRow);
        ScrollDown;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CursorRight;
begin
    Inc(FCol);
    if FCol >= FColCount then begin
        FCol := 0;
        CursorDown;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CursorLeft;
begin
    Dec(FCol);
    if FCol < 0 then begin
        FCol := FColCount - 1;
        CursorUp;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.CarriageReturn;
begin
    FCol := 0;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TScreen.GetEscapeParam(From : Integer; var Value : Integer) : Integer;
begin
    while (From <= Length(FEscBuffer)) and (FEscBuffer[From] = ' ') do
        From := From + 1;

    Value := 0;
    while (From <= Length(FEscBuffer)) and (FEscBuffer[From] in ['0'..'9']) do begin
        Value := Value * 10 + Ord(FEscBuffer[From]) - Ord('0');
        From := From + 1;
    end;

    Result := From;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.UnimplementedEscape(EscCmd : Char);
{var
    Buf : String;}
begin
    DebugString('Unimplemented Escape Sequence: ' + FEscBuffer + EscCmd + #13 + #10);
{   Buf := FEscBuffer + EscCmd + #0;
    MessageBox(0, @Buf[1], 'Unimplemented Escape Sequence', MB_OK); }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.InvalidEscape(EscCmd : Char);
{var
    Buf : String;}
begin
    DebugString('Invalid Escape Sequence: ' + FEscBuffer + EscCmd + #13 + #10);
{   Buf := FEscBuffer + EscCmd + #0;
    MessageBox(0, @Buf[1], 'Invalid Escape Sequence', MB_OK); }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessESC_D;                   { Index                   }
begin
    UnimplementedEscape('D');
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Move cursor Up, scroll down if necessary                                  }
procedure TScreen.ProcessESC_M;                   { Reverse index           }
begin
    Dec(FRow);
    if FRow < FScrollRowTop then begin
        FRow := FScrollRowTop;
        ScrollDown;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessESC_E;                   { Next line               }
begin
    UnimplementedEscape('E');
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_u;                  { Restore Cursor          }
begin
    UnimplementedEscape('u');
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ IBM character set operation (not part of the ANSI standard)		    }
{ <ESC>[0I		=> Set IBM character set			    }
{ <ESC>[1;nnnI		=> Literal mode for nnn next characters		    }
{ <ESC>[2;onoffI	=> Switch carbon mode on (1) or off (0)		    }
{ <ESC>[3;ch;cl;sh;slI	=> Receive carbon mode keyboard code		    }
{ <ESC>[4I              => Select ANSI character set                        }
procedure TScreen.ProcessCSI_I;
var
    From, mode, nnn : Integer;
    ch, cl, sh, sl  : Integer;
begin
    From := GetEscapeParam(2, Mode);

    case Mode of
    0:  begin                { Select IBM character set                     }
            FNoXlat := TRUE;
        end;
    1:	begin                { Set literal mode for next N characters       }
            if FEscBuffer[From] = ';' then
                GetEscapeParam(From + 1, FCntLiteral)
            else
                FCntLiteral := 1;
        end;
    2:	begin		     { Switch carbon mode on or off                 }
            if FEscBuffer[From] = ';' then
                GetEscapeParam(From + 1, nnn)
            else
                nnn := 0;
            FCarbonMode := (nnn <> 0);
        end;
    3:	begin		     { Receive carbon mode key code                 }
            ch := 0; cl := 0; sh := 0; sl := 0;
            if FEscBuffer[From] = ';' then begin
                From := GetEscapeParam(From + 1, cl);
                if FEscBuffer[From] = ';' then begin
                    From := GetEscapeParam(From + 1, ch);
                    if FEscBuffer[From] = ';' then begin
                        From := GetEscapeParam(From + 1, sl);
                        if FEscBuffer[From] = ';' then begin
                            GetEscapeParam(From + 1, sh);
                        end;
                    end;
                end;
            end;
            DebugString('Special key ' +
                        IntToHex(ch, 2) + IntToHex(cl, 2) + ' ' +
                        IntToHex(sh, 2) + IntToHex(sl, 2));
        end;
    4:	begin		     { Select ANSI character set                    }
            FNoXlat := FALSE;
        end;
    else
        UnimplementedEscape('I');
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.BackSpace;
begin
    if FCol > 0 then
        Dec(FCol);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ClearScreen;
var
    Row : Integer;
begin
    for Row := 0 to FRowCount - 1 do
        Lines[Row].Clear(FAttribute);
    FRow := 0;
    FCol := 0;
    FAllInvalid := TRUE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.InvClear;
begin
    with FInvRect do begin
        Top    := 9999;
        Left   := 9999;
        Right  := -1;
        Bottom := -1;
    end;
    FAllInvalid := FALSE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.InvRect(nRow, nCol : Integer);
begin
    if not FAllInvalid then begin
        if FInvRect.Top > nRow then
            FInvRect.Top := nRow;
        if FInvRect.Bottom < nRow then
            FInvRect.Bottom := nRow;
        if FInvRect.Left > nCol then
            FInvRect.Left := nCol;
        if FInvRect.Right < nCol then
            FInvRect.Right := nCol;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ The FLines array is inverted with the last host line at position 0 and
  the first host line as position FRowCount - 1. }
procedure Tscreen.SetLines(I : Integer; Value : TLine);
begin
    if I >= FRowCount then
        FLines^[0] := Value
    else
        FLines^[FRowCount - 1 - I] := Value;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TScreen.GetLines(I : Integer) : TLine;
begin
    if I >= FRowCount then
        Result := FLines^[0]
    else
        Result := FLines^[FRowCount - 1 - I];
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.Eol;
begin
    with Lines[FRow] do begin
        FillChar(Txt[FCol], FColCount - FCol, ' ');
        FillChar(Att[FCol], (FColCount - FCol) * SizeOf(Att[FCol]), FAttribute);
    end;
    InvRect(Frow, FCol);
    InvRect(Frow, FColCount);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.Eop;
var
    NRow : Integer;
begin
    Eol;
    for NRow := FRow + 1 to FRowCount - 1 do
        Lines[NRow].Clear(FAttribute);
    if FRow = 0 then
        FAllInvalid := TRUE
    else begin
       InvRect(FRow, 0);
       InvRect(FRowCount, FColCount);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_J;                  { Clear the screen         }
var
    Mode : Integer;
    Row  : Integer;
begin
    GetEscapeParam(2, Mode);
    case Mode of
    0: begin                                   { Cursor to end of screen    }
           FAttribute := F_WHITE;
           Eop;
       end;
    1: begin                                   { Start of screen to cursor  }
           for Row := 0 to FRow do
               Lines[Row].Clear(FAttribute);
           InvRect(0, 0);
           InvRect(FRow, FColCount);
       end;
    2: begin                                   { Entire screen              }
           if vtoCopyBackOnClear in FOptions then CopyScreenToBack;
           ClearScreen;
       end;
    else
        InvalidEscape('J');
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_K;                  { Erase to End of Line    }
begin
    Eol;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_L;                   { Insert Line             }
var
    nLine : Integer;
    nRow  : Integer;
    Temp  : TLine;
begin
    FCol := 0;
    GetEscapeParam(2, nLine);
    if nLine = 0 then
        nLine := 1;

    if (FRow + nLine) > FScrollRowBottom then begin
        for nRow := FRow to FScrollRowBottom do
            Lines[nRow].Clear(FAttribute);
        Exit;
    end;

    for nRow := FScrollRowBottom downto FRow + nLine do begin
        Temp                := Lines[nRow];
        Lines[nRow]         := Lines[nRow - nLine];
        Lines[nRow - nLine] := Temp;
    end;

    for nRow := FRow to FRow + nLine - 1 do
        Lines[nRow].Clear(FAttribute);

    FAllInvalid := TRUE;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_M;                   { Delete Line             }
var
    nLine : Integer;
    nRow  : Integer;
    Temp  : TLine;
begin
    FAllInvalid := TRUE;
    FCol := 0;
    GetEscapeParam(2, nLine);
    if nLine = 0 then
        nLine := 1;

    if (FRow + nLine) > FScrollRowBottom then begin
        for nRow := FRow to FScrollRowBottom do
            Lines[nRow].Clear(FAttribute);
        Exit;
    end;

    for nRow := FRow to FRow + nLine - 1 do
        Lines[nRow].Clear(F_WHITE {FAttribute});  { 12/11/99 }
    for nRow := FRow to FScrollRowBottom - nLine do begin
        Temp                := Lines[nRow];
        Lines[nRow]         := Lines[nRow + nLine];
        Lines[nRow + nLine] := Temp;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_m_lc;               { Select Attributes       }
var
    From, n : Integer;
begin
    if FEscBuffer[1] <> '[' then
        Exit;

    if Length(FEscBuffer) < 2 then begin
        FAttribute    := F_WHITE;
        FReverseVideo := FALSE;
        Exit;
    end;

    From := 2;
    while From <= Length(FEscBuffer) do begin
        if FEscBuffer[From] in [' ', '[', ';'] then
            Inc(From)
        else begin
            From := GetEscapeParam(From, n);
            case n of
            0:  begin                   { All attributes off       }
                    FAttribute    := F_WHITE;
                    FReverseVideo := FALSE;
                    FUnderLine    := FALSE;
                end;
            1:  begin                   { High intensity           }
                    FAttribute := FAttribute or F_INTENSE;
                end;
            4:  begin                   { Underline                }
                    FUnderLine := TRUE;
                end;
            5:  begin                   { Blinking                 }
                    FAttribute := FAttribute or B_BLINK;
                end;
            7:  begin                   { Reverse video            }
                    FReverseVideo := TRUE;
                end;
            8:  begin                   { Secret                   }
                    FAttribute := 0;
                end;
            10: begin                   { Don't force high bit     }
                    FForceHighBit := FALSE;
                end;
            12: begin                   { Force high bit on        }
                    FForceHighBit := TRUE;
                end;
            22: begin                   { Normal intensity         }
                    FAttribute := FAttribute and (not F_INTENSE);
                end;
            27: begin                   { Normal characters        }
                    FAttribute    := F_WHITE;
                    FReverseVideo := FALSE;
                end;
            30, 31, 32, 33, 34, 35, 36, 37:
                begin                   { Foreground color         }
                    FAttribute := (n mod 10) or (FAttribute and $F8);
                end;
            40, 41, 42, 43, 44, 45, 46, 47:
                begin                   { Background color         }
                    FAttribute := ((n mod 10) shl 4) or (FAttribute and $8F);
                end;
            else
                InvalidEscape('m');
            end;
        end;
    end;
    if FReverseVideo then begin
        FAttribute := ((FAttribute and 7) shl 4) or
                      ((FAttribute shr 4) and 7) or
                      (FAttribute and $88);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_n_lc;                { Cursor position report  }
begin
    UnimplementedEscape('n');
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_at;                 { Insert character        }
var
    nChar : Integer;
    nCnt  : Integer;
    nCol  : Integer;
    Line  : TLine;
begin
    GetEscapeParam(2, nChar);
    if nChar = 0 then
        nChar := 1;

    nCnt := FColCount - FCol - nChar;
    if nCnt <= 0 then begin
        Eol;
        Exit;
    end;

    Line := Lines[FRow];
    for nCol := FColCount - 1 downto FCol + nChar do begin
        Line.Txt[nCol] := Line.Txt[nCol - nChar];
        Line.Att[nCol] := Line.Att[nCol - nChar];
        InvRect(Frow, nCol);
    end;

    for nCol := FCol to FCol + nChar - 1 do begin
        Line.Txt[nCol] := ' ';
        Line.Att[nCol] := FAttribute;
        InvRect(Frow, nCol);
    end;

end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_r_lc;                { Scrolling margins       }
var
    From, Top, Bottom : Integer;
begin
    From := GetEscapeParam(2, Top);
    if Top = 0 then begin                         { Default = full screen   }
        FScrollRowTop    := 0;
        FScrollRowBottom := FRowCount - 1;
    end
    else begin
        while (From <= Length(FEscBuffer)) and (FEscBuffer[From] = ' ') do
            From := From + 1;
        if FEscBuffer[From] = ';' then
            GetEscapeParam(From + 1, Bottom)
        else
            Bottom := 1;

        FScrollRowTop    := Top    - 1;
        FScrollRowBottom := Bottom - 1;

        if (FScrollRowBottom <= FScrollRowTop) or
           (FScrollRowTop < 0) or
           (FScrollRowBottom >= FRowCount) then begin
            FScrollRowTop    := 0;
            FScrollRowBottom := FRowCount - 1;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_s_lc;                { Save cursor location    }
begin
    ProcessCSI_7;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_u_lc;                { Restore cursor location }
begin
    ProcessCSI_8;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_7;                   { Save cursor location    }
begin
    FRowSaved := FRow;
    FColSaved := FCol;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_8;                   { Restore cursor location }
begin
    if FRowSaved = -1 then
        GotoXY(0, 0)
    else
        GotoXY(FColSaved, FRowSaved);
    FRowSaved := -1;
    FColSaved := -1;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_H;                   { Set Cursor Position     }
var
    From, Row, Col : Integer;
begin
    From := GetEscapeParam(2, Row);
    while (From <= Length(FEscBuffer)) and (FEscBuffer[From] = ' ') do
        From := From + 1;
    if FEscBuffer[From] = ';' then
        GetEscapeParam(From + 1, Col)
    else
        Col := 1;

    GotoXY(Col - 1, Row - 1);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_h_lc;                { Terminal mode set       }
var
    Priv : Boolean;
    Mode : Integer;
begin
    if FEscBuffer[1] <> '[' then begin
        UnimplementedEscape('h');
        Exit;
    end;

    Priv := (FEscBuffer[2] = '?');
    if not Priv then begin
        UnimplementedEscape('h');
        Exit;
    end;

    GetEscapeParam(3, Mode);
    case Mode of
    1 :  { ANSI cursor keys }
         FCKeyMode := TRUE;
    4 :  { Smooth scroll OFF }
         { Ignore };
    7:   { Auto-wrap OFF }
         FAutoWrap := TRUE;
    25:  { Cursor visible }
         begin
             FCursorOff := FALSE;
             if Assigned(FOnCursorVisible) then
                 FOnCursorVisible(Self);
         end;
    else
        UnimplementedEscape('h');
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_l_lc;                { Terminal mode reset     }
var
    Priv : Boolean;
    Mode : Integer;
begin
    if FEscBuffer[1] <> '[' then begin
        UnimplementedEscape('l');
        Exit;
    end;

    Priv := (FEscBuffer[2] = '?');
    if not Priv then begin
        UnimplementedEscape('l');
        Exit;
    end;

    GetEscapeParam(3, Mode);
    case Mode of
    1 :  { ANSI cursor keys }
         FCKeyMode := FALSE;
    4 :  { Smooth scroll OFF }
         { Ignore };
    7:   { Auto-wrap OFF }
         FAutoWrap := FALSE;
    25:  { Cursor invisible }
         begin
             FCursorOff := TRUE;
             if Assigned(FOnCursorVisible) then
                 FOnCursorVisible(Self);
         end;
    else
        UnimplementedEscape('l');
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_A;                   { Cursor Up               }
var
    Row : Integer;
begin
    GetEscapeParam(2, Row);
    if Row <= 0 then
        Row := 1;
    FRow := FRow - Row;
    if FRow < 0 then
        FRow := 0;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_B;                   { Cursor Down             }
var
    Row : Integer;
begin
    GetEscapeParam(2, Row);
    if Row <= 0 then
        Row := 1;
    FRow := FRow + Row;
    if FRow >= FRowCount then
        FRow := FRowCount - 1;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_C;                   { Cursor Right            }
var
    Col : Integer;
begin
    GetEscapeParam(2, Col);
    if Col <= 0 then
        Col := 1;
    FCol := FCol + Col;
    if FCol >= FColCount then begin
        if FAutoWrap then begin
            FCol := FCol - FColCount;
            Inc(FRow);
            if FRow >= FRowCount then
                FRow := FRowCount - 1;
        end
        else
            FCol := FColCount - 1;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_D;                   { Cursor Left             }
var
    Col : Integer;
begin
    GetEscapeParam(2, Col);
    if Col <= 0 then
        Col := 1;
    FCol := FCol - Col;
    if FCol < 0 then
        FCol := 0;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_P;                   { Delete Character        }
var
    Count : Integer;
    nCol  : Integer;
begin
    GetEscapeParam(2, Count);
    if Count <= 0 then
        Count := 1;
    with Lines[FRow] do begin
        for nCol := Fcol to FColCount - Count - 1 do begin
            Txt[nCol] := Txt[nCol + Count];
            Att[nCol] := Att[nCol + Count];
        end;
        for nCol := FcolCount - Count - 1 to FColCount - 1 do begin
            Txt[nCol] := ' ';
            Att[nCol] := F_WHITE;
        end;
    end;
    InvRect(Frow, FCol);
    InvRect(Frow, FColCount);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_S;                   { Scroll up               }
begin
    ScrollUp;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessCSI_T;                  { Scroll down             }
begin
    UnimplementedEscape('T');
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.process_charset_G0(EscCmd : Char); { G0 character set     }
begin
    case EscCmd of
    '0': begin
             FCharSetG0       := EscCmd;
             FXlatInputTable  := @ibm_iso8859_1_G1;
             FXlatOutputTable := @ibm_iso8859_1_G1;
             FNoXlat          := FNoXlatInitial;
{             FNoXlat          := FALSE;}
         end;
    'B': begin
             FCharSetG0       := EscCmd;
             FXlatInputTable  := @ibm_iso8859_1_G0;
             FXlatOutputTable := @ibm_iso8859_1_G0;
             FNoXlat          := FNoXlatInitial;
         end;
    else
        InvalidEscape(EscCmd);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.process_charset_G1(EscCmd : Char); { G1 character set     }
begin
    FCharSetG1 := EscCmd;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.process_charset_G2(EscCmd : Char); { G2 character set     }
begin
    FCharSetG2 := EscCmd;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.process_charset_G3(EscCmd : Char); { G2 character set     }
begin
    FCharSetG3 := EscCmd;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.ProcessEscape(EscCmd : Char);
begin
    if Length(FEscBuffer) = 0 then begin
        case EscCmd of
        'D': ProcessESC_D;         { Index                   }
        'M': ProcessESC_M;         { Reverse index           }
        'E': ProcessESC_E;         { Next line               }
        'H': ;                     { Tabulation set          }
        '7': ProcessCSI_7;         { Save cursor             }
        '8': ProcessCSI_8;      { Restore Cursor          }
        '=': ;     { VT52 }        { Enter Alternate keypad  }
        '>': ;     { VT52 }        { Exit Alternate keypad   }
        '<': ;     { VT52 }        { Enter ANSI mode         }
        else
            InvalidEscape(EscCmd);
            WriteLiteralChar(EscCmd);
        end;

        Exit;
    end;

    case FEscBuffer[1] of
    ' ': begin
             case EscCmd of
             'F': ;
             else
                 InvalidEscape(EscCmd);
             end;
         end;
    '[': begin
             case EscCmd of
             'I': ProcessCSI_I;                { Select IBM char set     }
                                                { Extension F. Piette !!  }
             'J': ProcessCSI_J;                { Clear the screen        }
             'K': ProcessCSI_K;                { Erase to End of Line    }
             'L': ProcessCSI_L;                { Insert Line             }
             'M': ProcessCSI_M;                { Delete Line             }
             'm': ProcessCSI_m_lc;             { Select Attributes       }
             'n': ProcessCSI_n_lc;             { Cursor position report  }
             '@': ProcessCSI_at;               { Insert character        }
             'r': ProcessCSI_r_lc;             { Set Top and Bottom marg }
             's': ProcessCSI_s_lc;             { Save cursor location    }
             'u': ProcessCSI_u_lc;             { Restore cursor location }
             'H': ProcessCSI_H;                { Set Cursor Position     }
             'f': ProcessCSI_H;                { Set Cursor Position     }
             'g': ;                            { Tabulation Clear        }
             'h': ProcessCSI_h_lc;             { Terminal mode set       }
             'l': ProcessCSI_l_lc;             { Terminal mode reset     }
             'A': ProcessCSI_A;                { Cursor Up               }
             'B': ProcessCSI_B;                { Cursor Down             }
             'C': ProcessCSI_C;                { Cursor Right            }
             'D': ProcessCSI_D;                { Cursor Left             }
             'P': ProcessCSI_P;                { Delete Character        }
             'S': ProcessCSI_S;                { Scroll up               }
             'T': ProcessCSI_T;                { Scroll down             }
             '>': ;                            {                         }
             else
                 InvalidEscape(EscCmd);
             end;
         end;
    '(': process_charset_G0(EscCmd);           { G0 character set        }
    ')': process_charset_G1(EscCmd);           { G1 character set        }
    '*': process_charset_G2(EscCmd);           { G2 character set        }
    '+': process_charset_G3(EscCmd);           { G3 character set        }
    else
        InvalidEscape(EscCmd);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.WriteLiteralChar(Ch : Char);
var
    Line : TLine;
begin
    if FCol >= FColCount then begin
        if FAutoWrap then begin
            FCol := 0;
            Inc(FRow);
            if FRow >= FRowCount then begin
                Dec(FRow);
                ScrollUp;
            end;
        end;
    end;

    if FForceHighBit then
        Ch := Chr(ord(ch) or $80);

    Line := Lines[FRow];
    Line.Txt[FCol] := Ch;
    Line.Att[FCol] := FAttribute;
    InvRect(Frow, FCol);

    if FCol < High(Line.Txt) then
        Inc(FCol);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.SetAttr(Att : Char);
begin
     { Not implemented }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Write a single character at current cursor location.                      }
{ Update cursor position.                                                   }
procedure TScreen.WriteChar(Ch : Char);
var
    bProcess : Boolean;
begin
    if FCntLiteral > 0 then begin
        if (FCntLiteral and 1) <> 0 then
            WriteLiteralChar(Ch)
        else
            SetAttr(Ch);
        Dec(FCntLiteral);
        Exit;
    end;

    if FNoXlat then
        Ch := FXlatInputTable^[ord(Ch)];

    if FEscFLag then begin
        bProcess := FALSE;
        if (Length(FEscBuffer) = 0) and
           (Ch in ['D', 'M', 'E', 'H', '7', '8', '=', '>', '<']) then
             bProcess := TRUE
        else if (Length(FEscBuffer) = 1) and
           (FEscBuffer[1] in ['(', ')', '*', '+']) then
            bProcess := TRUE
        else if (Ch in ['0'..'9', ';', '?', ' ']) or
                ((Length(FEscBuffer) = 0) and
                 (ch in ['[', '(', ')', '*', '+'])) then begin
            FEscBuffer := FEscBuffer + Ch;
            if Length(FEscBuffer) >= High(FEscBuffer) then begin
                MessageBeep(MB_ICONASTERISK);
                FEscBuffer := '';
                FEscFlag   := FALSE;
            end;
        end
        else
            bProcess := TRUE;

        if bProcess then begin
            ProcessEscape(Ch);
            FEscBuffer := '';
            FEscFlag   := FALSE;
        end;

        Exit;
    end;

    case Ch of
    #0:  ;
    #7:  MessageBeep(MB_ICONASTERISK);
    #8:  BackSpace;
    #9:  begin
             repeat
                 Inc(FCol);
             until (FCol Mod 8) = 0;
         end;
    #10: begin
             CursorDown;
             if FAutoCR then
                 CarriageReturn;
         end;
    #13: begin
             CarriageReturn;
             if FAutoLF then
                 CursorDown;
         end;
    #14: begin
             FXlatInputTable  := @ibm_iso8859_1_G1;
             FXlatOutputTable := @ibm_iso8859_1_G1;
         end;
    #15: begin
             FXlatInputTable  := @ibm_iso8859_1_G0;
             FXlatOutputTable := @ibm_iso8859_1_G0;
         end;
    #27: begin
             FEscBuffer := '';
             FEscFlag   := TRUE;
         end;
    else
        WriteLiteralChar(Ch);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Write characters at current cursor location. Update cursor position.      }
procedure TScreen.WriteStr(Str : String);
var
    I : Integer;
begin
    for I := 1 to Length(Str) do
        WriteChar(Str[I]);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Read characters from the cursor to end of line                            }
function TScreen.ReadStr : String;
var
    Line : TLine;
    Len  : Integer;
begin
    Line := Lines[FRow];
    Len  := FColCount - FCol;
    if Len <= 0 then
        Result := ''
    else begin
        SetLength(Result, Len);
        Move(Line.Txt[FCol], Result[1], Len);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TScreen.GotoXY(X, Y : Integer);
begin
    if X < 0 then
        FCol := 0
    else if X >= FColCount then
        FCol := FColCount - 1
    else
        FCol := X;

    if Y < 0 then
        FRow := 0
    else if Y >= FRowCount then
        FRow := FRowCount - 1
    else
        FRow := Y;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetCaret;
begin
    if not FScreen.Focused then    { Jan 10, 2005 }
        Exit;

{$IFDEF CHAR_ZOOM}
    SetCaretPos(FCharPos[FScreen.FCol] + FLeftMargin + 2,
                FLinePos[FScreen.FRow - FTopLine] + FTopMargin + 3);
{$ELSE}
    SetCaretPos(FScreen.FCol * FCharWidth + FLeftMargin,
                (FScreen.FRow - FTopLine) * FLineHeight + FTopMargin);
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ Adjusts the scrollbar properties to match the number of host and scrollback
  lines that we can scroll through. }
procedure TCustomEmulVT.AdjustScrollBar;
var
    VisibleLines    : Integer;
begin
    if not FVScrollBar.Visible then
        Exit;
    FVScrollBar.Min := FScreen.FBackEndRow;
{$IFDEF CHAR_ZOOM}
    VisibleLines := Trunc((Height - FTopMargin - FBottomMargin) / (LineHeight * FLineZoom));
{$ELSE}
    VisibleLines := (Height - FTopMargin - FBottomMargin) Div LineHeight;
{$ENDIF}
    if VisibleLines > FScreen.FRowCount then
        VisibleLines := FScreen.FRowCount;
    FVScrollBar.Max         := FScreen.FRowCount - VisibleLines;
    FVScrollBar.Position    := FTopLine;
    FVScrollBar.SmallChange := 1;
    FVScrollBar.LargeChange := VisibleLines;
    FVScrollBar.Enabled     := FVScrollBar.Max > FVScrollBar.Min;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.Clear;
begin
    FScreen.ClearScreen;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetCursor(Row, Col : Integer);
begin
    FScreen.GotoXY(Col - 1, Row - 1);
{    SetCaret; }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WriteChar(Ch : Char);
begin
    if FCaretCreated and FCaretShown then begin
        HideCaret(Handle);
        FCaretShown := FALSE;
    end;

    if FLog then
        Write(FFileHandle, Ch);
    FScreen.WriteChar(ch);
    if FAutoRepaint then
        UpdateScreen;
{    SetCaret;   }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WriteStr(Str : String);
var
    I : Integer;
begin
    if FCaretCreated and FCaretShown then begin
        HideCaret(Handle);
        FCaretShown := FALSE;
    end;

    for I := 1 to Length(Str) do begin
        if FLog then
            Write(FFileHandle, Str[I]);
        FScreen.WriteChar(Str[I]);
    end;
    if FAutoRepaint then
        UpdateScreen;
{    SetCaret; }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WriteBuffer(Buffer : Pointer; Len : Integer);
var
    I : Integer;
begin
    if FCaretCreated and FCaretShown then begin
        HideCaret(Handle);
        FCaretShown := FALSE;
    end;

    for I := 0 to Len - 1 do begin
        if FLog then
            Write(FFileHandle, PChar(Buffer)[I]);
        FScreen.WriteChar(PChar(Buffer)[I]);
    end;
    if FAutoRepaint then
        UpdateScreen;
{    SetCaret; }
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.ReadStr : String;
begin
    Result := FScreen.ReadStr;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.CopyHostScreen;
begin
    FScreen.CopyScreenToBack;
    AdjustScrollBar;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
constructor TCustomEmulVT.Create(AOwner: TComponent);
type
    TMyLogPalette = record
        palVersion: Word;
        palNumEntries: Word;
        palPalEntry: array[0..NumPaletteEntries - 1] of TPaletteEntry;
    end;
    TPLogPalette = ^TLogPalette;
var
    plgpl      : ^TMyLogPalette;
    I          : Integer;
begin
    inherited Create(AOwner);
    ControlStyle := ControlStyle + [csOpaque];

    New(plgpl);
    plgpl^.palNumEntries := High(plgpl^.palPalEntry) + 1;
    plgpl^.palVersion    := $300;

    FTopMargin          := 4;
    FLeftMargin         := 6;
    FRightMargin        := 6;
    FBottomMargin       := 4;

    FPaletteEntries[0].peRed    := 0;                { Black }
    FPaletteEntries[0].peGreen  := 0;
    FPaletteEntries[0].peBlue   := 0;
    FPaletteEntries[1].peRed    := 168;              { Red }
    FPaletteEntries[1].peGreen  := 0;
    FPaletteEntries[1].peBlue   := 0;
    FPaletteEntries[2].peRed    := 0;                { Green  }
    FPaletteEntries[2].peGreen  := 168;
    FPaletteEntries[2].peBlue   := 0;
    FPaletteEntries[3].peRed    := 168;              { Yellow }
    FPaletteEntries[3].peGreen  := 168;
    FPaletteEntries[3].peBlue   := 0;
    FPaletteEntries[4].peRed    := 0;                { Dark Blue }
    FPaletteEntries[4].peGreen  := 0;
    FPaletteEntries[4].peBlue   := 168;
    FPaletteEntries[5].peRed    := 168;              { Magenta }
    FPaletteEntries[5].peGreen  := 0;
    FPaletteEntries[5].peBlue   := 168;
    FPaletteEntries[6].peRed    := 0;                { Cyan }
    FPaletteEntries[6].peGreen  := 112;
    FPaletteEntries[6].peBlue   := 216;
    FPaletteEntries[7].peRed    := 200;              { White }
    FPaletteEntries[7].peGreen  := 200;
    FPaletteEntries[7].peBlue   := 200;
    FPaletteEntries[8].peRed    := 84;               { Grey }
    FPaletteEntries[8].peGreen  := 84;
    FPaletteEntries[8].peBlue   := 84;
    FPaletteEntries[9].peRed    := 255;              { Red Highlight }
    FPaletteEntries[9].peGreen  := 84;
    FPaletteEntries[9].peBlue   := 212;
    FPaletteEntries[10].peRed   := 84;               { Green Highlight }
    FPaletteEntries[10].peGreen := 255;
    FPaletteEntries[10].peBlue  := 84;
    FPaletteEntries[11].peRed   := 255;              { Yellow Highlight }
    FPaletteEntries[11].peGreen := 255;
    FPaletteEntries[11].peBlue  := 84;
    FPaletteEntries[12].peRed   := 84;               { Blue Highlight }
    FPaletteEntries[12].peGreen := 84;
    FPaletteEntries[12].peBlue  := 255;
    FPaletteEntries[13].peRed   := 255;              { Magenta Highlight }
    FPaletteEntries[13].peGreen := 84;
    FPaletteEntries[13].peBlue  := 255;
    FPaletteEntries[14].peRed   := 84;               { Cyan highlight }
    FPaletteEntries[14].peGreen := 255;
    FPaletteEntries[14].peBlue  := 255;
    FPaletteEntries[15].peRed   := 255;              { White Highlight }
    FPaletteEntries[15].peGreen := 255;
    FPaletteEntries[15].peBlue  := 255;

    for I := 0 to High(plgpl^.palPalEntry) do begin
        plgpl^.PalPalEntry[I].peRed   := FPaletteEntries[I].peRed;
        plgpl^.PalPalEntry[I].peGreen := FPaletteEntries[I].peGreen;
        plgpl^.PalPalEntry[I].peBlue  := FPaletteEntries[I].peBlue;
        plgpl^.PalPalEntry[I].peFlags := PC_NOCOLLAPSE;
    end;

    FPal := CreatePalette(TPLogPalette(plgpl)^);
    Dispose(plgpl);

    FScreen             := TScreen.Create;
    FVScrollBar         := TScrollBar.Create(Self);
    FFont               := TFont.Create;
    FFont.Name          := 'Terminal';
    FFont.Size          := 12;
    FFont.Style         := [];
    FCharZoom           := 1.0;
    FLineZoom           := 1.0;
    SetupFont;

    FScreen.FXlatInputTable     := @ibm_iso8859_1_G0;
    FScreen.FXlatOutputTable    := @ibm_iso8859_1_G0;
    FScreen.OnCursorVisible     := CursorVisibleEvent;

    FCursorVisible      := TRUE;
    Width               := 250;
    Height              := 100;
    FBorderStyle        := bsSingle;
    FBorderWidth        := 1;
    FAutoRepaint        := TRUE;
    FFkeys              := 1;
    FGraphicDraw        := FALSE;

    with FVScrollBar do begin
        Parent   := Self;
        Kind     := sbVertical;
        Width    := 16;
        Visible  := TRUE;
        Align    := alRight;
        OnScroll := VScrollBarScroll;
    end;
    AdjustScrollBar;

    with FScreen do begin
        GotoXY(0, 0);
        WriteStr('EmulVT');
        GotoXY(0, 1);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetRows(Value : Integer);
begin
    with FScreen do begin
        if FRowCount <> Value then begin
            SetRowCount(Value);
            AdjustScrollBar;
            ClearScreen;
            Repaint;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetRows : Integer;
begin
    Result := FScreen.FRowCount;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetCols(Value : Integer);
begin
    with FScreen do begin
        if FColCount <> Value then begin
            FColCount := Value;
            ClearScreen;
            Repaint;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetCols : Integer;
begin
    Result := FScreen.FColCount;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.CursorVisibleEvent(Sender : TObject);
begin
    if FScreen.FCursorOff then begin
        if FCaretShown then begin
            HideCaret(Handle);
            FCaretShown := FALSE;
        end;
    end
    else begin
        if FScreen.Focused and not FCaretShown then begin
            ShowCaret(Handle);
            FCaretShown := TRUE;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetAutoLF(Value : Boolean);
begin
    FScreen.FAutoLF := Value;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetAutoCR(Value : Boolean);
begin
    FScreen.FAutoCR := Value;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetLog(Value : Boolean);
begin
    if FLog = Value then
        Exit;

    FLog := Value;

    if FLog then begin
{$I-}
        AssignFile(FFileHandle, 'EMULVT.LOG');
        Append(FFileHandle);
        if IOResult <> 0 then
            Rewrite(FFileHandle);
        Write(FFileHandle, '<Open>');
{$I+}
    end
    else begin
        Write(FFileHandle, '<Close>');
        CloseFile(FFileHandle);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetXlat(Value : Boolean);
begin
    FScreen.FNoXlat        := not Value;
    FScreen.FNoXlatInitial := not Value;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetXlat : Boolean;
begin
    Result := not FScreen.FNoXlatInitial;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetAutoLF : Boolean;
begin
    Result := FScreen.FAutoLF;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetAutoCR : Boolean;
begin
    Result := FScreen.FAutoCR;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
destructor TCustomEmulVT.Destroy;
begin
    if FLog then
        Log := FALSE;

    FFont.Free;
    FVScrollBar.Free;
    FScreen.Free;
    DeleteObject(FPal);
    inherited Destroy;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetBackRows(Value : Integer);
begin
    with FScreen do begin
        if FBackRowCount <> Value then begin
            SetBackRowCount(Value);
            AdjustScrollBar;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetTopLine(Value : Integer);
begin
    if Value < FVScrollBar.Min then
        Value := FVScrollBar.Min;
    if Value > FVScrollBar.Max then
        Value := FVScrollBar.Max;
    FTopLine := Value;
    FVScrollBar.Position := FTopLine;
    Repaint;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetBackRows : Integer;
begin
    Result := FScreen.FBackRowCount;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetBackColor(Value : TBackColors);
begin
    FScreen.FBackColor := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetBackColor : TBackColors;
begin
    Result := FScreen.FBackColor;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetMarginColor(const Value: Integer);
begin
    FMarginColor := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetLeftMargin(const Value: Integer);
begin
    FLeftMargin := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetBottomMargin(const Value: Integer);
begin
    FBottomMargin := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetRightMargin(const Value: Integer);
begin
    FRightMargin := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetTopMargin(const Value: Integer);
begin
    FTopMargin := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetOptions(Value : TScreenOptions);
begin
    FScreen.FOptions := Value;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.GetOptions : TScreenOptions;
begin
    Result := FScreen.FOptions;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetupFont;
var
    DC      : HDC;
    Metrics : TTextMetric;
    hObject : THandle;
begin
    DC      := GetDC(0);
    hObject := SelectObject(DC, FFont.Handle);
    GetTextMetrics(DC, Metrics);
    SelectObject(DC, hOBject);
    ReleaseDC(0, DC);

    SetCharWidth(Metrics.tmMaxCharWidth);
    SetLineHeight(Metrics.tmHeight);
    FInternalLeading := Metrics.tmInternalLeading;
    Invalidate;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetCharWidth(newValue : Integer);
var
    nCol : Integer;
begin
    FCharWidth := newValue;
    for nCol := Low(FCharPos) to High(FCharPos) do
        FCharPos[nCol] := Trunc(FCharWidth * nCol * FCharZoom);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetCharZoom(newValue : Single);
begin
    FCharZoom := newValue;
    SetCharWidth(FCharWidth);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetLineHeight(Value : Integer);
var
    nRow : Integer;
begin
    FLineHeight := Value;
    for nRow := 0 to High(FLinePos) do  { Jan 10, 2002 }
        FLinePos[nRow] := Trunc(FLineHeight * nRow * FLineZoom);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetLineZoom(newValue : Single);
begin
    FLineZoom := newValue;
    SetLineHeight(FLineHeight);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.SetFont(Value : TFont);
begin
    FFont.Assign(Value);
{$IFNDEF SINGLE_CHAR_PAINT}
    FFont.Pitch := fpFixed;
{$ENDIF}
    SetupFont;
    SetCaret;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WMLButtonDown(var Message: TWMLButtonDown);
begin
    inherited;
    SetFocus;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.VScrollBarScroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
    FTopLine := ScrollPos;
    Repaint;
    SetFocus;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.DoKeyBuffer(Buffer : PChar; Len : Integer);
var
    J : Integer;
    ch : Char;
begin
    if Assigned(FOnKeyBuffer) then
        FOnKeyBuffer(Self, Buffer, Len)
    else begin
        for J := 0 to Len - 1 do begin
            ch := Buffer[J];
            KeyPress(ch);
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.FindFKeys(ScanCode: Char; Shift: TShiftState;
                                 Ext: Boolean) : PFuncKeyValue;
var
    I      : Integer;
    pFKeys : PFuncKeysTable;
begin
    Result := nil;
    case FKeys of
    0 : pFKeys := @FKeys1;
    1 : pFKeys := @FKeys2;
    2 : pFKeys := @FKeys3;
    else
        pFKeys := @FKeys2;
    end;

    for I := Low(pFKeys^) to High(pFKeys^) do begin
        if (pFKeys^[I].ScanCode <> #0) and (pFKeys^[I].ScanCode = ScanCode) and
           (pFKeys^[I].Shift = Shift) and
           (pFKeys^[I].Ext = Ext) then begin
            Result := @pFKeys^[I].Value;
            Break;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.ProcessFKeys(ScanCode: Char; Shift: TShiftState;
                                    Ext: Boolean) : Boolean;
var
    I      : Integer;
    pFKeys : PFuncKeysTable;
begin
    Result := FALSE;
    case FKeys of
    0 : pFKeys := @FKeys1;
    1 : pFKeys := @FKeys2;
    2 : pFKeys := @FKeys3;
    else
        pFKeys := @FKeys2;
    end;

    for I := Low(pFKeys^) to High(pFKeys^) do begin
        if (pFKeys^[I].ScanCode <> #0) and (pFKeys^[I].ScanCode = ScanCode) and
           (pFKeys^[I].Shift = Shift) and
           (pFKeys^[I].Ext = Ext) then begin
            Result := TRUE;
            DoKeyBuffer(@pFKeys^[I].Value[1], Length(pFKeys^[I].Value));
            Break;
        end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.AppMessageHandler(var Msg: TMsg; var Handled: Boolean);
const
    v1 : String = 'aeiou';
    v2 : String = 'âêîôû';
    v3 : String = 'äëïöü';
    SpyFlag : Boolean = FALSE;
var
    Shift     : TShiftState;
    ShiftLock : Boolean;
    VirtKey   : Integer;
    Key       : Char;
    I         : Integer;
    ScanCode  : Char;
    Ext       : Boolean;
    SpyBuffer : String;
    FnBuffer  : String;
    pFV       : PFuncKeyValue;
begin
    if (Msg.hWnd = Handle) and (Msg.Message = WM_KEYDOWN) then begin
        VirtKey   := Msg.wParam;
        Key       := chr(Msg.wParam and $FF);
{        DebugString('AppMessageHandler KEYDOWN ' + IntToHex(Msg.wParam, 4) + #13 + #10); }
        Shift     := KeyDataToShiftState(Msg.lParam);
        ShiftLock := ((GetKeyState(VK_CAPITAL) and 1) > 0);
        ScanCode  := Chr(LOBYTE(HIWORD(Msg.lParam)));
        Ext       := ((Msg.lParam and $1000000) <> 0);

        if Assigned(FOnKeyDown) then begin
            FOnKeyDown(Self, VirtKey, Shift, ShiftLock, ScanCode, Ext);
            if VirtKey = 0 then begin
                Handled := TRUE;
                Exit;
            end;
        end;

        if (Msg.wParam <> VK_SHIFT) and
           (Msg.wParam <> VK_CONTROL) and
           (Msg.wParam <> VK_MENU) then begin
            if (ScanCode = '7') and
               (Shift = [ssAlt, ssCtrl]) and (Ext = FALSE) then begin
                { This is CTRL-ALT-* (on num pad) }
                SpyFlag := TRUE;
                Handled := TRUE;
                Exit;
            end;

            if SpyFlag then begin
                SpyFlag   := FALSE;
                pFV       := FindFKeys(ScanCode, Shift, Ext);
                SpyBuffer := IntToHex(Ord(ScanCode), 2) + ', ' +
                             ShiftStateToString(Shift) + ', ';

                if Ext then
                    SpyBuffer := SpyBuffer + 'TRUE'
                else
                    SpyBuffer := SpyBuffer + 'FALSE';

                if pFV <> nil then
                    SpyBuffer := SpyBuffer + ', ''' +
                                 FuncKeyValueToString(pFV^) + '''';

                SpyBuffer := SpyBuffer + #0;
                ClipBoard.SetTextBuf(@SpyBuffer[1]);

                FnBuffer := 'Key definition from tnchrk' +
                            IntToStr(FKeys) + '.cfg' + #0;
                Application.MessageBox(@SpyBuffer[1], @FnBuffer[1], MB_OK);
                Handled := TRUE;
                Exit;
            end;

            if ProcessFKeys(ScanCode, Shift, Ext) then begin
                Handled := TRUE;
                Exit;
            end;
        end;

        case Msg.wParam of
        VK_SHIFT, VK_CONTROL, VK_MENU: ;

        VK_NEXT, VK_PRIOR, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_HOME, VK_END:
            begin
                if ProcessFKeys(ScanCode, Shift, TRUE) then begin
                    Handled := TRUE;
                    Exit;
                end;
            end;
        VK_TAB, VK_RETURN, VK_ESCAPE, VK_BACK:
            begin
                Handled := TRUE;
            end;

        $DD:
            begin
                if not (ssAlt in Shift) then begin
                    Key     := #0;
                    Handled := TRUE;
                    if (ssShift in Shift) then
                        FFlagTrema := TRUE
                    else
                        FFlagCirconflexe := TRUE;
                end;
            end;

        ord('A')..ord('Z') :
            begin
                if (ssCtrl in Shift) then
                    Key := chr(Word(Key) and $1F)
                else if not ShiftLock and not (ssShift in Shift) then
                    Key := chr(Word(Key) or $20);
                if (FFlagCirconflexe) then begin
                    for I := Length(v1) downto 1 do begin
                        if Key = v1[I] then begin
                            Key := v2[I];
                            Break;
                        end;
                    end;
                    FFlagCirconflexe := FALSE;
                end;
                if (FFlagTrema) then begin
                    for I := Length(v1) downto 1 do begin
                        if Key = v1[I] then begin
                            Key := v3[I];
                            Break;
                        end;
                    end;
                    FFlagTrema       := FALSE;
                end;
                Handled := TRUE;
            end;
        end;

{        DebugString('Char = ' + IntToHex(Integer(Key), 2) + #13 + #10); }
        if Handled and (Key <> #0) then
            KeyPress(Key);
    end;

    if not Handled and Assigned(FAppOnMessage) then
        FAppOnMessage(Msg, Handled);
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.KeyPress(var Key: Char);
begin
    if not FScreen.FNoXlat then
        Key := FScreen.FXlatOutputTable^[ord(Key)];

    inherited KeyPress(Key);
    if FLocalEcho then begin
        WriteChar(Key);
        if not FAutoRepaint then
            UpdateScreen;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WMSetFocus(var Message: TWMSetFocus);
begin
{    inherited; }
    FScreen.Focused := TRUE;
{    SetupFont; }

    if not FCursorVisible then
        Exit;

    CreateCaret(Handle, 0, 2, FLineHeight);
    FCaretCreated := TRUE;
    SetCaret;
    if not FScreen.FCursorOff then begin
        ShowCaret(Handle);
        FCaretShown := TRUE;
    end;

    FAppOnMessage := Application.OnMessage;
    Application.OnMessage := AppMessageHandler;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WMKillFocus(var Message: TWMKillFocus);
begin
{    inherited; }
    FScreen.Focused := FALSE;

    if not FCursorVisible then
        Exit;

    if FCaretShown then begin
        HideCaret(Handle);
        FCaretShown := FALSE;
    end;

    if FCaretCreated then begin
        DestroyCaret;
        FCaretCreated := FALSE;
    end;

    Application.OnMessage := FAppOnMessage;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.MouseToCell(X, Y: Integer; var ACol, ARow: Longint);
begin
{$IFDEF CHAR_ZOOM}
    aRow := FScreen.FRowCount - 1;
    while (Y - FTopMargin) <= FLinePos[aRow] do
        Dec(aRow);
{$ELSE}
    aRow := (Y - FTopMargin) div FLineHeight;
{$ENDIF}
    if aRow < 0 then
        aRow := 0
    else if aRow >= FScreen.FRowCount then
        aRow := FScreen.FRowCount - 1;

{$IFDEF CHAR_ZOOM}
    aCol := FScreen.FColCount - 1;
    while (X - FLeftMargin) <= FCharPos[aCol] do
        Dec(aCol);
{$ELSE}
    aCol := (X - FLeftMargin) div FCharWidth;
{$ENDIF}
    if aCol < 0 then
        aCol := 0
    else if aCol >= FScreen.FColCount then
        aCol := FScreen.FColCount - 1;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.ShowCursor;
begin
    SetCaret;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WMPaletteChanged(var Message : TMessage);
{var
    HandleDC : HDC;}
begin
{        if Message.wParam <> Handle then begin
            HandleDC := GetDC(Handle);
            SelectPalette(HandleDC, FPal, FALSE);
            if RealizePalette(HandleDC) <> 0 then begin
                InvalidateRect(Handle, nil, TRUE);
                MessageBeep(0);
            end;
            ReleaseDC(Handle, HandleDC);
        end;
}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.UpdateScreen;
var
    rc : TRect;
begin
    if FScreen.FAllInvalid then
        InvalidateRect(Handle, nil, FALSE)
    else begin
{$Q-}
            with FScreen.FInvRect do begin
{$IFDEF CHAR_ZOOM}
                if Left = 9999 then begin
                    rc.Top    := 0;
                    rc.Bottom := 0;
                    rc.Left   := 0;
                    rc.Right  := 0;
                end
                else begin
                    rc.Top    := FTopMargin  + FLinePos[Top - FTopLine] + FInternalLeading;
                    rc.Bottom := FTopMargin  + FLinePos[Bottom + 1 - FTopLine] + FInternalLeading;
                    rc.Left   := FLeftMargin + FCharPos[Left];
                    rc.Right  := FLeftMargin + FCharPos[Right + 1];
                end;
{$ELSE}
                rc.Top    := FTopMargin  + FLineHeight * (Top - FTopLine) + FInternalLeading;
                rc.Bottom := FTopMargin  + FLineHeight * (Bottom + 1 - FTopLine) + FInternalLeading;
                rc.Left   := FLeftMargin + FCharWidth * Left;
                rc.Right  := FLeftMargin + FCharWidth * (Right + 1);
{$ENDIF}
            end;
            InvalidateRect(Handle, @rc, FALSE);
{$Q+}
    end;

    { Invalidate the region where the caret is. I should'nt do that, but }
    { if I do'nt, the caret remains where it is ! Bug ?                  }
{$IFDEF CHAR_ZOOM}
    rc.Top    := FLinePos[FScreen.FRow - FTopLine] + FTopMargin;
    rc.Bottom := FLinePos[FScreen.FRow - FTopLine + 1] + FTopMargin;
    rc.Left   := FLeftMargin + FCharPos[FScreen.FCol];
    rc.Right  := FLeftMargin + FCharPos[FScreen.FCol + 1];
{$ELSE}
    rc.Top    := FTopMargin  + FLineHeight * (FScreen.FRow - FTopLine);
    rc.Bottom := rc.Top + FLineHeight;
    rc.Left   := FLeftMargin + FCharWidth * FScreen.FCol;
    rc.Right  := rc.Left + FCharWidth;
{$ENDIF}
    InvalidateRect(Handle, @rc, FALSE);

    FScreen.InvClear;

    if FCaretCreated then begin
        ShowCaret(Handle);
        FCaretShown := TRUE;
    end;
    SetCaret;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.SnapPixelToRow(Y : Integer) : Integer;
var
    nRow : Integer;
begin
    nRow := PixelToRow(Y);
{$IFDEF CHAR_ZOOM}
    Result := FTopMargin + FLinePos[nRow];
{$ELSE}
    Result := FTopMargin + nRow * FLineHeight;
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.SnapPixelToCol(X : Integer) : Integer;
var
    nCol : Integer;
begin
    nCol := PixelToCol(X);
{$IFDEF CHAR_ZOOM}
    Result := FLeftMargin + FCharPos[nCol];
{$ELSE}
    Result := FLeftMargin + nCol * FCharWidth;
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.PixelToRow(Y : Integer) : Integer;
var
    nRow : Integer;
begin
{$IFDEF CHAR_ZOOM}
    nRow := FScreen.FRowCount - 1;
    while (nRow > 0) and ((Y - FTopMargin) < FLinePos[nRow]) do
        Dec(nRow);
{$ELSE}
    nRow := (Y - FTopMargin) div FLineHeight;
{$ENDIF}
    if nRow < 0 then
        nRow := 0;
    Result := nRow;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function TCustomEmulVT.PixelToCol(X : Integer) : Integer;
var
    nCol : Integer;
begin
{$IFDEF CHAR_ZOOM}
    nCol := FScreen.FColCount - 1;
    while (X - FLeftMargin) < FCharPos[nCol] do
        Dec(nCol);
{$ELSE}
    nCol := (X - FLeftMargin) div FCharWidth;
{$ENDIF}
    if nCol < 0 then
        nCol := 0;
    Result := nCol;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
{ This procedure will paint graphic char from the OEM charset (lines,       }
{ corners, T and other like) using GDI functions. This will result in       }
{ autosized characters, necessary for example to draw a frame when zoom     }
{ affect character and line spacing.                                        }
procedure TCustomEmulVT.PaintGraphicChar(
    DC   : HDC;
    X, Y : Integer;
    rc   : PRect;
    ch   : Char);
const
    OneSpace : Char = ' ';
var
    X1, X2, X3 : Integer;
    Y1, Y2, Y3 : Integer;
    Co         : TColor;
begin
    ExtTextOut(DC,
               X, Y,
               ETO_OPAQUE or ETO_CLIPPED, rc,
               @OneSpace, 1, nil);
    X1 := X;
    X3 := rc^.Right;
    X2 := (X1 + X3) div 2;
    Y1 := rc^.Top;
    Y3 := rc^.Bottom;
    Y2 := (Y1 + Y3) div 2;
    case Ch of
    #$C4: begin       { Horizontal single line }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
          end;
    #$B3: begin       { Vertical single line }
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
          end;
    #$DA: begin       { Upper Left Single Corner }
              Canvas.MoveTo(X3, Y2);
              Canvas.LineTo(X2, Y2);
              Canvas.LineTo(X2, Y3);
          end;
    #$C0: begin       { Bottom Left Single Corner }
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y2);
              Canvas.LineTo(X3, Y2);
          end;
    #$C1: begin       { Reverse T }
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y2);
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
          end;
    #$C2: begin       { T }
              Canvas.MoveTo(X2, Y3);
              Canvas.LineTo(X2, Y2);
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
          end;
    #$C3: begin       { Left T }
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
              Canvas.MoveTo(X2, Y2);
              Canvas.LineTo(X3, Y2);
          end;
    #$B4: begin       { Right T }
              Canvas.MoveTo(X2,     Y1);
              Canvas.LineTo(X2,     Y3);
              Canvas.MoveTo(X2,     Y2);
              Canvas.LineTo(X1 - 1, Y2);
          end;
    #$BF: begin       { Top Right Single Corner }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X2, Y2);
              Canvas.LineTo(X2, Y3);
          end;
    #$D9: begin       { Bottom Right Single Corner }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X2, Y2);
              Canvas.LineTo(X2, Y1 - 1);
          end;
    #$D6: begin       { Upper Left Single/Double Corner }
              Canvas.MoveTo(X3, Y2);
              Canvas.LineTo(X2 - 1, Y2);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y2);
              Canvas.LineTo(X2 + 1, Y3);
          end;
    #$D3: begin       { Bottom Left Single/Double Corner }
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y2);
              Canvas.LineTo(X3, Y2);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2);
          end;
    #$B7: begin       { Top Right Single/Double Corner }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X2 + 1, Y2);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y2);
              Canvas.LineTo(X2 - 1, Y3);
          end;
    #$BD: begin       { Bottom Right Single/Double Corner }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2);
              Canvas.LineTo(X1 - 1, Y2);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y2);
          end;
    #$D5: begin       { Upper Left Double/Single Corner }
              Canvas.MoveTo(X3, Y2 - 1);
              Canvas.LineTo(X2, Y2 - 1);
              Canvas.LineTo(X2, Y3);
              Canvas.MoveTo(X3, Y2 + 1);
              Canvas.LineTo(X2, Y2 + 1);
          end;
    #$D4: begin       { Bottom Left Double/Single Corner }
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X2, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
          end;
    #$B8: begin       { Top Right Double/Single Corner }
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X2, Y2 - 1);
              Canvas.LineTo(X2, Y3);
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X2, Y2 + 1);
          end;
    #$BE: begin       { Bottom Right Double/Single Corner }
              Canvas.MoveTo(X2,     Y1);
              Canvas.LineTo(X2,     Y2 + 1);
              Canvas.LineTo(X1 - 1, Y2 + 1);
              Canvas.MoveTo(X1,     Y2 - 1);
              Canvas.LineTo(X2,     Y2 - 1);
          end;
    #$CD: begin       { Horizontal Double line }
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
          end;
    #$BA: begin       { Vertical Double line }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y3);
          end;
    #$D1: begin       { T Top Horizontal Double line }
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
              Canvas.MoveTo(X2, Y2 + 1);
              Canvas.LineTo(X2, Y3);
          end;
    #$CF: begin       { T Bottom Horizontal Double line }
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
              Canvas.MoveTo(X2, Y2 - 1);
              Canvas.LineTo(X2, Y1);
          end;
    #$C6: begin       { T Left Horizontal Double line }
              Canvas.MoveTo(X2, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X2, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
          end;
    #$B5: begin       { T Right Horizontal Double line }
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X2, Y2 + 1);
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X2, Y2 - 1);
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
          end;
    #$C9: begin       { Upper Left Double Corner }
              Canvas.MoveTo(X3,     Y2 - 1);
              Canvas.LineTo(X2 - 1, Y2 - 1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X3,     Y2 + 1);
              Canvas.LineTo(X2 + 1, Y2 + 1);
              Canvas.LineTo(X2 + 1, Y3);
          end;
    #$C8: begin       { Bottom Left Double Corner }
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y2 + 1);
              Canvas.LineTo(X3,     Y2 + 1);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2 - 1);
              Canvas.LineTo(X3,     Y2 - 1);
          end;
    #$BB: begin       { Top Right Double Corner }
              Canvas.MoveTo(X1,     Y2 - 1);
              Canvas.LineTo(X2 + 1, Y2 - 1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X1,     Y2 + 1);
              Canvas.LineTo(X2 - 1, Y2 + 1);
              Canvas.LineTo(X2 - 1, Y3);
          end;
    #$BC: begin       { Bottom Right Double Corner }
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y2 - 1);
              Canvas.LineTo(X1 - 1, Y2 - 1);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2 + 1);
              Canvas.LineTo(X1 - 1, Y2 + 1);
          end;
    #$CC: begin       { Double left T }
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2 - 1);
              Canvas.LineTo(X3,     Y2 - 1);
              Canvas.MoveTo(X3,     Y2 + 1);
              Canvas.LineTo(X2 + 1, Y2 + 1);
              Canvas.LineTo(X2 + 1, Y3);
          end;
    #$B9: begin       { Double Right T }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y2 - 1);
              Canvas.LineTo(X1 - 1, Y2 - 1);
              Canvas.MoveTo(X1,     Y2 + 1);
              Canvas.LineTo(X2 - 1, Y2 + 1);
              Canvas.LineTo(X2 - 1, Y3);
          end;
    #$C7: begin       { Double T Single Left }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y2);
              Canvas.LineTo(X3,     Y2);
          end;
    #$B6: begin       { Double T Single Right }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 - 1, Y2);
              Canvas.LineTo(X1 - 1, Y2);
          end;
    #$D2: begin       { Single T Double Top }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
              Canvas.MoveTo(X2 - 1, Y2);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y2);
              Canvas.LineTo(X2 + 1, Y3);
          end;
    #$D0: begin       { Single T Double Bottom }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
              Canvas.MoveTo(X2 - 1, Y2);
              Canvas.LineTo(X2 - 1, Y1);
              Canvas.MoveTo(X2 + 1, Y2);
              Canvas.LineTo(X2 + 1, Y1);
          end;
    #$DB: begin       { Full Block }
              Canvas.Rectangle(X1, Y1, X3, Y3);
          end;
    #$DC: begin       { Half Bottom Block }
              Canvas.Rectangle(X1, Y2, X3, Y3);
          end;
    #$DD: begin       { Half Left Block }
              Canvas.Rectangle(X1, Y1, X2, Y3);
          end;
    #$DE: begin       { Half Right Block }
              Canvas.Rectangle(X2, Y1, X3, Y3);
          end;
    #$DF: begin       { Half Top Block }
              Canvas.Rectangle(X1, Y1, X2, Y2);
          end;
    #$C5: begin       { Single Cross }
              Canvas.MoveTo(X1, Y2);
              Canvas.LineTo(X3, Y2);
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
          end;
    #$CE: begin       { Double Cross }
              Canvas.MoveTo(X1,     Y2 - 1);
              Canvas.LineTo(X2 - 1, Y2 - 1);
              Canvas.LineTo(X2 - 1, Y1);
              Canvas.MoveTo(X1,     Y2 + 1);
              Canvas.LineTo(X2 - 1, Y2 + 1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2 - 1);
              Canvas.LineTo(X3,     Y2 - 1);
              Canvas.MoveTo(X2 + 1, Y3);
              Canvas.LineTo(X2 + 1, Y2 + 1);
              Canvas.LineTo(X3,     Y2 + 1);
          end;
    #$D8: begin      { Cross Double Horizontal Single vertical }
              Canvas.MoveTo(X1, Y2 + 1);
              Canvas.LineTo(X3, Y2 + 1);
              Canvas.MoveTo(X1, Y2 - 1);
              Canvas.LineTo(X3, Y2 - 1);
              Canvas.MoveTo(X2, Y1);
              Canvas.LineTo(X2, Y3);
          end;
    #$D7: begin      { Cross Single Horizontal Double Vertical }
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y3);
              Canvas.MoveTo(X2 - 1, Y1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X1,     Y2);
              Canvas.LineTo(X3,     Y2);
          end;
    #$CA: begin      { Double T bottom }
              Canvas.MoveTo(X1,     Y2 - 1);
              Canvas.LineTo(X2 - 1, Y2 - 1);
              Canvas.LineTo(X2 - 1, Y1);
              Canvas.MoveTo(X2 + 1, Y1);
              Canvas.LineTo(X2 + 1, Y2 - 1);
              Canvas.LineTo(X3,     Y2 - 1);
              Canvas.MoveTo(X1,     Y2 + 1);
              Canvas.LineTo(X3,     Y2 + 1);
          end;
    #$CB: begin      { Double T  }
              Canvas.MoveTo(X1,     Y2 + 1);
              Canvas.LineTo(X2 - 1, Y2 + 1);
              Canvas.LineTo(X2 - 1, Y3);
              Canvas.MoveTo(X2 + 1, Y3);
              Canvas.LineTo(X2 + 1, Y2 + 1);
              Canvas.LineTo(X3,     Y2 + 1);
              Canvas.MoveTo(X1,     Y2 - 1);
              Canvas.LineTo(X3,     Y2 - 1);
          end;
    #$B0: begin
              Co := Canvas.Pen.Color;
              for Y := Y1 to Y3 do begin
                  X := X1 + (Y mod 3);
                  while X < X3 do begin
                      Canvas.Pixels[X, Y] := Co;
                      X := X + 3;
                  end;
              end;
          end;
    #$B1: begin
              Co := Canvas.Pen.Color;
              for Y := Y1 to Y3 do begin
                  X := X1 + (Y and 1);
                  while X < X3 do begin
                      Canvas.Pixels[X, Y] := Co;
                      X := X + 2;
                  end;
              end;
          end;
    #$B2: begin
              Co := Canvas.Pen.Color;
              for Y := Y1 to Y3 do begin
                  X := X1 + (Y mod 3);
                  while X < X3 do begin
                      Canvas.Pixels[X, Y] := Co;
                      Inc(X);
                      if X < X3 then
                          Canvas.Pixels[X, Y] := Co;
                      Inc(X);
                      Inc(X);
                  end;
              end;
          end;
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.PaintOneLine(
    DC         : HDC;
    Y, Y1      : Integer;
    const Line : TLine;
    nColFrom   : Integer;
    nColTo     : Integer;
    Blank      : Boolean);
var
    rc        : TRect;
    nCnt      : Integer;
    nAtt      : Byte;
    X         : Integer;
    nChr      : Integer;
    Ch        : Char;
const
    BlankLine : array [0..MAX_COL] of char = '';
begin
    nAtt := Line.Att[nColFrom];

{    if nAtt = $0B then
        X := 0; }
{
    SetBkColor(DC, PALETTEINDEX(nAtt div $0F));
    SetTextColor(DC, PALETTEINDEX(nAtt and $0F));
}
    if not FMonoChrome then begin
        with FPaletteEntries[(nAtt shr 4) and $0F] do
            SetBkColor(DC, PALETTERGB(peRed, peGreen, peBlue));
        with FPaletteEntries[nAtt and $0F] do begin
            SetTextColor(DC, PALETTERGB(peRed, peGreen, peBlue));
            Canvas.Pen.Color   := PALETTERGB(peRed, peGreen, peBlue);
            Canvas.Brush.Color := PALETTERGB(peRed, peGreen, peBlue);
        end;
    end
    else begin
        if (nAtt div $0F) <> 0 then
            SetBkColor(DC, RGB(127, 127, 127))
        else
            SetBkColor(DC, RGB(255, 255, 255));

        if (nAtt and $0F) <> 0 then
            SetTextColor(DC, RGB(0, 0, 0))
        else
            SetTextColor(DC, RGB(255, 255, 255));
    end;

    nCnt      := nColTo - nColFrom;
    nChr      := 0;
{$IFDEF SINGLE_CHAR_PAINT}
    while nChr < nCnt do begin
{$IFDEF CHAR_ZOOM}
        X         := FLeftMargin + FCharPos[nColFrom + nChr];
        rc.Top    := Y  + FInternalLeading;
        rc.Bottom := Y1 + FInternalLeading;
        rc.Left   := X;
        rc.Right  := FLeftMargin + FCharPos[nColFrom + nChr + 1];
{$ELSE}
        X         := FLeftMargin + (nColFrom + nChr) * FCharWidth;
        rc.Top    := Y  + FInternalLeading;
        rc.Bottom := Y1 + FInternalLeading;
        rc.Left   := X;
        rc.Right  := rc.Left + FCharWidth;
{$ENDIF}
        if (nColFrom + nChr) = 0 then
           rc.Left := rc.Left - FLeftMargin;
        if (nColFrom + nChr) >= FScreen.FColCount then
           rc.Right := rc.Right + FRightMargin;
        if Blank then
           Ch := ' '
        else
           Ch := Line.Txt[nColFrom + nChr];
        if FGraphicDraw and
           (FScreen.FXlatOutputTable = @ibm_iso8859_1_G0) and
           (Ch >= #$B0) and (Ch <= #$DF) and
           (Ch in [#$B3, #$C4, #$DA, #$C0, #$C1, #$C2, #$C3, #$B4, #$BF, #$D9,
                   #$DB, #$DC, #$DD, #$DE, #$DF,
                   #$BA, #$CD, #$C9, #$C8, #$BB, #$BC,
                   #$CC, #$B9, #$C7, #$B6, #$D2, #$D0,
                   #$D5, #$D4, #$B8, #$BE,
                   #$C6, #$D1, #$B5, #$CF,
                   #$D6, #$B7, #$D3, #$BD,
                   #$C5, #$CE, #$D8, #$D7, #$CA, #$CB,
                   #$B0, #$B1, #$B2]) then
            PaintGraphicChar(DC, X, Y, @rc, Ch)
        else
            ExtTextOut(DC, X, Y, ETO_OPAQUE or ETO_CLIPPED, @rc, @Ch, 1, nil);
        Inc(nChr);
    end;
{$ELSE}
{$IFDEF CHAR_ZOOM}
    X         := LeftMargin + FCharPos[nColFrom];
    rc.Top    := Y  + FInternalLeading;
    rc.Bottom := Y1 + FInternalLeading;
    rc.Left   := X;
    rc.Right  := LeftMargin + FCharPos[nColFrom + nCnt];
{$ELSE}
    X         := LeftMargin + nColFrom * FCharWidth;
    rc.Top    := Y  + FInternalLeading;
    rc.Bottom := Y1 + FInternalLeading;
    rc.Left   := X;
    rc.Right  := rc.Left + nCnt * FCharWidth;
{$ENDIF}
    if nColFrom = 0 then
       rc.Left := rc.Left - LeftMargin;
    if nColTo >= FScreen.FColCount then
       rc.Right := rc.Right + RightMargin;
    if Blank then
        ExtTextOut(DC,
                   X, Y,
                   ETO_OPAQUE or ETO_CLIPPED, @rc,
                   @BlankLine[0], nCnt, nil)
    else
        ExtTextOut(DC,
                   X, Y,
                   ETO_OPAQUE or ETO_CLIPPED, @rc,
                   @Line.Txt[nColFrom], nCnt, nil);
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
function WRectangle(DC: HDC; X1, Y1, X2, Y2 : Integer) : LongBool;
begin
{$IFDEF USEWINDOWS}
    Result := Windows.Rectangle(DC, X1, Y1, X2, Y2);
{$ELSE}
    Result := Winprocs.Rectangle(DC, X1, Y1, X2, Y2);
{$ENDIF}
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}
procedure TCustomEmulVT.WMPaint(var Message: TWMPaint);
var
    DC        : HDC;
    PS        : TPaintStruct;
    Y, Y1     : Integer;
    rc        : TRect;
    OldPen    : THandle;
    OldBrush  : THandle;
    OldFont   : THandle;
    rcPaint   : TRect;
    DrawRct   : TRect;
    nRow      : Integer;
    nCol      : Integer;
    nColFrom  : Integer;
    Line      : TLine;
    BackBrush : HBrush;
    BackIndex : Integer;
begin
    { This may be a bit of overkill but we have to keep the scrollbar tracking
      with the number of lines visible on the screen.  The calling program can
      change the Height of the screen and we don't have a good way to know that.
      This routine will get called whenever the screen gets updated so it is a
      good time to update the scrollbar. }
    AdjustScrollBar;

    if not GetUpdateRect(WindowHandle, rc, FALSE) then
        Exit;

    BackBrush := 0;
    OldBrush  := 0;

    DC := Message.DC;
    if DC = 0 then
        DC := BeginPaint(WindowHandle, PS);
    try
        if not FMonoChrome then begin
            SelectPalette(DC, FPal, FALSE);
            RealizePalette(DC);
            if FMarginColor = -1 then
                BackIndex := FScreen.FAttribute div $0F
            else
                BackIndex := FMarginColor mod NumPaletteEntries;
            with FPaletteEntries[BackIndex] do
                BackBrush := CreateSolidBrush(PALETTERGB(peRed, peGreen, peBlue));
            OldBrush := SelectObject(DC, BackBrush);
        end;

    {$IFDEF USEWINDOWS}
        Windows.GetClientRect(WindowHandle, DrawRct);
    {$ELSE}
        WinProcs.GetClientRect(WindowHandle, DrawRct);
    {$ENDIF}
        rcPaint  := PS.rcPaint;
        rc.Left  := 2;
        rc.Right := DrawRct.Right - 2;
        nRow     := PixelToRow(rcPaint.top);
        nRow := nRow - 1;
        if nRow < 0 then
            nRow := 0;

{$IFDEF CHAR_ZOOM}
        Y  := FTopMargin + FLinePos[nRow];
        Y1 := FTopMargin + FLinePos[nRow + 1];
{$ELSE}
        Y  := FTopMargin + nRow * FLineHeight;
        Y1 := Y + FLineHeight;
{$ENDIF}

        if rcPaint.Top <= FTopMargin then begin
            OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
            WRectangle(DC, rcPaint.left, rcPaint.Top,
                       rcPaint.Right + 1,
                       FTopMargin + FInternalLeading + 1);
            SelectObject(DC, OldPen);
        end;

        if (nRow = 0) and (FInternalLeading > 0) then begin
            OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
            WRectangle(DC, rcPaint.left, rcPaint.Top,
                       rcPaint.Right + 1,
                       Y + FInternalLeading + 1);
            SelectObject(DC, OldPen);
        end;

        OldFont := SelectObject(DC, FFont.Handle);
        nRow    := nRow + FTopLine;
        while nRow < FScreen.FRowCount do begin
            rc.Top    := Y;
            rc.Bottom := Y + FLineHeight;
            if rc.Bottom > (DrawRct.Bottom - FBottomMargin) then begin
                OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
                WRectangle(DC, rc.Left - 2, rc.Top, rc.Right + 1,
                           DrawRct.Bottom - 1);
                SelectObject(DC, OldPen);
                Break;
            end;
            Line := FScreen.Lines[nRow];

            nCol     := 0;
            nColFrom := 0;
            while nCol < FScreen.FColCount do begin
                while (nCol < FScreen.FColCount) and
                      (Line.Att[nCol] = Line.Att[nColFrom]) do
                    Inc(nCol);

                PaintOneLine(DC, Y, Y1, Line, nColFrom, nCol, FALSE);
                nColFrom := nCol;
            end;

            nRow := nRow + 1;
{$IFDEF CHAR_ZOOM}
            Y    := FTopMargin + FLinePos[nRow - FTopLine];
            Y1   := FTopMargin + FLinePos[nRow + 1 - FTopLine];
{$ELSE}
            Y    := Y + FLineHeight;
            Y1   := Y + FLineHeight;
{$ENDIF}
            if Y > rcPaint.Bottom then
                Break;
        end;

        { Fill region between last text line and bottom of the window }
        OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
        if (FScreen.FRowCount - FTopLine) <= MAX_ROW then begin { WM + SE 09/08/00 }
            WRectangle(DC, rc.Left - 2,
                       FTopMargin + FLinePos[FScreen.FRowCount - FTopLine]{ + 1},
                       rc.Right + 3, DrawRct.Bottom + 1);
        end;
        SelectObject(DC, OldPen);

{$IFDEF CHAR_ZOOM}
        if (FLeftMargin + FCharPos[FScreen.FColCount]) < rc.Right then begin
            OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
            WRectangle(DC, FLeftMargin + FCharPos[FScreen.FColCount],
                       FTopMargin,   { 09/03/99 }
                       rcPaint.Right + 1, DrawRct.Bottom + 1);
            SelectObject(DC, OldPen);
        end;
{$ELSE}
        if (FLeftMargin + FScreen.FColCount * FCharWidth) < rc.Right then begin
            OldPen := SelectObject(DC, GetStockObject(NULL_PEN));
            WRectangle(DC, FLeftMargin + FScreen.FColCount * FCharWidth,
                       FTopMargin, rc.Right + 1, DrawRct.Bottom - 1);
            SelectObject(DC, OldPen);
        end;
{$ENDIF}

        if FSelectRect.Top <> -1 then begin
            SelectObject(DC, GetStockObject(NULL_BRUSH));
            SelectObject(DC, GetStockObject(BLACK_PEN));
            WRectangle(DC, FSelectRect.Left,
                       FSelectRect.Top,
                       FSelectRect.Right + 1,
                       FSelectRect.Bottom - 1);
            SelectObject(DC, GetStockObject(WHITE_PEN));
            WRectangle(DC, FSelectRect.Left - 1,
                       FSelectRect.Top - 1,
                       FSelectRect.Right + 2,
                       FSelectRect.Bottom);
        end;

        SelectObject(DC, OldFont);
        if OldBrush <> 0 then
            SelectObject(DC, OldBrush);
        if BackBrush <> 0 then
            DeleteObject(BackBrush);
    finally
        if Message.DC = 0 then
            EndPaint(WindowHandle, PS);
    end;
end;


{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

end.

