{%MainUnit win32int.pp}

{
 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}
{$IFOPT C-}
// Uncomment for local trace
//  {$C+}
//  {$DEFINE ASSERT_IS_ON}
{$ENDIF}
type
  TWinControlAccess = class(TWinControl);
{*************************************************************}
{            callback routines                                }
{*************************************************************}

procedure PrepareSynchronize;
begin
  TWin32WidgetSet(WidgetSet).HandleWakeMainThread(nil);
end;

{-----------------------------------------------------------------------------
  Function: PropEnumProc
  Params: Window - The window with the property
          Str    - The property name
          Data   - The property value
  Returns: Whether the enumeration should continue

  Enumerates and removes properties for the target window
 -----------------------------------------------------------------------------}
function PropEnumProc(Window: Hwnd; Str: PChar; Data: Handle): LongBool; stdcall;
begin
  Result:=false;
  if PtrUInt(Str) <= $FFFF then exit; // global atom handle
  RemoveProp(Window, Str);
  Result := True;
end;

{------------------------------------------------------------------------------
 Function: CallDefaultWindowProc
 Params: Window - The window that receives a message
         Msg    - The message received
         WParam - Word parameter
         LParam - Long-integer parameter
 Returns: 0 if Msg is handled; non-zero long-integer result otherwise

 Passes message on to 'default' handler. This can be a control specific window
 procedure or the default window procedure.
 ------------------------------------------------------------------------------}
function CallDefaultWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
  LParam: Windows.LParam): LResult;
  
  function IsComboboxAndHasEdit(Window: HWnd): Boolean;
  var
    Info: TComboboxInfo;
  begin
    Result := WndClassName(Window) = LCLComboboxClsName;
    if not Result then
      Exit;
    Info.cbSize := SizeOf(Info);
    Win32Extra.GetComboBoxInfo(Window, @Info);
    Result := (Info.hwndItem <> 0) and GetWin32WindowInfo(Info.hwndItem)^.isComboEdit;
  end;
var
  PrevWndProc: Windows.WNDPROC;
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  depthLen: integer;
{$endif}
  setComboWindow: boolean;
begin
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  depthLen := Length(MessageStackDepth);
  if depthLen > 0 then
    MessageStackDepth[depthLen] := '#';
{$endif}
  PrevWndProc := GetWin32WindowInfo(Window)^.DefWndProc;
  if (PrevWndProc = nil) or (PrevWndProc = @WindowProc) // <- prevent recursion
  then begin
    if UnicodeEnabledOS
    then Result := Windows.DefWindowProcW(Window, Msg, WParam, LParam)
    else Result := Windows.DefWindowProc(Window, Msg, WParam, LParam)
  end
  else begin
    // combobox child edit weirdness: combobox handling WM_SIZE will compare text
    // to list of strings, and if appears in there, will set the text, and select it
    // WM_GETTEXTLENGTH, WM_GETTEXT, WM_SETTEXT, EM_SETSEL
    // combobox sends WM_SIZE to itself indirectly, check recursion
    setComboWindow :=
        (Msg = WM_SIZE) and
        (ComboBoxHandleSizeWindow = 0) and
        IsComboboxAndHasEdit(Window);
    if setComboWindow then
      ComboBoxHandleSizeWindow := Window;
    if UnicodeEnabledOS then
      Result := Windows.CallWindowProcW(PrevWndProc, Window, Msg, WParam, LParam)
    else
      Result := Windows.CallWindowProc(PrevWndProc, Window, Msg, WParam, LParam);
    if setComboWindow then
      ComboBoxHandleSizeWindow := 0;
  end;
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  if depthLen > 0 then
    MessageStackDepth[depthLen] := ' ';
{$endif}
end;

procedure DrawParentBackground(Window: HWND; ControlDC: HDC);
var
  Parent: HWND;
  P: TPoint;
begin
  if ThemeServices.ThemesEnabled then
    ThemeServices.DrawParentBackground(Window, ControlDC, nil, False)
  else
  begin
    Parent := Windows.GetParent(Window);
    P.X := 0;
    P.Y := 0;
    Windows.MapWindowPoints(Window, Parent, P, 1);
    Windows.OffsetViewportOrgEx(ControlDC, -P.X, -P.Y, P);
    Windows.SendMessage(Parent, WM_ERASEBKGND, WParam(ControlDC), 0);
    Windows.SendMessage(Parent, WM_PRINTCLIENT, WParam(ControlDC), PRF_CLIENT);
    Windows.SetViewportExtEx(ControlDC, P.X, P.Y, nil);
  end;
end;

type
  TEraseBkgndCommand =
  (
    ecDefault,             // todo: add comments
    ecDiscard,             //
    ecDiscardNoRemove,     //
    ecDoubleBufferNoRemove //
  );
const
  EraseBkgndStackMask = $3;
  EraseBkgndStackShift = 2;
var
  EraseBkgndStack: dword = 0;

