{*********************************************************}
{*                    OVCCALC.PAS 4.06                   *}
{*********************************************************}

{* ***** 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 Orpheus                                    *}
{*                                                                            *}
{* The Initial Developer of the Original Code is TurboPower Software          *}
{*                                                                            *}
{* Portions created by TurboPower Software Inc. are Copyright (C)1995-2002    *}
{* TurboPower Software Inc. All Rights Reserved.                              *}
{*                                                                            *}
{* Contributor(s):                                                            *}
{*                                                                            *}
{* ***** END LICENSE BLOCK *****                                              *}

{$I OVC.INC}

{$B-} {Complete Boolean Evaluation}
{$I+} {Input/Output-Checking}
{$P+} {Open Parameters}
{$T-} {Typed @ Operator}
{.W-} {Windows Stack Frame}
{$X+} {Extended Syntax}

unit ovccalc;
  {-calculator component}

interface

uses
  {$IFNDEF LCL} Windows, Messages, {$ELSE} LclIntf, LMessages, Types, LclType, GraphType, MyMisc, {$ENDIF} 
  Buttons, Classes, ClipBrd, Controls, ExtCtrls, Forms, Graphics,
  Menus, StdCtrls, SysUtils,
  OvcData, OvcConst, OvcBase, OvcMisc;

type
  TOvcCalculatorButton = (
    cbNone, cbTape, cbBack, cbClearEntry, cbClear, cbAdd, cbSub, cbMul, cbDiv,
    cb0, cb1, cb2, cb3, cb4, cb5,  cb6, cb7, cb8, cb9,
    cbDecimal, cbEqual, cbInvert, cbChangeSign, cbPercent, cbSqrt,
    cbMemClear, cbMemRecall, cbMemStore, cbMemAdd, cbMemSub, cbSubTotal);

  TOvcButtonInfo = packed record
    Position : TRect;      {position and size}
    Caption  : string[10]; {button text}
    Visible  : Boolean;    {true to display button}
  end;

  TOvcButtonArray = array[cbTape..cbMemSub] of TOvcButtonInfo;

type
  TOvcCalculatorOperation = (
    coNone, coAdd, coSub, coMul, coDiv,
    coEqual, coInvert, coPercent, coSqrt,
    coMemClear, coMemRecall, coMemStore, coMemAdd, coMemSub, coSubTotal);

  TOvcCalcState = (csValid, csLocked, csClear);
  TOvcCalcStates = set of TOvcCalcState;

type
  TOvcCalcColorArray = array[0..7] of TColor;
  TOvcCalcColorScheme = (cscalcCustom, cscalcWindows, cscalcDark,
                        cscalcOcean, cscalcPlain);
  TOvcCalcSchemeArray = array[TOvcCalcColorScheme] of TOvcCalcColorArray;
  TOvcCalcDisplayString = array[TOvcCalculatorButton] of string;
  TOvcCalcButtonToOperation = array[cbNone..cbSubTotal] of TOvcCalculatorOperation;


const
  {DisabledMemoryButtons, Display, DisplayTextColor, EditButtons,
   FunctionButtons, MemoryButtons, NumberButtons, OperatorButtons}
  CalcScheme : TOvcCalcSchemeArray =
    ((0, 0, 0, 0, 0, 0, 0, 0),
     (clGray, clWindow, clWindowText, clMaroon, clNavy, clRed,  clBlue,   clRed),
     (clGray, clBlack,  clAqua,       clBlack,  clTeal, clNavy, clMaroon, clBlue),
     (clGray, clAqua,   clBlack,      clPurple, clNavy, clNavy, clAqua,   clBlue),
     (clGray, clWhite,  clNavy,       clBlack,  clNavy, clNavy, clBlue,   clBlue)
    );
{ You must set the Length of the first entry (cbNone) to the Length of the largest entry}
  CalcDisplayString : TOvcCalcDisplayString =
    ('  ','  ','  ','CE','C' ,'+' ,'-' ,'*' ,'/',
     '  ','  ','  ','  ','  ','  ','  ','  ','  ','  ',
     '  ','=' ,'1/','-+','%' ,'SQ',
     'MC','MR','MS','M+','M-','*' );

  CalcButtontoOperation : TOvcCalcButtonToOperation =
    (coNone, coNone, coNone, coNone, coNone, coAdd, coSub, coMul, coDiv,
     coNone, coNone, coNone, coNone, coNone, coNone,  coNone, coNone, coNone, coNone,
     coNone, coEqual, coInvert, coNone, coPercent, coSqrt,
     coMemClear, coMemRecall, coMemStore, coMemAdd, coMemSub, coSubTotal);

type
  TOvcCalcColors = class(TPersistent)
  {.Z+}
  private
    {property variables}
    FUpdating     : Boolean;
    FOnChange     : TNotifyEvent;

    {internal variables}
    SettingScheme : Boolean;

    {internal methods}
    procedure DoOnChange;

    {property methods}
    function GetColor(const Index : Integer) : TColor;
    procedure SetColor(const Index : Integer; const Value : TColor);
    procedure SetColorScheme(const Value : TOvcCalcColorScheme);
    procedure SetDisplayTextColor(const Value : TColor);

  public
    {property variables}
    FCalcColors   : TOvcCalcColorArray;
    FColorScheme  : TOvcCalcColorScheme;

    procedure Assign(Source : TPersistent);
      override;
    procedure BeginUpdate;
    procedure EndUpdate;

    property OnChange : TNotifyEvent
      read FOnChange write FOnChange;
  {.Z-}

  published
    property ColorScheme : TOvcCalcColorScheme
      read FColorScheme write SetColorScheme;
    property DisabledMemoryButtons : TColor index 0
      read GetColor write SetColor;
    property Display : TColor index 1
      read GetColor write SetColor;
    property DisplayTextColor : TColor
      read FCalcColors[2] write SetDisplayTextColor nodefault;
    property EditButtons : TColor index 3
      read GetColor write SetColor;
    property FunctionButtons : TColor index 4
      read GetColor write SetColor;
    property MemoryButtons : TColor index 5
      read GetColor write SetColor;
    property NumberButtons : TColor index 6
      read GetColor write SetColor;
    property OperatorButtons : TColor index 7
      read GetColor write SetColor;
  end;

type
  {.Z+}
  TOvcCalcPanel = class(TPanel)
  protected
    procedure Click;
      override;
  public
  end;
  {.Z-}

type
  {.Z+}
  TOvcCustomCalculatorEngine = class
  protected {private}
    {property variables}
    FDecimals            : Integer;
    FShowSeparatePercent : Boolean;

    {internal variables}
    cCalculated          : Extended;
    cLastOperation       : TOvcCalculatorOperation;
    cOperationCount      : Integer;
    cMemory              : Extended;     {value stored in memory register}
    cOperands            : array [0..3] of Extended;     {the operand stack}
    cState               : TOvcCalcStates;

  public
    function AddOperand(const Value : Extended; const Button : TOvcCalculatorOperation) : Boolean;
        virtual; abstract;
    function AddOperation(const Button : TOvcCalculatorOperation) : Boolean;
        virtual; abstract;
    procedure ClearAll;
    procedure PushOperand(const Value : Extended);
    function PopOperand : Extended;
    function TopOperand : Extended;

    {public properties}
    property Decimals : Integer
      read FDecimals write FDecimals;
    property LastOperation : TOvcCalculatorOperation
      read cLastOperation write cLastOperation;
    property Memory : Extended
      read cMemory write cMemory;
    property OperationCount : Integer
      read cOperationCount write cOperationCount;
    property ShowSeparatePercent : Boolean
      read FShowSeparatePercent write FShowSeparatePercent;
    property State : TOvcCalcStates
      read cState write cState;
  end;
  {.Z-}

type
  {.Z+}
  TOvcCalcTape = class(TObject)
  protected {private}
    {property variables}
    FMaxPaperCount    : Integer;
    FShowTape         : Boolean;
    FTapeDisplaySpace : Integer;
    FVisible          : Boolean;

    {internal variables}
    taListBox         : TListBox;
    taTapeColor       : TColor;
    taHeight          : Integer;
    taOwner           : TComponent;
    taOperandSize     : Integer;
    taFont            : TFont;
    taMaxTapeCount    : Integer;
    taTapeInitialized : Boolean;
    taWidth           : Integer;

    procedure ValidateListBox;
    function GetFont : TFont;
    procedure SetFont(const Value : TFont);
    function GetHeight : Integer;
    procedure SetHeight(const Value : Integer);
    function GetTape : TStrings;
    procedure SetTape(const Value : TStrings);
    function GetTapeColor : TColor;
    procedure SetTapeColor(const Value : TColor);
    function GetTop : Integer;
    procedure SetTop(const Value : Integer);
    function GetTopIndex : Integer;
    procedure SetTopIndex(const Value : Integer);
    function GetVisible : Boolean;
    procedure SetVisible(const Value : Boolean);
    function GetWidth : Integer;
    procedure SetWidth(const Value : Integer);

  protected
    procedure Add(const Value : string);
    procedure DeleteFirst;
    procedure taOnClick(Sender : TObject);
    procedure taOnDblClick(Sender : TObject);
    procedure taOnDrawItem(Control: TWinControl; Index: Integer;
                           Rect:TRect;State: TOwnerDrawState);
    procedure taTapeFontChange(Sender : TObject);

  public
    constructor Create(const AOwner : TComponent; const AOperandSize : Integer);
    destructor Destroy;
      override;

    procedure InitializeTape;
    procedure SetBounds(const ALeft, ATop, AWidth, AHeight : Integer);
    function GetDisplayedItemCount : Integer;
    procedure AddToTape(const Value : string;
                         const OpString : string);
    procedure AddToTapeLeft(const Value : string);
    procedure ClearTape;
    procedure RefreshDisplays;
    procedure SpaceTape(const Value : char);

    property Font : TFont
      read GetFont write SetFont;
    property Height : Integer
      read GetHeight write SetHeight;
    property MaxPaperCount : Integer
      read FMaxPaperCount write FMaxPaperCount;
    property ShowTape : Boolean
      read FShowTape write FShowTape;
    property Tape : TStrings
      read GetTape write SetTape;
    property TapeColor : TColor
      read GetTapeColor write SetTapeColor;
    property TapeDisplaySpace : Integer
      read FTapeDisplaySpace write FTapeDisplaySpace;
    property Top : Integer
      read GetTop write SetTop;
    property TopIndex : Integer
      read GetTopIndex write SetTopIndex;
    property Visible : Boolean
      read GetVisible write SetVisible;
    property Width : Integer
      read GetWidth write SetWidth;
  end;
  {.Z-}

