{ Example of creating a modeless dialog box.
  In example tEx043.pp  and  tEx046.pp  we show a examples of a calculator.

  Here we see a more natural way of defining who treats the event messages.
  We can have as many calculator as wanted, and they are independent.

  The Object creator is again used, to modify the behaviour of the
  dialog box.

  Example of simple calculatora in modeless dialog boxex.
  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 tEx047;

// 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
 ,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
  end; // TMyObjectCreator = object(TObjectCreator)

type
  PMyDialog = ^TMyDialog;
  TMyDialog = object(TDialog)
  //"""""""""""""""""""""""""
  // We want to modify the default behaviour of the dialog box.
  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(poParentW:PWindowBase);

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

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

type
  PMyWindow = ^TMyWindow;
  TMyWindow = object(TWindow)
  //"""""""""""""""""""""""""
  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

  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 glnNotUsed : integer; // To avoid an error in the DevPascal 1.9 environment
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

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

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

// 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;  // TMyDialog.Init

function TMyDialog.WMCommand(wPar:WPARAM; lPar:LPARAM):BOOL;
//==========================================================
// Handles the menu messages
var
  nn          : integer;
  rPt         : POINT;
  wCtrlID     : word32;
begin
if (HiWord(wPar) <> BN_CLICKED) then begin
   WMCommand:=inherited WMCommand(wPar, lPar);
   exit;
   end;
//DBW(1, 'WMCommand=' + DBNotifToStr(HiWord(wPar)) + ITS5(LoWord(wPar)));
wCtrlID:=LoWord(wPar);

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 ?
// Close the main window, thus quit the application.
if (wCtrlID = idQUIT) then  PostMessage(poMyMainWin^.hWin, WM_CLOSE, 0, 0);

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

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

constructor TMyWindow.Init(strWinTitlePrm:ShortString);
//=====================================================
begin
inherited Init(strWinTitlePrm);
end;  // TMyWindow.Init

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.
// Opens also the help window, indicating the key action.
PostMessage(hWin, WM_COMMAND, 121, 0);
PostMessage(hWin, WM_COMMAND, 101, 0);
end;  // TMyWindow.WinCreate

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

// Accelerators act as button click.
// Send the command to the active dialog
case LoWord(wPar) of
  id0..id9,
  idPERIODE,
  idPM,
  idADD,
  idSUB,
  idMUL,
  idDIV,
  idENTER,
  idCLR,
  idSWAP,
  idSTORE,
  idRECAL,
  idSQRT,
  idINV,
  idQUIT
          : if (gglpoActiveDialog <> NIL) then
               gglpoActiveDialog^.WMCommand(LoWord(wPar), 0);

  end; // case

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('Example047');

// Creation of the main window
new(poMyMainWin, Init('tEx047.  Another example of a simple Calculator using accelerators'));

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

