(* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is TurboPower SysTools
 *
 * The Initial Developer of the Original Code is
 * TurboPower Software
 *
 * Portions created by the Initial Developer are Copyright (C) 1996-2002
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** *)

unit BcdCalU;

interface

uses
  SysUtils, Windows, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls, ExtCtrls, Buttons, Menus, Clipbrd;

type
  BCDCharSet = set of Char;
  BCDOperSet = set of Char;

type
  TBCDCalcDlg = class(TForm)
    ZeroBtn: TBitBtn;
    DecKey: TBitBtn;
    ThreeKey: TBitBtn;
    OneKey: TBitBtn;
    TwoKey: TBitBtn;
    SixKey: TBitBtn;
    FourKey: TBitBtn;
    FiveKey: TBitBtn;
    NineKey: TBitBtn;
    SevenKey: TBitBtn;
    EightKey: TBitBtn;
    SqrtBtn: TBitBtn;
    LnBtn: TBitBtn;
    ExpBtn: TBitBtn;
    XtoYBtn: TBitBtn;
    AddBtn: TBitBtn;
    SubBtn: TBitBtn;
    MulBtn: TBitBtn;
    DivBtn: TBitBtn;
    PlusMinusBtn: TBitBtn;
    ClearBtn: TBitBtn;
    EqualBtn: TBitBtn;
    ClearEntryBtn: TBitBtn;
    Bevel1: TBevel;
    gb1: TGroupBox;
    BCDString: TEdit;
    BSBtn: TBitBtn;
    Memo1: TMemo;
    PopupMenu1: TPopupMenu;
    Copy1: TMenuItem;
    Paste1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure CloseBtnClick(Sender: TObject);
    procedure ClearBtnClick(Sender: TObject);
    procedure ZeroBtnClick(Sender: TObject);
    procedure DecKeyClick(Sender: TObject);
    procedure OneKeyClick(Sender: TObject);
    procedure TwoKeyClick(Sender: TObject);
    procedure ThreeKeyClick(Sender: TObject);
    procedure FourKeyClick(Sender: TObject);
    procedure FiveKeyClick(Sender: TObject);
    procedure SixKeyClick(Sender: TObject);
    procedure SevenKeyClick(Sender: TObject);
    procedure EightKeyClick(Sender: TObject);
    procedure NineKeyClick(Sender: TObject);
    procedure PlusMinusBtnClick(Sender: TObject);
    procedure AddBtnClick(Sender: TObject);
    procedure SubBtnClick(Sender: TObject);
    procedure MulBtnClick(Sender: TObject);
    procedure DivBtnClick(Sender: TObject);
    procedure SqrtBtnClick(Sender: TObject);
    procedure ExpBtnClick(Sender: TObject);
    procedure LnBtnClick(Sender: TObject);
    procedure XtoYBtnClick(Sender: TObject);
    procedure EqualBtnClick(Sender: TObject);
    procedure ClearEntryBtnClick(Sender: TObject);
    procedure BSBtnClick(Sender: TObject);
    procedure Copy1Click(Sender: TObject);
    procedure Paste1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    BCDChar   : BCDCharSet;
    BCDOper   : BCDOperSet;
    PendOp    : Char;
    DFHold    : Integer;
    XBuffer   : string[20];
    ClearOnNext : Boolean;

    procedure SendKeyPress(Sender : TObject; C : Char);
  end;


var
  BCDCalcDlg: TBCDCalcDlg;

implementation

{$R *.lfm}

uses
  StConst,
  StBase,
  StStrL,
  StBCD;

procedure TBCDCalcDlg.FormCreate(Sender: TObject);
begin
  BCDChar := ['0'..'9', SysUtils.DecimalSeparator, 'p'];
  BCDOper := ['+', '-', '/', '*', '^', 'e', 'l', 's', '='];
  DecKey.Caption := FormatSettings.DecimalSeparator;
  Memo1.Lines.Text := '0';
  PendOp := #0;
  DFHold := 0;
  XBuffer := '0';
  ClearOnNext := False;
end;


function BytesToString(Value : PByte; Size : Cardinal) : string;
  {-convert byte array to string, no spaces or hex enunciators, e.g., '$'}
var
  I,
  Index  : Cardinal;
  S      : String[3];