type
  TOvcCalcButtonPressedEvent =
    procedure(Sender : TObject; Button : TOvcCalculatorButton)
    of object;

  TOvcCalculatorOption = (coShowItemCount, coShowMemoryButtons,
    coShowClearTapeButton, coShowTape, coShowSeparatePercent);
  TOvcCalculatorOptions = set of TOvcCalculatorOption;

  TOvcCustomCalculator = class(TOvcCustomControl)
  {.Z+}
  protected {private}
    {property variables}
    FBorderStyle       : TBorderStyle;
    FColors            : TOvcCalcColors;
    FDisplay           : Extended;     {the calculated value}
    FDisplayStr        : string;       {the string that is displayed}
    FLastOperand       : Extended;
    FOptions           : TOvcCalculatorOptions;
    FTapeSeparatorChar : Char;
{$IFDEF LCL}
    FCtl3D             : Boolean;
{$ENDIF}

    {event variables}
    FOnButtonPressed   : TOvcCalcButtonPressedEvent;

    {internal variables}
    cButtons           : TOvcButtonArray;
    cDecimalEntered    : Boolean;
    cDownButton        : TOvcCalculatorButton;
    cHitTest           : TPoint;       {location of mouse cursor}
    cLastButton        : TOvcCalculatorButton;
    cMargin            : Integer;
    cMinus0            : Boolean;
    cOverBar           : Boolean;
    cPanel             : TOvcCalcPanel;
    cPopup             : Boolean;      {true if being created as a popup}
    cScrBarWidth       : Integer;
    cSizeOffset        : Integer;      { the offset of the sizing line }
    cSizing            : Boolean;      { Are we showing the sizing cursor? }
    cTabCursor         : HCursor;      {design-time tab slecting cursor handle}
    cTape              : TOvcCalcTape;
    cEngine            : TOvcCustomCalculatorEngine;

    {internal methods}
    procedure cAdjustHeight;
    procedure cCalculateLook;
    procedure cClearAll;
    procedure cColorChange(Sender : TObject);
    procedure cDisplayError;
    procedure cDrawCalcButton(const Button : TOvcButtonInfo; const Pressed : Boolean);
    procedure cDrawFocusState;
    procedure cDrawSizeLine;
    procedure cEvaluate(const Button : TOvcCalculatorButton);
    function cFormatString(const Value : Extended) : string;
    function cGetFontWidth : Integer;
    procedure cInvalidateIndicator;
    procedure cRefreshDisplays;
    procedure cSetDisplayString(const Value : string);
    procedure cTapeFontChange(Sender : TObject);

    {property methods}
    function GetDecimals : Integer;
    function GetMaxPaperCount : Integer;
    function GetMemory : Extended;
    function GetOperand : Extended;
    function GetTape : TStrings;
    function GetTapeFont : TFont;
    function GetTapeHeight : Integer;
    function GetVisible : Boolean;
    procedure SetBorderStyle(const Value : TBorderStyle);
    procedure SetDecimals(const Value : Integer);
    procedure SetDisplay(const Value : Extended);
    procedure SetDisplayStr(const Value : string);
    procedure SetMaxPaperCount(const Value : Integer);
    procedure SetMemory(const Value : Extended);
    procedure SetOperand(const Value : Extended);
    procedure SetOptions(const Value : TOvcCalculatorOptions);
    procedure SetTape(const Value : TStrings);
    procedure SetTapeFont(const Value : TFont);
    procedure SetTapeHeight(const Value : Integer);
    procedure SetVisible(const Value : Boolean);

    {VCL control methods}
{$IFNDEF LCL}
    procedure CMCtl3DChanged(var Msg : TMessage);
      message CM_CTL3DCHANGED;
{$ENDIF}
    procedure CMDesignHitTest(var Msg : TCMDesignHitTest);
      message CM_DESIGNHITTEST;
    procedure CMEnter(var Msg : TMessage);
      message CM_ENTER;
    procedure CMExit(var Msg : TMessage);
      message CM_EXIT;
    procedure CMFontChanged(var Msg : TMessage);
      message CM_FONTCHANGED;

    {windows message handlers}
    procedure WMCancelMode(var Msg : TMessage);
      message WM_CANCELMODE;
    procedure WMEraseBkgnd(var Msg : TWMEraseBkgnd);
      message WM_ERASEBKGND;
    procedure WMGetText(var Msg : TWMGetText);
      message WM_GETTEXT;
    procedure WMGetTextLength(var Msg : TWMGetTextLength);
      message WM_GETTEXTLENGTH;
    procedure WMKeyDown(var Msg : TWMKeyDown);
      message WM_KEYDOWN;
    procedure WMKillFocus(var Msg : TWMKillFocus);
      message WM_KILLFOCUS;
    procedure WMLButtonDown(var Msg : TWMMouse);
      message WM_LBUTTONDOWN;
    procedure WMLButtonUp(var Msg : TWMMouse);
      message WM_LBUTTONUP;
    procedure WMMouseMove(var Msg : TWMMouse);
      message WM_MOUSEMOVE;
    procedure WMNCHitTest(var Msg : TWMNCHitTest);
      message WM_NCHITTEST;
    procedure WMSetText(var Msg : TWMSetText);
      message WM_SETTEXT;
    procedure WMSetCursor(var Msg : TWMSetCursor);
      message WM_SETCURSOR;

  protected
    procedure CreateParams(var Params : TCreateParams);
      override;
    procedure CreateWnd;
      override;
    procedure KeyDown(var Key : Word; Shift : TShiftState);
      override;
    procedure MouseDown(Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
      override;
    procedure MouseUp(Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
      override;
    procedure Paint;
      override;
  {.Z-}

    {protected properties}
    property BorderStyle : TBorderStyle
      read FBorderStyle write SetBorderStyle;
    property Colors : TOvcCalcColors
      read FColors write FColors;
    property Decimals : Integer
      read GetDecimals write SetDecimals;
    property MaxPaperCount : Integer
      read GetMaxPaperCount write SetMaxPaperCount;
    property Options : TOvcCalculatorOptions
      read  FOptions write SetOptions;
    property TapeFont : TFont
      read GetTapeFont write SetTapeFont;
    property TapeHeight : Integer
      read GetTapeHeight write SetTapeHeight;
    property TapeSeparatorChar : Char
      read FTapeSeparatorChar write FTapeSeparatorChar;
    property Visible : Boolean
      read GetVisible write SetVisible;
{$IFDEF LCL}
    property Ctl3D : Boolean read FCtl3D write FCtl3D;
{$ENDIF}

    {protected events}
    property OnButtonPressed : TOvcCalcButtonPressedEvent
      read FOnButtonPressed  write FOnButtonPressed;

  public
  {.Z+}
    constructor Create(AOwner : TComponent);
      override;
    constructor CreateEx(AOwner : TComponent; AsPopup : Boolean);
      virtual;
    destructor Destroy;
      override;
    procedure KeyPress(var Key : Char);
      override;
    procedure PushOperand(const Value : Extended);
    procedure SetBounds(ALeft, ATop, AWidth, AHeight : Integer);
      override;
  {.Z-}

    procedure CopyToClipboard;
    procedure PasteFromClipboard;
    procedure PressButton(Button : TOvcCalculatorButton);

    {public properties}
    property LastOperand : Extended
      read FLastOperand write FLastOperand;
    property Memory : Extended
      read GetMemory write SetMemory;
    property Operand : Extended
      read GetOperand write SetOperand;
    property DisplayStr : string
      read FDisplayStr write SetDisplayStr;
    property DisplayValue : Extended
      read FDisplay write SetDisplay;
    property Tape : TStrings
      read GetTape write SetTape;
  end;

  TOvcCalculator = class(TOvcCustomCalculator)
  published
    {properties}
    {$IFDEF VERSION4}
    property Anchors;
    property Constraints;
    property DragKind;
    {$ENDIF}
    property About;
    property Align;
    property BorderStyle default bsNone;
    property Ctl3D;
    property Font;  {must be prior to "Colors"}
    property TapeFont; {must be prior to "Colors"}
    property Colors;
    property Cursor;
    property Decimals;
    property DragCursor;
    property DragMode;
    property Enabled;
    property LabelInfo;
    property MaxPaperCount default 9999;
    property TapeHeight ; {Must be Prior to Options}
    property Options default [coShowMemoryButtons, coShowItemCount];
{$IFNDEF LCL}
    property ParentCtl3D;
{$ENDIF}
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property TabOrder;
    property TabStop default True;
    property TapeSeparatorChar default '_';
    property Visible default True;

    {events}
    property OnButtonPressed;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnStartDrag;
  end;


implementation

const
  calcDefMinSize = 30;


{*** TOvcCalcColors ***}

procedure TOvcCalcColors.Assign(Source : TPersistent);
begin
  if Source is TOvcCalcColors then begin
    FCalcColors := TOvcCalcColors(Source).FCalcColors;
    FColorScheme := TOvcCalcColors(Source).FColorScheme;
    FOnChange := TOvcCalcColors(Source).FOnChange;
  end else
    inherited Assign(Source);
end;

procedure TOvcCalcColors.BeginUpdate;
begin
  FUpdating := True;
end;

procedure TOvcCalcColors.EndUpdate;
begin
  FUpdating := False;
  DoOnChange;
end;

procedure TOvcCalcColors.DoOnChange;
begin
  if not FUpdating and Assigned(FOnChange) then
    FOnChange(Self);

  if not SettingScheme then
    FColorScheme := cscalcCustom;
end;

function TOvcCalcColors.GetColor(const Index : Integer) : TColor;
begin
  Result := FCalcColors[Index];
end;

procedure TOvcCalcColors.SetColor(const Index : Integer; const Value : TColor);
begin
  if Value <> FCalcColors[Index] then begin
    FCalcColors[Index] := Value;
    DoOnChange;
  end;
end;

procedure TOvcCalcColors.SetColorScheme(const Value : TOvcCalcColorScheme);
begin
  if Value <> FColorScheme then begin
    SettingScheme := True;
    try
      FColorScheme := Value;
      if Value <> cscalcCustom then begin
        FCalcColors := CalcScheme[Value];
        DoOnChange;
      end;
    finally
      SettingScheme := False;
    end;
  end;
end;

procedure TOvcCalcColors.SetDisplayTextColor(const Value : TColor);
begin
  if Value <> FCalcColors[2] then begin
    FCalcColors[2] := Value;
    DoOnChange;
  end;
end;


{*** TOvcCalcTape ***}

constructor TOvcCalcTape.Create(const AOwner : TComponent; const AOperandSize : Integer);
begin
  inherited Create;
  taOwner           := AOwner;
  FVisible          := False;
  taOperandSize     := AOperandSize;
  taFont            := TFont.Create;
  taFont.Name       := 'Courier New';
  taFont.Size       := 10;
  taFont.Style      := [];
end;

destructor TOvcCalcTape.Destroy;
begin
  taFont.Free;
  taFont := nil;

  inherited Destroy;
end;

procedure TOvcCalcTape.ValidateListBox;
begin
  if not Assigned(taListBox) then begin
    taListBox := TListBox.Create(taOwner);
    with taListBox do begin
      OnClick         := taOnClick;
      OnDblClick      := taOnDblClick;
      OnDrawItem      := taOnDrawItem;
      Style           := lbOwnerDrawFixed;
      Parent          := taOwner as TWinControl;
      ParentFont      := False;
{$IFNDEF LCL}
      ParentCtl3D     := True;
{$ENDIF}      
      BorderStyle     := bsSingle;
      Color           := taTapeColor;
      Visible         := FVisible;
      Width           := taWidth;
      Height          := taHeight;
      Font.Assign(taFont);
      Font.OnChange   := taFont.OnChange;
      taFont.OnChange := taTapeFontChange;
      TabStop         := False;
    end;
    taTapeInitialized := False;
  end;
  InitializeTape;
end;

procedure TOvcCalcTape.Add(const Value : string);
begin
  ValidateListBox;
  taListBox.Items.Add(Value);
end;

procedure TOvcCalcTape.DeleteFirst;
begin
  ValidateListBox;
  with taListBox, Items do
    if Strings[0] = '' then
      taListBox.Items.Delete(0)
    else
      Inc(taMaxTapeCount);
end;

procedure TOvcCalcTape.SetFont(const Value : TFont);
begin
  taFont.Assign(Value);
  taFont.OnChange(Self);
end;

function TOvcCalcTape.GetFont : TFont;
begin
  Result := taFont;
end;

procedure TOvcCalcTape.SetHeight(const Value : Integer);
begin
  taHeight := Value;
  if Visible then begin
    ValidateListBox;
    taListBox.Height := Value;
  end;
end;

function TOvcCalcTape.GetHeight : Integer;
begin
  if Visible then begin
    ValidateListBox;
    Result := taListBox.Height;
  end else
    Result := taHeight;
end;

function TOvcCalcTape.GetTape : TStrings;
begin
  ValidateListBox;
  Result := taListBox.Items;
end;

procedure TOvcCalcTape.SetTape(const Value : TStrings);
begin
  ValidateListBox;
  taListBox.Items.Assign(Value);
end;

function TOvcCalcTape.GetTapeColor : TColor;
begin
  if Visible then begin
    ValidateListBox;
    Result := taListBox.Color;
  end else
    Result := taTapeColor;
end;

procedure TOvcCalcTape.SetTapeColor(const Value : TColor);
begin
  taTapeColor := Value;
  if Visible then begin
    ValidateListBox;
    taListBox.Color := Value;
  end;
end;

procedure TOvcCalcTape.SetTop(const Value : Integer);
begin
  ValidateListBox;
  taListBox.Top := Value;
end;

function TOvcCalcTape.GetTop : Integer;
begin
  ValidateListBox;
  Result := taListBox.Top;
end;

function TOvcCalcTape.GetVisible : Boolean;
begin
  Result := FVisible;
end;

procedure TOvcCalcTape.SetVisible(const Value : Boolean);
begin
  FVisible := Value;
  if Assigned(taListBox) then begin
    if not Value and taListBox.Visible then begin
      if csDesigning in taListBox.Owner.ComponentState then begin
        {$IFDEF VERSION4}
        taListBox.Visible := Value;
        taListBox.Height := 0;
        {$ELSE}
        taListBox.Free;
        taListBox := nil;
        {$ENDIF}
      end else
        taListBox.Visible := Value;
    end else if Value and not taListBox.Visible then begin
      taListBox.Visible := Value;
      {$IFDEF VERSION4}
      taListBox.Height := taHeight;
      {$ENDIF}
    end;
  end else if Value then begin
    ValidateListBox;
    taListBox.Visible := Value;
  end;
end;

procedure TOvcCalcTape.SetWidth(const Value : Integer);
begin
  taWidth := Value;
  if Visible then begin
    ValidateListBox;
    taListBox.Width := Value;
  end;
end;

function TOvcCalcTape.GetWidth : Integer;
begin
  if Visible then begin
    ValidateListBox;
    Result := taListBox.Width;
  end else
    Result := taWidth;
end;

procedure TOvcCalcTape.SetTopIndex(const Value : Integer);
begin
  ValidateListBox;
  taListBox.TopIndex := Value;
end;

function TOvcCalcTape.GetTopIndex : Integer;
begin
  ValidateListBox;
  Result := taListBox.TopIndex;
end;

procedure TOvcCalcTape.SetBounds(const ALeft, ATop, AWidth, AHeight : Integer);
begin
  ValidateListBox;
  taListBox.SetBounds(ALeft, ATop, AWidth, AHeight);
end;

procedure TOvcCalcTape.InitializeTape;
begin
  if not Assigned(taListBox) then
    Exit;
  if csDesigning in taListBox.Owner.ComponentState then
    if not taListBox.HandleAllocated then
      Exit;
  if taTapeInitialized then
    Exit;
  ClearTape;
  taTapeInitialized := True;
end;

procedure TOvcCalcTape.taOnClick(Sender : TObject);
begin
  ValidateListBox;
  (taListBox.Owner as TOvcCustomCalculator).SetFocus;
end;

procedure TOvcCalcTape.taOnDblClick(Sender : TObject);
var
  Str : string;
begin
  ValidateListBox;
  if (taListBox.Items.Count < 1) then
    Exit;
  Str := taListBox.Items.Strings[taListBox.ItemIndex];
  try
    if (Str[1] = '0') and
       (Str[2] <> '.') then
      Exit;
    if taListBox.Items.Strings[taListBox.ItemIndex] <> '' then begin
      (taListBox.Owner as TOvcCustomCalculator).DisplayValue :=
        StrToFloat(Copy(Str,1,  Length(Str) - taOperandSize));
      (taListBox.Owner as TOvcCustomCalculator).LastOperand :=
        StrToFloat(Copy(Str,1,  Length(Str) - taOperandSize));
      (taListBox.Owner as TOvcCustomCalculator).Operand :=
        StrToFloat(Copy(Str,1,  Length(Str) - taOperandSize));
      (taListBox.Owner as TOvcCustomCalculator).DisplayStr :=
        Copy(Str,1,             Length(Str) - taOperandSize);
      (taListBox.Owner as TOvcCustomCalculator).SetFocus;
    end;
  except
  end;
end;

procedure TOvcCalcTape.taOnDrawItem(Control: TWinControl; Index: Integer;
                                   Rect:TRect;State: TOwnerDrawState);
var
  SaveColor : TColor;
  SaveBack : TColor;
  Str : String;
  I, FirstUsedIndex : Integer;
begin
  FirstUsedIndex := 0;
  if Index > FMaxPaperCount then
    with (Control as TListBox) do begin
      for I := 0 to Index do begin
        if Items[I] <> '' then begin
          FirstUsedIndex := I;
          Break;
        end;
      end;
    end;

  Str := (Control as TListBox).Items[Index];
  with (Control as TListBox).Canvas do begin        { draw on control canvas, not on the form }
    FillRect(Rect);        { clear the rectangle }

    SaveColor := (Control as TListBox).Canvas.Font.Color;
    try
      SaveBack := (Control as TListBox).Canvas.Brush.Color;
      try
        if (Trim(Str) <> '') then begin
          if (Trim(Str)[1] = '-') then
            (Control as TListBox).Canvas.Font.Color := clRed;
          if FTapeDisplaySpace > Length(Str) then
            Str := Str + StringOfChar(' ', FTapeDisplaySpace - Length(Str));
          TextOut(Rect.Left, Rect.Top, Copy(Str, 1, Length(Str) - 1));
          if Index - FirstUsedIndex >= FMaxPaperCount then
            (Control as TListBox).Canvas.Brush.Color := clRed;
          TextOut(PenPos.X, PenPos.Y, Copy(Str, Length(Str), 1));
        end;
      finally
        (Control as TListBox).Canvas.Brush.Color := SaveBack;
      end;
    finally
      (Control as TListBox).Canvas.Font.Color := SaveColor;
    end;
  end;
end;

procedure TOvcCalcTape.taTapeFontChange(Sender : TObject);
begin
  if Visible then begin
    taListBox.Font.Assign(taFont);
    taListBox.Font.OnChange(Sender);
  end;
end;

function TOvcCalcTape.GetDisplayedItemCount : Integer;
var
  DC         : hDC;
  SaveFont   : hFont;
  Size       : TSize;
begin
  if not Assigned(taListBox) then begin
    Result := 0;
    Exit;
  end;

  DC := GetDC(0);
  SaveFont := SelectObject(DC, taListBox.Font.Handle);
  GetTextExtentPoint(DC, ' 0123456789', 11, Size);
  Result := taListBox.ClientHeight div Size.cy;
  if Result < 3 then
    Result := 3;
  SelectObject(DC, SaveFont);
  ReleaseDC(0, DC);
end;

procedure TOvcCalcTape.AddToTape(const Value : string; const OpString : string);
  {-adds an operand to the tape display}
var
  TapeString : string;
  DSpace     : Integer;
begin
  DSpace := FTapeDisplaySpace - Length(Value);
  TapeString := StringOfChar(' ', DSpace - taOperandSize);
  TapeString := TapeString + Value + ' ' + OpString;
  Add(TapeString);
  DeleteFirst;
  TopIndex := taMaxTapeCount - GetDisplayedItemCount + 2;
end;

{adds an operand to the tape display}
procedure TOvcCalcTape.AddToTapeLeft(const Value : string);
var
  TapeString : string;
  DSpace : Integer;
begin
  DSpace := FTapeDisplaySpace - Length(Value);
  TapeString := StringOfChar(' ', DSpace);
  TapeString := Value + TapeString;
  Add(Value);
  DeleteFirst;
  TopIndex := taMaxTapeCount - GetDisplayedItemCount + 2;
end;

procedure TOvcCalcTape.ClearTape;
var
  I : Integer;
begin
  if not Assigned(taListBox) then
    Exit;
  if csDesigning in taListBox.Owner.ComponentState then
    if not taListBox.HandleAllocated then
      Exit;
  taMaxTapeCount := 30; {set starting line count}

  taListBox.Items.Clear;
  for I := 0 to taMaxTapeCount - 1 do
    taListBox.Items.Add('');
  taListBox.TopIndex := taMaxTapeCount - GetDisplayedItemCount + 2;
end;

procedure TOvcCalcTape.RefreshDisplays;
var
  I, Diff : Integer;
  S : string;

  function AllSame(const Str : string) : Boolean;
  var
    I : Integer;
  begin
    Result := True;
    for I := 2 to Length(Str) do begin
      if Str[1] <> Str[I] then
        Exit;
    end;
  end;

begin
  if not Assigned(taListBox) then
    Exit;
  if not taListBox.HandleAllocated then
    Exit;

  if FShowTape then begin
    for I := 0 to taMaxTapeCount - 1 do begin
      S := taListBox.Items.Strings[I];
      if S <> '' then begin
        Diff := FTapeDisplaySpace - Length(S);
        if S[1] = ' ' then begin
          if Diff >= 0 then
            S := StringOfChar(' ', Diff) + S
          else if AllSame(copy(S, 1, -Diff)) then
            S := copy(S,-Diff + 1, Length(S));
        end else begin
          if AllSame(S) and (not (S[1] in ['0'..'9'])) then
            if Diff >= 0 then
              S := S + StringOfChar(S[1], Diff)
            else
              S := copy(S, 1, Length(S)-Diff + 1)
          else if (Diff >= 0) and not ((S[1] <> '0') or (S[2] <> '.')) then
            S :=  StringOfChar(' ', Diff) + S;
        end;
        taListBox.Items.Strings[I] := S;
      end;
    end;
    taListBox.TopIndex := taMaxTapeCount - GetDisplayedItemCount + 2;
  end;
end;

procedure TOvcCalcTape.SpaceTape(const Value : char);
var
  TapeString : string;
begin
  TapeString := StringOfChar(Value, FTapeDisplaySpace);
  Add(TapeString);
  DeleteFirst;
  TopIndex := taMaxTapeCount - GetDisplayedItemCount + 2;
end;


{*** TOvcCustomCalculatorEngine ***}

procedure TOvcCustomCalculatorEngine.ClearAll;
var
  I : Integer;
begin
  for I := 0 to 3 do
    cOperands[I] := 0;
  cLastOperation := coNone;
  cOperationCount := 0;
  cState := [csValid, csClear];
end;

procedure TOvcCustomCalculatorEngine.PushOperand(const Value : Extended);
var
  I : Integer;
begin
  for I := 2 downto 0 do
    cOperands[I+1] := cOperands[I];
  cOperands[0] := Value;
end;

function TOvcCustomCalculatorEngine.PopOperand : Extended;
var
  I : Integer;
begin
  Result := cOperands[0];
  for I := 0 to 2 do
    cOperands[I] := cOperands[I+1];
  cOperands[3] := 0;
end;

function TOvcCustomCalculatorEngine.TopOperand : Extended;
begin
  Result := cOperands[0];
end;


{*** TOvcBasicCalculatorEngine ***}
type
  TOvcBasicCalculatorEngine = class(TOvcCustomCalculatorEngine)
  protected {private}
    {internal methods}
    procedure cEvaluate(const Operation : TOvcCalculatorOperation);
  public
    function AddOperand(const Value : Extended; const Button : TOvcCalculatorOperation) : Boolean;
        override;
    function AddOperation(const Button : TOvcCalculatorOperation) : Boolean;
        override;
  end;

function TOvcBasicCalculatorEngine.AddOperand(
  const Value : Extended;
  const Button : TOvcCalculatorOperation) : Boolean;
var
  I : Integer;
begin
  Result := False;
  if Button <> coNone then begin
    if csValid in cState then begin
      Result := True;
      for I := 2 downto 0 do
        cOperands[I+1] := cOperands[I];
      cOperands[0] := Value;
    end;
  end;
end;

procedure TOvcBasicCalculatorEngine.cEvaluate(const Operation : TOvcCalculatorOperation);
begin
  if csValid in cState then begin
    {evaluate the expression}
    case Operation of
      coAdd        : begin
                       cOperands[1] := cOperands[1] + cOperands[0];
                       PopOperand;
                     end;
      coSub        : begin
                       cOperands[1] := cOperands[1] - cOperands[0];
                       PopOperand;
                     end;
      coMul        : begin
                       cOperands[1] := cOperands[1] * cOperands[0];
                       PopOperand;
                     end;
      coDiv        : begin
                       cOperands[1] := cOperands[1] / cOperands[0];
                       PopOperand;
                     end;
      coEqual      : ;
      coNone       : ;
      coPercent    : begin
                       if cLastOperation in [coAdd, coSub] then
                         cOperands[0] := (cOperands[0] / 100) * cOperands[1]  {do markup/down}
                       else
                         cOperands[0] := cOperands[0] / 100; {as a percentage}
                       cState := [csValid, csClear];
                     end;
      coMemStore   : begin
                       cMemory := cOperands[0];
                       Include(cState, csClear);
                     end;
      coMemRecall  : begin
                       cOperands[0] := cMemory;
                       cState := [csValid, csClear];
                     end;
      coMemClear   : begin
                       cMemory := 0;
                     end;
      coMemAdd,
      coMemSub     : begin
                       try
                         if Operation = coMemAdd then
                           cMemory := cMemory + cOperands[0]
                         else
                           cMemory := cMemory - cOperands[0];
                       except
                         cMemory := 0;
                       end;
                       Include(cState, csClear);
                     end;
      coInvert     : begin
                       cOperands[0] := 1 / cOperands[0];
                     end;
      coSqrt       : begin
                       cOperands[0] := Sqrt(cOperands[0]);
                     end;
    end;
  end;
end;

function TOvcBasicCalculatorEngine.AddOperation(const Button : TOvcCalculatorOperation) : Boolean;
begin
  Result := False;
  if csValid in cState then begin
    {evaluate the expression}
    case Button of
      coAdd        : begin
                       cEvaluate(cLastOperation);
                       cState := [csValid, csClear];
                       if cLastOperation in [coAdd, coSub] then
                         Inc(cOperationCount)
                       else
                         cOperationCount := 1;
                       cLastOperation := Button;
                       Result := True;
                     end;
      coSub        : begin
                       cEvaluate(cLastOperation);
                       cState := [csValid, csClear];
                       if cLastOperation in [coAdd, coSub] then
                         Inc(cOperationCount)
                       else
                         cOperationCount := 1;
                       cLastOperation := Button;
                       Result := True;
                     end;
      coMul        : begin
                       cEvaluate(cLastOperation);
                       cState := [csValid, csClear];
                       if cLastOperation = Button then
                         cOperationCount := cOperationCount + 1
                       else
                         cOperationCount := 1;
                       cLastOperation := Button;
                       Result := True;
                     end;
      coDiv        : begin
                       cEvaluate(cLastOperation);
                       cState := [csValid, csClear];
                       if cLastOperation = Button then
                         cOperationCount := cOperationCount + 1
                       else
                         cOperationCount := 1;
                       cLastOperation := Button;
                       Result := True;
                     end;
      coEqual      : begin
                       Include(cState, csClear);
                       if cLastOperation <> coNone then begin
                         cEvaluate(cLastOperation);
                         cState := [csClear, csValid];
                         if cLastOperation = coEqual then
                           cLastOperation := coNone
                         else
                           cLastOperation := Button;
                       end;
                       Result := True;
                     end;
      coNone       : Result := True;
      coPercent    : begin
                       cEvaluate(Button);
                       if not ShowSeparatePercent then begin
                         cEvaluate(cLastOperation);
                         cState := [csValid, csClear];
                         if cLastOperation = Button then
                           cOperationCount := cOperationCount + 1
                         else
                           cOperationCount := 0;
                         cLastOperation := coEqual;
                         Result := True;
                       end else begin
                         if cLastOperation = Button then
                           cOperationCount := cOperationCount + 1
                         else
                           cOperationCount := 0;
                         Result := True;
                       end;
                     end;
      coMemStore   : begin
                       cEvaluate(Button);
                     end;
      coMemRecall  : begin
                       cEvaluate(Button);
                       Result := True;
                     end;
      coMemClear   : begin
                       cEvaluate(Button);
                     end;
      coMemAdd,
      coMemSub     : begin
                       cEvaluate(Button);
                     end;
      coInvert     : begin
                       cEvaluate(Button);
                       Result := True;
                     end;
      coSqrt       : begin
                       cEvaluate(Button);
                       Result := True;
                     end;
    end;
  end;
end;


{*** TOvcCalcPanel ***}

procedure TOvcCalcPanel.Click;
begin
  (Owner as TOvcCustomCalculator).SetFocus;
end;


{*** TOvcCustomCalculator ***}

procedure TOvcCustomCalculator.cAdjustHeight;
var
  DC         : hDC;
  SaveFont   : hFont;
  I          : Integer;
  SysMetrics : TTextMetric;
  Metrics    : TTextMetric;
begin
  DC := GetDC(0);
  GetTextMetrics(DC, SysMetrics);
  SaveFont := SelectObject(DC, Font.Handle);
  GetTextMetrics(DC, Metrics);
  SelectObject(DC, SaveFont);
  ReleaseDC(0, DC);
  if NewStyleControls then begin
    if Ctl3D then
      I := 8
    else
      I := 6;
    I := GetSystemMetrics(SM_CYBORDER) * I;
  end else begin
    I := SysMetrics.tmHeight;
    if I > Metrics.tmHeight then
      I := Metrics.tmHeight;
    I := I div 4 + GetSystemMetrics(SM_CYBORDER) * 4;
  end;
  cPanel.Height := Metrics.tmHeight + I;
end;

procedure TOvcCustomCalculator.cCalculateLook;
var
  CW  : Integer;  {client width}
  BW  : Integer;  {button width}
  BH  : Integer;  {button height}
  LBW : Integer;  {large button width}
  M1  : Integer;  {margin between buttons}
  M2  : Integer;  {left and right edge margins}
  M3  : Integer;  {margin between panel and frst row of buttons}
  M4  : Integer;  {margin between memory buttons and other buttons}
  TM  : Integer;  {area where the panel is placed}
  X   : Integer;
  Y   : Integer;
  PW  : Integer;  {panel width}
  B   : TOvcCalculatorButton;
begin
  if not HandleAllocated then
    Exit;

  {set panel height based on font}
  cAdjustHeight;

  for B := Low(cButtons) to High(cButtons) do
    cButtons[B].Visible := True;

  CW := ClientWidth;

  if Width <= 200 then begin
    M1 := 2;
    M2 := 4;
  end else begin
    M1 := 4;
    M2 := 6;
  end;
  {save left/right/top/bottom margin value}
  cMargin := M2;

  M4 := M2;
  if coShowMemoryButtons in FOptions then begin
    BW := (CW - 3*M2 - 4*M1) div 6;
    M4 := CW - 2*M2 - 6*BW - 4*M1;
  end else begin
    BW := (CW - 2*M2 - 4*M1) div 5;
    if (CW - 2*M2 - 4*M1) div 6 >= 4 then
      Inc(M2, 2)
    else if (CW - 2*M2 - 4*M1) div 6 >= 2 then
      Inc(M2, 1);
  end;

  {button height, using an estimate for TM}
  TM := M2 + M2 + cPanel.Height;

  if coShowTape in FOptions then
    TM := TM + M2 + cTape.Height;

  BH := (ClientHeight - TM - M2 - 4*M1) div 5;

  {calculate actual area below panel}
  M3 := ClientHeight - M2 - cPanel.Height - 5*BH - 4*M1 - M2;

  {calculate actual height of area above buttons}
  TM := M2 + M3 + cPanel.Height;

  {large button width}
  if coShowClearTapeButton in FOptions then
    LBW := (5*BW + 3*M1 - 2*M1) div 4
  else
    LBW := (4*BW + 3*M1 - 2*M1) div 3;

  {calculate the width of the edit window}
  cMargin := M2;
  if coShowMemoryButtons in FOptions then
    PW := 6*BW + M4 + 4*M1
  else
    PW := 5*BW + 4*M1;

  if coShowTape in FOptions then
    PW := PW - cScrBarWidth;


  {position tape display and edit panel}
  if coShowTape in FOptions then begin
    cTape.Visible := True;
    cTape.SetBounds(cMargin, cMargin, PW + cScrBarWidth, cTape.Height);
    cPanel.SetBounds(cMargin + 2, cTape.Height + M2 +
                     cMargin, PW, cPanel.Height);
  end else begin
    cTape.Visible := False;
    cPanel.SetBounds(cMargin, cMargin, PW, cPanel.Height);
  end;

  {calculate # of characters required to fill display space}
  {"FontWidth div 2" makes sure there is no cut off charaters}
  if coShowTape in FOptions then
    cTape.TapeDisplaySpace := (cTape.Width - cScrBarWidth - (cGetFontWidth div 2))
                      div cGetFontWidth
  else
    cTape.TapeDisplaySpace := (cPanel.Width - (cGetFontWidth div 2)) div cGetFontWidth;

  cTape.InitializeTape;

  {redraw the edit panel and Tape}
  cRefreshDisplays;

  {memory column}
  if coShowMemoryButtons in FOptions then begin
    X := M2;
    Y := TM;
    cButtons[cbMemClear].Position := Rect(X, Y, X+BW, Y+BH);
    cButtons[cbMemClear].Caption := GetOrphStr(SCCalcMC);

    Y := TM + BH + M1;
    cButtons[cbMemRecall].Position := Rect(X, Y, X+BW, Y+BH);
    cButtons[cbMemRecall].Caption := GetOrphStr(SCCalcMR);

    Y := TM + 2*BH + 2*M1;
    cButtons[cbMemStore].Position := Rect(X, Y, X+BW, Y+BH);
    cButtons[cbMemStore].Caption := GetOrphStr(SCCalcMS);

    Y := TM + 3*BH + 3*M1;
    cButtons[cbMemAdd].Position := Rect(X, Y, X+BW, Y+BH);
    cButtons[cbMemAdd].Caption := GetOrphStr(SCCalcMPlus);

    Y := TM + 4*BH + 4*M1;
    cButtons[cbMemSub].Position := Rect(X, Y, X+BW, Y+BH);
    cButtons[cbMemSub].Caption := GetOrphStr(SCCalcMMinus);
  end else
    for B := cbMemClear to cbMemSub do
      cButtons[B].Visible := False;

  {row 1 - large buttons}
  Y := TM;
  if coShowMemoryButtons in FOptions then
    if coShowClearTapeButton in FOptions then
      X := BW + M2 + M4
    else
      X := 2*BW + M4 + M2 + M1
  else
    if coShowClearTapeButton in FOptions then
      X := M2
    else
      X := BW + M2 + M1;

  cButtons[cbTape].Position := Rect(X, Y, X+LBW, Y+BH);
  cButtons[cbTape].Caption := GetOrphStr(SCCalcCT);

  if coShowClearTapeButton in FOptions then begin
    cButtons[cbTape].Visible := True;
    Inc(X, LBW+M1);
    if ((BW+M1)*5 - (LBW+M1)*4) >= 3 then
      Inc(X, 1);
  end else begin
    cButtons[cbTape].Visible := False;
  end;

  cButtons[cbBack].Position := Rect(X, Y, X+LBW, Y+BH);
  cButtons[cbBack].Caption := GetOrphStr(SCCalcBack);

  Inc(X, LBW+M1);
  if coShowClearTapeButton in FOptions then begin
    if ((BW+M1)*5 - (LBW+M1)*4) >= 2 then
      Inc(X, 1);
  end else begin
    if ((BW+M1)*4 - (LBW+M1)*3) >= 2 then
      Inc(X, 1);
  end;
  cButtons[cbClearEntry].Position := Rect(X, Y, X+LBW, Y+BH);
  cButtons[cbClearEntry].Caption := GetOrphStr(SCCalcCE);

  Inc(X, LBW+M1);
  if coShowClearTapeButton in FOptions then begin
    if ((BW+M1)*5 - (LBW+M1)*4) >= 1 then
      Inc(X, 1);
  end else begin
    if ((BW+M1)*4 - (LBW+M1)*3) >= 1 then
      Inc(X, 1);
  end;
  cButtons[cbClear].Position := Rect(X, Y, X+LBW, Y+BH);
  cButtons[cbClear].Caption := GetOrphStr(SCCalcC);

  {row 2}
  Y := TM + BH + M1;
  if coShowMemoryButtons in FOptions then
    X := M2 + BW + M4
  else
    X := M2;
  cButtons[cb7].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb7].Caption := '7';

  Inc(X, BW+M1);
  cButtons[cb8].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb8].Caption := '8';

  Inc(X, BW+M1);
  cButtons[cb9].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb9].Caption := '9';

  Inc(X, BW+M1);
  cButtons[cbDiv].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbDiv].Caption := '/';

  Inc(X, BW+M1);
  cButtons[cbSqrt].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbSqrt].Caption := GetOrphStr(SCCalcSqrt);

  {row 3}
  Y := TM + 2*BH + 2*M1;
  if coShowMemoryButtons in FOptions then
    X := M2 + BW + M4
  else
    X := M2;
  cButtons[cb4].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb4].Caption := '4';

  Inc(X, BW+M1);
  cButtons[cb5].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb5].Caption := '5';

  Inc(X, BW+M1);
  cButtons[cb6].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb6].Caption := '6';

  Inc(X, BW+M1);
  cButtons[cbMul].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbMul].Caption := '*';

  Inc(X, BW+M1);
  cButtons[cbPercent].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbPercent].Caption := '%';

  {row 4}
  Y := TM + 3*BH + 3*M1;
  if coShowMemoryButtons in FOptions then
    X := M2 + BW + M4
  else
    X := M2;
  cButtons[cb1].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb1].Caption := '1';

  Inc(X, BW+M1);
  cButtons[cb2].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb2].Caption := '2';

  Inc(X, BW+M1);
  cButtons[cb3].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb3].Caption := '3';

  Inc(X, BW+M1);
  cButtons[cbSub].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbSub].Caption := '-';

  Inc(X, BW+M1);
  cButtons[cbInvert].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbInvert].Caption := '1/x';

  {row 5}
  Y := TM + 4*BH + 4*M1;
  if coShowMemoryButtons in FOptions then
    X := M2 + BW + M4
  else
    X := M2;
  cButtons[cb0].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cb0].Caption := '0';

  Inc(X, BW+M1);
  cButtons[cbChangeSign].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbChangeSign].Caption := '+/-';

  Inc(X, BW+M1);
  cButtons[cbDecimal].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbDecimal].Caption := DecimalSeparator;

  Inc(X, BW+M1);
  cButtons[cbAdd].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbAdd].Caption := '+';

  Inc(X, BW+M1);
  cButtons[cbEqual].Position := Rect(X, Y, X+BW, Y+BH);
  cButtons[cbEqual].Caption := '=';
