unit TMGHTML2;

(*
NOTES:  By Kevin Toppenberg, MD 5/27/09

Code heavily modified from original code found at www.supermemo.com/source/
Their notes (below) indicate that the code may be freely used.
---------------
This unit encapsulates SHDocVw.dll and MSHTML.dll functionality by subclassing
THtmlEditorBrowser object as THtmlEditor object

THtmlEditor was designed for easy use of HTML display and editing capacity in
SuperMemo 2002 for Windows developed by SuperMemo R&D in Fall 2001.

SuperMemo 2002 implements HTML-based incremental reading in which extensive HTML
support is vital.

Pieces of this units can be used by anyone in other Delphi applications that make
use of HTML WYSIWYG interfaces made open by Microsoft.
*)

(*
NOTICE: Also Derived from EmbeddedED.  See notes in that code block.
*)

interface

uses SysUtils, WinTypes, Dialogs, StdCtrls, Menus,
     EmbeddedED,
     ActiveX, MSHTMLEvents, SHDocVw, {MSHTML,} MSHTML_EWB,
     AppEvnts, controls,
     IeConst,Messages,Classes,Forms,Graphics;

type  
  TSetFontMode = (sfAll,sfSize,sfColor,sfName,sfStyle,sfCharset); 

  TRGBColor = record
       R : byte; 
       G : byte;
       B : byte;
  end; {record}
  
  TMGColor = record
    case boolean of
      True: (Color : TColor);
      False: (RGBColor : TRGBColor);
  end; {record}
                               
type 
    // THtmlObj=class(TWebBrowser)
    THtmlObj=class(TEmbeddedED)
    private
      CtrlToBeProcessed  :     boolean; 
      ShiftToBeProcessed :     boolean;
      CtrlReturnToBeProcessed: boolean;
      Modified:                boolean;
      FOrigAppOnMessage :      TMessageEvent;  
      FApplication :           TApplication;
      FActive :                boolean;
      FEditable:               boolean;       
      ColorDialog:             TColorDialog;       
      AllowNextBlur :          boolean;
      function  GetHTMLText:string;
      procedure SetHTMLText(HTML:String);
      function  GetText:string;
      procedure SetText(HTML:string);  
      function  GetEditableState : boolean;
      procedure SetEditableState (EditOn : boolean);         
      procedure SetBackgroundColor(Color:TColor);
      function  GetBackgroundColor : TColor;       
      function ColorToMSHTMLStr(color : TColor) : string; 
      function MSHTMLStrToColor(MSHTMLColor : string) : TColor;
      procedure SetTextForegroundColor(Color:TColor);
      function  GetTextForegroundColor : TColor; 
      procedure SetTextBackgroundColor(Color:TColor);
      function  GetTextBackgroundColor : TColor;
      function  GetFontSize : integer;
      procedure SetFontSize (Size : integer);                   
      function  GetFontName : string;
      procedure SetFontName (Name : string);
      function  GetSelText:string;        
      procedure SetSelText (HTMLText : string); 
      procedure ReassignKeyboardHandler(TurnOn : boolean);
      procedure GlobalMsgHandler(var Msg: TMsg; var Handled: Boolean);
      procedure HandleBlur(Sender: TObject);
      procedure SubMessageHandler(var Msg: TMessage); override;
      function SubFocusHandler(fGotFocus: BOOL): HResult; override;
      function GetActive : boolean;
      {end private}
    public
      {end public}
      PopupMenu:     TPopupMenu;
      KeyStruck : boolean; // A VERY crude determiner as to if Modified.
      NextControl : TWinControl;
      PrevControl : TWinControl;
      procedure SetMsgActive (Active : boolean);
      constructor Create(Owner:TControl; Application : TApplication);
      destructor Destroy; override;
      procedure Clear;
      procedure ToggleBullet;
      procedure ToggleItalic;
      procedure ToggleBold;         
      procedure ToggleNumbering;
      procedure ToggleUnderline;
      procedure ToggleSubscript;
      procedure ToggleSuperscript;
      procedure Indent;
      procedure Outdent;
      procedure AlignLeft;
      procedure AlignRight;
      procedure AlignCenter;
      procedure TextForeColorDialog;
      procedure TextBackColorDialog;     
      procedure FontDialog;
      function  SelStart:integer;
      function  SelEnd:integer;
      function  SelLength:integer;
      function  GetTextRange:IHtmlTxtRange;
      procedure ReplaceSelection(HTML:string);
      procedure Loaded; Override;      
      function  GetTextLen : integer;
      function  MoveCaretToEnd : boolean;
      function  MoveCaretToPos(ScreenPos: TPoint) : HRESULT;  //kt added
      procedure InsertTextAtCaret(Text : AnsiString); //Note: Text is NOT HTMLtext
      property  HTMLText:string read GetHTMLText write SetHTMLText;
      property  Text:string read GetText write SetText;
      //property Active : boolean read FActive write SetMsgActive;        
      property  Active : boolean read GetActive;
      property  Editable : boolean read GetEditableState write SetEditableState;
      property  BackgroundColor : TColor read GetBackgroundColor write SetBackgroundColor;
      property  FontSize : integer read GetFontSize write SetFontSize;
      property  FontName : string read GetFontName write SetFontName;
      property  SelText : string read GetSelText write SetSelText;
    end;