begin
  {$IFOPT H+}
  SetLength(Result,2*Size);
  {$ELSE}
  Result[0] := AnsiChar(Size*2);
  {$ENDIF}

  for I := 1 to Size do
  begin
    Index := I*2;
  {$IFOPT H+}
    S := HexBL(Byte(PAnsiChar(Value)[I-1]));
  {$ELSE}
    S := HexBS(Byte(PAnsiChar(Value)[I-1]);
  {$ENDIF}
    Result[(Index)-1] := S[1];
    Result[Index] := S[2];
  end;
end;

function StringToBytes(IString : string; var Value; Size : LongInt) : Boolean;
  {-convert string (by groups of 2 char) to byte values}
var
  Code,
  Index,
  I     : Integer;
  Q     : TBcd;
  S     : array[1..3] of AnsiChar;
begin
  if ((Length(IString) div 2) <> Size) then
  begin
    Result := False;
    Exit;
  end;

  Result := True;
  for I := 1 to Size do
  begin
    Index := (2*(I-1))+1;
    S[1] := '$';
    S[2] := IString[Index];
    S[3] := IString[Index+1];
    Val(S,Q[I-1],Code);
    if (Code <> 0) then
    begin
      Result := False;
      Exit;
    end;
 end;
  Move(Q,Value,Size);
end;

procedure TBCDCalcDlg.FormKeyPress(Sender: TObject; var Key: Char);
var
  HldOp : Char;
  L     : Integer;
  BCD1  : TBcd;
  S     : string[21];
begin
  if Memo1.Lines[0] = '0' then
    Memo1.Lines[0] := '';

  if Key = #13 then begin
    if XBuffer = '0' then
        XBuffer := Memo1.Lines[0]
    else begin
      EqualBtnClick(Sender);
      XBuffer := '0';
    end;
    Key := #0;
    ClearOnNext := True;
  end;

  if Key in BCDChar then begin
    if (Length(Memo1.Lines[0]) = 0) and (Key = SysUtils.DecimalSeparator) then begin
      Memo1.Lines[0] := '0';
    end;
    if (Key = 'p') then begin
      S := Memo1.Lines[0];
      if (S[1] <> '-') then
        Insert('-',S,1)
      else
        Delete(S,1,1);
      Memo1.Lines[0] := S;
      BCD1 := ValBcd(S);
      BCDString.Text := BytesToString(@BCD1,SizeOf(BCD1));
      Key := #0;
    end else begin
      if ClearOnNext then begin
        Memo1.Lines[0] := '';
        ClearOnNext := False;
      end;
    end;
  end;

  if Key in BCDOper then begin
    if not (Key in ['s', 'e', 'l']) then begin
      if Memo1.Lines[0] = '' then
        Memo1.Lines[0] := '0';
      if (XBuffer <> '0') then
        EqualBtnClick(Sender);
      XBuffer := Memo1.Lines[0];
      BCD1 := ValBcd(XBuffer);
      BCDString.Text := BytesToString(@BCD1,SizeOf(BCD1));
      PendOp := Key;
      Key := #0;
      ClearOnNext := True;
    end else begin
      HldOp := PendOp;
      PendOp := Key;
      EqualBtnClick(Sender);
      PendOp := HldOp;
      Key := #0;
    end;
  end;

  if (Key in BCDChar) then begin
    S := Memo1.Lines[0];
    L := Length(S);
    if (L < Memo1.MaxLength) then begin
      Memo1.Lines[0] := S + Key;
    end;
    Key := #0
  end;
  Memo1.SetFocus;
  Memo1.SelStart := Length(Memo1.Lines[0]);
  Memo1.SelLength := 0;
end;


procedure TBCDCalcDlg.CloseBtnClick(Sender: TObject);
begin
  Close;
end;

procedure TBCDCalcDlg.ClearBtnClick(Sender: TObject);
begin
  XBuffer := '0';
  Memo1.Lines[0] := '0';
  BCDString.Text := '';
  PendOp := #0;
  ClearOnNext := True;
end;

procedure TBCDCalcDlg.ClearEntryBtnClick(Sender: TObject);
begin
  Memo1.Lines[0] := '0';
  ClearOnNext := True;
end;

procedure TBCDCalcDlg.SendKeyPress(Sender : TObject; C : Char);
var
  KP : Char;
begin
  KP := C;
  FormKeyPress(Sender,KP);
end;

procedure TBCDCalcDlg.ZeroBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'0');
end;

procedure TBCDCalcDlg.DecKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender, SysUtils.DecimalSeparator);
end;

procedure TBCDCalcDlg.OneKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'1');
end;

procedure TBCDCalcDlg.TwoKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'2');
end;

procedure TBCDCalcDlg.ThreeKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'3');
end;

procedure TBCDCalcDlg.FourKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'4');
end;

