unit NumericEdit;

interface

uses
  Classes,
  SysUtils,
  LResources,
  Forms,
  Controls,
  Graphics,
  Dialogs,
  StdCtrls;

const
  MaxUnsignedIntLength = 10; // Based on MaxInt
  MaxSignedIntLength = 11;
  MaxUnsignedFloatLength = 17; // Based on float plus decimal comma plus E
  MaxSignedFloatLength = 18;

type

  // TNumericEdit

  TNumericEdit = class(TCustomEdit)
  private
    FAllowFloatingPoint: boolean;
    FAllowNegative: boolean;
    FFloatFormat: string;
    procedure SetAllowFloatingPoint(const AValue: boolean);
    function GetIntegerValue: integer;
    procedure SetAllowNegative(const AValue: boolean);
    procedure SetIntegerValue(const AValue: integer);
    function GetFloatingPointValue: double;
    procedure SetFloatingPointValue(const AValue: double);
  protected
    procedure KeyPress(var Key: char); override;
  public
    constructor Create(AOwner: TComponent); override;
    property IntegerValue: integer Read GetIntegerValue Write SetIntegerValue;
    property FloatingPointValue: double Read GetFloatingPointValue Write SetFloatingPointValue;
  published
    property AllowFloatingPoint: boolean Read FAllowFloatingPoint Write SetAllowFloatingPoint default False;
    property AllowNegative: boolean Read FAllowNegative Write SetAllowNegative default False;
    property Action;
    property Align;
    property Alignment default taRightJustify;
    property Anchors;
    property AutoSize;
    property AutoSelect;
    property BidiMode;
    property BorderStyle;
    property BorderSpacing;
    property CharCase;
    property Color;
    property Constraints;
    property DragCursor;
    property DragKind;
    property DragMode;
    property EchoMode;
    property Enabled;
    property FloatFormat: string Read FFloatFormat Write FFloatFormat;
    property Font;
    property HideSelection;
    property MaxLength default MaxUnsignedIntLength;
    property ParentBidiMode;
    property OnChange;
    property OnChangeBounds;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEditingDone;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDrag;
    property OnUTF8KeyPress;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PasswordChar;
    property PopupMenu;
    property ReadOnly;
    property ShowHint;
    property TabStop;
    property TabOrder;
    property Text;
    property Visible;
  end;

procedure Register;

implementation

const
  MaxLengths: array[false..true, false..true] of integer =
    ((MaxUnsignedIntLength, MaxSignedIntLength), (MaxUnsignedFloatLength, MaxSignedFloatLength));

procedure Register;
begin
  RegisterComponents('Standard', [TNumericEdit]);
end;

// TNumericEdit

procedure TNumericEdit.SetAllowFloatingPoint(const AValue: boolean);
begin
  if (FAllowFloatingPoint <> AValue) then
  begin
    FAllowFloatingPoint := AValue;
    if FAllowFloatingPoint then
      Text := FormatFloat(FFloatFormat, StrToFloat(Text))
    else
      Text := IntToStr(Round(StrToFloat(Text)));
    MaxLength := MaxLengths[AllowFloatingPoint, AllowNegative];
  end;
end;

procedure TNumericEdit.SetAllowNegative(const AValue: boolean);
begin
  if (FAllowNegative <>AValue ) then begin
    FAllowFloatingPoint:=AValue;
    if (not FAllowNegative) and (StrToFloat(Text) < 0) then begin
      if AllowFloatingPoint then
        Text := FormatFloat(FFloatFormat, 0)
      else
        Text := '0';
    end;
    MaxLength := MaxLengths[AllowFloatingPoint, AllowNegative];
  end;
end;

function TNumericEdit.GetIntegerValue: integer;
begin
  if AllowFloatingPoint then
    Result := Round(StrToFloat(Text))
  else
    Result := StrToInt(Text);
end;

procedure TNumericEdit.SetIntegerValue(const AValue: integer);
begin
  if AllowFloatingPoint then
    Text := FormatFloat(FFloatFormat, AValue)
  else
    Text := IntToStr(AValue);
end;

function TNumericEdit.GetFloatingPointValue: double;
begin
  Result := StrToFloat(Text);
end;

procedure TNumericEdit.SetFloatingPointValue(const AValue: double);
begin
  if AllowFloatingPoint then
    Text := FormatFloat(FFloatFormat, AValue)
  else
    Text := IntToStr(Round(AValue));
end;

procedure TNumericEdit.KeyPress(var Key: char);
begin

  // Treat both ',' and '.' as the local decimal separators
  if ((Key = ',') or (Key = '.')) then
    Key := DecimalSeparator;

  // Discard all keys that are meaningless
  if not ((Key in [#8, #9, #13, '0'..'9']) or ((Key = '-') and AllowNegative) or ((Key = DecimalSeparator) and AllowFloatingPoint)) then
    Key := #0
  // Only allow one decimal separator or minus per input field
  else if ((Key = DecimalSeparator) or (Key = '-')) and (Pos(Key, Text) > 0) then
    Key := #0
  // Only accept minus at the very first position of the text
  else if (Key = '-') and (SelStart <> 0) then
    Key := #0
  // Do not allow anything to be entered aheard of the "minus"
  // Use Pos() instead of Text[1] to avoid segfaults with empty strings
  else if ((SelStart = 0) and (Pos('-', Text) > 0)) then // ?
    Key := #0;

  if (Key <> #0) then
    inherited KeyPress(Key); // Required?

end;

constructor TNumericEdit.Create(AOwner: TComponent);
begin

  inherited Create(AOwner);

  // Set value defaults
  Text := '0';
  Alignment := taRightJustify;
  AllowFloatingPoint := False;
  AllowNegative := False;
  FloatFormat := '###0.000';
  MaxLength := MaxUnsignedIntLength;

end;

end.