{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
function EraseBkgndStackToString: string;
var
  I: dword;
begin
  SetLength(Result, 8);
  for I := 0 to 7 do
    Result[8-I] := char(ord('0') + ((EraseBkgndStack shr (I*2)) and $3));
end;
{$endif}

procedure PushEraseBkgndCommand(Command: TEraseBkgndCommand);
begin
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  case Command of
    ecDiscard: DebugLn(MessageStackDepth,
      ' *forcing next WM_ERASEBKGND to discard message');
    ecDiscardNoRemove: DebugLn(MessageStackDepth,
      ' *forcing next WM_ERASEBKGND to discard message, no remove');
    ecDoubleBufferNoRemove: DebugLn(MessageStackDepth,
      ' *forcing next WM_ERASEBKGND to use double buffer, after that, discard no remove');
  end;
  DebugLn(MessageStackDepth, ' *erasebkgndstack: ', EraseBkgndStackToString);
{$endif}
  EraseBkgndStack := (EraseBkgndStack shl EraseBkgndStackShift) or dword(Ord(Command));
end;

type
  TDoubleBuffer = record
    DC: HDC;
    Bitmap: HBITMAP;
    BitmapWidth: integer;
    BitmapHeight: integer;
  end;

var
  CurDoubleBuffer: TDoubleBuffer = (DC: 0; Bitmap: 0; BitmapWidth: 0; BitmapHeight: 0);
  DisabledForms: TList = nil;
  CurrentWindow: HWND = 0;

function CheckMouseMovement: boolean;
  // returns true if mouse did not move between lmousebutton down
var
  lCursorPos: TPoint;
  moveX, moveY: integer;
begin
  GetCursorPos(lCursorPos);
  moveX := lCursorPos.X - MouseDownPos.X;
  moveY := lCursorPos.Y - MouseDownPos.Y;
  Result := (-3 <= moveX) and (moveX <= 3) and (-3 <= moveY) and (moveY <= 3);
end;

function GetNeedParentPaint(AWindowInfo: PWin32WindowInfo; AWinControl: TWinControl): boolean;
begin
  Result := AWindowInfo^.needParentPaint
    and ((AWinControl = nil) or not (csOpaque in AWinControl.ControlStyle));
  if ThemeServices.ThemesEnabled then
    Result := Result or (Assigned(AWinControl) and ([csParentBackground, csOpaque] * AWinControl.ControlStyle = [csParentBackground]));
end;

procedure DisposeComboEditWindowInfo(ComboBox: TCustomComboBox);
var
  Buddy: HWND;
  Info: TComboboxInfo;
begin
  Info.cbSize := SizeOf(Info);
  Win32Extra.GetComboBoxInfo(Combobox.Handle, @Info);
  Buddy := Info.hwndItem;
  if (Buddy <> Info.hwndCombo) and (Buddy <> 0) then
    DisposeWindowInfo(Buddy);
end;

function GetLCLWindowFromPoint(BaseControl: TControl; const Point: TPoint): HWND;
var
  ParentForm: TCustomForm;
  ParentRect: TRect;
  TheControl: TControl;
begin
  Result := 0;
  ParentForm := GetParentForm(BaseControl);
  if ParentForm <> nil then
  begin
    TheControl := ParentForm.ControlAtPos(ParentForm.ScreenToClient(Point), [capfAllowDisabled, capfAllowWinControls,
      capfRecursive, capfHasScrollOffset]);
    if TheControl is TWinControl then
      Result := TWinControlAccess(TheControl).WindowHandle;
    if Result = 0 then
    begin
      ParentRect := Rect(ParentForm.Left, ParentForm.Top,
        ParentForm.Left + ParentForm.Width, ParentForm.Top + ParentForm.Height);
      if PtInRect(ParentRect, Point) then
        Result := ParentForm.Handle;
    end;
  end;
end;

// Used by WindowProc :

function GetMenuParent(ASearch, AParent: HMENU): HMENU;
var
  c, i: integer;
  sub: HMENU;
begin
  c := GetMenuItemCount(AParent);
  for i:= 0 to c - 1 do
  begin
    sub := GetSubMenu(AParent, i);
    if sub = ASearch then
      Exit(AParent);
    Result := GetMenuParent(ASearch, sub);     // Recursive call
    if Result <> 0 then Exit;
  end;
  Result := 0;
end;

function GetIsNativeControl(AWindow: HWND): Boolean;
var
  S: String;
begin
  S := WndClassName(AWindow);
  Result := (S <> ClsName) and (S <> ClsHintName);
end;

procedure ClearSiblingRadioButtons(RadioButton: TRadioButton);
var
  Parent: TWinControl;
  Sibling: TControl;
  WinControl: TWinControlAccess absolute Sibling;
  LParamFlag: LRESULT;
  i: Integer;
begin
  Parent := RadioButton.Parent;
  for i:= 0 to Parent.ControlCount - 1 do
  begin
    Sibling := Parent.Controls[i];
    if (Sibling is TRadioButton) and (Sibling <> RadioButton) then
    begin
      // Pass previous state through LParam so the event handling can decide
      // when to propagate LM_CHANGE (New State <> Previous State)
      LParamFlag := Windows.SendMessage(WinControl.WindowHandle, BM_GETCHECK, 0, 0);
      // Pass SKIP_LMCHANGE through LParam if previous state is already unchecked
      if LParamFlag = BST_UNCHECKED then
        LParamFlag := SKIP_LMCHANGE;
      Windows.SendMessage(WinControl.WindowHandle, BM_SETCHECK,
        Windows.WParam(BST_UNCHECKED), Windows.LParam(LParamFlag));
    end;
  end;
end;

// sets the text of the combobox,
// because some events are risen, before the text is actually changed
procedure UpdateComboBoxText(ComboBox: TCustomComboBox);
var
  Index: Integer;
begin
  Index := ComboBox.ItemIndex;
  // Index might be -1, if current text is not in the list.
  if (Index>=0) then
    TWin32WSWinControl.SetText(ComboBox, ComboBox.Items[Index]);
end;

procedure EnableChildWindows(WinControl: TWinControl; Enable: boolean);
var
  i: integer;
  ChildControl: TWinControl;
begin
  for i := 0 to WinControl.ControlCount-1 do
  begin
    if WinControl.Controls[i] is TWinControl then
    begin
      ChildControl := TWinControl(WinControl.Controls[i]);
      if Enable then
      begin
        if ChildControl.Enabled then
          EnableWindow(ChildControl.Handle, true);
      end
      else
        EnableWindow(ChildControl.Handle, false);

      EnableChildWindows(ChildControl, Enable);   // Recursive call
    end;
  end;
end;

// A helper class for WindowProc to make it easier to split code into smaller pieces.
// The original function was about 2400 lines.

type
  TWindowProcHelper = class
  private
    // WindowProc parameters
    Window: HWnd;            // DWord / QWord
    Msg: UInt;               // LongWord
    WParam: Windows.WParam;  // PtrInt
    LParam: Windows.LParam;  // PtrInt
    // Other variables
    LMessage: TLMessage;
    PLMsg: PLMessage;
    lWinControl: TWinControl;
    WinProcess: Boolean;
    NotifyUserInput: Boolean;
    WindowInfo: PWin32WindowInfo;
    // Used by SendPaintMessage
    BackupBuffer: TDoubleBuffer;
    WindowWidth, WindowHeight: Integer;
    PaintMsg: TLMPaint;
    RTLLayout: Boolean;
    // Structures for message handling
    OrgCharCode: word; // used in WM_CHAR handling
    LMScroll: TLMScroll; // used by WM_HSCROLL
    LMKey: TLMKey; // used by WM_KEYDOWN WM_KEYUP
    LMChar: TLMChar; // used by WM_CHAR
    LMMouse: TLMMouse; // used by WM_LBUTTONDBLCLK
    LMContextMenu: TLMContextMenu;
    LMMouseMove: TLMMouseMove; // used by WM_MOUSEMOVE
    LMMouseEvent: TLMMouseEvent; // used by WM_MOUSEWHEEL
    LMMove: TLMMove; // used by WM_MOVE
    LMNotify: TLMNotify; // used by WM_NOTIFY
    DrawListItemStruct: TDrawListItemStruct; //used by WM_DRAWITEM
    NMHdr: PNMHdr; // used by WM_NOTIFY

    procedure CalcClipRgn(PaintRegion: HRGN);
    function DoChildEdit(out WinResult: LResult): Boolean;
    procedure DoCmdCheckBoxParam;
    function DoCmdComboBoxParam: Boolean;
    procedure DoMsgActivateApp;
    procedure DoMsgChar(var WinResult: LResult);
    procedure DoMsgColor(ChildWindowInfo: PWin32WindowInfo);
    procedure DoMsgDrawItem;
    procedure DoMsgEnable;
    function DoMsgEraseBkgnd(var WinResult: LResult): Boolean;
    procedure DoMsgKeyDownUp(aMsg: Cardinal; var WinResult: LResult);
    procedure DoMsgLButtonCommon;
    procedure DoMsgLButtonDoubleClick;
    procedure DoMsgLButtonDown;
    procedure DoMsgMeasureItem;
    procedure DoMsgMouseMove;
    procedure DoMsgMouseUpDownClick(aMsg: Cardinal);
    procedure DoMsgContextMenu;
    function DoMsgMouseWheel(var WinResult: LResult): Boolean;
    function DoMsgMove: Boolean;
    procedure DoMsgNCLButtonDown;
    function DoMsgNotify(var WinResult: LResult): Boolean;
    procedure DoMsgShowWindow;
    procedure DoMsgSize;
    procedure DoMsgSysKey(aMsg: Cardinal);
    procedure DoSysCmdKeyMenu;
    procedure DoSysCmdMinimize;
    procedure DoSysCmdRestore;
    function GetPopMenuItemObject: TObject;
    function GetMenuItemObject(ByPosition: Boolean): TObject;
    function PrepareDoubleBuffer(vDC: HDC; out DoubleBufferBitmapOld: HBITMAP): Boolean;
    procedure SetLMCharData(aMsg: Cardinal; UpdateKeyData: Boolean = False);
    procedure SetLMKeyData(aMsg: Cardinal; UpdateKeyData: Boolean = False);
    procedure SetLMessageAndParams(aMsg: Cardinal; ResetWinProcess: Boolean = False);
    procedure SendPaintMessage(ControlDC: HDC);
    procedure HandleScrollMessage(LMsg: integer);
    procedure HandleSetCursor;
    procedure HandleSysCommand;
    function IsComboEditSelection: boolean;
    procedure HandleBitBtnCustomDraw(ABitBtn: TCustomBitBtn);
    procedure HandleDropFiles;
    function HandleUnicodeChar(var AChar: Word): boolean;
    procedure UpdateDrawItems;
    procedure UpdateDrawListItem(aMsg: UInt);
    procedure UpdateLMMovePos(X, Y: Smallint);
    procedure UpdateUIState(CharCode: Word);
    function DoWindowProc: LResult;    // Called from the actual WindowProc.
  end;

// Implementation of TWindowProcHelper

procedure TWindowProcHelper.SetLMCharData(aMsg: Cardinal; UpdateKeyData: Boolean);
begin
  LMChar.Msg := aMsg;
  LMChar.CharCode := Word(WParam);
  if UpdateKeyData then
    LMChar.KeyData := LParam;
end;

procedure TWindowProcHelper.SetLMKeyData(aMsg: Cardinal; UpdateKeyData: Boolean);
begin
  LMKey.Msg := aMsg;
  LMKey.CharCode := Word(WParam);
  if UpdateKeyData then
    LMKey.KeyData := LParam;
end;

procedure TWindowProcHelper.SetLMessageAndParams(aMsg: Cardinal; ResetWinProcess: Boolean);
begin
  LMessage.Msg := aMsg;
  LMessage.WParam := WParam;
  LMessage.LParam := LParam;
  if ResetWinProcess then
    WinProcess := False;
end;

function TWindowProcHelper.GetPopMenuItemObject: TObject;
var
  MainMenuHandle: HMENU;
  MenuInfo: MENUITEMINFO;
begin
  MenuInfo.cbSize := MMenuItemInfoSize;
  MenuInfo.fMask := MIIM_DATA;

  MainMenuHandle := GetMenuParent(HMENU(WParam), GetMenu(Window));
  if GetMenuItemInfo(MainMenuHandle, LOWORD(LParam), true, @MenuInfo) then
    Result := TObject(MenuInfo.dwItemData)
  else
    Result := nil;
end;

function TWindowProcHelper.GetMenuItemObject(ByPosition: Boolean): TObject;
var
  MenuInfo: MENUITEMINFO;
  PopupMenu: TPopupMenu;
begin
  // first we have to decide if the command is from a popup menu
  // or from the window main menu
  // if the 'PopupMenu' property exists, there is a big probability
  // that the command is from a popup menu

  PopupMenu := WindowInfo^.PopupMenu;
  if Assigned(PopupMenu) then
  begin
    Result := PopupMenu.FindItem(LOWORD(Integer(WParam)), fkCommand);
    if Assigned(Result) then
      Exit;
  end;

  // nothing found, process main menu
  MenuInfo.cbSize := MMenuItemInfoSize;
  MenuInfo.fMask := MIIM_DATA;

  if GetMenuItemInfo(GetMenu(Window), LOWORD(Integer(WParam)), ByPosition, @MenuInfo) then
    Result := TObject(MenuInfo.dwItemData)
  else
    Result := nil;
end;

function TWindowProcHelper.PrepareDoubleBuffer(vDC: HDC; out DoubleBufferBitmapOld: HBITMAP): Boolean;
// Returns True if BackupBuffer was saved.
begin
  Result := CurDoubleBuffer.DC <> 0;
  if Result then
  begin
    // we've been called from another paint handler. To prevent killing of
    // not own DC and HBITMAP lets save then and restore on exit
    BackupBuffer := CurDoubleBuffer;
    FillChar(CurDoubleBuffer, SizeOf(CurDoubleBuffer), 0);
  end;
  CurDoubleBuffer.DC := Windows.CreateCompatibleDC(vDC);

  GetWindowSize(Window, WindowWidth, WindowHeight);
  if (CurDoubleBuffer.BitmapWidth < WindowWidth) or (CurDoubleBuffer.BitmapHeight < WindowHeight) then
  begin
    if CurDoubleBuffer.Bitmap <> 0 then
      Windows.DeleteObject(CurDoubleBuffer.Bitmap);
    CurDoubleBuffer.BitmapWidth := WindowWidth;
    CurDoubleBuffer.BitmapHeight := WindowHeight;
    CurDoubleBuffer.Bitmap := Windows.CreateCompatibleBitmap(vDC, WindowWidth, WindowHeight);
    if RTLLayout then  // change the default layout - LTR - of memory DC
      {if (GetLayout(vDC) and LAYOUT_BITMAPORIENTATIONPRESERVED) > 0 then  // GetLayout is not in win32extra
        SetLayout(CurDoubleBuffer.DC, LAYOUT_RTL or LAYOUT_BITMAPORIENTATIONPRESERVED)
      else //}
        SetLayout(CurDoubleBuffer.DC, LAYOUT_RTL);
  end;
  DoubleBufferBitmapOld := Windows.SelectObject(CurDoubleBuffer.DC, CurDoubleBuffer.Bitmap);
  PaintMsg.DC := CurDoubleBuffer.DC;
  {$ifdef MSG_DEBUG}
    DebugLn(MessageStackDepth, ' *double buffering on DC: ', IntToHex(CurDoubleBuffer.DC, sizeof(HDC)*2));
  {$endif}
end;

procedure TWindowProcHelper.CalcClipRgn(PaintRegion: HRGN);
var
  nSize, BytesCount: DWORD;
  RgnData: PRgnData;
  WindowOrg: Windows.POINT;
  XFRM: TXFORM;
  MirroredPaintRgn: HRGN;
begin
  // winnt returns in screen coordinates
  // win9x returns in window coordinates
  if Win32Platform = VER_PLATFORM_WIN32_NT then
  begin
    WindowOrg.X := 0;
    WindowOrg.Y := 0;
    MapWindowPoints(Window, 0, WindowOrg, 1);
    if RTLLayout then // We need the left side of the client area in screen coordinates
      WindowOrg.X := WindowOrg.X - lWinControl.ClientWidth;
    Windows.OffsetRgn(PaintRegion, -WindowOrg.X, -WindowOrg.Y);
  end;

  if RTLLayout then // Paint region needs to be mirrored before using it for clipping!
  begin
    {
    //Method 1 - Switch Layout to LTR, Clip, Switch back to RTL
    //Sometimes it's off by one or two pixels!!
    SetLayout(CurDoubleBuffer.DC, LAYOUT_LTR);
    Windows.SelectClipRgn(CurDoubleBuffer.DC, PaintRegion);
    SetLayout(CurDoubleBuffer.DC, LAYOUT_RTL);//}

    //Method 2 - Create a mirrored region based on the one we have
    nSize := GetRegionData(PaintRegion, 0, nil);
    RgnData := GetMem(nSize);
    XFRM.eDx:=0;   XFRM.eDy:=0;
    XFRM.eM11:=-1; XFRM.eM12:=0;
    XFRM.eM21:=0;  XFRM.eM22:=1;

    // ToDo: BytesCount is not initialized.
    MirroredPaintRgn := ExtCreateRegion(@XFRM, BytesCount, RgnData^);
    Windows.SelectClipRgn(CurDoubleBuffer.DC, MirroredPaintRgn);
    Windows.DeleteObject(MirroredPaintRgn);
    Freemem(RgnData);
  end
  else
    Windows.SelectClipRgn(CurDoubleBuffer.DC, PaintRegion);
end;

procedure TWindowProcHelper.SendPaintMessage(ControlDC: HDC);
var
  DC: HDC;
  PaintRegion: HRGN;
  PS : TPaintStruct;
  DoubleBufferBitmapOld: HBITMAP;
  ORect: TRect;
{$ifdef DEBUG_DOUBLEBUFFER}
  ClipBox: Windows.RECT;
{$endif}
  ParentPaintWindow: HWND;
  DCIndex: integer;
  parLeft, parTop: integer;
  BufferWasSaved: Boolean;
  useDoubleBuffer: Boolean;
  isNativeControl: Boolean;
  needParentPaint: Boolean;
begin
  // note: ignores the received DC
  // do not use default deliver message
  if lWinControl = nil then
  begin
    lWinControl := GetWin32WindowInfo(Window)^.PWinControl;
    if lWinControl = nil then exit;
  end;

  // create a paint message
  isNativeControl := GetIsNativeControl(Window);
  needParentPaint := GetNeedParentPaint(WindowInfo, lWinControl);
  // if needParentPaint and not isTabPage then background will be drawn in
  // WM_ERASEBKGND and WM_CTLCOLORSTATIC for native controls
  // sent by default paint handler
  if WindowInfo^.isTabPage or (needParentPaint and (not isNativeControl or (ControlDC <> 0))) then
    ParentPaintWindow := Windows.GetParent(Window)
  else
    ParentPaintWindow := 0;
{$IFDEF DBG_SendPaintMessage}
  DebugLnEnter(['>>> SendPaintMessage for CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window),
     ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl),
     ' NativeCtrl=', dbgs(isNativeControl), ' ndParentPaint=', dbgs(needParentPaint),
     ' isTab=', dbgs(WindowInfo^.isTabPage)    ]);
  try
{$ENDIF}

  // if painting background of some control for tabpage, don't handle erase background
  // in parent of tabpage
  if WindowInfo^.isTabPage then
    PushEraseBkgndCommand(ecDiscard);

  // check if double buffering is requested
  useDoubleBuffer := (ControlDC = 0) and (lWinControl.DoubleBuffered or ThemeServices.ThemesEnabled);
  BufferWasSaved := False;
{$ifdef MSG_DEBUG}
  if not useDoubleBuffer then
    DebugLn(MessageStackDepth, ' *painting, but not double buffering');
{$endif}
  WinProcess := false;
  try
    if ControlDC = 0 then
    begin
      // ignore first erase background on themed control, paint will do everything
      if ThemeServices.ThemesEnabled then
        PushEraseBkgndCommand(ecDoubleBufferNoRemove);
      DC := Windows.BeginPaint(Window, @PS);
{$IFDEF DBG_SendPaintMessage}
      if ThemeServices.ThemesEnabled then
        DebugLn(['SendPaintMessage for CtrlDC=', dbgs(ControlDC), ' Remove one from EraseBkgndStack val=', (EraseBkgndStack and 3)]);
{$ENDIF}
      if ThemeServices.ThemesEnabled then
        EraseBkgndStack := EraseBkgndStack shr EraseBkgndStackShift;
      if useDoubleBuffer then
      begin
        RTLLayout := (GetWindowLong(Window, GWL_EXSTYLE) and WS_EX_LAYOUTRTL) = WS_EX_LAYOUTRTL;
        BufferWasSaved := PrepareDoubleBuffer(DC, DoubleBufferBitmapOld);
{$ifdef DEBUG_DOUBLEBUFFER}
        ORect.Left := 0;
        ORect.Top := 0;
        ORect.Right := CurDoubleBuffer.BitmapWidth;
        ORect.Bottom := CurDoubleBuffer.BitmapHeight;
        Windows.FillRect(CurDoubleBuffer.DC, ORect, GetSysColorBrush(COLOR_DESKTOP));
{$endif}
        PaintRegion := CreateRectRgn(0, 0, 1, 1);
        if GetRandomRgn(DC, PaintRegion, SYSRGN) = 1 then
          CalcClipRgn(PaintRegion);
{$ifdef DEBUG_DOUBLEBUFFER}
        Windows.GetClipBox(CurDoubleBuffer.DC, ClipBox);
        DebugLn('Double buffering in DC ', IntToHex(CurDoubleBuffer.DC, sizeof(HDC)*2),
          ' with clipping rect (',
          IntToStr(ClipBox.Left), ',', IntToStr(ClipBox.Top), ';',
          IntToStr(ClipBox.Right), ',', IntToStr(ClipBox.Bottom), ')');
{$endif}
        // a copy of the region is selected into the DC, so we
        // can free our region immediately
        Windows.DeleteObject(PaintRegion);
      end;
    end else begin
      DC := ControlDC;
      PaintRegion := 0;
      if useDoubleBuffer then
        BufferWasSaved := PrepareDoubleBuffer(DC, DoubleBufferBitmapOld);
    end;

    if ParentPaintWindow <> 0 then
      GetWin32ControlPos(Window, ParentPaintWindow, parLeft, parTop);
    //Is not necessary to check the result of GetLCLClientBoundsOffset since
    //the false condition (lWincontrol = nil or lWincontrol <> TWinControl) is never met
    //The rect is always initialized with 0
    GetLCLClientBoundsOffset(lWinControl, ORect);
    PaintMsg.Msg := LM_PAINT;
    PaintMsg.PaintStruct := @PS;
    if not useDoubleBuffer then
      PaintMsg.DC := DC;
    if not needParentPaint then
    begin
      // send through message to allow message override, moreover use SendMessage
      // to allow subclass window proc override this message too
{$IFDEF DBG_SendPaintMessage}
      DebugLnEnter('> SendPaintMessage call WM_ERASEBKGND for CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window), ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl));
{$ENDIF}
      Include(TWinControlAccess(lWinControl).FWinControlFlags, wcfEraseBackground);
      Windows.SendMessage(lWinControl.Handle, WM_ERASEBKGND, Windows.WPARAM(PaintMsg.DC), 0);
      Exclude(TWinControlAccess(lWinControl).FWinControlFlags, wcfEraseBackground);
{$IFDEF DBG_SendPaintMessage}
      DebugLnExit('< SendPaintMessage back from WM_ERASEBKGND for CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window), ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl));
{$ENDIF}
    end;
    if ParentPaintWindow <> 0 then
    begin
{$ifdef MSG_DEBUG}
      DebugLn(MessageStackDepth, ' *painting background by sending paint message to parent window ',
        IntToHex(ParentPaintWindow, 8));
{$endif}
      // tabpage parent and got a dc to draw in, divert paint to parent
      DCIndex := Windows.SaveDC(PaintMsg.DC);
      DrawParentBackground(Window, PaintMsg.DC);
      Windows.RestoreDC(PaintMsg.DC, DCIndex);
    end;
    if (ControlDC = 0) or not needParentPaint then
    begin
      DCIndex := Windows.SaveDC(PaintMsg.DC);
      MoveWindowOrgEx(PaintMsg.DC, ORect.Left, ORect.Top);
{$ifdef DEBUG_DOUBLEBUFFER}
      Windows.GetClipBox(PaintMsg.DC, ClipBox);
      DebugLn('LCL Drawing in DC ', IntToHex(PaintMsg.DC, 8), ' with clipping rect (',
        IntToStr(ClipBox.Left), ',', IntToStr(ClipBox.Top), ';',
        IntToStr(ClipBox.Right), ',', IntToStr(ClipBox.Bottom), ')');
{$endif}
{$IFDEF DBG_SendPaintMessage}
      DebugLnEnter('> SendPaintMessage call DeliverMessage for CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window), ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl));
{$ENDIF}
      DeliverMessage(lWinControl, PaintMsg);
{$IFDEF DBG_SendPaintMessage}
      DebugLnExit('< SendPaintMessage back from DeliverMessage Ufor CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window), ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl));
{$ENDIF}
      Windows.RestoreDC(PaintMsg.DC, DCIndex);
    end;
    if useDoubleBuffer then
      Windows.BitBlt(DC, 0, 0, WindowWidth, WindowHeight, CurDoubleBuffer.DC, 0, 0, SRCCOPY);
    if ControlDC = 0 then
      Windows.EndPaint(Window, @PS);
  finally
    if useDoubleBuffer then
    begin
      SelectObject(CurDoubleBuffer.DC, DoubleBufferBitmapOld);
      DeleteDC(CurDoubleBuffer.DC);
      CurDoubleBuffer.DC := 0;
      if BufferWasSaved then
      begin
        if CurDoubleBuffer.Bitmap <> 0 then
          DeleteObject(CurDoubleBuffer.Bitmap);
        CurDoubleBuffer := BackupBuffer;
      end;
{$ifdef DEBUG_DOUBLEBUFFER}
      if CopyBitmapToClipboard then
      begin
//        Windows.OpenClipboard(0);
//        Windows.EmptyClipboard;
//        Windows.SetClipboardData(CF_BITMAP, DoubleBufferBitmap);
//        Windows.CloseClipboard;
        CopyBitmapToClipboard := false;
      end;
{$endif}
    end;
  end;
{$IFDEF DBG_SendPaintMessage}
  finally
  DebugLnExit('<<< SendPaintMessage for CtrlDC=', dbgs(ControlDC), ' Window=', dbgs(Window), ' WinCtrl=',dbgs(PtrUInt(lWinControl)), ' ', DbgSName(lWinControl));
  end;
{$ENDIF}
end;

procedure TWindowProcHelper.HandleScrollMessage(LMsg: integer);
var
  ScrollInfo: TScrollInfo;
begin
  with LMScroll do
  begin
    Msg := LMsg;
    ScrollCode := LOWORD(WParam);
    SmallPos := 0;
    ScrollBar := HWND(LParam);
    Pos := 0;
  end;

  if not (LOWORD(WParam) in [SB_THUMBTRACK, SB_THUMBPOSITION])
  then begin
    WindowInfo^.TrackValid := False;
    Exit;
  end;

  // Note on thumb tracking
  // When using the scrollwheel, windows sends SB_THUMBTRACK
  // messages, but only when scroll.max < 32K. So in that case
  // Hi(WParam) won't cycle.
  // When ending scrollbar tracking we also get those
  // messages. Now Hi(WParam) is cycling.
  // To get the correct value you need to use GetScrollInfo.
  //
  // Now there is a problem. GetScrollInfo returns always the old
  // position. So in case we get track messages, we'll keep the
  // last trackposition.
  // To get the correct position, we use the most significant
  // part of the last known value (or the value returned by
  // ScrollInfo). The missing least significant part is given
  // by Hi(WParam), since it is cycling, the or of both will give
  // the position
  // This only works if the difference between the last pos and
  // the new pos is < 64K, so it might fail if we don't get track
  // messages
  // MWE.

  ScrollInfo.cbSize := SizeOf(ScrollInfo);
  if LOWORD(WParam) = SB_THUMBTRACK
  then begin
    ScrollInfo.fMask := SIF_TRACKPOS;
    // older windows versions may not support trackpos, so fill it with some default
    if WindowInfo^.TrackValid
    then ScrollInfo.nTrackPos := Integer(WindowInfo^.TrackPos and $FFFF0000) or HIWORD(WParam)
    else ScrollInfo.nTrackPos := HIWORD(WParam);
  end
  else begin
    ScrollInfo.fMask := SIF_POS;
    ScrollInfo.nPos := HIWORD(WParam);
  end;

  if LParam <> 0
  then begin
    // The message is send by a scrollbar
    GetScrollInfo(HWND(LParam), SB_CTL, ScrollInfo);
  end
  else begin
    // The message is send by a window's standard scrollbar
    if LMsg = LM_HSCROLL
    then GetScrollInfo(Window, SB_HORZ, ScrollInfo)
    else GetScrollInfo(Window, SB_VERT, ScrollInfo);
  end;

  if LOWORD(WParam) = SB_THUMBTRACK
  then begin
    LMScroll.Pos := ScrollInfo.nTrackPos;
    WindowInfo^.TrackPos := ScrollInfo.nTrackPos;
    WindowInfo^.TrackValid := True;
  end
  else begin
    if WindowInfo^.TrackValid
    then LMScroll.Pos := LongInt(WindowInfo^.TrackPos and $FFFF0000) or HIWORD(WParam)
    else LMScroll.Pos := (ScrollInfo.nPos and $FFFF0000) or HIWORD(WParam);
  end;

  if LMScroll.Pos < High(LMScroll.SmallPos)
  then LMScroll.SmallPos := LMScroll.Pos
  else LMScroll.SmallPos := High(LMScroll.SmallPos);
end;

procedure TWindowProcHelper.HandleSetCursor;
var
  lControl: TControl;
  BoundsOffset: TRect;
  ACursor: TCursor;
  MouseMessage: Word;
  P: TPoint;
begin
  if Assigned(lWinControl) then
  begin
    if not (csDesigning in lWinControl.ComponentState) and (LOWORD(LParam) = HTCLIENT) then
    begin
      ACursor := Screen.Cursor;
      if ACursor = crDefault then
      begin
        Windows.GetCursorPos(Windows.POINT(P));
        Windows.ScreenToClient(Window, Windows.POINT(P));
        if GetLCLClientBoundsOffset(lWinControl, BoundsOffset) then
        begin
          Dec(P.X, BoundsOffset.Left);
          Dec(P.Y, BoundsOffset.Top);
        end;
        // TGraphicControl controls do not get WM_SETCURSOR messages...
        lControl := lWinControl.ControlAtPos(P, [capfOnlyClientAreas,
          capfAllowWinControls, capfHasScrollOffset, capfRecursive]);
        if lControl = nil then
          lControl := lWinControl;
        ACursor := lControl.Cursor;
      end;
      if ACursor <> crDefault then
      begin
        // DebugLn('Set cursor. Control = ', LControl.Name, ' cur = ',ACursor);
        Windows.SetCursor(Screen.Cursors[ACursor]);
        LMessage.Result := 1;
      end;
    end
    else
    if (LOWORD(LParam) = Word(HTERROR)) then
    begin
      MouseMessage := HIWORD(LParam);
      // a mouse click on a window
      if ((MouseMessage = WM_LBUTTONDOWN) or
          (MouseMessage = WM_RBUTTONDOWN) or
          (MouseMessage = WM_XBUTTONDOWN)) and
         Assigned(Screen) and Assigned(Screen.ActiveCustomForm) and
         Screen.ActiveCustomForm.HandleAllocated and
         (GetForegroundWindow <> Screen.ActiveCustomForm.Handle) then
      begin
        // A mouse click is happen on our application window which is not active
        // we need to active it ourself. This is needed only when click is happen
        // on disabled window (e.g. ShowModal is called and non modal window is clicked)
        Win32WidgetSet.AppBringToFront;
        LMessage.Result := 1;
      end;
    end;
  end;
  if LMessage.Result = 0 then
    SetLMessageAndParams(LM_SETCURSOR);
  WinProcess := False;
end;

procedure TWindowProcHelper.DoSysCmdKeyMenu;
var
  ParentForm: TCustomForm;
  TargetWindow, prevFocus: HWND;
begin
  ParentForm := GetParentForm(lWinControl);
  if (ParentForm <> nil) and ((ParentForm.Menu = nil) or (csDesigning in ParentForm.ComponentState))
  and (Application <> nil) and (Application.MainForm <> nil)
  and (Application.MainForm <> ParentForm)
  and Application.MainForm.HandleAllocated then
  begin
    TargetWindow := Application.MainFormHandle;
    if IsWindowEnabled(TargetWindow) and IsWindowVisible(TargetWindow) then
    begin
      prevFocus := Windows.GetFocus;
      Windows.SetFocus(targetWindow);
      PLMsg^.Result := Windows.SendMessage(TargetWindow, WM_SYSCOMMAND, WParam, LParam);
      Windows.SetFocus(prevFocus);
      WinProcess := False;
    end;
  end;
end;

procedure TWindowProcHelper.DoSysCmdMinimize;
begin
  if Assigned(lWinControl) and (Application.MainForm = lWinControl)
  and not Application.MainFormOnTaskBar then
    Window := Win32WidgetSet.AppHandle; //redirection

  if (Window = Win32WidgetSet.AppHandle) and not Application.MainFormOnTaskBar then
  begin
    HidePopups(Win32WidgetSet.AppHandle);
    if Assigned(Application.MainForm) then
    begin
      Windows.SetWindowPos(Window, HWND_TOP,
        Application.MainForm.Left, Application.MainForm.Top,
        Application.MainForm.Width, 0, SWP_NOACTIVATE);
      if Application.MainForm.HandleAllocated then
        Windows.ShowWindow(Application.MainFormHandle, SW_HIDE);
    end;

    PLMsg^.Result := Windows.DefWindowProc(Window, WM_SYSCOMMAND, WParam, LParam);
    WinProcess := False;
    Application.IntfAppMinimize;
  end
  else
  if Assigned(lWinControl) and (lWinControl = Application.MainForm) then
  begin
    PLMsg^.Result := Windows.DefWindowProc(Window, WM_SYSCOMMAND, WParam, LParam);
    WinProcess := False;
    Application.IntfAppMinimize;
  end;
end;

procedure TWindowProcHelper.DoSysCmdRestore;
begin
  if (Window = Win32WidgetSet.AppHandle) and not Application.MainFormOnTaskBar then
  begin
    PLMsg^.Result := Windows.DefWindowProc(Window, WM_SYSCOMMAND, WParam, LParam);
    WinProcess := False;
    if Assigned(Application.MainForm) and Application.MainForm.HandleAllocated then
    begin
      if Application.MainForm.HandleObjectShouldBeVisible then
        Windows.ShowWindow(Application.MainFormHandle, SW_SHOWNA);
      RestorePopups;
    end;
    Application.IntfAppRestore;
  end
  else if Assigned(lWinControl) and (lWinControl = Application.MainForm) then
    Application.IntfAppRestore;
end;

procedure TWindowProcHelper.HandleSysCommand;
begin
  // forward keystroke to show window menu, if parent form has no menu
  // if wparam contains SC_KEYMENU, lparam contains key pressed
  // keymenu+space should always bring up system menu
  case (WParam and $FFF0) of
    SC_KEYMENU:
      if (lWinControl <> nil) and (lParam <> VK_SPACE) then
        DoSysCmdKeyMenu;
    SC_MINIMIZE:
      if Assigned(Application) then
        DoSysCmdMinimize;
    SC_RESTORE:
      if Assigned(Application) then
        DoSysCmdRestore;
  end;
end;

function TWindowProcHelper.IsComboEditSelection: boolean;
begin
  Result := WindowInfo^.isComboEdit and (ComboBoxHandleSizeWindow = Windows.GetParent(Window));
end;

procedure TWindowProcHelper.HandleBitBtnCustomDraw(ABitBtn: TCustomBitBtn);
var
  DrawInfo: PNMCustomDraw;
  ARect: TRect;
  ShowFocus: Boolean;
begin
  DrawInfo := PNMCustomDraw(NMHdr);
  case DrawInfo^.dwDrawStage of
    CDDS_PREPAINT, CDDS_POSTPAINT:
    begin
      lmNotify.Result := CDRF_DODEFAULT or CDRF_NOTIFYPOSTPAINT;
      WinProcess := False;
      if ABitBtn.Focused then
      begin
        if WindowsVersion >= wv2000 then
          ShowFocus := (Windows.SendMessage(ABitBtn.Handle, WM_QUERYUISTATE, 0, 0) and UISF_HIDEFOCUS) = 0
        else
          ShowFocus := True;
        if ShowFocus then
        begin
          ARect := DrawInfo^.rc;
          InflateRect(ARect, -3, -3);
          if not IsRectEmpty(ARect) then
            Windows.DrawFocusRect(DrawInfo^.hdc, ARect);
        end;
      end;
    end;
  end;
end;

procedure TWindowProcHelper.HandleDropFiles;
var
  Files: Array of String;
  Drop: HDROP;
  L: LongWord;
  I, C: Integer;
  DropForm: TWinControl;
  AnsiBuffer: string;
  WideBuffer: WideString;
begin
  Drop := HDROP(WParam);
  try
    C := DragQueryFile(Drop, $FFFFFFFF, nil, 0); // get dropped files count
    if C <= 0 then Exit;

    SetLength(Files, C);
    for I := 0 to C - 1 do
    begin
      if UnicodeEnabledOS then
      begin
        L := DragQueryFileW(Drop, I, nil, 0); // get I. file name length
        SetLength(WideBuffer, L);
        L := DragQueryFileW(Drop, I, @WideBuffer[1], L + 1);
        SetLength(WideBuffer, L);
        Files[I] := UTF16ToUTF8(WideBuffer);
      end
      else
      begin
        L := DragQueryFile(Drop, I, nil, 0); // get I. file name length
        SetLength(AnsiBuffer, L);
        L := DragQueryFile(Drop, I, @AnsiBuffer[1], L + 1);
        SetLength(WideBuffer, L);
        Files[I] := ANSIToUTF8(AnsiBuffer);
      end;
    end;

    if Length(Files) > 0 then
    begin
      DropForm := lWinControl.IntfGetDropFilesTarget;
      if DropForm is TCustomForm then
        TCustomForm(DropForm).IntfDropFiles(Files);
      if Application <> nil then
        Application.IntfDropFiles(Files);
    end;
  finally
    DragFinish(Drop);
  end;
end;

// returns false if the UnicodeChar is not handled
function TWindowProcHelper.HandleUnicodeChar(var AChar: Word): boolean;
var
  OldUTF8Char, UTF8Char: TUTF8Char;
  WS: WideString;
begin
  Result := False;
  UTF8Char := UTF16ToUTF8(WideString(WideChar(AChar)));
  OldUTF8Char := UTF8Char;
  if Assigned(lWinControl) then
  begin
    // if somewhere key is changed to '' then don't process this message
    WinProcess := not lWinControl.IntfUTF8KeyPress(UTF8Char, 1, False);
    // if somewhere key is changed then don't perform a regular keypress
    Result := not WinProcess or (UTF8Char <> OldUTF8Char);
    if UTF8Char <> OldUTF8Char then
    begin
      WS := UTF8ToUTF16(UTF8Char);
      if Length(WS) > 0 then
        AChar := Word(WS[1])
      else
        AChar := 0;
    end;
  end;
end;

procedure TWindowProcHelper.UpdateUIState(CharCode: Word);
// This piece of code is taken from ThemeMgr.pas of Mike Lischke
// Beginning with Windows 2000 the UI in an application may hide focus rectangles and accelerator key indication.
// We have to take care to show them if the user starts navigating using the keyboard.

  function FindParentForm: TCustomForm; inline;
  begin
    if lWinControl <> nil then
      Result := GetParentForm(lWinControl)
    else
    if Application <> nil then
      Result := Application.MainForm
    else
      Result := nil;
  end;

var
  ParentForm: TCustomForm;
begin
  case CharCode of
    VK_LEFT..VK_DOWN, VK_TAB:
      begin
        ParentForm := FindParentForm;
        if ParentForm <> nil then
          SendMessage(ParentForm.Handle, WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEFOCUS), 0);
      end;
    VK_MENU:
      begin
        ParentForm := FindParentForm;
        if ParentForm <> nil then
          SendMessage(ParentForm.Handle, WM_CHANGEUISTATE, MakeLong(UIS_CLEAR, UISF_HIDEACCEL), 0);
      end;
  end;
end;

function TWindowProcHelper.DoChildEdit(out WinResult: LResult): Boolean;
var
  Info: TComboboxInfo;
begin
  // combobox child edit weirdness
  // prevent combobox WM_SIZE message to get/set/compare text to list, to select text
  if IsComboEditSelection then
  begin
    case Msg of
      WM_GETTEXTLENGTH, EM_SETSEL:
      begin
        WinResult := 0;
        Exit(True);
      end;
      WM_GETTEXT:
      begin
        if WParam > 0 then
          PChar(LParam)^ := #0;
        WinResult := 0;
        Exit(True);
      end;
    end;
  end;
  lWinControl := WindowInfo^.AWinControl;
  {for ComboBox IME sends WM_IME_NOTIFY with WParam=WM_IME_ENDCOMPOSITION}
  if (Msg = WM_IME_NOTIFY) and (WPARAM=WM_IME_ENDCOMPOSITION) then
    WindowInfo^.IMEComposed:=True;

  // filter messages we want to pass on to LCL
  if (Msg <> WM_KILLFOCUS) and (Msg <> WM_SETFOCUS)
    {$ifndef RedirectDestroyMessages}and (Msg <> WM_NCDESTROY){$endif}
    and not ((Msg >= WM_CUT) and (Msg <= WM_CLEAR))
    and ((Msg < WM_KEYFIRST) or (Msg > WM_KEYLAST))
    and ((Msg < WM_MOUSEFIRST) or (Msg > WM_MOUSELAST)) then
  begin
    WinResult := CallDefaultWindowProc(Window, Msg, WParam, LParam);
    Exit(True);
  end
  else
  if (Msg = WM_KILLFOCUS) or (Msg = WM_SETFOCUS) then
  begin
    // if focus jumps inside combo then no need to notify LCL
    Info.cbSize := SizeOf(Info);
    Win32Extra.GetComboBoxInfo(lWinControl.Handle, @Info);
    if (HWND(WParam) = Info.hwndList) or
       (HWND(WParam) = Info.hwndItem) or
       (HWND(WParam) = Info.hwndCombo) then
    begin
      WinResult := CallDefaultWindowProc(Window, Msg, WParam, LParam);
      Exit(True);
    end;
  end;
  Result := False;
end;

procedure TWindowProcHelper.DoMsgChar(var WinResult: LResult);
begin
  // first send a IntfUTF8KeyPress to the LCL
  // if the key was not handled send a CN_CHAR for AnsiChar<=#127
  OrgCharCode := Word(WParam);
  if not HandleUnicodeChar(OrgCharCode) then
  begin
    PLMsg := @LMChar;
    with LMChar do
    begin
      Msg := CN_CHAR;
      KeyData := LParam;
      if UnicodeEnabledOS then
        CharCode := Word(Char(WideChar(WParam)))
      else
        CharCode := Word(WParam);
      OrgCharCode := CharCode;
      WinResult := 0;
    end;
    WinProcess := false;
  end
  else
    WParam := OrgCharCode;
end;

procedure TWindowProcHelper.DoCmdCheckBoxParam;
var
  Flags: dword;
begin
  case HIWORD(WParam) of
    BN_CLICKED:
      begin
        // to allow cbGrayed state at the same time as not AllowGrayed
        // in checkboxes (needed by dbcheckbox for null fields) we need
        // to handle checkbox state ourselves, according to msdn state
        // sequence goes from checked->cleared->grayed etc.
        Flags := SendMessage(lWinControl.Handle, BM_GETCHECK, 0, 0);
        //do not update the check state if is TRadioButton and is already checked
        if (Flags <> BST_CHECKED) or not (lWinControl is TRadioButton) then
        begin
          if (Flags=BST_CHECKED) then
            Flags := BST_UNCHECKED
          else
          if (Flags=BST_UNCHECKED) and
             TCustomCheckbox(lWinControl).AllowGrayed then
            Flags := BST_INDETERMINATE
          else
            Flags := BST_CHECKED;
          //pass 0 through LParam to force sending LM_CHANGE
          Windows.SendMessage(lWinControl.Handle, BM_SETCHECK, Windows.WPARAM(Flags), 0);
        end;
        LMessage.Msg := LM_CLICKED;
      end;
    BN_KILLFOCUS:
      LMessage.Msg := LM_EXIT;
  end
end;

function TWindowProcHelper.DoCmdComboBoxParam: Boolean;
begin
  case HIWORD(WParam) of
    CBN_DROPDOWN: TCustomCombobox(lWinControl).IntfGetItems;
    CBN_EDITCHANGE: LMessage.Msg := LM_CHANGED;
    { CBN_EDITCHANGE is only sent after the user changes the edit box.
      CBN_SELCHANGE is sent when the user changes the text by
      selecting in the list, but before text is actually changed.
      itemindex is updated, so set text manually }
    CBN_SELCHANGE:
    begin
      if TCustomComboBox(lWinControl).Style in [csSimple, csDropDown] then
        UpdateComboBoxText(TCustomComboBox(lWinControl));
      SendSimpleMessage(lWinControl, LM_CHANGED);
      LMessage.Msg := LM_SELCHANGE;
    end;
    CBN_CLOSEUP:
    begin
      // according to msdn CBN_CLOSEUP can happen before CBN_SELCHANGE and
      // unfortunately it is simple truth. but we need correct order in the LCL
      PostMessage(lWinControl.Handle, CN_COMMAND, WParam, LParam);
      Exit(True);
    end;
  end;
  Result := False;
end;

procedure TWindowProcHelper.DoMsgColor(ChildWindowInfo: PWin32WindowInfo);
var
  WindowDC: HDC;
  WindowColor: TColor;
  ChildWinControl: TWinControl;
begin
  WindowDC := HDC(WParam);
  ChildWinControl := ChildWindowInfo^.WinControl;
  if ChildWinControl = nil then
    ChildWinControl := ChildWindowInfo^.AWinControl;

  case Msg of
    WM_CTLCOLORSTATIC,
    WM_CTLCOLORBTN: begin
      if GetNeedParentPaint(ChildWindowInfo, ChildWinControl) and
        not ChildWindowInfo^.ThemedCustomDraw then
      begin
        // need to draw transparently, draw background
        DrawParentBackground(HWND(LParam), WindowDC);
        LMessage.Result := GetStockObject(HOLLOW_BRUSH);
        SetBkMode(WindowDC, TRANSPARENT);
        WinProcess := false;
      end;
    end;
    WM_CTLCOLORSCROLLBAR: begin
      WinProcess := false;
    end;
  end;

  if WinProcess then
  begin
    if ChildWinControl <> nil then
    begin
      WindowColor := ChildWinControl.Font.Color;
      if WindowColor = clDefault then
        WindowColor := ChildWinControl.GetDefaultColor(dctFont);
      Windows.SetTextColor(WindowDC, ColorToRGB(WindowColor));
      WindowColor := ChildWinControl.Brush.Color;
      if WindowColor = clDefault then
        WindowColor := ChildWinControl.GetDefaultColor(dctBrush);
      Windows.SetBkColor(WindowDC, ColorToRGB(WindowColor));
      LMessage.Result := LResult(ChildWinControl.Brush.Reference.Handle);
      // Override default handling
      WinProcess := false;
    end;
  end;
end;

procedure TWindowProcHelper.UpdateDrawListItem(aMsg: UInt);
var
  PDrawIS: PDrawItemStruct;
begin
  PDrawIS := PDrawItemStruct(LParam);
  if PDrawIS^.itemID <> dword(-1) then
  begin
    LMessage.Msg := aMsg;
    TLMDrawListItem(LMessage).DrawListItemStruct := @DrawListItemStruct;
    with DrawListItemStruct do
    begin
      ItemID := PDrawIS^.itemID;
      Area := PDrawIS^.rcItem;
      ItemState := TOwnerDrawState(PDrawIS^.itemState);
      DC := PDrawIS^._hDC;
    end;
    if (aMsg = LM_DRAWLISTITEM) and (WindowInfo <> @DefaultWindowInfo) then
    begin
      WindowInfo^.DrawItemIndex := PDrawIS^.itemID;
      WindowInfo^.DrawItemSelected := (PDrawIS^.itemState and ODS_SELECTED) = ODS_SELECTED;
    end;
    WinProcess := false;
  end;
end;

procedure TWindowProcHelper.UpdateDrawItems;
begin
  with TLMDrawItems(LMessage) do
  begin
    Msg := LM_DRAWITEM;
    Ctl := 0;
    DrawItemStruct := PDrawItemStruct(LParam);
  end;
  WinProcess := false;
end;

procedure TWindowProcHelper.DoMsgDrawItem;
var
  menuItem: TObject;
  PDrawIS: PDrawItemStruct;
begin
  PDrawIS := PDrawItemStruct(LParam);
  if (WParam = 0) and (PDrawIS^.ctlType = ODT_MENU) then
  begin
    menuItem := TObject(PDrawIS^.itemData);
    if menuItem is TMenuItem then
      DrawMenuItem(TMenuItem(menuItem),
        PDrawIS^._hDC, PDrawIS^.rcItem, PDrawIS^.itemAction, PDrawIS^.itemState);
    UpdateDrawItems;
  end
  else
  begin
    WindowInfo := GetWin32WindowInfo(PDrawIS^.hwndItem);
    if WindowInfo^.WinControl<>nil then
      lWinControl := WindowInfo^.WinControl;
    {$IFDEF MSG_DEBUG}
    debugln(format('Received WM_DRAWITEM type %d handle %x',
                   [PDrawIS^.ctlType, integer(PDrawIS^.hwndItem)]));
    {$ENDIF}

    if (lWinControl<>nil) and
       (((lWinControl is TCustomListbox) and
          (TCustomListBox(lWinControl).Style <> lbStandard)) or
       ((lWinControl is TCustomCombobox) and
          ((TCustomCombobox(lWinControl).Style = csOwnerDrawFixed) or
          (TCustomCombobox(lWinControl).Style = csOwnerDrawVariable))))
    then
      UpdateDrawListItem(LM_DRAWLISTITEM)
    else
    if (lWinControl <> nil) and (lWinControl is TListView) and
      (TListView(lWinControl).ViewStyle = vsReport) and
      (PDrawIS^.ctlType = ODT_LISTVIEW) and
      (TListView(lWinControl).OwnerDraw)
    then
      UpdateDrawListItem(CN_DRAWITEM)
    else
      UpdateDrawItems;
  end;
end;

procedure TWindowProcHelper.DoMsgEnable;
begin
  if WParam <> 0 Then
    LMessage.Msg := LM_SETEDITABLE;
  if Window = Win32WidgetSet.AppHandle then
    if WParam = 0 then
    begin
      RemoveStayOnTopFlags(Window);
      DisabledForms := Screen.DisableForms(nil, DisabledForms);
    end
    else begin
      RestoreStayOnTopFlags(Window);
      Screen.EnableForms(DisabledForms);
    end;

  // disable child windows of for example groupboxes, but not of forms
  if Assigned(lWinControl) and not (lWinControl is TCustomForm) then
    EnableChildWindows(lWinControl, WParam<>0);

  // ugly hack to give bitbtns a nice look
  // When no theming active, the internal image needs to be
  // recreated when the enabled state is changed
  if not ThemeServices.ThemesEnabled and (lWinControl is TCustomBitBtn) then
    DrawBitBtnImage(TCustomBitBtn(lWinControl), TCustomBitBtn(lWinControl).Caption);
end;

function TWindowProcHelper.DoMsgEraseBkgnd(var WinResult: LResult): Boolean;
var
  eraseBkgndCommand: TEraseBkgndCommand;
begin
  eraseBkgndCommand := TEraseBkgndCommand(EraseBkgndStack and EraseBkgndStackMask);
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  DebugLnEnter(['>>> Do WM_ERASEBKGND for WParam= ', WParam, ' LParam=',LParam,
      ' CurDbleBuffer.DC=', dbgs(CurDoubleBuffer.DC), ' Window=', dbgs(Window),
      ' WinCtrl=',PtrUInt(lWinControl), ' ', DbgSName(lWinControl),
      ' isTab=', dbgs(WindowInfo^.isTabPage)  ]);
  try
  case eraseBkgndCommand of
    ecDefault: DebugLn(MessageStackDepth, ' *command: default');
    ecDiscardNoRemove, ecDiscard: DebugLn(MessageStackDepth, ' *command: completely ignore');
    ecDoubleBufferNoRemove: DebugLn(MessageStackDepth, ' *command: use double buffer');
  end;
  DebugLn(MessageStackDepth, ' *erasebkgndstack: ', EraseBkgndStackToString);
{$endif}
  if eraseBkgndCommand = ecDoubleBufferNoRemove then
  begin
    if CurDoubleBuffer.DC <> 0 then
      WParam := Windows.WParam(CurDoubleBuffer.DC);
    if WindowInfo^.isTabPage then
      EraseBkgndStack := (EraseBkgndStack and not ((1 shl EraseBkgndStackShift)-1))
        or dword(ecDiscardNoRemove);
  end
  else
  if eraseBkgndCommand <> ecDiscardNoRemove then
    EraseBkgndStack := EraseBkgndStack shr EraseBkgndStackShift;
  if eraseBkgndCommand in [ecDiscard, ecDiscardNoRemove] then
  begin
    WinResult := 0;
    Exit(True);
  end;
  if not GetNeedParentPaint(WindowInfo, lWinControl) or (eraseBkgndCommand = ecDoubleBufferNoRemove) then
  begin
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
    DebugLn(['WM_ERASEBKGND  *NO* ParentPaint for WParam= ', WParam, ' LParam=',LParam, ' Window=', dbgs(Window) ]);
{$endif}
    SetLMessageAndParams(LM_ERASEBKGND);
  end else
  begin
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
    DebugLn(['WM_ERASEBKGND got NeedParentPaint for WParam= ', WParam, ' LParam=',LParam, ' Window=', dbgs(Window) ]);
{$endif}
    if not ThemeServices.ThemesEnabled then
      SendPaintMessage(HDC(WParam));
    LMessage.Result := 1;
  end;
  WinProcess := False;
{$if defined(MSG_DEBUG) or defined(DBG_SendPaintMessage)}
  finally
  DebugLnExit(['<<< Do WM_ERASEBKGND for WParam= ', WParam, ' LParam=',LParam,
      ' Window=', dbgs(Window), ' MsgStackDepth=', MessageStackDepth, ' *erasebkgndstack: ', EraseBkgndStackToString
       ]);
  end;
{$endif}
  Result := False;
end;

procedure TWindowProcHelper.DoMsgKeyDownUp(aMsg: Cardinal; var WinResult: LResult);
begin
  NotifyUserInput := True;
  PLMsg := @LMKey;
  UpdateUIState(Word(WParam));
  SetLMKeyData(aMsg, True);
  WinResult := 0;
  WinProcess := false;
end;

procedure TWindowProcHelper.DoMsgLButtonCommon;
begin
  MouseDownTime := GetTickCount64;
  NotifyUserInput := True;
  PLMsg := @LMMouse;
  with LMMouse Do
  begin
    case MouseDownCount of
      2: Msg := LM_LBUTTONDBLCLK;
      3: Msg := LM_LBUTTONTRIPLECLK;
      4: Msg := LM_LBUTTONQUADCLK;
      else Msg := LM_LBUTTONDOWN;
    end;
    XPos := GET_X_LPARAM(LParam);
    YPos := GET_Y_LPARAM(LParam);
    Keys := WParam;
  end;
end;

procedure TWindowProcHelper.DoMsgLButtonDoubleClick;
begin
  // always within the time-window
  if (MouseDownCount < 1) or (MouseDownCount > 4) then
    MouseDownCount := 1;
  inc(MouseDownCount);
  DoMsgLButtonCommon;
end;

procedure TWindowProcHelper.DoMsgLButtonDown;
begin
  if (MouseDownCount < 1) or (MouseDownCount > 4) then
    MouseDownCount := 1;
  if (MouseDownWindow = Window)
      and (GetTickCount64 - MouseDownTime <= GetDoubleClickTime)
      and CheckMouseMovement then
    inc(MouseDownCount)
  else
    MouseDownCount := 1;
  MouseDownWindow := Window;
  GetCursorPos(MouseDownPos);
  DoMsgLButtonCommon;
end;

procedure TWindowProcHelper.DoMsgMouseUpDownClick(aMsg: Cardinal);
begin
  NotifyUserInput := True;
  PLMsg := @LMMouse;
  with LMMouse Do
  begin
    Msg := aMsg;
    XPos := GET_X_LPARAM(LParam);
    YPos := GET_Y_LPARAM(LParam);
    Keys := WParam;
  end;
end;

procedure TWindowProcHelper.DoMsgContextMenu;
begin
  WinProcess := False;
  NotifyUserInput := True;
  PLMsg := @LMContextMenu;
  with LMContextMenu do
  begin
    Msg := LM_CONTEXTMENU;
    XPos := GET_X_LPARAM(LParam);
    YPos := GET_Y_LPARAM(LParam);
    hWnd := Window;
  end;
end;

procedure TWindowProcHelper.DoMsgMouseMove;
begin
  NotifyUserInput := True;
  PLMsg := @LMMouseMove;
  with LMMouseMove Do
  begin
    Msg := LM_MOUSEMOVE;
    XPos := GET_X_LPARAM(LParam);
    YPos := GET_Y_LPARAM(LParam);
    Keys := WParam;
    // check if this is a spurious WM_MOUSEMOVE message, pos not actually changed
    if (XPos = WindowInfo^.MouseX) and (YPos = WindowInfo^.MouseY) then
    begin
      // do not fire message after all (position not changed)
      Msg := LM_NULL;
      NotifyUserInput := false;
    end else
    if WindowInfo <> @DefaultWindowInfo then
    begin
      // position changed, update window info
      WindowInfo^.MouseX := XPos;
      WindowInfo^.MouseY := YPos;
    end;
  end;
end;

function TWindowProcHelper.DoMsgMouseWheel(var WinResult: LResult): Boolean;
var
  TargetWindow: HWND;
  P: TPoint;
begin
  NotifyUserInput := True;
  PLMsg := @LMMouseEvent;
  with LMMouseEvent Do
  begin
    X := GET_X_LPARAM(LParam);
    Y := GET_Y_LPARAM(LParam);
    // check if mouse cursor within this window, otherwise send message to
    // window the mouse is hovering over
    P.X := X;
    P.Y := Y;
    TargetWindow := Win32WidgetSet.WindowFromPoint(P);
    //fallback to LCL function to get the actual window
    if TargetWindow = 0 then
      TargetWindow := GetLCLWindowFromPoint(lWinControl, P);
    if (TargetWindow = 0) or not IsWindowEnabled(TargetWindow) then
      Exit(True);

    // check if the window is an edit control of a combobox, if so,
    // redirect it to the combobox, not the edit control
    if GetWin32WindowInfo(TargetWindow)^.isComboEdit then
      TargetWindow := Windows.GetParent(TargetWindow);

    // check InMouseWheelRedirection to prevent recursion
    if not InMouseWheelRedirection and (TargetWindow <> Window) then
    begin
      InMouseWheelRedirection := true;
      WinResult := SendMessage(TargetWindow, WM_MOUSEWHEEL, WParam, LParam);
      InMouseWheelRedirection := false;
      Exit(True);
    end
    else if TargetWindow <> Window then
      Exit(True);
    // the mousewheel message is for us
    Msg := LM_MOUSEWHEEL;
    Windows.ScreenToClient(TargetWindow, P);
    X := P.X;
    Y := P.Y;
    Button := LOWORD(Integer(WParam));
    WheelDelta := SmallInt(HIWORD(Integer(WParam)));
    State := KeysToShiftState(Button);
    WinResult := 0;
    UserData := Pointer(GetWindowLong(Window, GWL_USERDATA));
    WinProcess := false;
  end;
  Result := False;
end;

procedure TWindowProcHelper.DoMsgNCLButtonDown;
begin
  SetLMessageAndParams(Msg);
  NotifyUserInput := True;

  //Drag&Dock support TCustomForm => Start BeginDrag()
  if (lWinControl <> nil) and not (csDesigning in lWinControl.ComponentState) then
  begin
    if WParam = HTCAPTION  then
      if lWinControl is TCustomForm then
        if (TWinControlAccess(lWinControl).DragKind = dkDock)
        and (TWinControlAccess(lWinControl).DragMode = dmAutomatic) then
          lWinControl.BeginDrag(true);
  end;
  // I see no other way to prevent crash at moment. This message calls WM_CLOSE
  // which frees our form and we get a destructed lWinControl
  lWinControl := nil;
end;

function TWindowProcHelper.DoMsgNotify(var WinResult: LResult): Boolean;
begin
  WindowInfo := GetWin32WindowInfo(PNMHdr(LParam)^.hwndFrom);
{$ifdef MSG_DEBUG}
  DebugLn([MessageStackDepth, 'Notify code: ', PNMHdr(LParam)^.code]);
{$endif}
  if Assigned(WindowInfo^.ParentMsgHandler) then
  begin
    LMNotify.Result := 0;
    if WindowInfo^.ParentMsgHandler(WindowInfo^.WinControl,
      Window, WM_NOTIFY, WParam, LParam, LMNotify.Result, WinProcess) then
    begin
      WinResult := LMNotify.Result;
      Exit(True);
    end;
  end;
  case PNMHdr(LParam)^.code of
    MCN_SELCHANGE:
    begin
      LMessage.Msg := LM_CHANGED;
      if WindowInfo^.WinControl <> nil then
        lWinControl := WindowInfo^.WinControl;
    end;
  else
    PLMsg:=@LMNotify;
    LMNotify.Msg := LM_NOTIFY;
    LMNotify.IDCtrl := WParam;
    LMNotify.NMHdr := PNMHDR(LParam);
    case LMNotify.NMHdr^.code of
      NM_CUSTOMDRAW:
      begin
        if WindowInfo^.WinControl is TCustomBitBtn then
          HandleBitBtnCustomDraw(TCustomBitBtn(WindowInfo^.WinControl))
        else
        if GetNeedParentPaint(WindowInfo, lWinControl) and WindowInfo^.ThemedCustomDraw then
        begin
          case PNMCustomDraw(LParam)^.dwDrawStage of
            CDDS_PREPAINT:
            begin
              WinResult := CDRF_NOTIFYITEMDRAW;
              WinProcess := false;
            end;
            CDDS_ITEMPREPAINT:
            begin
              WinResult := CDRF_DODEFAULT;
              WinProcess := false;
            end;
          end;
        end;
      end;
    end;
  end;
  Result := False;
end;

procedure TWindowProcHelper.DoMsgShowWindow;
var
  Flags: dword;
begin
  with TLMShowWindow(LMessage) Do
  begin
    Msg := LM_SHOWWINDOW;
    Show := WParam <> 0;
    Status := LParam;
  end;
  if Assigned(lWinControl) and ((WParam <> 0) or not lWinControl.Visible) and
      ((WParam = 0) or lWinControl.Visible) and Assigned(Application) and
      (lWinControl = Application.MainForm) and not Application.MainFormOnTaskBar then
  begin
    if WParam=0 then
      Flags := SW_HIDE
    else
      Flags := SW_SHOWNOACTIVATE;
    Windows.ShowWindow(Win32WidgetSet.AppHandle, Flags);
  end;
end;

procedure TWindowProcHelper.DoMsgSysKey(aMsg: Cardinal);
begin
  NotifyUserInput := True;
  PLMsg := @LMKey;
  SetLMKeyData(aMsg, True);
  WinProcess := false;
end;

procedure TWindowProcHelper.DoMsgMeasureItem;
var
  menuItem: TObject;
  menuHDC: HDC;
  TmpSize: TSize; // used by WM_MEASUREITEM
begin
  case PMeasureItemStruct(LParam)^.CtlType of
    ODT_MENU:
      begin
        menuItem := TObject(PMeasureItemStruct(LParam)^.itemData);
        if menuItem is TMenuItem then
        begin
          menuHDC := GetDC(Window);
          TmpSize := MenuItemSize(TMenuItem(menuItem), menuHDC);
          PMeasureItemStruct(LParam)^.itemWidth := TmpSize.cx;
          PMeasureItemStruct(LParam)^.itemHeight := TmpSize.cy;
          ReleaseDC(Window, menuHDC);
          Winprocess := False;
        end
        {$ifdef MSG_DEBUG}
        else
          DebugLn('WM_MEASUREITEM for a menuitem catched but menuitem is not TmenuItem');
        {$endif}
      end;
    else
    if WParam <> 0 then
    begin
      lWinControl := TWinControl(WParam);
      //if Assigned(lWinControl) then   <- already tested
      SetLMessageAndParams(LM_MEASUREITEM, True);
    end;
  end;
end;

procedure TWindowProcHelper.DoMsgActivateApp;
begin
  if Window = Win32WidgetSet.AppHandle then
  begin
    if WParam <> 0 then // activated
    begin
      //WriteLn('Restore');
      RestoreStayOnTopFlags(Window);
      if Assigned(Application) then
        Application.IntfAppActivate(True);
    end
    else begin // deactivated
      //WriteLn('Remove');
      RemoveStayOnTopFlags(Window);
      if Assigned(Application) then
        Application.IntfAppDeactivate(True);
    end;
  end;
end;

procedure TWindowProcHelper.UpdateLMMovePos(X, Y: Smallint);
begin
  LMMove.XPos := X;
  LMMove.YPos := Y;
end;

function TWindowProcHelper.DoMsgMove: Boolean;
var
  NewLeft, NewTop: integer;
  WindowPlacement: TWINDOWPLACEMENT;
  R: TRect;
begin
  PLMsg := @LMMove;
  LMMove.Msg := LM_MOVE;
  // MoveType := WParam;   WParam is not defined!
  LMMove.MoveType := Move_SourceIsInterface;
  if GetWindowLong(Window, GWL_STYLE) and WS_CHILD = 0 then
  begin
    WindowPlacement.length := SizeOf(WindowPlacement);
    if IsIconic(Window) and GetWindowPlacement(Window, @WindowPlacement) then
      UpdateLMMovePos(WindowPlacement.rcNormalPosition.Left,
                      WindowPlacement.rcNormalPosition.Top)
    else if Windows.GetWindowRect(Window, @R) then
      UpdateLMMovePos(R.Left, R.Top)
    else
      LMMove.Msg := LM_NULL;
  end else
  begin
    if GetWindowRelativePosition(Window, NewLeft, NewTop) then
      UpdateLMMovePos(NewLeft, NewTop)
    else
      LMMove.Msg := LM_NULL;
  end;
  if lWinControl <> nil then
  begin
    {$IFDEF VerboseSizeMsg}
  with LMMove Do begin
      DebugLn('Win32CallBack WM_MOVE ', dbgsName(lWinControl),
        ' NewPos=',dbgs(XPos),',',dbgs(YPos));
  end;
      {$ENDIF}
    if (lWinControl.Left = LMMove.XPos) and (lWinControl.Top = LMMove.YPos) then
      Exit(True);
  end;
  Result := False;
end;

procedure TWindowProcHelper.DoMsgSize;
var
  NewWidth, NewHeight: integer;
  OverlayWindow: HWND;
{$IFDEF VerboseSizeMsg}
  R: TRect;
{$ENDIF}
begin
  with TLMSize(LMessage) do
  begin
    Msg := LM_SIZE;
    SizeType := WParam or Size_SourceIsInterface;

    // this is needed since we don't minimize the main form window
    // we only hide and show it back on mimize and restore in case MainFormOnTaskbar = False
    if (Window = Win32WidgetSet.AppHandle) and
       Assigned(Application.MainForm) and Application.MainForm.HandleAllocated then
    begin
      lWinControl := Application.MainForm;
      Window := Application.MainFormHandle;
      // lie LCL about the window state
      if IsIconic(Win32WidgetSet.AppHandle) then
        SizeType := SIZE_MINIMIZED or Size_SourceIsInterface
      else
      if IsZoomed(Window) then
        SizeType := SIZE_MAXIMIZED or Size_SourceIsInterface
      else
        SizeType := SIZE_RESTORED or Size_SourceIsInterface;
    end;

    GetWindowSize(Window, NewWidth, NewHeight);
    Width := NewWidth;
    Height := NewHeight;
    if Assigned(lWinControl) then
    begin
      {$IFDEF VerboseSizeMsg}
      GetClientRect(Window,R);
      DebugLn('Win32Callback: WM_SIZE '+ dbgsName(lWinControl)+
        ' NewSize=', dbgs(Width)+','+dbgs(Height)+
        ' HasVScroll='+dbgs((GetWindowLong(Window, GWL_STYLE) and WS_VSCROLL) <> 0)+
        ' HasHScroll='+dbgs((GetWindowLong(Window, GWL_STYLE) and WS_HSCROLL) <> 0)+
        ' OldClientSize='+dbgs(lWinControl.CachedClientWidth)+','+dbgs(lWinControl.CachedClientHeight)+
        ' NewClientSize='+dbgs(R.Right)+','+dbgs(R.Bottom));
      {$ENDIF}
      lWinControl.InvalidateClientRectCache(false);
    end;
    OverlayWindow := GetWin32WindowInfo(Window)^.Overlay;
    if OverlayWindow <> 0 then
      Windows.SetWindowPos(OverlayWindow, HWND_TOP, 0, 0, NewWidth, NewHeight, SWP_NOMOVE);
  end;
end;

// This is called from the actual WindowProc.

function TWindowProcHelper.DoWindowProc: LResult;
var
  ChildWindowInfo: PWin32WindowInfo;
  TargetObject: TObject;
  TargetWindow: HWND;
  WmSysCommandProcess: Boolean;
  CancelEndSession : Boolean; // used by WM_QUERYENDSESSION
  // used by WM_CHAR, WM_SYSCHAR and WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP
  CharCodeNotEmpty: boolean;
  R: TRect;
begin
  FillChar(LMessage, SizeOf(LMessage), 0);
  PLMsg := @LMessage;
  WinProcess := True;
  NotifyUserInput := False;

  WindowInfo := GetWin32WindowInfo(Window);
  if WindowInfo^.isChildEdit then
  begin
    if DoChildEdit(Result) then Exit;
  end else begin
    lWinControl := WindowInfo^.WinControl;
  end;
  if (IgnoreNextCharWindow <> 0) and ((Msg = WM_CHAR) or (Msg = WM_SYSCHAR)) then
  begin
    if IgnoreNextCharWindow = Window then
    begin
      IgnoreNextCharWindow := 0;
      Result := 1;
      Exit;
    end;
    IgnoreNextCharWindow := 0;
  end;

  case Msg of
    WM_NULL:
      if (Window = Win32WidgetSet.AppHandle) then
      begin
        CheckSynchronize;
        TWin32Widgetset(Widgetset).CheckPipeEvents;
      end;
    WM_ENTERIDLE: Application.Idle(False);
    WM_ACTIVATE:  SetLMessageAndParams(LM_ACTIVATE);
    WM_IME_ENDCOMPOSITION:
    begin
      {IME Windows the composition has finished}
      WindowInfo^.IMEComposed:=True;
      SetLMessageAndParams(Msg);  //WinProcess := False;
    end;
    WM_CANCELMODE:     LMessage.Msg := LM_CANCELMODE;
    WM_CAPTURECHANGED: LMessage.Msg := LM_CAPTURECHANGED;
    WM_CHAR:           DoMsgChar(Result);

    WM_MENUCHAR:
    begin
      PLMsg^.Result := FindMenuItemAccelerator(LOWORD(WParam), HMENU(LParam));
      WinProcess := false;
    end;

    WM_CLOSE:
    begin
      if (Window = Win32WidgetSet.AppHandle) and Assigned(Application.MainForm) then
        Windows.SendMessage(Application.MainFormHandle, WM_CLOSE, 0, 0)
      else
        LMessage.Msg := LM_CLOSEQUERY;
      // default is to destroy window, inhibit
      WinProcess := false;
    end;
    
    WM_INITMENUPOPUP:
    begin
      if HIWORD(lParam) = 0 then //if not system menu
      begin
        TargetObject := GetPopMenuItemObject;
        if TargetObject is TMenuItem then
        begin
          LMessage.Msg := LM_ACTIVATE;
          TargetObject.Dispatch(LMessage);
          lWinControl := nil;
        end;
      end;
    end;
    
    WM_MENUSELECT:
    begin
      TargetObject := GetMenuItemObject((HIWORD(Integer(WParam)) and MF_POPUP) <> 0);
      if TargetObject is TMenuItem then
        TMenuItem(TargetObject).IntfDoSelect
      else
        Application.Hint := '';
    end;
    
    WM_COMMAND:
    begin
      if LParam = 0 then
      begin
        {menuitem or shortcut}
        TargetObject := GetMenuItemObject(False);
        if TargetObject is TMenuItem then
        begin
          if (HIWORD(WParam) = 0) or (HIWORD(WParam) = 1) then
          begin
            LMessage.Msg := LM_ACTIVATE;
            TargetObject.Dispatch(LMessage);
          end;
          lWinControl := nil;
        end;
      end
      else begin
        ChildWindowInfo := GetWin32WindowInfo(HWND(LParam));
        lWinControl := ChildWindowInfo^.WinControl;
        // buddy controls use 'awincontrol' to designate associated wincontrol
        if lWinControl = nil then
          lWinControl := ChildWindowInfo^.AWinControl;

        if Assigned(ChildWindowInfo^.ParentMsgHandler) then
        begin
          if ChildWindowInfo^.ParentMsgHandler(lWinControl,
            Window, WM_COMMAND, WParam, LParam, LMessage.Result, WinProcess) then Exit(LMessage.Result);
        end;

        // TToggleBox is a TCustomCheckBox too, but we don't want to handle
        // state changes of TToggleBox ourselfves
        if (lWinControl is TCustomCheckBox) and not (lWinControl is TToggleBox) then
          DoCmdCheckBoxParam
        else if lWinControl is TButtonControl then
          case HIWORD(WParam) of
            BN_CLICKED:   LMessage.Msg := LM_CLICKED;
            BN_KILLFOCUS: LMessage.Msg := LM_EXIT;
          end
        else
        if (lWinControl is TCustomEdit) then
        begin
          if (lWinControl is TCustomMemo) then
          case HIWORD(WParam) of
            // multiline edit doesn't send EN_CHANGE, so use EN_UPDATE
            EN_UPDATE: LMessage.Msg := CM_TEXTCHANGED;
          end
          else
          case HIWORD(WParam) of
            EN_CHANGE: LMessage.Msg := CM_TEXTCHANGED;
          end;
        end
        else if (lWinControl is TCustomListBox) then
          case HIWORD(WParam) of
            LBN_SELCHANGE:  LMessage.Msg := LM_SELCHANGE;
          end
        else if lWinControl is TCustomCombobox then
          if DoCmdComboBoxParam then Exit;
      end;

      // no specific message found? try send a general msg
      lWinControl.Perform(CN_COMMAND, WParam, LParam);
    end;

    WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
    begin
      // it's needed for winxp themes where controls send the WM_ERASEBKGND
      // message to their parent to clear their background and then draw
      // transparently
      // only static and button controls have transparent parts
      // others need to erased with their window color
      // scrollbar also has buttons
      ChildWindowInfo := GetWin32WindowInfo(HWND(LParam));
      if Assigned(ChildWindowInfo^.ParentMsgHandler)
      and ChildWindowInfo^.ParentMsgHandler(lWinControl,
        Window, Msg, WParam, LParam, LMessage.Result, WinProcess) then Exit(LMessage.Result);
      DoMsgColor(ChildWindowInfo);
    end;
    WM_CLEAR: LMessage.Msg := LM_CLEAR;
    WM_COPY:  LMessage.Msg := LM_COPY;
    WM_CUT:   LMessage.Msg := LM_CUT;
    {$ifndef RedirectDestroyMessages}
    WM_DESTROY:
    begin
      if CurrentWindow=Window then
        CurrentWindow := 0;
      if lWinControl is TCustomComboBox then
        DisposeComboEditWindowInfo(TCustomComboBox(lWinControl));
      if WindowInfo^.Overlay<>HWND(nil) then
        Windows.DestroyWindow(WindowInfo^.Overlay);
      LMessage.Msg := LM_DESTROY;
    end;
    {$endif}
    WM_DESTROYCLIPBOARD:
    begin
      if assigned(OnClipBoardRequest) then begin
        {$IFDEF VerboseWin32Clipbrd}
        debugln('WM_DESTROYCLIPBOARD');
        {$ENDIF}
        OnClipBoardRequest(0, nil);
        OnClipBoardRequest := nil;
        LMessage.Result := 0;
      end;
    end;
    WM_DRAWITEM: DoMsgDrawItem;
    WM_ENABLE:   DoMsgEnable;
    WM_ERASEBKGND:
      if DoMsgEraseBkgnd(Result) then Exit;
    WM_EXITMENULOOP:
      // is it a popup menu
      if longbool(WPARAM) and Assigned(WindowInfo^.PopupMenu) then
        WindowInfo^.PopupMenu.Close;
    WM_GETDLGCODE:
    begin
      LMessage.Result := DLGC_WANTALLKEYS;
      WinProcess := False;
    end;
    WM_HELP:
      // Don't ask windows to process the message here. It will be processed
      // either by TCustomForm LM_HELP handler or passed to parent by DefaultHandler
      SetLMessageAndParams(LM_HELP, True);
    WM_HOTKEY:
      SetLMessageAndParams(WM_HOTKEY, True);
    WM_HSCROLL,
    WM_VSCROLL:
    begin
      PLMsg := @LMScroll;
      if LParam <> 0 then
      begin
        ChildWindowInfo := GetWin32WindowInfo(HWND(LParam));
        lWinControl := ChildWindowInfo^.WinControl;
        if Assigned(ChildWindowInfo^.ParentMsgHandler) then
          if ChildWindowInfo^.ParentMsgHandler(lWinControl,
            Window, Msg, WParam, LParam, PLMsg^.Result, WinProcess) then Exit(PLMsg^.Result);
      end;
      HandleScrollMessage(Msg);
    end;
    WM_KEYDOWN:
    begin
      DoMsgKeyDownUp(CN_KEYDOWN, Result);
      WindowInfo^.IMEComposed:=False;
      IgnoreNextCharWindow := Window;
    end;
    WM_KEYUP:
    begin
      DoMsgKeyDownUp(CN_KEYUP, Result);
      if WindowInfo^.IMEComposed then
        LMKey.Msg:=LM_NULL;
    end;
    WM_KILLFOCUS:
    begin
{$ifdef DEBUG_CARET}
      DebugLn(['WM_KILLFOCUS received for window ', IntToHex(Window, 8), ' NewFocus = ', IntToHex(WParam, 8), ' Text = ', WndText(WParam)]);
{$endif}
      LMessage.Msg := LM_KILLFOCUS;
      LMessage.WParam := WParam;
    end;
    //TODO:LM_KILLCHAR,LM_KILLWORD,LM_KILLLINE
    WM_LBUTTONDBLCLK: DoMsgLButtonDoubleClick;
    WM_LBUTTONDOWN:   DoMsgLButtonDown;
    WM_LBUTTONUP:     DoMsgMouseUpDownClick(LM_LBUTTONUP);
    WM_MBUTTONDBLCLK: DoMsgMouseUpDownClick(LM_MBUTTONDBLCLK);
    WM_MBUTTONDOWN:   DoMsgMouseUpDownClick(LM_MBUTTONDOWN);
    WM_MBUTTONUP:     DoMsgMouseUpDownClick(LM_MBUTTONUP);
    WM_XBUTTONDBLCLK: DoMsgMouseUpDownClick(LM_XBUTTONDBLCLK);
    WM_XBUTTONDOWN:   DoMsgMouseUpDownClick(LM_XBUTTONDOWN);
    WM_XBUTTONUP:     DoMsgMouseUpDownClick(LM_XBUTTONUP);
    WM_MOUSEHOVER:
    begin
      NotifyUserInput := True;
      LMessage.Msg := LM_MOUSEENTER;
    end;
    WM_MOUSELEAVE:
    begin
      NotifyUserInput := True;
      LMessage.Msg := LM_MOUSELEAVE;
    end;
    WM_MOUSEMOVE:  DoMsgMouseMove;
    WM_MOUSEWHEEL: if DoMsgMouseWheel(Result) then Exit;
    WM_DROPFILES:
      begin
        {$IFDEF EnableWMDropFiles}
        SetLMessageAndParams(LM_DROPFILES);
        {$ENDIF}
        HandleDropFiles;
      end;
    //TODO:LM_MOVEPAGE,LM_MOVETOROW,LM_MOVETOCOLUMN
    WM_NCHITTEST:     SetLMessageAndParams(LM_NCHITTEST);
    WM_NCLBUTTONDOWN: DoMsgNCLButtonDown;

    WM_NCMOUSEMOVE,
    WM_NCMOUSEHOVER:
    begin
      SetLMessageAndParams(Msg);
      NotifyUserInput := True;
      Application.DoBeforeMouseMessage(nil);
    end;
    WM_NOTIFY: if DoMsgNotify(Result) then Exit;
    WM_PAINT: SendPaintMessage(HDC(WParam)); // SendPaintMessage sets winprocess to false
    WM_PRINTCLIENT:
      if ((LParam and PRF_CLIENT) = PRF_CLIENT) and (lWinControl <> nil) then
        SendPaintMessage(HDC(WParam));
    WM_PASTE:         LMessage.Msg := LM_PASTE;
    WM_RBUTTONDBLCLK: DoMsgMouseUpDownClick(LM_RBUTTONDBLCLK);
    WM_RBUTTONDOWN:   DoMsgMouseUpDownClick(LM_RBUTTONDOWN);
    WM_RBUTTONUP:
    begin
      DoMsgMouseUpDownClick(LM_RBUTTONUP);
      WinProcess := false;
      Result := 0;
    end;
    WM_CONTEXTMENU:
    begin
      DoMsgContextMenu;
      Result := 0;
    end;
    WM_SETCURSOR:  HandleSetCursor;
    CM_ACTIVATE:
    begin
      if (Window = Win32WidgetSet.AppHandle) then
      begin
        // if application window is still focused then move the focus
        // to the next top window
        if not IsIconic(Window) and (GetFocus = Window) then
        begin
          TargetWindow := LookupTopWindow(Window);
          if TargetWindow <> Window then
            SetFocus(TargetWindow);
        end;
        Result := 0;
        Exit;
      end;
      WinProcess := False;
    end;
    WM_SETFOCUS:
    begin
{$ifdef DEBUG_CARET}
      DebugLn('WM_SETFOCUS received for window ', IntToHex(Window, 8));
{$endif}
      // move focus to another application window but process event first
      if (Window = Win32WidgetSet.AppHandle) then
        PostMessage(Window, CM_ACTIVATE, 0, 0);
      LMessage.Msg := LM_SETFOCUS;
    end;
    WM_SHOWWINDOW: DoMsgShowWindow;
    WM_SYSCHAR:
    begin
      PLMsg := @LMChar;
      SetLMCharData(CN_SYSCHAR, True);
      Result := 0;
      WinProcess := false;
    end;
    WM_SYSCOMMAND:
    begin
      HandleSysCommand;
      SetLMessageAndParams(Msg);
      WmSysCommandProcess := WinProcess;
      WinProcess := False;
    end;
    WM_SYSKEYDOWN:
    begin
      UpdateUIState(Word(WParam));
      DoMsgSysKey(CN_SYSKEYDOWN);
      Result := 0;
      IgnoreNextCharWindow := Window;
    end;
    WM_SYSKEYUP:
    begin
      DoMsgSysKey(CN_SYSKEYUP);
      Result := 0;
    end;
    WM_TIMER:  SetLMessageAndParams(LM_TIMER);
    WM_WINDOWPOSCHANGING:
    begin
      with TLMWindowPosMsg(LMessage) Do
      begin
        Msg := LM_WINDOWPOSCHANGING;
        Unused := WParam;
        WindowPos := PWindowPos(LParam);
      end;
    end;
    WM_WINDOWPOSCHANGED:
    begin
      with TLMWindowPosMsg(LMessage) Do
      begin
        Msg := LM_WINDOWPOSCHANGED;
        Unused := WParam;
        WindowPos := PWindowPos(LParam);
      end;
      // cross-interface compatible: complete invalidate on resize
      if (PWindowPos(LParam)^.flags and SWP_NOSIZE) = 0 then
        Windows.InvalidateRect(Window, nil, True);
    end;
    WM_MEASUREITEM:   DoMsgMeasureItem;
    WM_SETTINGCHANGE: Application.IntfSettingsChange;
    WM_THEMECHANGED:
      // winxp theme changed, recheck whether themes are enabled
      if Window = Win32WidgetSet.AppHandle then
      begin
        ThemeServices.UpdateThemes;
        Graphics.UpdateHandleObjects;
        ThemeServices.IntfDoOnThemeChange;
      end;
    WM_UPDATEUISTATE:
      if ThemeServices.ThemesEnabled then
        InvalidateRect(Window, nil, True);

    { >= WM_USER }

    WM_LCL_SOCK_ASYNC:
    begin
      if (Window = Win32WidgetSet.AppHandle) and
          Assigned(Win32WidgetSet.FOnAsyncSocketMsg) then
        Exit(Win32WidgetSet.FOnAsyncSocketMsg(WParam, LParam))
    end;
    WM_IME_COMPOSITION,
    WM_IME_COMPOSITIONFULL,
    WM_IME_CONTROL,
    //WM_IME_ENDCOMPOSITION,
    WM_IME_NOTIFY,
    WM_IME_REQUEST,
    WM_IME_SELECT,
    WM_IME_SETCONTEXT,
    WM_IME_STARTCOMPOSITION:
      SetLMessageAndParams(Msg, True);
  else
    // pass along user defined messages
    if Msg >= WM_USER then
      SetLMessageAndParams(Msg, True);
  end;  // case Msg of

  if WinProcess then
  begin
    PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam);
    WinProcess := False;
  end;

  case Msg of
    WM_ACTIVATEAPP: DoMsgActivateApp;
    WM_MOVE:        if DoMsgMove then Exit(0);
    WM_SIZE:        DoMsgSize;
    BM_SETCHECK:
    begin
      //LParam holds BST_CHECKED, BST_UNCHECKED or SKIP_LMCHANGE;
      if LParam <> SKIP_LMCHANGE then
        LMessage.Msg := LM_CHANGED;
      if lWinControl is TRadioButton then
      begin
        //Uncheck siblings
        if WParam = BST_CHECKED then
          ClearSiblingRadioButtons(TRadioButton(lWinControl));
      end;
    end;
    WM_ENDSESSION:
    begin
      if Assigned(Application) and (Win32WidgetSet.AppHandle = Window) and
         (WParam > 0) then
      begin
        // look at WM_QUERYENDSESSION about LParam
        LMessage.Msg := LM_NULL; // no need to go through delivermessage
        Application.IntfEndSession();
        LMessage.Result := 0;
      end;
    end;

    WM_QUERYENDSESSION:
    begin
      if Assigned(Application) and (Win32WidgetSet.AppHandle = Window) then
      begin
        LMessage.Msg := LM_NULL; // no need to go through delivermessage
        CancelEndSession := LMessage.Result=0;
        // it is possible to pass whether user LogOff or Shutdonw through a flag
        // but seems there is no way to do this in a cross-platform way =>
        // skip it for now
        Application.IntfQueryEndSession(CancelEndSession);
        if CancelEndSession
          then LMessage.Result := 0
          else LMessage.Result := 1;
      end;
    end;
    WM_NCPAINT:
    begin
      if (lWinControl <> nil) and TWin32ThemeServices(ThemeServices).ThemesEnabled and
         not (lWinControl is TCustomForm) and (lWinControl is TCustomControl) then
      begin
        TWin32ThemeServices(ThemeServices).PaintBorder(lWinControl, True);
        LMessage.Result := 0;
      end;
    end;
  end;  // case Msg of

  // convert from win32 client to lcl client pos.
  //
  // hack to prevent GetLCLClientBoundsOffset from changing mouse client
  // coordinates for TScrollingWinControls, this is required in
  // IsControlMouseMsg and ControlAtPos where unscrolled client coordinates
  // are expected.
  if (PLMsg = @LMMouseMove) and not (lWinControl is TScrollingWinControl) then
  begin
    if GetLCLClientBoundsOffset(lWinControl, R) then
    begin
      Dec(LMMouseMove.XPos, R.Left);
      Dec(LMMouseMove.YPos, R.Top);
    end;
  end else
  if (PLMsg = @LMMouse) and not (lWinControl is TScrollingWinControl) then
  begin
    if GetLCLClientBoundsOffset(lWinControl, R) then
    begin
      Dec(LMMouse.XPos, R.Left);
      Dec(LMMouse.YPos, R.Top);
    end;
  end;

  // application processing
  if NotifyUserInput then
  begin
    CurrentWindow := Window;
    NotifyApplicationUserInput(lWinControl, PLMsg^.Msg);
    // Invalidate associated lWinControl if current window has been destroyed
    if CurrentWindow = 0 then
      lWinControl := nil;
  end;

  if Assigned(lWinControl) and (PLMsg^.Msg <> LM_NULL) then
    DeliverMessage(lWinControl, PLMsg^);

  // respond to result of LCL handling the message
  case PLMsg^.Msg of
    LM_ERASEBKGND, LM_SETCURSOR, LM_RBUTTONUP, LM_CONTEXTMENU, LM_MOUSEWHEEL:
      if PLMsg^.Result = 0 then
        WinProcess := True;

    WM_SYSCOMMAND:
      WinProcess := WmSysCommandProcess;

    CN_CHAR, CN_SYSCHAR:
    begin
      // if key not yet processed, let windows process it
      WinProcess := LMChar.Result = 0;
      if UnicodeEnabledOS then
      begin
        // if charcode was modified by LCL, convert ansi char
        // to unicode char, if not change was made WParam has
        // the right unicode char so just use it.
        if (LMChar.Result = 1) or (OrgCharCode <> LMChar.CharCode) then
          WParam := Word(WideChar(LMChar.CharCode));
      end
      else
        WParam := LMChar.CharCode;
    end;

    CN_KEYDOWN, CN_KEYUP, CN_SYSKEYDOWN, CN_SYSKEYUP:
    begin
      // if key not yet processed, let windows process it
      WinProcess := LMKey.Result = 0;
      WParam := LMKey.CharCode;
    end;
    WM_IME_COMPOSITION,
    WM_IME_COMPOSITIONFULL,
    WM_IME_CONTROL,
    WM_IME_ENDCOMPOSITION,
    WM_IME_NOTIFY,
    WM_IME_REQUEST,
    WM_IME_SELECT,
    WM_IME_SETCONTEXT,
    WM_IME_STARTCOMPOSITION:
    begin
      WinProcess := LMessage.Result = 0;
    end;
  else
    case Msg of
      {$ifndef RedirectDestroyMessages}
      WM_NCDESTROY:
      begin
        // free our own data associated with window
        if DisposeWindowInfo(Window) then
          WindowInfo := nil;
        EnumProps(Window, @PropEnumProc);
      end;
      {$endif}
    end;
  end;

  if WinProcess then
  begin
    if ((Msg=WM_CHAR) and ((WParam=VK_RETURN) or (WPARAM=VK_ESCAPE)) and
       ((lWinControl is TCustomCombobox) or
        ((lWinControl is TCustomEdit) and not (lWinControl is TCustomMemo ))
       ))
       or (Msg=WM_SYSCHAR)      // Windows message processing is postponed
    then
      // this thing will beep, don't call defaultWindowProc
    else
      PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam);

    case Msg of
      WM_CHAR, WM_KEYDOWN, WM_KEYUP,
      WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP:
      begin
        PLMsg^.Result := 0;
        case Msg of
          WM_CHAR:
          begin
            // if want chars, then handled already
            PLMsg^.Result := CallDefaultWindowProc(Window, WM_GETDLGCODE, 0, 0) and DLGC_WANTCHARS;
            SetLMCharData(LM_CHAR);
          end;
          WM_SYSCHAR:    SetLMCharData(LM_SYSCHAR);
          WM_KEYDOWN:    SetLMKeyData(LM_KEYDOWN);
          WM_KEYUP:      SetLMKeyData(LM_KEYUP);
          WM_SYSKEYDOWN: SetLMKeyData(LM_SYSKEYDOWN);
          WM_SYSKEYUP:   SetLMKeyData(LM_SYSKEYUP);
        end;

        case Msg of
          WM_CHAR, WM_SYSCHAR:
            CharCodeNotEmpty := (LMChar.CharCode<>0);
          else
            CharCodeNotEmpty := (LMKey.CharCode<>0);
        end;
        // we cannot tell for sure windows didn't want the key
        // for WM_CHAR check WM_GETDLGCODE/DLGC_WANTCHARS
        // winapi too inconsistent about return value
        if (lWinControl <> nil) and (PLMsg^.Result = 0) and CharCodeNotEmpty then
          DeliverMessage(lWinControl, PLMsg^);

        // Windows message processing for WM_SYSCHAR not processed (will get WM_MENUCHAR)
        if (Msg=WM_SYSCHAR) and (PLMsg^.Result = 0) and CharCodeNotEmpty then
          PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam);

        // handle Ctrl-A for edit controls
        if (PLMsg^.Result = 0) and (Msg = WM_KEYDOWN) and (WParam = Ord('A'))
          and (GetKeyState(VK_CONTROL) < 0) and (GetKeyState(VK_MENU) >= 0) then
        begin
          if WndClassName(Window) = EditClsName then
            Windows.SendMessage(Window, EM_SETSEL, 0, -1);  // select all
        end;
      end;
    end;
  end;

  // ignore WM_(SYS)CHAR message if LCL handled WM_(SYS)KEYDOWN
  if ((Msg = WM_KEYDOWN) or (Msg = WM_SYSKEYDOWN)) then
    if (PLMsg^.Result = 0) then
      IgnoreNextCharWindow := 0;

  { LMInsertText has no Result field }

  if      PLMsg = @LMScroll     then Result := LMScroll.Result
  else if PLMsg = @LMKey        then Result := LMKey.Result
  else if PLMsg = @LMChar       then Result := LMChar.Result
  else if PLMsg = @LMMouse      then Result := LMMouse.Result
  else if PLMsg = @LMMouseMove  then Result := LMMouseMove.Result
  else if PLMsg = @LMMove       then Result := LMMove.Result
  else if PLMsg = @LMNotify     then Result := LMNotify.Result
  else if PLMsg = @LMMouseEvent then Result := LMMouseEvent.Result
  else                               Result := PLMsg^.Result;
end;

{------------------------------------------------------------------------------
 Function: WindowProc
 Params: Window - The window that receives a message
         Msg    - The message received
         WParam - Word parameter
         LParam - Long-integer parameter
  Returns: 0 if Msg is handled; non-zero long-integer result otherwise

  Handles the messages sent to the specified window, in parameter Window, by
  Windows or other applications
 ------------------------------------------------------------------------------}
function
{$ifdef MSG_DEBUG}
  RealWindowProc
{$else}
  WindowProc
{$endif}
  (Window: HWnd; Msg: UInt; WParam: Windows.WParam; LParam: Windows.LParam): LResult; stdcall;
var
  Helper: TWindowProcHelper;
begin
  Helper := TWindowProcHelper.Create;
  try
    Helper.Window := Window;
    Helper.Msg := Msg;
    Helper.WParam := WParam;
    Helper.LParam := LParam;
    Helper.NMHdr := PNMHdr(LParam);
    Result := Helper.DoWindowProc;
  finally
    Helper.Free;
  end;
end;

{$ifdef MSG_DEBUG}

function WindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
    LParam: Windows.LParam): LResult; stdcall;
begin
  DebugLn(MessageStackDepth, 'WindowProc called for window=', IntToHex(Window, 8),' msg=',
    WM_To_String(msg),' wparam=', IntToHex(WParam, sizeof(WParam)*2), ' lparam=', IntToHex(lparam, sizeof(lparam)*2));
  MessageStackDepth := MessageStackDepth + ' ';

  Result := RealWindowProc(Window, Msg, WParam, LParam);

  setlength(MessageStackDepth, length(MessageStackDepth)-1);
end;

{$endif}

{------------------------------------------------------------------------------
 Function: OverlayWindowProc
 Params: Window - The window that receives a message
         Msg    - The message received
         WParam - Word parameter
         LParam - Long-integer parameter
  Returns: 0 if Msg is handled; non-zero long-integer result otherwise

  Handles messages specifically for the window used by GetDesignerDC
 ------------------------------------------------------------------------------}
function OverlayWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
    LParam: Windows.LParam): LResult; stdcall;