procedure TBCDCalcDlg.FiveKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'5');
end;

procedure TBCDCalcDlg.SixKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'6');
end;

procedure TBCDCalcDlg.SevenKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'7');
end;

procedure TBCDCalcDlg.EightKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'8');
end;

procedure TBCDCalcDlg.NineKeyClick(Sender: TObject);
begin
  SendKeyPress(Sender,'9');
end;

procedure TBCDCalcDlg.PlusMinusBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'p');
end;

procedure TBCDCalcDlg.AddBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'+');
end;

procedure TBCDCalcDlg.SubBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'-');
end;

procedure TBCDCalcDlg.MulBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'*');
end;

procedure TBCDCalcDlg.DivBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'/');
end;

procedure TBCDCalcDlg.SqrtBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'s');
end;

procedure TBCDCalcDlg.ExpBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'e');
end;

procedure TBCDCalcDlg.LnBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'l');
end;

procedure TBCDCalcDlg.XtoYBtnClick(Sender: TObject);
begin
  SendKeyPress(Sender,'^');
end;

procedure TBCDCalcDlg.EqualBtnClick(Sender: TObject);
var
//  RV    : Extended;
  S     : string[21];
  BCD   : TBcd;
begin
  if PendOp <> #0 then begin
    S := Memo1.Lines[0];
    if S = '' then begin
      MessageBeep(0);
      Exit;
    end;
    case PendOp of
      '+' : begin
//              RV := StrToFloat(XBuffer) + StrToFloat(S);
              BCD := AddBCD(ValBCD(XBuffer), ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      '-' : begin
//              RV := StrToFloat(XBuffer) - StrToFloat(S);
              BCD := SubBCD(ValBCD(XBuffer), ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      '*' : begin
//              RV := StrToFloat(XBuffer) * StrToFloat(S);
              BCD := MulBCD(ValBCD(XBuffer), ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      '/' : begin
//              RV := StrToFloat(S);
              BCD := ValBCD(S);
//              if RV = 0 then begin
              if CmpBcd(BCD, ZeroBcd) = 0 then begin
                Memo1.Lines[0] := 'Divide by zero error';
                PendOp := #0;
                ClearOnNext := False;
              end else begin
//                RV := StrToFloat(XBuffer) / StrToFloat(S);
                BCD := DivBCD(ValBCD(XBuffer), BCD);
//                Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//                BCD := ValBcd(Memo1.Lines[0]);
                BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
              end;
            end;
      's' : begin
//              RV := Sqrt(StrToFloat(S));
              BCD := SqrtBcd(ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      'e' : begin
//              RV := Exp(StrToFloat(S));
              BCD := ExpBCD(ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      'l' : begin
//              RV := ln(StrToFloat(S));
              BCD := lnBCD(ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;
      '^' : begin
//              RV := exp(ln(StrToFloat(XBuffer)) * StrToFloat(S));
              BCD := PowBCD(ValBCD(XBuffer), ValBCD(S));
//              Memo1.Lines[0] := FloatToStr(RV);
              Memo1.Lines[0] := RightTrimCharsL(Trim(StrBCD(BCD, 35, 20)), '0');
//              BCD := ValBcd(Memo1.Lines[0]);
              BCDString.Text := BytesToString(@BCD,SizeOf(BCD));
            end;

    end;
  end;
  PendOp := #0;
  ClearOnNext := True;
  Memo1.SetFocus;
  Memo1.SelStart := 0;
  Memo1.SelLength := 0;
end;


procedure TBCDCalcDlg.BSBtnClick(Sender: TObject);
begin
  Memo1.Lines[0] := Copy(Memo1.Lines[0], 1, Length(Memo1.Lines[0]) - 1);
  if Length(Memo1.Lines[0]) = 0 then
    ClearBtnClick(ClearBtn);
end;

procedure TBCDCalcDlg.Copy1Click(Sender: TObject);
begin
  Memo1.SelectAll;
  Memo1.CopyToClipboard;
  Memo1.SelStart := 0;
end;

procedure TBCDCalcDlg.Paste1Click(Sender: TObject);
var
  S : string;
  IsNeg : Boolean;
begin
  S := Clipboard.AsText;
  IsNeg := False;
  if (S[1] = '-') then begin
    IsNeg := True;
    S := Copy(S, 2, Length(S) - 1);
  end;

  if IsStrNumericL(S, '0123456789' + SysUtils.DecimalSeparator) then begin
    if IsNeg then S := '-' + S;
    Memo1.Lines[0] := S;
  end;
end;

end.