implementation


uses 
  WinProcs,Variants,Clipbrd, StrUtils, Math,
  Windows;

const   
  FontScale=3;  
  MaxTextLength = 100;
  nl = #13#10;
  
procedure EError(EText : string; E : Exception);
begin
  MessageDlg(EText,mtError,[mbOK],0);
end;


constructor THtmlObj.Create(Owner:TControl; Application : TApplication);
begin
  inherited Create(Owner);  //Note: Owner should be a descendant of TControl;  
  FApplication := Application;
  FOrigAppOnMessage := Application.OnMessage;
  OnBlur := HandleBlur;
  AllowNextBlur := false;  
  KeyStruck := false;
  NextControl := nil;
  PrevControl := nil;  
end;

destructor THtmlObj.Destroy;
begin
  SetMsgActive(false); //Turns off local OnMessage handling
  inherited Destroy;
end;

procedure THtmlObj.SetMsgActive (Active : boolean);
//NOTE: This object grabs the OnMessage for the entire application, so that 
//      it can intercept the right-click.  As a result, the object needs a
//      way that it can turn off this feature when it is covered up by other
//      windows application subwindows etc.  This function provides this.
begin
  FActive := Active;
  ReassignKeyboardHandler(FActive);
end;

procedure THtmlObj.SetHTMLText(Html : String); 
var //V : OleVariant;
    V2 : variant;
    body : IHTMLElement;
    status : string;
    temp : string;
begin
  DocumentHTML := Html;
  exit; //kt
  (*
  try
    Stop;
    if Doc =nil then exit;
    body := Doc.body;

    if UpperCase(Doc.designMode) <> 'ON' then begin
      Doc.designMode := 'on';
      repeat  //NOTE: potential endless loop.  Perhaps loop only status='loading'?
        status := Doc.readyState;
        {Possible status values:
          uninitialized	-- Object is not initialized with data.
          loading	      -- Object is loading its data.
          loaded	      -- Object has finished loading its data.
          interactive	  -- User can interact with the object even though it is not fully loaded.
          complete	    -- Object is completely initialized.                                      }      
        if status <> 'complete' then FApplication.ProcessMessages;
      until (status = 'complete') or (status='interactive') or (status='loaded');
    end;  
    body := Doc.body;
    if (body = nil) then begin   //Do so stuff to get IE to make a 'body'.
      V2 := VarArrayCreate([0, 0], VarVariant);
      V2[0] := ' ';  //Html;
      Doc.Write(PSafeArray(System.TVarData(V2).VArray));
      body := Doc.body;
      Doc.close;    
      repeat  
        status := Doc.readyState; //For possible status values, see above) 
        if status <> 'complete' then FApplication.ProcessMessages;
      until (status = 'complete') or (status='interactive') or (status='loaded');
      body := Doc.body;
    end;  
    body.innerHTML := Html;
    temp := body.innerHTML;  //to test if it was set or not...
    Modified:=true;
  except
    on E:Exception do EError('Error setting HTML text',E);
  end;  
  *)