end;

procedure TOvcCustomCalculator.cClearAll;
begin
  if not HandleAllocated then
    Exit;

  cEngine.ClearAll;
  FLastOperand := 0;
  DisplayValue := 0;
  FDisplayStr := '0';
  cMinus0 := False;
  cTape.InitializeTape;
  cPanel.Caption := StringOfChar(' ',
                       (cTape.TapeDisplaySpace
                        - Length('0')
                        - Length(CalcDisplayString[cbNone]))
                       ) + '0' + '  ';
end;

procedure TOvcCustomCalculator.cColorChange(Sender : TObject);
begin
  {update panel background color}
  if Assigned(cPanel) then begin
    cPanel.Color := FColors.Display;
    cPanel.Font.Color := FColors.DisplayTextColor;
    {update the main font color}
    if not (csLoading in ComponentState) and (Font <> nil) then
      Font.Color := FColors.DisplayTextColor;
  end;

  if Assigned(cTape) then begin
    cTape.TapeColor := FColors.Display;
  end;

  Invalidate;
end;

procedure TOvcCustomCalculator.cDisplayError;
begin
  cSetDisplayString('****** ');
  cEngine.State := [csLocked]; {user will have to clear this}
  MessageBeep(0);
end;

procedure TOvcCustomCalculator.cDrawCalcButton(const Button : TOvcButtonInfo; const Pressed : Boolean);
var
  TR  : TRect;
  Buf : array[0..255] of Char;
