{ TCustomUpDown

 *****************************************************************************
 *                                                                           *
 *  This file is part of the Lazarus Component Library (LCL)                 *
 *                                                                           *
 *  See the file COPYING.LCL, included in this distribution,                 *
 *  for details about the copyright.                                         *
 *                                                                           *
 *  This program is distributed in the hope that it will be useful,          *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                     *
 *                                                                           *
 *****************************************************************************

Problems -
  - Doesn't draw Themed Arrows/doesn't match system colors
  - Associate Key down and Tabbing(VK_Up, VK_Down)
}
Type
  TUpDownButton = Class(TSpeedButton)
  Private
    FUpDown : TCustomUpDown;
    FButtonType : TUDBtnType;
  Protected
    Procedure Click; Override;
    Procedure ButtonMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    Procedure ButtonMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  Public
    constructor CreateWithParams(UpDown : TCustomUpDown;
      ButtonType : TUDBtnType);

    Procedure Paint; Override;
  end;

Procedure TUpDownButton.ButtonMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  With FUpDown do begin
    BTimerProc := @Self.Click;
    BTimerBounds := Bounds(Self.ClientOrigin.X, Self.ClientOrigin.Y,
      Self.Width,Self.Height);
    If Not Assigned(BTimer) then
      BTimer := TTimer.Create(FUpDown);
    With BTimer do begin
      Enabled := False;
      Interval := 100;
      OnTimer := @BTimerExec;
      Enabled := True;
    end;
  end;
end;

Procedure TUpDownButton.ButtonMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  With FUpDown do
    If Assigned(BTimer) then begin
      BTimer.Free;
      BTimer := nil;
      BTimerBounds := Rect(0,0,0,0);
      BTimerProc := nil;
    end;
end;

Procedure TUpDownButton.Click;
begin
  With FUpDown do begin
    If not CanChange then
      exit;
    Case FButtonType of
      btPrev :
        begin
          If Position - Increment >= Min then
            Position := Position - Increment
          else
            If Wrap then
              Position := Max + (Position - Increment - Min) + 1
          else
            Position := Min;
        end;
      btNext :
        begin
          If Position + Increment <= Max then
            Position := Position + Increment
          else
            If Wrap then
              Position := Min + (Position + Increment - Max) - 1
          else
            Position := Max;
        end;
        
    end;
    FUpDown.ClickUpDown(FButtonType);
  end;
end;

constructor TUpDownButton.CreateWithParams(UpDown : TCustomUpDown;
  ButtonType : TUDBtnType);
begin
  Inherited Create(UpDown);
  FUpDown := UpDown;
  FButtonType := ButtonType;

  Parent := FUpDown;
  TabStop := False;
  ControlStyle := ControlStyle + [csNoFocus];
  OnMouseDown := @ButtonMouseDown;
  OnMouseUp := @ButtonMouseUp;
  Show;
end;

Procedure TUpDownButton.Paint;
var
  Points : Array[0..3] of TPoint;
begin
  Inherited Paint;
  If (((Width <= 5) or (Height <= 10)) and
    (FUpDown.Orientation = udVertical)) or
    (((Height <= 5) or (Width <= 10)) and
    (FUpDown.Orientation = udHorizontal))
  then
    exit;
  Case FButtonType of
    btPrev :
      begin
        If FUpDown.Orientation = udVertical then begin
          Points[0].X := 2;
          Points[0].Y := 2;
          Points[1].X := Width - 2;
          Points[1].Y := 2;
          Points[2].X := Width div 2;
          Points[2].Y := Height - 2;
        end
        else begin
          Points[0].X := 2;
          Points[0].Y := Height div 2;
          Points[1].X := Width - 2;
          Points[1].Y := 2;
          Points[2].X := Width - 2;
          Points[2].Y := Height - 2;
        end;
      end;
    btNext :
      begin
        If FUpDown.Orientation = udVertical then begin
          Points[0].X := Width div 2;
          Points[0].Y := 2;
          Points[1].X := Width - 2;
          Points[1].Y := Height - 2;
          Points[2].X := 2;
          Points[2].Y := Height - 2;
        end
        else begin
          Points[0].X := 2;
          Points[0].Y := 2;
          Points[1].X := Width - 2;
          Points[1].Y := Height div 2;
          Points[2].X := 2;
          Points[2].Y := Height - 2;
        end;
     end
  end;
  Canvas.Color := clBtnText;//Not perfect, but it works
  Canvas.Pen.Color := clBtnText;
  Points[3] := Points[0];
  Canvas.Polygon(@Points[0], 4, False);