end;
  

function THtmlObj.GetHTMLText:string;
var WS:WideString;
    ch:WideChar;
    n:integer;
    w:word;
    s:string;
begin
  //Result:=DocumentHTML;
  Result:='';
  if Doc=nil then exit;
  WS:=Doc.body.innerHTML;
  for n:=1 to length(WS) do begin
    ch := WS[n];
    w := word(ch);
    if w>255 then begin
       s:=IntToStr(w);
       s:='&#'+s+';';
    end else s:=ch;
    Result:=Result+s;
  end;  
end;

function THtmlObj.GetText:string;
var WS:WideString;
    ch:WideChar;
    n:integer;
    w:word;
    s:string;
begin
  Result:='';
  if DOC=nil then exit;
  WS:=Doc.body.innerText;
  for n:=1 to length(WS) do begin
    ch:=WS[n];
    w:=word(ch);
    if w>255 then begin
      w:=(w mod 256)+48;
      s:=IntToStr(w);
      s:=char(w);
    end else s:=ch;
    Result:=Result+s;
  end;
end;

procedure THtmlObj.SetText(HTML:string);
begin
  if (DOC=nil)or(DOC.body=nil) then SetHTMLText(HTML)
  else DOC.body.innerHTML:=HTML;
end;

procedure THtmlObj.Clear;
begin
  //kt if IsDirty then
    NewDocument;
    KeyStruck := false;
  //SetHTMLText('');
end;

function THtmlObj.GetEditableState : boolean;
var mode : string;
begin
  mode := Doc.designMode;
  result := (mode = 'On');
end;

procedure THtmlObj.SetEditableState(EditOn : boolean);
var LastMode : string;
    count : integer;
begin
  LastMode := 'Inherit';
  try
    count := 0;
    repeat
      inc (count);
      if Doc = nil then begin
        FApplication.ProcessMessages;
        Sleep (100);
        continue;
      end else if Doc.body = nil then begin
        FApplication.ProcessMessages;
        Sleep (100);
        continue;
      end;  
      if EditOn then begin
        Doc.body.setAttribute('contentEditable','true',0);
        Doc.designMode := 'On';  //kt
        FEditable:=true;
        //SetFocus;
      end else begin
        Doc.body.setAttribute('contentEditable','false',0);
        Doc.designMode := 'Off';  //kt
        FEditable:=false;
      end;  
      LastMode := Doc.designMode;
    until (LastMode <> 'Inherit') or (count > 20);  
  except
    on E:Exception do EError('Error switching into HTML editing state',E);
  end;
end;

procedure THtmlObj.SetBackgroundColor(Color:TColor);
begin
  if Doc=nil then exit;
  //WaitLoad(true); //kt  
  WaitForDocComplete;
  if Doc.body=nil then exit;
  Doc.body.style.backgroundColor := ColorToMSHTMLStr(Color);
end;

function  THtmlObj.GetBackgroundColor : TColor;       
begin
  Result := clBlack; //default;
  if Doc=nil then exit;
  if Doc.body=nil then exit;
  Result := MSHTMLStrToColor(Doc.body.style.backgroundColor);
end;