begin
  if Button.Visible then begin
    TR := DrawButtonFace(Canvas, Button.Position, 1, bsNew, False, Pressed, False);
    StrPLCopy(Buf, Button.Caption, 255);
    DrawText(Canvas.Handle, Buf, StrLen(Buf), TR,
             DT_CENTER or DT_VCENTER or DT_SINGLELINE);

    if Focused and (Button.Caption = '=') then
      cDrawFocusState;
  end;
end;

procedure TOvcCustomCalculator.cDrawFocusState;
var
  R : TRect;
begin
  R := cButtons[cbEqual].Position;
  InflateRect(R, -3, -3);
{$IFNDEF LCL}
  Canvas.DrawFocusRect(R);
{$ENDIF}
end;

procedure TOvcCustomCalculator.cDrawSizeLine;
var
  OldPen : TPen;
begin
  if (cSizing) then
    with Canvas do begin
      OldPen := TPen.Create;
      try
        OldPen.Assign(Pen);
        Pen.Color := clBlack;
        Pen.Mode := pmXor;
        Pen.Style := psDot;
        Pen.Width := 1;
        MoveTo(0, cSizeOffset);
        LineTo(ClientWidth, cSizeOffset);
      finally
        Canvas.Pen := OldPen;
        OldPen.Free;
      end;
    end;