end;

constructor TCustomUpDown.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  fCompStyle := csPanel;
  ControlStyle := ControlStyle  - [csDoubleClicks] +
    [csClickEvents, csOpaque,  csReplicatable, csNoFocus];
  MinBtn := TUpDownButton.CreateWithParams(Self, btPrev);
  MaxBtn := TUpDownButton.CreateWithParams(Self, btNext);
  InheritedChangeBounds := True;
  SetBounds(0,0,17,31);
  InheritedChangeBounds := False;
  BTimerProc := nil;
  BTimerBounds := Rect(0,0,0,0);
  FArrowKeys := True;
  FMax := 100;
  FIncrement := 1;
  FAlignButton := udRight;
  FOrientation := udVertical;
  FThousands := True;
end;

destructor TCustomUpDown.Destroy;
begin
  FAssociate := nil;
  InheritedChangeBounds := True;
  inherited destroy;
end;

Procedure TCustomUpDown.BTimerExec(Sender : TObject);
begin
  If Assigned(BTimerProc) then
    If PtInRect(BTimerBounds,Mouse.CursorPos) then
      BTimerProc;
end;

procedure TCustomUpDown.ChangeBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  ARect : TRect;
begin
  If InheritedChangeBounds or (csLoading in ComponentState) then
    Inherited ChangeBounds(ALeft, ATop, AWidth, AHeight)
  else begin
    InheritedChangeBounds := True;
    ARect := ClientRect;
    InvalidateRect(Handle, @ARect, False);
    SetAlignButton(FAlignButton);
    SetOrientation(FOrientation);
    SetPosition(FPosition);
    InheritedChangeBounds := False;
    Inherited ChangeBounds(ALeft, ATop, AWidth, AHeight);
  end;
end;

Function TCustomUpDown.CanChange: Boolean;
begin
  Result := True;
  if Assigned(FOnChanging) then
    FOnChanging(Self, Result);
end;

procedure TCustomUpDown.ClickUpDown(Button: TUDBtnType);
begin
  if Assigned(FOnClick) then FOnClick(Self, Button);
end;

procedure TCustomUpDown.SetAssociate(Value: TWinControl);
var
  I: Integer;

  function IsClass(ClassType: TClass; const Name: string): Boolean;
  begin
    Result := True;
    while ClassType <> nil do
    begin
      if ClassType.ClassNameIs(Name) then Exit;
      ClassType := ClassType.ClassParent;
    end;
    Result := False;
  end;

begin
  if Value <> nil then
    for I := 0 to Parent.ControlCount - 1 do
      if (Parent.Controls[I] is TCustomUpDown) and
        (Parent.Controls[I] <> Self)
      then
        if TCustomUpDown(Parent.Controls[I]).Associate = Value then
          raise Exception.CreateFmt('%s is already associated with %s',
            [Value.Name, Parent.Controls[I].Name]);

  if FAssociate <> nil then
  begin
    FAssociate.OnKeyDown := OldKeyDown;
    OldKeyDown := nil;
    FAssociate := nil;
  end;

  if (Value <> nil) and (Value.Parent = Self.Parent) and
    not (Value is TCustomUpDown) and
    not (Value is TCustomTreeView) and not (Value is TCustomListView)
  then
  begin
    FAssociate := Value;
    SetOrientation(FOrientation);
    SetPosition(FPosition);
    OldKeyDown := Value.OnKeyDown;
    Value.OnKeyDown := @AssociateKeyDown;
    Value.SetText(IntToStr(FPosition));
  end;