function THtmlObj.ColorToMSHTMLStr(color : TColor) : string; 
//Note: TColor stores colors lo-byte --> hi-byte as RGB
//Function returns '#RRGGBB'
var tempColor : TMGColor;        
begin
  tempColor.Color := color;
  Result := '#'+
            IntToHex(tempColor.RGBColor.R,2)+  
            IntToHex(tempColor.RGBColor.G,2)+
            IntToHex(tempColor.RGBColor.B,2);
end;

function THtmlObj.MSHTMLStrToColor(MSHTMLColor : string) : TColor;
//Function converts '#RRGGBB' -- TColor
//Note: TColor stores colors lo-byte --> hi-byte as RGB
var tempColor : TMGColor;            
    strHexRed,strHexGreen,strHexBlue : string[2];
begin
  Result := clBlack;  //FIX!!!! IMPLEMENT LATER...
  if Pos('#',MSHTMLColor)=1 then begin
   // MSHTMLColor := MidStr(MSHTMLColor,2,99);
   strHexRed := MidStr(MSHTMLColor,2,2);
   strHexGreen := MidStr(MSHTMLColor,4,2);
   strHexBlue := MidStr(MSHTMLColor,6,2);
   tempColor.RGBColor.R := StrToIntDef('$'+StrHexRed,0);
   tempColor.RGBColor.G := StrToIntDef('$'+StrHexGreen,0);
   tempColor.RGBColor.B := StrToIntDef('$'+StrHexBlue,0);
   Result := tempColor.Color;
   //NOTE: This function has not yet been tested....
  end;
end;

procedure THtmlObj.ToggleBullet;
begin
  if DOC=nil then exit;
  //SpecialCommand(IDM_UnORDERLIST,false,true,false,Null);
  DOC.execCommand('InsertUnorderedList',false,null);  
  Modified:=true;
end;

procedure THtmlObj.ToggleItalic;
begin
  if DOC=nil then exit;
  DOC.execCommand('Italic',false,null);  
  Modified:=true;
end;

procedure THtmlObj.ToggleBold;
begin
  if DOC=nil then exit;
  DOC.execCommand('Bold',false,null);
  Modified:=true;
end;

procedure THtmlObj.ToggleNumbering;
begin
  if DOC=nil then exit;
  DOC.execCommand('InsertOrderedList',false,null);
//  SpecialCommand(IDM_ORDERLIST,false,true,false,Null);
  Modified:=true;
end;

procedure THtmlObj.ToggleUnderline;
begin
   if DOC=nil then exit;
   DOC.execCommand('Underline',false,null);
  Modified:=true;
end;

procedure THtmlObj.ToggleSubscript;
begin
  if DOC=nil then exit;
  DOC.execCommand('Subscript',False,0);
  Modified:=true;
end;

procedure THtmlObj.ToggleSuperscript;
begin
  if DOC=nil then exit;
  DOC.execCommand('Superscript',False,0);
  Modified:=true;
end;


procedure THtmlObj.Indent;
begin
  if DOC=nil then exit;
  DOC.ExecCommand('Indent',false,0);
  Modified:=true;
end;

procedure THtmlObj.Outdent;
begin
  if DOC=nil then exit;
  DOC.ExecCommand('Outdent',false,0);
  Modified:=true;
end;


procedure THtmlObj.AlignLeft;
begin
  if DOC=nil then exit;
  DOC.ExecCommand('JustifyLeft',false,0);
  Modified:=true;
end;

procedure THtmlObj.AlignRight;
begin
  if DOC=nil then exit;
  DOC.ExecCommand('JustifyRight',false,0);
  Modified:=true;
end;

procedure THtmlObj.AlignCenter;
begin
  if DOC=nil then exit;
  DOC.ExecCommand('JustifyCenter',false,0);
  Modified:=true;
end;

procedure THtmlObj.TextForeColorDialog;
begin
  if ColorDialog = nil then begin
    ColorDialog := TColorDialog.Create(self);
  end;
  if ColorDialog.Execute then begin
    SetTextForegroundColor(ColorDialog.Color);
  end;  
  Modified:=true;