end;

procedure TOvcCustomCalculator.cEvaluate(const Button : TOvcCalculatorButton);
begin
  if csValid in cEngine.State then begin
    try
      {evaluate the expression}
      if cEngine.AddOperation(CalcButtontoOperation[Button]) then begin
        DisplayValue := cEngine.TopOperand;
        if Button in [cbAdd, cbSub, cbMul, cbDiv, cbEqual, cbPercent, cbNone] then
          if (Button in [cbAdd, cbSub, cbMul, cbDiv]) and (cLastButton = Button) then
            cTape.AddToTape(cFormatString(LastOperand), CalcDisplayString[Button])
          else
            cTape.AddToTape(FDisplayStr, CalcDisplayString[Button]);
        if (Button = cbEqual) and (cEngine.LastOperation = coEqual) then begin
          if coShowItemCount in FOptions then
            cTape.AddToTapeLeft(Format('%3.3d',[cEngine.OperationCount+1]));
          cTape.AddToTape(cFormatString(DisplayValue), CalcDisplayString[cbSubTotal]);
          cTape.SpaceTape(TapeSeparatorChar);
        end;
        FDisplayStr := cFormatString(DisplayValue);
      end;
    except
      cDisplayError;
    end;
  end;
end;

function TOvcCustomCalculator.cFormatString(const Value : Extended) : string;
begin
  if cEngine.Decimals = 0 then
    Result := Format('%g',[Value])
  else if cEngine.Decimals < 0 then
    Result := Format('%.*f',[-cEngine.Decimals, Value])
  else
    Result := Format('%.*f',[cEngine.Decimals, Value]);
