{ Example of creating a modeless dialog box.
  Example of a simple calculator in a modeless dialog box.
  This example shows the use of the wObjectCreator2.pp
  to modify the default behaviour of objects created in the library.
  It shows the use of the dialog box creator and
  some treatment of keyboard input.
  It shows also the use of popup windows to display warnings.
  It could be developped much more, but it's not my goal.
  Many details are missing...
  --------------------------------------------}
program tEx043;

// To tell FreePascal that the application is a Graphical User Interface
// i.e. is running in windows, not in text mode.
{$APPTYPE GUI}

Uses
  wApplication0
 ,wObjectCreator2
 ,wWindow0
 ,wDialog0
 ,wButtonPush0
 ,wEditBox0
 ,wPopupWindow0
 ,wDialogCreatorLOW2
 ,wVariableNumLink2
 ,wWindowBase0
 ,wObjectCreatorLOW2 // for gglpoObjCreate
 ,wErrors0
 ,wGlobals2
 ,wDebug0
 ,Windows;

const
  // Number of number memorised in the stack of the calculator.
  // The calculator use the inverse notation. For example, to compute 2 + 3, type
  // 2 ENTER 3 +
  // 3 * (4 + 7) => 3 ENTER 4 ENTER 7 + *
  STACKSIZE = 8;

  // id of buttons
  id0        = 200;
  id9        = 209;
  idPERIODE  = 210;
  idPM       = 300;
  idADD      = 301;
  idSUB      = 302;
  idMUL      = 303;
  idDIV      = 304;
  idENTER    = 400;
  idCLR      = 401; 
  idSWAP     = 402;
  idSTORE    = 501;
  idRECAL    = 502;
  idSQRT     = 601;
  idINV      = 602;
  idQUIT     = 999;

type
  TMyObjectCreator = object(TObjectCreator)
  //"""""""""""""""""""""""""""""""""""""""
  // Just to tell the library that it must create a
  // TMyDialog, TMyButtonPush and TMyEditBox  objects instead of
  // TDialog, TButtonPush and TEditBox Objects.
  constructor Init;

  function CreateDialog(poParent:Pointer):Pointer; virtual;
  // the pointer will be on a TMyDialog object

  function CreateButtonPush(poParent:Pointer):Pointer; virtual;
  // the pointer will be on a TMyButtonPush object

  function CreateEditBox(poParent:Pointer):Pointer; virtual;
  // the pointer will be on a TMyEditBox object
  end; // TMyObjectCreator = object(TObjectCreator)