end;

procedure THtmlObj.TextBackColorDialog;
begin
  if ColorDialog = nil then begin
    ColorDialog := TColorDialog.Create(self);
  end;
  if ColorDialog.Execute then begin
    SetTextBackgroundColor(ColorDialog.Color);
  end;  
  Modified:=true;
end;

procedure THtmlObj.SetTextForegroundColor(Color:TColor);
begin
  if DOC=nil then exit;
  DOC.ExecCommand('ForeColor',false,Color);
  Modified:=true;
end;

function THtmlObj.GetTextForegroundColor:TColor;
var Background :  OleVariant;
    vt         :  TVarType;
begin
  Result:=clWindow;
  try
    if DOC=nil then exit;
    Background:=DOC.queryCommandValue('ForeColor');
    vt:=varType(Background);
    if vt<>varNull then Result:=Background;
  except
    on E:Exception do EError('Error retrieving foreground color',E);
  end;
end;

procedure THtmlObj.SetTextBackgroundColor(Color:TColor);
begin
  if DOC=nil then exit;
  DOC.ExecCommand('BackColor',false,Color);
  Modified:=true;
end;

function THtmlObj.GetTextBackgroundColor:TColor;
var Background :  OleVariant;
    vt         :  TVarType;
begin
  Result:=clWindow;
  try
    if DOC=nil then exit;
    Background:=DOC.queryCommandValue('BackColor');
    vt:=varType(Background);
    if vt<>varNull then Result:=Background;
  except
    on E:Exception do EError('Error retrieving background color',E);
  end;
end;

procedure THtmlObj.FontDialog;
begin
  DoCommand(IDM_FONT);
  Modified:=true;
end;

function THtmlObj.GetFontSize : integer;
var FontSize : OleVariant;
    vt       : TVarType;
    
begin
  FontSize:=Doc.queryCommandValue('FontSize');
  vt:=varType(FontSize);
  if vt<>varNull then Result := FontSize*FontScale
  else Result :=12*FontScale; //kt
end;

procedure THtmlObj.SetFontSize (Size : integer);
begin
  if Doc=nil then exit;
  Doc.ExecCommand('FontSize', false, Size div FontScale);
end;

function THtmlObj.GetFontName : string;
var FontName :OleVariant;
    vt : TVarType;
begin
  if DOC=nil then exit;
  FontName:=DOC.queryCommandValue('FontName');
  vt:=varType(FontName);
  if vt<>varNull then Result := FontName
  else Result :='Times New Roman'; //kt
end;

procedure THtmlObj.SetFontName (Name : string);
begin
  if DOC=nil then exit;
  DOC.ExecCommand('FontName', false, Name);
end;

function THtmlObj.SelStart:integer;
var TextRange:IHtmlTxtRange;
begin
  Result:=0;
  TextRange:=GetTextRange;
  if TextRange=nil then exit;
  Result:=Abs(Integer(TextRange.move('character',-MaxTextLength)));
end;

function THtmlObj.SelEnd:integer;
var TextRange:IHtmlTxtRange;
begin
  Result:=0;
  TextRange:=GetTextRange;
  if TextRange=nil then exit;
  Result:=Abs(Integer(TextRange.MoveEnd('character',-MaxTextLength)));
end;

function THtmlObj.SelLength:integer;
begin
  Result:=SelEnd-SelStart;
end;

function THtmlObj.GetTextRange:IHtmlTxtRange;
begin
  Result:=nil;
  try
    if DOC=nil then exit;
    while DOC.body=nil do begin
      //WaitLoad(true); //kt  
      WaitForDocComplete;
      if DOC.body=nil then begin
        if MessageDlg('Wait for document loading?',mtConfirmation,
                      [mbOK,mbCancel],0) <> mrOK then begin
          exit;
        end;  
      end;  
    end;
    if (DOC.Selection.type_='Text') or (DOC.Selection.type_='None') then begin
      Result:=DOC.Selection.CreateRange as IHtmlTxtRange;
    end;  
  except
    on E:Exception do EError('This type of selection cannot be processed',E);
  end;