end;

function TOvcCustomCalculator.cGetFontWidth : Integer;
var
  DC         : hDC;
  SaveFont   : hFont;
  Size       : TSize;
begin
  if not assigned(cPanel) then begin
    Result := 8; {Return something resonable }
    Exit;
  end;
  DC := GetDC(0);
  SaveFont := SelectObject(DC, cPanel.Font.Handle);
  GetTextExtentPoint(DC, ' 0123456789', 11, Size);
  Result := Round(Size.cx/11);
  SelectObject(DC, SaveFont);
  ReleaseDC(0, DC);
end;

procedure TOvcCustomCalculator.cInvalidateIndicator;
begin
  InvalidateRect(Handle, @cButtons[cbMemRecall].Position, False);
  InvalidateRect(Handle, @cButtons[cbMemClear].Position, False);
end;

procedure TOvcCustomCalculator.cRefreshDisplays;
begin
  if not cPanel.HandleAllocated then
    Exit;

  cTape.RefreshDisplays;
{  DisplayValue := DisplayValue; }
end;

procedure TOvcCustomCalculator.cSetDisplayString(const Value : string);
var
  DSpace : Integer;
begin
  try
    if cPanel.HandleAllocated then begin
      DSpace := cTape.TapeDisplaySpace
              - Length(Value)
              - Length(CalcDisplayString[cbNone]);
      cPanel.Caption := StringOfChar(' ', DSpace) + Value + '  ';
    end;
  except
    cDisplayError;
  end;
end;

procedure TOvcCustomCalculator.cTapeFontChange(Sender : TObject);
begin
  cPanel.Font := TapeFont;
end;

procedure TOvcCustomCalculator.SetBorderStyle(const Value : TBorderStyle);
begin
  if Value <> FBorderStyle then begin
    FBorderStyle := Value;
{$IFNDEF LCL}
    RecreateWnd;
{$ELSE}
    RecreateWnd(Self);
{$ENDIF}
  end;
end;

function TOvcCustomCalculator.GetDecimals : Integer;
begin
  Result := cEngine.Decimals;
end;

procedure TOvcCustomCalculator.SetDecimals(const Value : Integer);
begin
  if Value <> cEngine.Decimals then begin
    cEngine.Decimals := Value;
    ccalculateLook;
    Invalidate;
  end;
end;

function TOvcCustomCalculator.GetMemory : Extended;
begin
  Result := cEngine.Memory;
end;

procedure TOvcCustomCalculator.SetMemory(const Value : Extended);
begin
  if Value <> cEngine.Memory then begin
    cEngine.Memory := Value;
    cCalculateLook;
    Invalidate;
  end;
end;

procedure TOvcCustomCalculator.SetMaxPaperCount(const Value : Integer);
begin
  if Value <> cTape.MaxPaperCount then begin
    cTape.MaxPaperCount := Value;
    Invalidate;
  end;
end;

function TOvcCustomCalculator.GetMaxPaperCount : Integer;
begin
  Result := cTape.MaxPaperCount;
end;

procedure TOvcCustomCalculator.SetOptions(const Value : TOvcCalculatorOptions);
begin
  if Value <> FOptions then begin
    FOptions := Value;


    cTape.ShowTape := coShowTape in FOptions;
    cTape.Visible := coShowTape in FOptions;
    cEngine.ShowSeparatePercent := coShowSeparatePercent in FOptions;

    cCalculateLook;
    Invalidate;
  end;
end;

function TOvcCustomCalculator.GetTape : TStrings;
begin
  Result := cTape.Tape;
end;

procedure TOvcCustomCalculator.SetTape(const Value : TStrings);
begin
  cTape.Tape := Value;
end;

function TOvcCustomCalculator.GetTapeFont : TFont;
begin
  Result := cTape.Font;
end;

procedure TOvcCustomCalculator.SetTapeFont(const Value : TFont);
begin
  cTape.Font := Value;
end;

function TOvcCustomCalculator.GetTapeHeight : Integer;
begin
  Result := cTape.Height;
end;

procedure TOvcCustomCalculator.SetTapeHeight(const Value : Integer);
begin
  cTape.Height := Value;
  cCalculateLook;
  Invalidate;
end;

function TOvcCustomCalculator.GetVisible : Boolean;
begin
  Result := inherited Visible;
end;

procedure TOvcCustomCalculator.SetVisible(const Value : Boolean);
begin
  inherited Visible := Value;

  cTape.Visible := cTape.ShowTape;
end;

procedure TOvcCustomCalculator.SetDisplay(const Value : Extended);
var
  ValueString : string;
begin
  try
    FDisplay := Value;
    if cPanel.HandleAllocated then begin
      ValueString := cFormatString(Value);
      cSetDisplayString(ValueString);
    end;
  except
    cDisplayError;
  end;
end;

procedure TOvcCustomCalculator.SetDisplayStr(const Value : string);
begin
  FDisplayStr := Value;
  while (Length(FDisplayStr) > 0) and (FDisplayStr[1] = ' ') do
    FDisplayStr := Copy(FDisplayStr, 2, Length(FDisplayStr) - 1);
end;

function TOvcCustomCalculator.GetOperand : Extended;
begin
  Result := cEngine.TopOperand;
end;

procedure TOvcCustomCalculator.SetOperand(const Value : Extended);
begin
  if Value = cEngine.TopOperand then
    Exit;
  cEngine.PushOperand(Value);
end;

{$IFNDEF LCL}
procedure TOvcCustomCalculator.CMCtl3DChanged(var Msg : TMessage);
begin
  inherited;

  if (csLoading in ComponentState) or not HandleAllocated then
    Exit;

  if NewStyleControls and (FBorderStyle = bsSingle) then
{$IFNDEF LCL}
    RecreateWnd;
{$ENDIF}

  Invalidate;
end;
{$ENDIF}

procedure TOvcCustomCalculator.CMDesignHitTest(var Msg : TCMDesignHitTest);
begin
  Msg.Result := LongInt(cOverBar);
end;

procedure TOvcCustomCalculator.CMEnter(var Msg : TMessage);
var
  R : TRect;
begin
  inherited;

  {invalidate the "=" button to ensure that the focus rect is painted}
  R := cButtons[cbEqual].Position;
  InvalidateRect(Handle, @R, False);
end;

procedure TOvcCustomCalculator.CMExit(var Msg : TMessage);
var
  R : TRect;
begin
  inherited;

  {invalidate the "=" button to ensure that the focus rect is painted}
  R := cButtons[cbEqual].Position;
  InvalidateRect(Handle, @R, False);
end;

procedure TOvcCustomCalculator.CMFontChanged(var Msg : TMessage);
begin
  inherited;

  if not (csLoading in ComponentState) and Assigned(cPanel) then begin
    cPanel.Color := FColors.Display;
    cPanel.Font.Color := FColors.DisplayTextColor;
    FColors.FCalcColors[2] := Font.Color;
  end;

  cCalculateLook;
  Invalidate;
end;