type
  PMyDialog = ^TMyDialog;
  TMyDialog = object(TDialog)
  //"""""""""""""""""""""""""
  // We want to modify the default behaviour of the dialog box.
  constructor Init(poParentW:PWindowBase);

  function DialogProc(hDialog:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the dialog messages. See comments in  wDialog0.pp
  end; // TMyDialog = object(TDialog)

type
  PMyButtonPush = ^TMyButtonPush;
  TMyButtonPush = object(TButtonPush)
  //""""""""""""""""""""""""""""""""""
  // We want to modify the default behaviour of push buttons
  constructor Init(poParentW:PWindowBase);

  function CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the window messages
  end; // TMyButtonPush = object(TButtonPush)

type
  PMyEditBox = ^TMyEditBox;
  TMyEditBox = object(TEditBox)
  //"""""""""""""""""""""""""""
  // We want to modify the default behaviour of edit boxes
  constructor Init(poParentW:PWindowBase);

  function CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the window messages
  end; // TMyEditBox = object(TEditBox)

type
  PMyWindow = ^TMyWindow;
  TMyWindow = object(TWindow)
  //"""""""""""""""""""""""""
  avValues : array[1..STACKSIZE] of Float;
  // avValues[1] = visible value of the calculator
  // the others are in the stack

  vXValue : Float;
  // the value shown in the edit box, must be different if we don't
  // want rounding errors to occure.

  fClearNext : boolean;
  // The next digit clears the avValue[1] and replaces it by the digit

  fAddNext : boolean;
  // The next digit push the stack up and
  //  clears the avValue[1] and replaces it by the digit

  vPeriode : Float;
  // If the periode has been pressed, indicate how many digits are after the
  // period.

  vMemory : Float;
  // A simple memory.

  constructor Init(strWinTitlePrm:ShortString);
  destructor Done; virtual;

  procedure WinCreate(strWinClass:ShortString); virtual;
  // When creating the window, we ask to show the dialog box, not the window

  procedure ButtonClicked(poCtrl:PWindowBase0; wCtrlID:Word32); virtual;
  // When a button is clicked, and it is in a dialog which belongs to a Window,
  // then the button calls this method, with a pointer on itself
  // and the ID it has been given.

  function WMCommand(wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
  // Handles the menu messages

//  function WindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT; virtual;
    // Not Used
  end; // TMyWindow = object(TWindow)

var
  oApp        : TApplication;
  poMyMainWin : PMyWindow;
  poObjCr     : ^TMyObjectCreator;

//******************************************************************
// TMyObjectCreator = object(TObjectCreator)
//******************************************************************

constructor TMyObjectCreator.Init;
//================================
begin
// must be done, even if it seems useless.
inherited Init;
end;  // TMyObjectCreator.Init

function TMyObjectCreator.CreateDialog(poParent:Pointer):Pointer;
//================================================================
var poObj : PMyDialog;
begin
new(poObj, Init(poParent));
CreateDialog:=poObj;
end;  // TMyObjectCreator.CreateDialog

function TMyObjectCreator.CreateButtonPush(poParent:Pointer):Pointer;
//===================================================================
var poObj : PMyButtonPush;
begin
new(poObj, Init(poParent));
CreateButtonPush:=poObj;
end;  // TMyObjectCreator.CreateButtonPush

function TMyObjectCreator.CreateEditBox(poParent:Pointer):Pointer;
//===================================================================
var poObj : PMyEditBox;
begin
new(poObj, Init(poParent));
CreateEditBox:=poObj;
end;  // TMyObjectCreator.CreateditBox

//******************************************************************
// TMyDialog = object(TDialog)
//******************************************************************

constructor TMyDialog.Init(poParentW:PWindowBase);
//================================================
begin
// must be done, even if it seems useless.
inherited Init(poParentW);
end;  // TMyDialog.Init

function TMyDialog.DialogProc(hDialog:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT;
//=======================================================================================
// Handles the dialog messages. See comments in  wDialog0.pp
begin
{ if (wMsg <> WM_MouseMove)   and (wMsg <> WM_NCMouseMove) and
   (wMsg <> WM_NCDESTROY)   and (wMsg <> WM_ENTERIDLE ) and
   (wMsg <> WM_MENUSELECT )   and (wMsg <> WM_ENTERIDLE ) and
   (wMsg <> WM_CTLCOLORBTN)   and (wMsg <> WM_ACTIVATEAPP) and
   (wMsg <> WM_SetCursor)   and (wMsg <> WM_NCHittest) and
   (wMsg <> WM_CTLCOLOREDIT)   and (wMsg <> WM_PARENTNOTIFY) and
//   (wMsg <> WM_SetFocus) and (wMsg <> WM_KillFocus) and
//   (wMsg <> WM_KEYDOWN) and (wMsg <> WM_KEYUP) and
   (wMsg <> WM_Move)        and (wMsg <> WM_NCActivate) then
   DBW0(64, 'Dlg=' + DBMsgToStr(wMsg) + ITS(wPar) + ITS(lPar)); }

if (wMsg = WM_COMMAND) then begin
   DialogProc:=0;
   if (wPar = IDOK) then begin
      // Enter has been pressed, do not exit, but act as the enter button has been pressed
      poMyMainWin^.ButtonClicked(NIL, idENTER);
      exit;
      end;
   end;

DialogProc:=inherited DialogProc(hDialog, wMsg, wPar, lPar);
end; // TMyDialog.DialogProc

//******************************************************************
// TMyButtonPush = object(TButtonPush)
//******************************************************************

constructor TMyButtonPush.Init(poParentW:PWindowBase);
//======================================================
begin
inherited Init(poParentW);
end;  // TMyButtonPush.Init

function TMyButtonPush.CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT;
//==============================================================================================
// Handles the notification messages that the control sended to
// its parent.
begin
{ if (wMsg <> WM_MouseMove)   and (wMsg <> WM_NCMouseMove) and
   (wMsg <> WM_NCDESTROY )   and (wMsg <> WM_DESTROY ) and
   (wMsg <> WM_MENUSELECT )   and (wMsg <> WM_ENTERIDLE ) and
   (wMsg <> WM_SetCursor)   and (wMsg <> WM_NCHittest) and
//   (wMsg <> WM_SetFocus) and (wMsg <> WM_KillFocus) and
//   (wMsg <> WM_KEYDOWN) and (wMsg <> WM_KEYUP) and
   (wMsg <> WM_GETDLGCODE) and (wMsg <> WM_ERASEBKGND) and
   (wMsg <> WM_PAINT) and (wMsg <> WM_NCPAINT) and
   (wMsg <> WM_GETTEXT) and (wMsg <> WM_MOUSEACTIVATE) and
   (wMsg <> WM_Move)        and (wMsg <> WM_NCActivate) then
   DBW0(128, 'Btn=' + DBMsgToStr(wMsg) + ITS(wPar) + ITS(lPar)); }

CtrlWindowProc:=0;
if (wMsg = WM_GETDLGCODE) then exit; // Filter notifications to the dialog box

// Treat some keystrokes
if (wMsg = WM_KEYDOWN) then begin
   if (wPar >= VK_0) and (wPar <= VK_9) then begin
      // Test if the shift key is pressed.
      if ((GetAsyncKeyState(VK_SHIFT) and $8000) = 0) then
         poMyMainWin^.ButtonClicked(NIL, id0 + wPar - VK_0);
      end;
   if (wPar >= VK_NUMPAD0) and (wPar <= VK_NUMPAD9) then begin
      poMyMainWin^.ButtonClicked(NIL, id0 + wPar - VK_NUMPAD0);
      end;
   if (wPar = VK_RETURN) then begin
      poMyMainWin^.ButtonClicked(NIL, idENTER);
      end;
   if (wPar = VK_DELETE) then begin
      poMyMainWin^.ButtonClicked(NIL, idCLR);
      exit;
      end;
{   if (wPar = VK_???) then begin
      poMyMainWin^.ButtonClicked(NIL, idPM);
      exit;
      end; }
   if (wPar = VK_ADD) then begin
      poMyMainWin^.ButtonClicked(NIL, idADD);
      exit;
      end;
   if (wPar = VK_SUBTRACT) then begin
      poMyMainWin^.ButtonClicked(NIL, idSUB);
      exit;
      end;
   if (wPar = VK_MULTIPLY) then poMyMainWin^.ButtonClicked(NIL, idMUL);
   if (wPar = VK_DIVIDE) then poMyMainWin^.ButtonClicked(NIL, idDIV);
   if (wPar = VK_DECIMAL) then poMyMainWin^.ButtonClicked(NIL, idPERIODE);
   if (wPar = $BE) then begin
      // This could depend on the keyboard. it should be the periode key.
      poMyMainWin^.ButtonClicked(NIL, idPERIODE);
      exit;
      end;

   exit;
   end; // if (wMsg = WM_KEYDOWN) then begin

CtrlWindowProc:=inherited CtrlWindowProc(hWindow, wMsg, wPar, lPar);
end;  // TMyButtonPush.CtrlWindowProc

//******************************************************************
// TMyEditBox = object(TEditBox)
//******************************************************************

constructor TMyEditBox.Init(poParentW:PWindowBase);
//======================================================
begin
inherited Init(poParentW);

// I want the text to be on the right side of the edit box
dwStyle:=dwStyle or ES_RIGHT;
end;  // TMyEditBox.Init

function TMyEditBox.CtrlWindowProc(hWindow:HWND; wMsg:UINT; wPar:WPARAM; lPar:LPARAM):LRESULT;
//==============================================================================================
// Handles the notification messages that the control sended to
// its parent.
var nn : integer;
begin
{ if (wMsg <> WM_MouseMove)   and (wMsg <> WM_NCMouseMove) and
   (wMsg <> WM_MENUSELECT )   and (wMsg <> WM_ENTERIDLE ) and
   (wMsg <> WM_SetCursor)   and (wMsg <> WM_NCHittest) and
//   (wMsg <> WM_SetFocus) and (wMsg <> WM_KillFocus) and
//   (wMsg <> WM_KEYDOWN) and (wMsg <> WM_KEYUP) and
   (wMsg <> WM_GETDLGCODE) and (wMsg <> WM_ERASEBKGND) and
   (wMsg <> WM_PAINT) and (wMsg <> WM_NCPAINT) and
   (wMsg <> WM_GETTEXT) and (wMsg <> WM_MOUSEACTIVATE) and
   (wMsg <> WM_Move)        and (wMsg <> WM_NCActivate) then
   DBW0(256, 'Edit=' + DBMsgToStr(wMsg) + ITS(wPar) + ITS(lPar)); }

CtrlWindowProc:=0;
// If the focus is in the edit box, treat some keystrokes.
if (wMsg = WM_CHAR) then begin
   if (wPar >= ord('0')) and (wPar <= ord('9')) then with poMyMainWin^ do begin
      // If ENTER of an operation was done just before, must shift the stack
      // and clear the content of the edit box.
      if (fClearNext or fAddNext) then begin
         if fAddNext then for nn:=STACKSIZE downto 2 do avValues[nn]:=avValues[nn-1];
         SetWindowText(Self.hWin, ''); // clear the edit box.
         fClearNext:=FALSE;
         fAddNext:=FALSE;
         vPeriode:=1.0;
         end;
      end; // if (wPar >= ord('0')) and
   end;

if (wMsg = WM_KEYUP) and
   ((wPar >= VK_0) and (wPar <= VK_9)) or
   ((wPar >= VK_NUMPAD0) and (wPar <= VK_NUMPAD9)) then with poMyMainWin^ do begin
   // here the value entered in the edit box is the one used to compute
   avValues[1]:=vXValue;
   end;

if (wMsg = WM_KEYDOWN) then begin
//    if (wPar = VK_RETURN) then treated by the TMyDialog
   if (wPar = VK_DELETE) then begin
      poMyMainWin^.ButtonClicked(NIL, idCLR);
      exit;
      end;
{   if (wPar = VK_???) then begin
      poMyMainWin^.ButtonClicked(NIL, idPM);
      exit;
      end; }
   if (wPar = VK_ADD) then begin
      poMyMainWin^.ButtonClicked(NIL, idADD);
      exit;
      end;
   if (wPar = VK_SUBTRACT) then begin
      poMyMainWin^.ButtonClicked(NIL, idSUB);
      exit;
      end;
   if (wPar = VK_MULTIPLY) then begin
      poMyMainWin^.ButtonClicked(NIL, idMUL);
      exit;
      end;
   if (wPar = VK_DIVIDE) then begin
      poMyMainWin^.ButtonClicked(NIL, idDIV);
      exit;
      end;
   end; // if (wMsg = WM_KEYDOWN) then begin

CtrlWindowProc:=inherited CtrlWindowProc(hWindow, wMsg, wPar, lPar);
end;  // TMyEditBox.CtrlWindowProc

//******************************************************************
// TMyWindow = object(TWindow)
//******************************************************************

constructor TMyWindow.Init(strWinTitlePrm:ShortString);
//=====================================================
var
  nn : integer;
begin
inherited Init(strWinTitlePrm);

// Stack is empty at the begining
for nn:=1 to STACKSIZE do begin
   FloatField(100+nn, avValues[nn]);
   avValues[nn]:=0.0;
   end;

vXValue:=0.0;
FloatField(100, vXValue);

fClearNext :=FALSE;
fAddNext   :=FALSE;
vPeriode   :=1; // no digits after the period.
vMemory    :=0;
end;  // TMyWindow.Init

destructor TMyWindow.Done;
//========================
begin
inherited Done;
end;  // TMyWindow.Done

procedure TMyWindow.WinCreate(strWinClass:ShortString);
//=====================================================
Begin
// We don't want to see the main window
//wSizeState := SIZE_MINIMIZED;

inherited WinCreate(strWinClass);

// Here calling :  DialogExecMSG(11199, @Self);  doesn't work,
// But posting a message to open the dialog box at startup is possible.
PostMessage(hWin, WM_COMMAND, 101, 0);
end;  // TMyWindow.WinCreate

procedure TMyWindow.ButtonClicked(poCtrl:PWindowBase0; wCtrlID:Word32);
//=====================================================================
var
  nn          : integer;
  poBtn       : PButtonPush;
  rPt         : POINT;

begin
// Be carefull, poCtrl can be = NIL, see directe calling of ButtonClicked above

poBtn:=PButtonPush(poCtrl); // We know that it points to a push button object.

MessageBeep(0);

//DBW(1, 'Button clicked=' + ITS(poBtn^.wCtrlID));

// Enter a digit
if (wCtrlID >= id0) and (wCtrlID <= id9) then begin
   if (fClearNext or fAddNext) then begin
      if fAddNext then for nn:=STACKSIZE downto 2 do avValues[nn]:=avValues[nn-1];
      avValues[1]:= wCtrlID - 200;
      fClearNext:=FALSE;
      fAddNext:=FALSE;
      vPeriode:=1.0;
      end                  // no period has been pressed.
   else if(vPeriode = 1.0) then avValues[1]:=avValues[1]*10.0 + wCtrlID - 200
   else begin   // enter digits after the periode (=decimal point)
      avValues[1]:=avValues[1] + vPeriode*(wCtrlID - 200);
      vPeriode:=vPeriode * 0.1;
      end;

   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   end;

if (wCtrlID = idPERIODE) then begin
   // indicate that the next digits are comming after the periode.
   if (fClearNext or fAddNext) then begin
      if fAddNext then for nn:=STACKSIZE downto 2 do avValues[nn]:=avValues[nn-1];
      avValues[1]:= 0.0;
      vXValue:=avValues[1];
      UpdateFromVar; // Update the value of the edit box
      fClearNext:=FALSE;
      fAddNext:=FALSE;
      end;

   if (vPeriode = 1) then vPeriode:=0.1;
   end;

if (wCtrlID = idCLR) then begin // CLR
   avValues[1]:=0.0;
   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   end;

if (wCtrlID = idSWAP) then begin
   vXValue:=avValues[2];
   avValues[2]:=avValues[1];
   avValues[1]:=vXValue;
   UpdateFromVar; // Update the value of the edit box
   fAddNext:=TRUE;
   end;

if (wCtrlID = idENTER) then begin // ENTER
   for nn:=STACKSIZE downto 2 do avValues[nn]:=avValues[nn-1];
   fClearNext:=TRUE;
   fAddNext:=FALSE;
   vPeriode:=1.0;
   UpdateFromVar; // Update the value of the edit box
   end;

if (wCtrlID = idPM) then begin // +/-
   avValues[1]:=-avValues[1];
   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   end;

if (wCtrlID = idADD) then begin // ADD
   avValues[1]:=avValues[2] + avValues[1];
   for nn:=3 to STACKSIZE do avValues[nn-1]:=avValues[nn];

   vXValue:=avValues[1];
   UpdateFromVar;  // Update the value of the edit box
   fAddNext:=TRUE; // The stack will move at next digit entrance
   end;

if (wCtrlID = idSUB) then begin // SUB
   avValues[1]:=avValues[2] - avValues[1];
   for nn:=3 to STACKSIZE do avValues[nn-1]:=avValues[nn];

   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   fAddNext:=TRUE;
   end;

if (wCtrlID = idMUL) then begin // MULT
   avValues[1]:=avValues[2] * avValues[1];
   for nn:=3 to STACKSIZE do avValues[nn-1]:=avValues[nn];

   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   fAddNext:=TRUE;
   end;

if (wCtrlID = idDIV) then begin // DIV
   if (avValues[1] <> 0) then begin
      avValues[1]:=avValues[2] / avValues[1];
      for nn:=3 to STACKSIZE do avValues[nn-1]:=avValues[nn];

      vXValue:=avValues[1];
      UpdateFromVar; // Update the value of the edit box
      end
   else begin
      // indicate that division by zero is not allowed.
      GetCursorPos(rPt);
      PopupWindowMessage(11100, rPt.x, rPt.y, @Self);
      end;

   fAddNext:=TRUE;
   end;

if (wCtrlID = idSTORE) then begin
   vMemory := avValues[1];
   fAddNext:=TRUE;
   end;

if (wCtrlID = idRECAL) then begin
   if (not fClearNext) then for nn:=STACKSIZE downto 2 do avValues[nn]:=avValues[nn-1];
   avValues[1]:=vMemory;
   vXValue:=avValues[1];
   UpdateFromVar; // Update the value of the edit box
   fAddNext:=TRUE;
   end;

if (wCtrlID = idSQRT) then begin
   if (avValues[1] >= 0) then begin
      avValues[1]:=sqrt(avValues[1]);
      vXValue:=avValues[1];
      UpdateFromVar; // Update the value of the edit box
      end
   else begin
      // indicate that division by zero is not allowed.
      GetCursorPos(rPt);
      PopupWindowMessage(11101, rPt.x, rPt.y, @Self);
      end;

   fAddNext:=TRUE;
   end;

if (wCtrlID = idINV) then begin
   if (avValues[1] <> 0) then begin
      avValues[1]:=1.0 / avValues[1];
      vXValue:=avValues[1];
      UpdateFromVar; // Update the value of the edit box
      end
   else begin
      // indicate that division by zero is not allowed.
      GetCursorPos(rPt);
      PopupWindowMessage(11100, rPt.x, rPt.y, @Self);
      end;

   fAddNext:=TRUE;
   end;

// Did we click on the quit button ?
// Quit the application, thus only the dialog box exist in this application.
if (wCtrlID = idQUIT) then  PostQuitMessage (0);
end;  // TMyWindow.ButtonClicked

function TMyWindow.WMCommand(wPar:WPARAM; lPar:LPARAM):LRESULT;
//=============================================================
// There are no menus treatment, only one dialog box
begin
// Execute the dialog box at starting
if (lPar = 0) and (wPar =101) then DialogCreateMSG(11199, @Self);

WMCommand:=inherited WMCommand(wPar, lPar);
end;  // TMyWindow.WMCommand

//**********************************************************************
Begin // MAIN ******
// create the Object creator, Hi Level in the library
// (i.e. which creates the deepest descendents)
// if not created by the final application, which will create even
// deepest descendents.
new(poObjCr, Init);

// Assign the pointer on the object creator,
// which will be used in the library to create ojects.
// This will allow the library to create objects defined by the
// final application.
gglpoObjCreate:=poObjCr;

// Name under which the main window is registered. Spaces are not allowed
oApp.Init('Example043');

// Creation of the main window
new(poMyMainWin, Init('tEx043.  Example of a simple Calculator in a dialog box'));

// The App.WindowClass can be modified here, if wanted. (Not usual)
oApp.Run;
oApp.Done;  // The main window object is disposed here.
End.