end;

Procedure TCustomUpDown.AssociateKeyDown(Sender: TObject; var Key: Word; ShiftState : TShiftState);
begin
  If Assigned(OldKeyDown) then
    OldKeyDown(Sender,Key, ShiftState);
  If ArrowKeys then
    If ShiftState = [] then
      Case Key of
        VK_Up,
        VK_Right:
          TSpeedButton(MaxBtn).Click;
        VK_Down,
        VK_Left:
          TSpeedButton(MinBtn).Click;
      end;
end;

procedure TCustomUpDown.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) and (AComponent = FAssociate) then
    if HandleAllocated then
      SetAssociate(nil);
end;

function TCustomUpDown.GetPosition: SmallInt;
var
  av,I : Smallint;
  str : string;
  InvalidNumber : Boolean;
begin
  If Associate <> nil then begin
    str := Associate.GetText;
    InvalidNumber := False;
    For I := Length(str) downto 1 do
      case str[I] of
        ',' : Delete(Str,I,1);
        '0'..'9':begin end;
        else
          InvalidNumber := True;
      end;
    If not InvalidNumber then
      AV := Trunc(StrToFloat(str))
    else begin
      AV := FPosition;
      Position := AV - 1;
      Position := AV;
      Result := FPosition;
      exit;
    end;
    If AV <> FPosition then
      FPosition := AV - 1;
    If AV > FMax then
      AV := FMax;
    If AV < FMin then
      AV := FMin;
    Position := AV;
  end;
  Result := FPosition;
end;

procedure TCustomUpDown.SetMin(Value: SmallInt);
begin
  if Value <> FMin then
  begin
    FMin := Value;
    If FPosition < FMin then
      Position := FMin;
  end;
end;

procedure TCustomUpDown.SetMax(Value: SmallInt);
begin
  if Value <> FMax then
  begin
    FMax := Value;
    If FPosition > FMax then
      Position := FMax;
  end;
end;

procedure TCustomUpDown.SetIncrement(Value: Integer);
begin
  if Value <> FIncrement then
    FIncrement := Value;
end;

procedure TCustomUpDown.SetPosition(Value: SmallInt);
var
  str : String;
begin
  FPosition := Value;
  If Thousands then
    Str := FloatToStrF(FPosition, ffNumber, 0, 0)
  else
    str := IntToStr(FPosition);
  if (not (csDesigning in ComponentState)) and (FAssociate <> nil) then
    FAssociate.SetText(Str);
end;

procedure TCustomUpDown.SetOrientation(Value: TUDOrientation);
begin
  FOrientation := Value;
  If Value = udHorizontal then begin
    MinBtn.SetBounds(0,0,Width div 2,Height);
    MaxBtn.SetBounds(Width div 2,0,Width div 2, ClientHeight);
  end
  else begin
    MaxBtn.SetBounds(0,0,Width,Height div 2);
    MinBtn.SetBounds(0,Height div 2,Width, ClientHeight div 2);
  end;
  SetAlignButton(FAlignButton);
end;

procedure TCustomUpDown.SetAlignButton(Value: TUDAlignButton);
begin
  FAlignButton := Value;
  If assigned(Associate) then begin
    If Value = udLeft then
      Left := Associate.Left - Width
    else
      Left := Associate.Left + Associate.Width;
    Height := Associate.Height;
    Top := Associate.Top;
  end;
end;

procedure TCustomUpDown.SetArrowKeys(Value: Boolean);
begin
  if Value <> FArrowKeys then
    FArrowKeys := Value;
end;

procedure TCustomUpDown.SetThousands(Value: Boolean);
begin
  if Value <> FThousands then
    FThousands := Value;
end;

procedure TCustomUpDown.SetWrap(Value: Boolean);
begin
  if Value <> FWrap then
    FWrap := Value;
end;