var
  Parent: HWND;
  Owner: TWinControl;
  Control: TControl;
  P: TPoint;
  LRect: Windows.RECT;
begin
  case Msg of
    WM_ERASEBKGND:
    begin
      Result := 1;
    end;
    WM_NCHITTEST:
    begin
      // By default overlay window handle all mouse messages
      Result := HTCLIENT;
      
      // Check if overlayed control want to handle mouse messages
      Parent := Windows.GetParent(Window);
      Owner := GetWin32WindowInfo(Parent)^.WinControl;
      P.x := GET_X_LPARAM(lParam);
      P.y := GET_Y_LPARAM(lParam);
      Windows.ScreenToClient(Parent, P);
      if (Owner is TCustomForm) then
      begin
        // ask form about control under mouse. we need TWinControl
        Control := Owner.ControlAtPos(P, [capfAllowWinControls, capfRecursive]);
        if (Control <> nil) and not (Control is TWinControl) then
          Control := Control.Parent;
      end
      else
        Control := nil;
      if (Control <> nil) then
      begin
        // Now ask control is it needs mouse messages
        MapWindowPoints(Parent, TWinControl(Control).Handle, P, 1);
        if TWSWinControlClass(TWinControl(Control).WidgetSetClass).GetDesignInteractive(TWinControl(Control), P) then
          Result := HTTRANSPARENT
      end;
    end;
    WM_KEYFIRST..WM_KEYLAST, WM_MOUSEFIRST..WM_MOUSELAST:
    begin
      // parent of overlay is the form
      Result := Windows.SendMessage(Windows.GetParent(Window), Msg, WParam, LParam);
    end;
    WM_NCDESTROY:
    begin
      // free our own data associated with window
      DisposeWindowInfo(Window);
      Result := 0;
    end;
    WM_MOVE:
    begin
      if (Int16(LoWord(LParam)) <> 0) or (Int16(HiWord(LParam)) <> 0) then
      begin
        Parent := Windows.GetParent(Window);
        Windows.GetClientRect(Parent, LRect);
        Windows.SetWindowPos(Window, HWND_TOP, 0, 0, LRect.Right, LRect.Bottom, 0);
      end;
    end;
  else
    if UnicodeEnabledOS
    then Result := Windows.DefWindowProcW(Window, Msg, WParam, LParam)
    else Result := Windows.DefWindowProc(Window, Msg, WParam, LParam);
  end;