end;

function THtmlObj.GetSelText:string;
var TextRange:IHtmlTxtRange;
begin
  Result:='';
  TextRange:=GetTextRange;
  if TextRange=nil then
     exit;
  Result:=TextRange.text;
end;

procedure THtmlObj.SetSelText (HTMLText : string);
begin
  ReplaceSelection(HTMLText);
end;

procedure THtmlObj.ReplaceSelection(HTML:string);
var TextRange:IHtmlTxtRange;
begin
  try
    TextRange:=GetTextRange;
    if TextRange=nil then exit;
    TextRange.PasteHTML(HTML); 
    Modified:=true;
  except
    on E:Exception do begin
      // implement later... ShortenString(HTML,80);
      EError('Error pasting HTML'+nl+
             'Microsoft HTML refuses to paste this string:'+nl+
             HTML+nl,E);
    end;
  end;
end;


function THtmlObj.MoveCaretToEnd : boolean;
//kt added
var //TextRange:IHtmlTxtRange;
    count : integer;
begin
  if not assigned (FTMGDisplayPointer) then begin
    Result := false;
    exit;
  end;
  Result:=(S_OK = FTMGDisplayPointer.MoveUnit(DISPLAY_MOVEUNIT_BottomOfWindow,0));
  count := 0;
  repeat
    Result:=(S_OK = FTMGDisplayPointer.MoveUnit(DISPLAY_MOVEUNIT_NextLine,-1));
    inc (count);
  until (Result = false) or (count > 500);
  Result:=(S_OK = FTMGDisplayPointer.MoveUnit(DISPLAY_MOVEUNIT_CurrentLineEnd,0));
  Result:=(S_OK = FCaret.MoveCaretToPointer(FTMGDisplayPointer,
                                            integer(FALSE),
                                            CARET_DIRECTION_SAME));
  {
  SendMessage(FmsHTMLwinHandle, WM_KEYDOWN, VK_END, 0);
  SendMessage(FmsHTMLwinHandle, WM_KEYUP, VK_END, 0);
  SendMessage(FmsHTMLwinHandle, WM_KEYDOWN, VK_END, 0);
  SendMessage(FmsHTMLwinHandle, WM_KEYUP, VK_END, 0);
  }
end;

function THtmlObj.MoveCaretToPos(ScreenPos: TPoint) : HRESULT;
//kt added entire function
var  OutTemp : DWORD;
begin
  if not assigned (FTMGDisplayPointer) then exit;
  FTMGDisplayPointer.moveToPoint(ScreenPos, COORD_SYSTEM_GLOBAL, nil, HT_OPT_AllowAfterEOL, OutTemp);
  Result := FCaret.MoveCaretToPointer(FTMGDisplayPointer,Integer(True),CARET_DIRECTION_INDETERMINATE);
  FCaret.Show(Integer(True));
end;

procedure THtmlObj.InsertTextAtCaret(Text : AnsiString);
//kt added.  Note: inserts external format (not HTML markup)
var P : PWideChar;
begin
  P := StringToOleStr(Text);
  FCaret.InsertText(P,Length(Text))
end;


procedure THtmlObj.Loaded; 
begin
  inherited Loaded;
end;

function THtmlObj.GetTextLen : integer;
begin
  Result := Length(GetText);
end;    


procedure THtmlObj.ReassignKeyboardHandler(TurnOn : boolean);
{assign HTML keyboard handler to HTML component; restore standard if TurnOn=false}
begin
  if TurnOn then begin
    FApplication.OnMessage := GlobalMsgHandler; 
  end else begin
    FApplication.OnMessage := FOrigAppOnMessage;
  end;      
end;