procedure TOvcCustomCalculator.WMEraseBkgnd(var Msg : TWMEraseBkgnd);
begin
  Msg.Result := 1;   {don't erase background, just say we did}
end;

procedure TOvcCustomCalculator.WMGetText(var Msg : TWMGetText);
begin
  if not cPanel.HandleAllocated then
    Exit;

  Msg.Result := SendMessage(cPanel.Handle, WM_GETTEXT,
    TMessage(Msg).wParam, TMessage(Msg).lParam);
end;

procedure TOvcCustomCalculator.WMGetTextLength(var Msg : TWMGetTextLength);
begin
  if not cPanel.HandleAllocated then
    Exit;

  Msg.Result := SendMessage(cPanel.Handle, WM_GETTEXTLENGTH,
    TMessage(Msg).wParam, TMessage(Msg).lParam);
end;

procedure TOvcCustomCalculator.WMKeyDown(var Msg : TWMKeyDown);
begin
  if Msg.CharCode = Ord('M') then begin
    if ({$IFNDEF LCL} GetAsyncKeyState(VK_CONTROL) {$ELSE} GetKeyState(VK_CONTROL) {$ENDIF} and $8000) <> 0 then begin
      PressButton(cbMemStore);
    end;
  end else if Msg.CharCode = VK_RETURN then
    PressButton(cbEqual);

  inherited;
end;

procedure TOvcCustomCalculator.WMSetText(var Msg : TWMSetText);
var
  I : Integer;
  C : AnsiChar;
begin
  cClearAll;
  for I := 0 to Pred(StrLen(Msg.Text)) do begin
    C := Msg.Text[I];
    KeyPress(C);
  end;
  Msg.Result := 1{true};
end;

procedure TOvcCustomCalculator.WMNCHitTest(var Msg : TWMNCHitTest);
begin
  inherited;

  cHitTest.X := Msg.Pos.X;
  cHitTest.Y := Msg.Pos.Y;
end;

procedure TOvcCustomCalculator.WMSetCursor(var Msg : TWMSetCursor);
var
  vHitTest : TPoint;

  procedure SetNewCursor(C : HCursor);
  begin
{$IFNDEF LCL}
    SetCursor(C);
{$ELSE}    
    LclIntf.SetCursor(C);  {Don't call control's SetCursor!}
{$ENDIF}    
    Msg.Result := Ord(True);
  end;

begin
  if not (coShowTape in FOptions) then
    Exit;

  if csDesigning in ComponentState then begin
    if (Msg.HitTest = HTCLIENT) then begin
      cOverBar := False;
      vHitTest := ScreenToClient(cHitTest);
      if vHitTest.Y > cTape.Top + cTape.Height then
        if vHitTest.Y < cTape.Top + cTape.Height+4 then
          cOverBar := True;
    end;

    {set appropriate cursor}
    if cOverBar then
      SetNewCursor(cTabCursor)
    else
      inherited;
  end else
    inherited;
end;

procedure TOvcCustomCalculator.WMCancelMode(var Msg : TMessage);
begin
  inherited;

  cSizing := False;
end;

procedure TOvcCustomCalculator.WMKillFocus(var Msg : TWMKillFocus);
begin
  inherited;

  Invalidate;
end;

procedure TOvcCustomCalculator.WMLButtonDown(var Msg : TWMMouse);
begin
  inherited;

  {are we currently showing a sizing cursor? if so the user wants to
   resize a column/row}
  if (cOverBar) then begin
    cSizeOffset := Msg.YPos;
    cSizing := True;
    cDrawSizeLine;
  end;
end;

procedure TOvcCustomCalculator.WMLButtonUp(var Msg : TWMMouse);
var
  Form : TForm;
begin
  inherited;

  if (cSizing) then begin
    cDrawSizeLine;
    cSizing := False;
    cTape.Height := cSizeOffset - 8;
    cCalculateLook;

    Refresh;
    if (csDesigning in ComponentState) then begin
      Form := TForm(GetParentForm(Self));
      if (Form <> nil) and (Form.Designer <> nil) then
        Form.Designer.Modified;
    end;
  end;
end;

procedure TOvcCustomCalculator.WMMouseMove(var Msg : TWMMouse);
begin
  inherited;

  if (cSizing) then begin
    cDrawSizeLine;
    if Msg.YPos >= calcDefMinSize + cTape.Top then
      if Msg.YPos <= Height - calcDefMinSize then
        cSizeOffset := Msg.YPos + 2
      else
        cSizeOffset := Height - calcDefMinSize
    else
      cSizeOffset := calcDefMinSize + cTape.Top;
    cDrawSizeLine;
  end;
end;

procedure TOvcCustomCalculator.CopyToClipboard;
begin
  Clipboard.AsText := Text;
end;

constructor TOvcCustomCalculator.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);

  if cPopup then
    ControlStyle           := ControlStyle + [csClickEvents, csFramed] - [csCaptureMouse]
  else
    ControlStyle           := ControlStyle + [csClickEvents, csFramed, csCaptureMouse];

  Color                    := clBtnFace;
  TabStop                  := True;
  Width                    := 200;
  Font.Name                := 'MS Sans Serif';
  Font.Size                := 8;
  Font.Style               := [];
  cDecimalEntered          := False;
  cSizing                  := False;
  cScrBarWidth             := 18;

  {create edit control}
  cPanel                   := TOvcCalcPanel.Create(Self);
  cPanel.Parent            := Self;
  cPanel.ParentFont        := False;
  cPanel.Font.Name         := 'Courier New';
  cPanel.Font.Size         := 10;
  cPanel.Font.Style        := [];
{$IFNDEF LCL}
  cPanel.ParentCtl3D       := True;
{$ENDIF}
  cPanel.Alignment         := taLeftJustify;
  cPanel.BevelOuter        := bvLowered;
  cPanel.BorderStyle       := bsNone;
  cPanel.Color             := clWindow;
  cPanel.BevelWidth        := 2;
  cPanel.Caption           := '0 ';

  {set property defaults}
  FBorderStyle             := bsNone;
  Height                   := 140;
  FTapeSeparatorChar       := '_';
  FOptions                 := [coShowMemoryButtons, coShowItemCount];
  FColors                  := TOvcCalcColors.Create;
  FColors.OnChange         := cColorChange;

  {assign default color scheme}
  FColors.FCalcColors      := CalcScheme[cscalcWindows];

  {create tape}
  cTape := TOvcCalcTape.Create(Self, Length(CalcDisplayString[cbNone]));
  cTape.ShowTape           := False;
  cTape.TapeColor          := clWindow;
  cTape.MaxPaperCount      := 9999;
  TapeHeight               := Height div 3;
  TapeFont.OnChange        := cTapeFontChange;
  TapeFont.Name            := 'Courier New';
  TapeFont.Size            := 10;
  TapeFont.Style           := [];
  cTape.Visible            := cTape.ShowTape;

  cEngine := TOvcBasicCalculatorEngine.Create;
  cEngine.Decimals         := 2;
  cEngine.ShowSeparatePercent := False;

  if csDesigning in ComponentState then
    cTabCursor := Screen.Cursors[crVSplit];
end;

constructor TOvcCustomCalculator.CreateEx(AOwner : TComponent; AsPopup : Boolean);
begin
  cPopup := AsPopup;
  Create(AOwner);
end;

procedure TOvcCustomCalculator.CreateParams(var Params : TCreateParams);
const
  BorderStyles : array[TBorderStyle] of LongInt = (0, WS_BORDER);
begin
  inherited CreateParams(Params);

  with Params do begin
    Style := LongInt(Style) or BorderStyles[FBorderStyle];
    if cPopup then begin
      Style := WS_POPUP or WS_BORDER;
      WindowClass.Style := WindowClass.Style or CS_SAVEBITS;
    end;
  end;

  if NewStyleControls and (Ctl3D or cPopup) and (FBorderStyle = bsSingle) then begin
    if not cPopup then
      Params.Style := Params.Style and not WS_BORDER;
    Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
  end;
end;

procedure TOvcCustomCalculator.CreateWnd;
begin
  inherited CreateWnd;

  cCalculateLook;
  cClearAll;

  cPanel.Color := FColors.Display;
end;

destructor TOvcCustomCalculator.Destroy;
begin
  cTape.Free;
  cTape := nil;

  cEngine.Free;
  cEngine := nil;

  FColors.Free;
  FColors := nil;

  cTabCursor := 0;

  inherited Destroy;
end;

procedure TOvcCustomCalculator.KeyDown(var Key : Word; Shift : TShiftState);
begin
  inherited KeyDown(Key, Shift);

  case Key of
    VK_DELETE : if Shift = [] then
                  PressButton(cbClearEntry);
    VK_F9     : if Shift = [] then
                  PressButton(cbChangeSign);
  end;
end;

procedure TOvcCustomCalculator.KeyPress(var Key : Char);
begin
  inherited KeyPress(Key);

  case Key of
    '0' : PressButton(cb0);
    '1' : PressButton(cb1);
    '2' : PressButton(cb2);
    '3' : PressButton(cb3);
    '4' : PressButton(cb4);
    '5' : PressButton(cb5);
    '6' : PressButton(cb6);
    '7' : PressButton(cb7);
    '8' : PressButton(cb8);
    '9' : PressButton(cb9);

    '+' : PressButton(cbAdd);
    '-' : PressButton(cbSub);
    '*' : PressButton(cbMul);
    '/' : PressButton(cbDiv);

    '.' : PressButton(cbDecimal);
    '=' : PressButton(cbEqual);
    'r' : PressButton(cbInvert);
    '%' : PressButton(cbPercent);
    '@' : PressButton(cbSqrt);

    ^L  : PressButton(cbMemClear);  {^L}
    ^R  : PressButton(cbMemRecall); {^R}
    ^P  : PressButton(cbMemAdd);    {^P}
    ^S  : PressButton(cbMemSub);    {^S}
    ^T  : PressButton(cbTape);      {^T}

    ^C  : CopyToClipboard;          {^C}{copy}
    ^V  : PasteFromClipboard;       {^V}{paste}

    #8  : PressButton(cbBack);      {backspace}
    #27 : PressButton(cbClear);     {esc}
  else
    if Key = DecimalSeparator then
      PressButton(cbDecimal);
  end;
end;