end;

{$ifdef RedirectDestroyMessages}
{------------------------------------------------------------------------------
 Function: DestroyWindowProc
 Params: Window - The window that receives a message
         Msg    - The message received
         WParam - Word parameter
         LParam - Long-integer parameter
  Returns: 0 if Msg is handled; non-zero long-integer result otherwise

  Handles messages after handle is destroyed
 ------------------------------------------------------------------------------}

function DestroyWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
    LParam: Windows.LParam): LResult; stdcall;
var
  LMessage: TLMessage;
  WindowInfo: PWin32WindowInfo;
  lWinControl: TWinControl;
begin
  CallDefaultWindowProc(Window, Msg, WParam, LParam);
  case Msg of
    WM_DESTROY:
    begin
      WindowInfo := GetWin32WindowInfo(Window);
      if WindowInfo^.isChildEdit then
        lWinControl := WindowInfo^.AWinControl
      else
        lWinControl := WindowInfo^.WinControl;
      if CurrentWindow = Window then
        CurrentWindow := 0;
      if lWinControl is TCustomComboBox then
        DisposeComboEditWindowInfo(TCustomComboBox(lWinControl));
      if WindowInfo^.Overlay<>HWND(nil) then
        Windows.DestroyWindow(WindowInfo^.Overlay);
      if lWinControl <> nil then
      begin
        FillChar(LMessage, SizeOf(LMessage), 0);
        LMessage.Msg := LM_DESTROY;
        DeliverMessage(lWinControl, LMessage);
      end;
    end;
    WM_NCDESTROY:
    begin
      // free our own data associated with window
      DisposeWindowInfo(Window);
      EnumProps(Window, @PropEnumProc);
    end;
  end;
end;
{$endif}

{------------------------------------------------------------------------------
 Procedure: TimerCallBackProc
 Params: window_hnd - handle of window for timer message, not set in this implementation
         msg        - WM_TIMER message
         idEvent    - timer identifier
         dwTime     - current system time

 Calls the timerfunction in the Timer Object in the LCL
 ------------------------------------------------------------------------------}
procedure TimerCallBackProc(window_hwnd : hwnd; msg : DWORD; idEvent: UINT_PTR; dwTime: DWORD); stdcall;
Var
  TimerInfo: PWin32TimerInfo;
  n: Integer;
begin
  if Assigned(Application) and Application.Terminated then exit;
  n := FTimerData.Count;
  while (n>0) do begin
    dec(n);
    TimerInfo := FTimerData[n];
    if TimerInfo^.TimerID=idEvent then begin
      TimerInfo^.TimerFunc;
      break;
    end;
  end;
end;

{$IFDEF ASSERT_IS_ON}
  {$UNDEF ASSERT_IS_ON}
  {$C-}
{$ENDIF}