procedure THtmlObj.GlobalMsgHandler(var Msg: TMsg; var Handled: Boolean);
{NOTE: This message handler will receive ALL messages directed to CPRS.  I
       have to do this, because something is filtering messages before they
       get to this THTMLObj object.  My goal is to do as little here as possible,
       and let the OnMessage for THTMLObj (found in EmbeddedED) take care of the rest.
 NOTE: This should get activated by OnFocus for object, and deactivated 
       by OnBlur, so it actually should only get messages when focused.   }       
var 
  i : Integer;
  NewMsg : TMessage;
  
  function TransformMessage (WinMsg : TMsg) : TMessage;
  begin
    Result.Msg := WinMsg.message;
    Result.WParam := WinMsg.wParam;
    Result.LParam := WinMsg.lParam;
    Result.Result := 0;
  end;
  
begin
  Handled:=false; //default to not handled  
  if (Msg.Message=WM_KEYDOWN) then begin
    if (Msg.WParam=VK_UP) or (Msg.WParam=VK_DOWN) or (Msg.WParam=VK_TAB) then begin
        NewMsg := TransformMessage(Msg);
        SubMessageHandler(NewMsg);               
        Handled := (NewMsg.Result = 1);
    end; 
  end; 
end;


procedure THtmlObj.SubMessageHandler(var Msg: TMessage);
//Called from parent's EDMessageHandler, or from GlobalMsgHandler
var  i : Integer;
     WinControl : TWinControl;

begin
  Msg.Result := 0; //default to not handled  
  if not ((Msg.Msg=WM_KEYDOWN) or
          (Msg.Msg=WM_KEYUP) or
          (Msg.Msg=WM_RBUTTONUP) ) then exit;  //Speedy exit of non-handled messages
  case Msg.Msg of
    WM_RBUTTONUP : begin
                     if CtrlToBeProcessed then begin
                       CtrlToBeProcessed := false;                   
                       exit; //Ctrl-right click is ignored
                     end;  
                     if assigned(PopupMenu) then PopupMenu.Popup(Mouse.CursorPos.X,Mouse.CursorPos.Y);
                     Msg.Result := 1; //Handled
                     exit;
                   end;
    WM_KEYDOWN :   begin
                     GetSystemTimeAsFileTime(KeyPressTime);
                     KeyStruck := true;
                     //beep(200,50);
                     case Msg.WParam of
                       VK_ESCAPE  : begin
                                      if Assigned(PrevControl) then begin
                                        AllowNextBlur := true;
                                        PrevControl.SetFocus;
                                      end;  
                                    end; 
                       VK_CONTROL : begin
                                      CtrlToBeProcessed:=true;
                                      Msg.Result := 1; //Handled
                                      exit;
                                    end;
                       VK_SHIFT :   begin
                                      ShiftToBeProcessed:=true;
                                      Msg.Result := 1; //Handled
                                      exit;
                                    end;
                       VK_TAB :     begin
                                      if (ShiftToBeProcessed and CtrlToBeProcessed) then begin
                                        //This isn't working for some reason...
                                        for i := 0 to 5 do begin
                                          PostMessage(FmsHTMLwinHandle, WM_KEYDOWN, VK_LEFT, 0);
                                        end;  
                                        ShiftToBeProcessed := false;
                                        CtrlToBeProcessed := false;
                                      end else if ShiftToBeProcessed then begin 
                                        if Assigned(PrevControl) then begin
                                          AllowNextBlur := true;
                                          PrevControl.SetFocus;
                                        end;  
                                        ShiftToBeProcessed := false;
                                      end else if CtrlToBeProcessed then begin 
                                        if Assigned(NextControl) then begin
                                          AllowNextBlur := true;
                                          NextControl.SetFocus;
                                        end;  
                                        CtrltoBeProcessed := false;
                                      end else begin
                                        for i := 0 to 5 do begin
                                          PostMessage(FmsHTMLwinHandle, WM_KEYDOWN, VK_SPACE, 0);
                                        end;  
                                      end;  
                                      Msg.Result := 1; //Handled
                                    end;
                       {             
                       VK_RETURN :  if CtrlReturnToBeProcessed then begin 
                                      Msg.Result := 1; //Handled
                                      CtrlReturnToBeProcessed := false;
                                    end else if CtrlToBeProcessed then begin
                                      Msg.Result := 1; //Handled
                                      CtrlToBeProcessed := False; 
                                      CtrlReturnToBeProcessed := true;
                                      //PostMessage(Msg.hwnd, WM_KEYUP, VK_CONTROL, 0);                                      
                                    end else if ShiftToBeProcessed=false then begin
                                      //kt if not FEditable then exit;
                                      keybd_event(VK_SHIFT,0,0,0);
                                      keybd_event(VK_RETURN,0,0,0);
                                      keybd_event(VK_SHIFT,0,KEYEVENTF_KEYUP,0);
                                      Msg.Result := 1; //Handled
                                    end;
                       }             
                       Ord('B') :  if CtrlToBeProcessed then begin
                                     //kt if not FEditable then exit;
                                     ToggleBold;
                                     Msg.Result := 1; //Handled
                                     exit;
                                   end;  
                       Ord('U') :  if CtrlToBeProcessed then begin
                                     //kt if not FEditable then exit;
                                     ToggleUnderline;
                                     Msg.Result := 1; //Handled
                                     exit;
                                   end;  
                       Ord('I') :  if CtrlToBeProcessed then begin
                                     //kt if not FEditable then exit;
                                     ToggleItalic;
                                     Msg.Result := 1; //Handled
                                   end;  
                     end; {case}
                   end;
    WM_KEYUP :     begin
                     case Msg.WParam of
                       VK_CONTROL : begin
                                      CtrlToBeProcessed:=false;
                                      Msg.Result := 1; //Handled
                                      if CtrlReturnToBeProcessed then begin
                                        PostMessage(FmsHTMLwinHandle, WM_KEYDOWN, VK_RETURN, 0);
                                      end;                                      
                                      exit;
                                    end;
                       VK_SHIFT :   begin
                                      ShiftToBeProcessed:=false;
                                      Msg.Result := 1; //Handled
                                      exit;
                                   end;
                                   
                     end; {case}
                     exit;
                   end;    
  end;  {case}