procedure TOvcCustomCalculator.MouseDown(Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
var
  B : TOvcCalculatorButton;
begin
  SetFocus;

  if Button = mbLeft then begin
    cDownButton := cbNone;
    for B := Low(cButtons) to High(cButtons) do
      if cButtons[B].Visible and PtInRect(cButtons[B].Position, Point(X,Y)) then begin
        if (B in [cbMemClear, cbMemRecall]) and (cEngine.Memory = 0) then
          Exit;
        cDownButton := B;
        InvalidateRect(Handle, @cButtons[cDownButton].Position, False);
        Break;
      end;
  end;

  inherited MouseDown(Button, Shift, X, Y);
end;

procedure TOvcCustomCalculator.MouseUp(Button : TMouseButton; Shift : TShiftState; X, Y : Integer);
begin
  if cDownButton = cbNone then
    Exit;

  InvalidateRect(Handle, @cButtons[cDownButton].Position, False);

  {if still over the button...}
  if PtInRect(cButtons[cDownButton].Position, Point(X,Y)) then
    PressButton(cDownButton);

  cDownButton := cbNone;

  inherited MouseUp(Button, Shift, X, Y);
end;

procedure TOvcCustomCalculator.PasteFromClipboard;
var
  I : Integer;
  C : AnsiChar;
  S : string;
begin
  S := Clipboard.AsText;
  if S > '' then begin
    cClearAll;
    for I := 1 to Length(S) do begin
      C := S[I];
      if (C in ['0'..'9', DecimalSeparator, '.', '+', '-', '*', '/', '=', '%']) then
        KeyPress(C);
    end;
  end;
end;

procedure TOvcCustomCalculator.PressButton(Button : TOvcCalculatorButton);

  procedure Initialize;
  begin
    if (cLastButton <> cbClear) and (Button = cbClear) then begin
      cClearAll;
      cTape.SpaceTape(TapeSeparatorChar);
    end;

    if (csLocked in cEngine.State) then begin
      MessageBeep(0);
      Exit;
    end;

    {this logic is here to make cbEqual clear all the second time}
    if (cLastButton in [cbEqual, cbClear, cbNone]) and (Button = cbEqual) then begin
      Button := cbClear;
      cClearAll;
    end;

    if (cLastButton = cbPercent) and (Button in [cbAdd, cbSub, cbMul, cbDiv, cbEqual]) then
      cEvaluate(Button)
    else if ((((cLastButton in [cbEqual, cbMemRecall]) and
               (Button in [cbAdd, cbSub, cbMul, cbDiv])))) and
             cEngine.AddOperand(StrToFloat(FDisplayStr), CalcButtontoOperation[Button]) then begin
      cEvaluate(Button);
    end else if (Button = cbMemStore) then begin
      if (cLastButton <> cbEqual) then
        cEvaluate(Button);
      SetMemory(DisplayValue);
    end else if cEngine.AddOperand(LastOperand, CalcButtontoOperation[Button]) then begin
      cEvaluate(Button);

    {remove special operations from stack}
      if Button in [cbInvert, cbSqrt, cbEqual] then
        LastOperand := cEngine.PopOperand;
    end;
  end;

  procedure NumberButton;
  var
    D    : Extended;
    DP   : Integer;
  begin
    begin
      if cEngine.LastOperation = coEqual then begin
        {clear pending operations if last command was =}
        cClearAll;
      end;

      if csClear in cEngine.State then begin
        if (Decimals < 0) then begin
          FDisplayStr := '0.' + StringOfChar('0', -Decimals);
        end else begin
          FDisplayStr := '';
          cDecimalEntered := False;
        end;
      end;

      FDisplayStr := FDisplayStr + cButtons[Button].Caption[1];
      if cMinus0 then begin
        FDisplayStr := '-' + FDisplayStr;
        cMinus0 := False;
      end;

      if (Decimals < 0) and not cDecimalEntered then begin
        if Pos(DecimalSeparator, FDisplayStr) > 0 then begin
          DP := Pos(DecimalSeparator, FDisplayStr);
          if FDisplayStr[1] = '0' then
            FDisplayStr := Copy(FDisplayStr,2,DP-2) +
                           Copy(FDisplayStr,DP+1,1) +
                           DecimalSeparator +
                           Copy(FDisplayStr,DP+2,Length(FDisplayStr) - DP)
          else
            FDisplayStr := Copy(FDisplayStr,1,DP-1) +
                           Copy(FDisplayStr,DP+1,1) +
                           DecimalSeparator +
                           Copy(FDisplayStr,DP+2,Length(FDisplayStr) - DP);
        end;
      end;
      D := StrToFloat(FDisplayStr);
      LastOperand := D;
      if (D <> 0) or
         (Pos(DecimalSeparator, FDisplayStr) > 0) then begin
        DisplayValue := D;
        cSetDisplayString(FDisplayStr);
        cEngine.State := [csValid];
      end else begin
        FDisplayStr := '0';
        DisplayValue := D;
        cEngine.State := [csValid, csClear];
      end;
    end;
  end;

  procedure DecimalButton;
  var
    D    : Extended;
  begin
    {check if the decimal was first character entered after a command}
    if csClear in cEngine.State then begin
      FDisplayStr := '0' + DecimalSeparator;
      cSetDisplayString(FDisplayStr);
      cDecimalEntered := True;
      cEngine.State := [csValid];
    end;

    {check if there is already a decimal separator in the string}
    if Pos(DecimalSeparator, FDisplayStr) = 0 then begin
      FDisplayStr := FDisplayStr + DecimalSeparator;
      if (pos(DecimalSeparator, FDisplayStr) = 1) then
        FDisplayStr := '0' + FDisplayStr;
      D := StrToFloat(FDisplayStr);
      cSetDisplayString(FDisplayStr);
      LastOperand := D;
      cEngine.State := [csValid];
      cDecimalEntered := True;
    end;
  end;

  procedure BackButton;
  var
    D    : Extended;
    DP   : Integer;
    SaveSign : string;
  begin
    if FDisplayStr = '' then exit;
    D := StrToFloat(FDisplayStr);
    if D <> 0 then begin
      if Length(FDisplayStr) > 1 then begin
        if (Decimals < 0) and not cDecimalEntered then begin
          if Pos(DecimalSeparator, FDisplayStr) > 0 then begin
            if FDisplayStr[1] = '-' then begin
              SaveSign :='-';
              FDisplayStr := Copy(FDisplayStr,2,Length(FDisplayStr)-1);
            end else begin
              SaveSign :='';
            end;
            DP := Pos(DecimalSeparator, FDisplayStr);
            FDisplayStr := '0' + Copy(FDisplayStr,1,DP-2) +
                           DecimalSeparator +
                           Copy(FDisplayStr,DP-1,1) +
                           Copy(FDisplayStr,DP+1,Length(FDisplayStr) - DP);
            if (FDisplayStr[1] = '0') and (FDisplayStr[2] <> '.') then
              FDisplayStr := Copy(FDisplayStr,2,Length(FDisplayStr)-1);
            FDisplayStr := SaveSign + FDisplayStr;
          end;
        end;
        FDisplayStr := Copy(FDisplayStr, 1, Length(FDisplayStr)-1);
        LastOperand := StrToFloat(FDisplayStr);
        cSetDisplayString(FDisplayStr);
      end else begin
        LastOperand := 0;
        cMinus0 := False;
        DisplayValue := LastOperand;
        cEngine.State := [csValid, csClear];
      end;
    end;
  end;

  procedure ClearEntryButton;
  begin
    begin
      FDisplayStr := '';
      LastOperand := 0;
      cMinus0 := False;
      DisplayValue := LastOperand;
    end;
  end;

  procedure ChangeSignButton;
  begin
    if Length(FDisplayStr) > 0 then begin
      if FDisplayStr[1] <> '-' then begin
        FDisplayStr := '-' + FDisplayStr;
        LastOperand := StrToFloat(FDisplayStr);
        cSetDisplayString(FDisplayStr);
      end else begin
        FDisplayStr := Copy(FDisplayStr, 2, Length(FDisplayStr)-1);
        LastOperand := StrToFloat(FDisplayStr);
        cSetDisplayString(FDisplayStr);
      end;
      DisplayValue := LastOperand;
    end else begin
      LastOperand := 0;
      cMinus0 := not cMinus0;
      DisplayValue := LastOperand;
      if cMinus0 then
        FDisplayStr := '-';
      cEngine.State := [csValid, csClear];
    end;
  end;

  procedure ClearTapeButton;
  var
    I : Integer;
  begin
    with Tape do begin
      for I := 0 to Count - 1 do begin
        Strings[I] := '';
      end;
      cTape.RefreshDisplays;
    end;
  end;

begin
  if not HandleAllocated then
    Exit;

  {simulate a button down if needed}
  if cDownButton = cbNone then begin
    cDownButton := Button;
    InvalidateRect(Handle, @cButtons[cDownButton].Position, False);
    Update;
  end;

  try try
    Initialize;
    case Button of
      cb0..cb9     : NumberButton;
      cbDecimal    : DecimalButton;
      cbBack       : BackButton;
      cbClearEntry : ClearEntryButton;
      cbMemStore,
      cbMemClear,
      cbMemAdd,
      cbMemSub     : cInvalidateIndicator;
      cbChangeSign : ChangeSignButton;
      cbTape       : ClearTapeButton;
      cbSqrt,
      cbInvert     : {};
    end;
  except
    cDisplayError;
  end;
  finally
    {simulate a button up, if the mouse button is up or we aren't focused}
    if not Focused or ({$IFNDEF LCL} GetAsyncKeyState(GetLeftButton) {$ELSE} GetKeyState(GetLeftButton) {$ENDIF} and $8000 = 0) then begin
      InvalidateRect(Handle, @cButtons[cDownButton].Position, False);
      cDownButton := cbNone;
      Update;
    end;
  end;

  cLastButton := Button;
  if Assigned(FOnButtonPressed) then
    FOnButtonPressed(Self, Button);
end;

procedure TOvcCustomCalculator.PushOperand(const Value : Extended);
begin
  cEngine.PushOperand(Value);
  LastOperand := Value;
  DisplayValue := Value;
end;

procedure TOvcCustomCalculator.Paint;
var
  B  : TOvcCalculatorButton;
begin
  Canvas.Font := Font;
  Canvas.Brush.Color := clBtnFace;
  Canvas.FillRect(ClientRect);

  if Ctl3D then begin
    cPanel.BevelOuter := bvLowered;
    cPanel.BorderStyle := bsNone;
  end else begin
    cPanel.BevelOuter := bvNone;
  cPanel.BorderStyle := bsSingle;
  end;

  {draw buttons}
  for B := Low(cButtons) to High(cButtons) do begin
    if (B in [cbMemClear, cbMemRecall, cbMemStore, cbMemAdd, cbMemSub]) then begin
      if (B in [cbMemClear, cbMemRecall]) and (cEngine.Memory = 0) then
        Canvas.Font.Color := FColors.DisabledMemoryButtons
      else
        Canvas.Font.Color := FColors.MemoryButtons;
    end else if (B in [cbBack, cbClearEntry, cbClear, cbTape]) then
      Canvas.Font.Color := FColors.EditButtons
    else if (B in [cbAdd, cbSub, cbMul, cbDiv, cbEqual]) then
      Canvas.Font.Color := FColors.OperatorButtons
    else if (B in [cb0..cb9, cbDecimal]) then
      Canvas.Font.Color := FColors.NumberButtons
    else if (B in [cbInvert, cbChangeSign, cbPercent, cbSqrt]) then
      Canvas.Font.Color := FColors.FunctionButtons;

    cDrawCalcButton(cButtons[B], (B = cDownButton));
  end;
end;

procedure TOvcCustomCalculator.SetBounds(ALeft, ATop, AWidth, AHeight : Integer);
begin
  if Height <> AHeight then
    if coShowTape in FOptions then
      if Top <> ATop then begin
        if TapeHeight + (AHeight - Height) > calcDefMinSize then begin
          TapeHeight := TapeHeight + (AHeight - Height);
        end else begin
          TapeHeight := calcDefMinSize;
        end
      end;

  inherited Setbounds(ALeft, ATop, AWidth, AHeight);

  cCalculateLook;
end;


end.