end;

procedure THtmlObj.HandleBlur(Sender: TObject);
//kt added function
  function RecentKeyPressed : boolean;
  var NowTime : FILETIME; //kt 
      KeyTime,NowTime2 : LARGE_INTEGER;
      Delta : int64;
  begin
    GetSystemTimeAsFileTime(NowTime); 
    NowTime2.LowPart := NowTime.dwLowDateTime;
    NowTime2.HighPart := NowTime.dwHighDateTime;
    KeyTime.LowPart := KeyPressTime.dwLowDateTime;
    KeyTime.HighPart := KeyPressTime.dwHighDateTime;
    Delta := floor( (NowTime2.QuadPart - KeyTime.QuadPart) / 100000);
    Result := (Delta < 100) and (Delta > 0);
  end;

begin
  //kt Handle loss of focus when attempting to cursor above top line, or below bottom line.
  if (not AllowNextBlur) and RecentKeyPressed then begin   //kt entire block
    SetFocusToDoc;
    //beep(880,100);
    KeyPressTime.dwLowDateTime := 0;
    KeyPressTime.dwHighDateTime := 0;
    exit; 
  end;
  AllowNextBlur := false;
  SetMsgActive(false);
end;

function THtmlObj.SubFocusHandler(fGotFocus: BOOL): HResult; 
begin
  SetMsgActive(fGotFocus);
end;

function THtmlObj.GetActive : boolean;
begin
  Result := TWinControl(Owner).Visible;
end;


initialization

finalization

end.

