{%MainUnit spinex.pp} { ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } const NvbStrings: Array[TNullValueBehaviour] of string = ( 'nvbShowTextHint', 'nvbLimitedNullValue', 'nvbMinValue', 'nvbMaxValue', 'nvbInitialValue' ); Digits = ['0'..'9']; AllowedControlChars = [#8,#9,^C,^X,^V,^Z]; function DbgS(ANvb: TNullValueBehaviour): String; begin Result := NvbStrings[ANvb]; end; { TSpinEditEx } procedure TSpinEditExBase.UpdateControl; var ANumber: T; begin if (MaxValue < MinValue) then FMaxValue := MinValue; if (FNullValueBehaviour <> nvbShowTextHint) then FValue := GetLimitedValue(FValue); if (not HandleAllocated) then Exit; if ([csLoading, csDestroying] * ComponentState <> []) then FUpdatePending := True else begin FUpdatePending := False; //Update the Text if (FNullValueBehaviour = nvbShowTextHint) then begin if not FSettingValue then begin if TextIsNumber(Text, ANumber) then Text := ValueToStr(GetLimitedValue(ANumber)) else Text := EmptyStr; end else begin if IsOutOfLimits(FValue) then Text := EmptyStr else Text := ValueToStr(FValue); end; end else Text := ValueToStr(GetLimitedValue(FValue)); end; end; procedure TSpinEditExBase.UpDownChangingEx(Sender: TObject; var AllowChange: Boolean; NewValue: SmallInt; Direction: TUpDownDirection); begin if ReadOnly then Exit; Case Direction of updUp: SpinUpDown(True); updDown: SpinUpDown(False); end; end; procedure TSpinEditExBase.UpDownClick(Sender: TObject; {%H-}Button: TUDBtnType); begin BuddyClick; end; function TSpinEditExBase.GetBuddyClassType: TControlClass; begin Result := TUpDown; end; procedure TSpinEditExBase.DoEnter; begin inherited DoEnter; FInitialValue := GetValue; end; function TSpinEditExBase.RealGetText: TCaption; begin if HandleAllocated then Result := inherited RealGetText else Result := ValueToStr(FValue); end; procedure TSpinEditExBase.Reset; begin if IsMasked then inherited Reset else Value := FInitialValue; end; procedure TSpinEditExBase.EditEditingDone; begin inherited EditEditingDone; GetValue; {$ifdef debugspinex} debugln(['TSpinEditExBase.EditingDone:']); debugln([' FValue = ',FValue,' Text = "',Text,'"']); {$endif} UpdateControl; end; procedure TSpinEditExBase.EditChange; begin {$ifdef debugspinex} debugln('TSpinEditExBase.EditChange'); {$endif} inherited EditChange; end; procedure TSpinEditExBase.EditKeyDown(var Key: word; Shift: TShiftState); begin inherited EditKeyDown(Key, Shift); if (Key = VK_Escape) and (Shift = []) then begin Key := 0; Reset; end else if FArrowKeys and (Key = VK_UP) and (Shift = []) then begin Key := 0; SpinUpDown(True); end else if FArrowKeys and (Key = VK_Down) and (Shift = []) then begin Key := 0; SpinUpDown(False); end end; procedure TSpinEditExBase.SetMaxValue(const AValue: T); begin if FMaxValue = AValue then Exit; FMaxValue := AValue; UpdateControl; end; procedure TSpinEditExBase.SetMinValue(const AValue: T); begin if FMinValue = AValue then Exit; FMinValue := AValue; UpdateControl; end; procedure TSpinEditExBase.SetIncrement(const AIncrement: T); begin if AIncrement = FIncrement then Exit; if AIncrement > 0 then FIncrement := AIncrement else FIncrement := -AIncrement; end; procedure TSpinEditExBase.InitializeWnd; begin inherited InitializeWnd; UpdateControl; end; procedure TSpinEditExBase.Loaded; begin inherited Loaded; UpDown.MinRepeatInterval := FMinRepeatValue; if FUpdatePending then UpdateControl; end; function TSpinEditExBase.MaxValueStored: Boolean; begin Result := not SameValue(FMaxValue, DefMaxValue); end; procedure TSpinEditExBase.EditMouseWheelUp(Shift: TShiftState; MousePos: TPoint; var Handled: Boolean); begin inherited EditMouseWheelUp(Shift, MousePos, Handled); if not Handled then SpinUpDown(True); end; procedure TSpinEditExBase.EditMouseWheelDown(Shift: TShiftState; MousePos: TPoint; var Handled: Boolean); begin inherited EditMouseWheelDown(Shift, MousePos, Handled); if not Handled then SpinUpDown(False); end; procedure TSpinEditExBase.SetValue(const AValue: T); var ValueFromText: T; begin {$ifdef debugspinex} debugln(['TSpinEditExBase.SetValue: AValue = ',AValue, ' , FValue=',FValue,' Text="',Text,'"']); {$endif} if (FValue = AValue) //if you set text by code (or paste it) and text is not a valid float, then FValue will hold the previous value //and in that case we should not exit here... and ({TryStrToFloat(Text, ValueFromText, FFS)} TextIsNumber(Text, ValueFromText) and (ValueFromText = FValue)) then Exit; FSettingValue := True; FValue := AValue; FUpdatePending := True; UpdateControl; FSettingValue := False; end; function TSpinEditExBase.GetValue: T; begin if HandleAllocated and not (wcfCreatingHandle in FWinControlFlags) then begin FValue := StrToValue(Text); end; Result := FValue; end; function TSpinEditExBase.IncrementStored: Boolean; begin Result := not SameValue(FIncrement, DefIncrement); end; function TSpinEditExBase.IsLimited: Boolean; begin Result := MaxValue > MinValue; end; function TSpinEditExBase.IsOutOfLimits(AValue: T): Boolean; begin Result := IsLimited and ((AValue < MinValue) or (AValue > MaxValue)); end; function TSpinEditExBase.GetEdit: TGEEdit; begin Result := BaseEditor; end; procedure TSpinEditExBase.SetMinRepeatValue(AValue: Byte); begin if FMinRepeatValue = AValue then Exit; FMinRepeatValue := AValue; if not (csLoading in ComponentState) then UpDown.MinRepeatInterval := FMinRepeatValue; end; procedure TSpinEditExBase.SpinUpDown(Up: Boolean); var OldValue, NewValue: T; begin if not TextIsNumber(Text, OldValue) then NewValue := MinValue else begin if IsOutOfLimits(OldValue) then NewValue := GetLimitedValue(OldValue) else begin if Up then NewValue := GetLimitedValue(SafeInc(OldValue)) else NewValue := GetLimitedValue(SafeDec(OldValue)); end; end; SetValue(NewValue); end; function TSpinEditExBase.GetNullValue: T; begin Result := FNullValue; end; function TSpinEditExBase.GetUpDown: TUpDown; begin Result := TUpDown(Buddy); end; procedure TSpinEditExBase.SetNullValue(AValue: T); begin if (FNullValue = AValue) then Exit; FNullValue := AValue; UpdateControl; end; constructor TSpinEditExBase.Create(TheOwner: TComponent); begin inherited Create(TheOwner); FArrowKeys := True; FIncrement := DefIncrement; FMaxValue := DefMaxValue; FUpdatePending := True; FSettingValue := False; FNullValueBehaviour := nvbMinValue; FMinRepeatValue := DefMinRepeatValue; UpDown.ControlStyle := UpDown.ControlStyle + [csNoDesignVisible]; Edit.Alignment := taRightJustify; { A note regarding the Updown control. It is by design that UpDown is not set to associate with the Edit. Amongst others, it would make it impossible to use with floats, nor have a NullValue. It also does align as it should when associated. } UpDown.OnChangingEx := @UpDownChangingEx; //OnCick signature of TUpDown differs from TControl.OnClick, //Yhe assigning of OnClick in inherited constructor //sets TControl(Buddy).OnClick to fire BuddyClick, and that won't do //since TUpDown does not fire a regular TControl.OnClick event UpDown.OnClick := @UpDownClick; with GetControlClassDefaultSize do SetInitialBounds(0, 0, CX, CY); end; function TSpinEditExBase.GetLimitedValue(const AValue: T): T; begin Result := AValue; //Delphi does not constrain when MinValue = MaxValue, and does if MaxValue > MinValue, //but the latter makes absolutely no sense at all. if FMaxValue > FMinValue then begin if Result < FMinValue then Result := FMinValue; if Result > FMaxValue then Result := FMaxValue; end; end; procedure TSpinEditExBase.FinalizeWnd; begin GetValue; inherited FinalizeWnd; end; { TCustomFloatSpinEditEx } function TCustomFloatSpinEditEx.GetDecimalSeparator: Char; begin Result := FFS.DecimalSeparator; end; procedure TCustomFloatSpinEditEx.SetDecimalSeparator(AValue: Char); begin if (AValue = FFS.DecimalSeparator) then Exit; FFS.DecimalSeparator := AValue; UpdateControl; end; procedure TCustomFloatSpinEditEx.EditKeyPress(var Key: char); begin inherited EditKeyPress(Key); {Disallow any key that is not a digit, decimalseparator or '-' For ease of use translate any decimalpoint or comma to DecimalSeparator Tab, BackSpace, Cut, Paste, Copy, Undo of course should be passed onto inherited KeyPress If FDecimals = 0, disallow decimalseparator also } if (Key in ['.',',']) then Key := FFS.Decimalseparator; if not (Key in (Digits + AllowedControlChars + [FFS.DecimalSeparator,'-'])) then Key := #0; if (Key = FFS.DecimalSeparator) and (FDecimals = 0) then Key := #0; if (Key = '-') and IsLimited and (MinValue >= 0) then Key := #0; end; function TCustomFloatSpinEditEx.TextIsNumber(const S: String; out ANumber: Double ): Boolean; begin {$ifdef debugspinex} DbgOut(['TextIsNumber, S ="',S,'": Result = ']); {$endif} try Result := TryStrToFloat(S, ANumber, FFS); except Result := False; end; {$ifdef debugspinex} debugln([Result]); {$endif} end; function TCustomFloatSpinEditEx.SafeInc(AValue: Double): Double; begin if ((AValue > 0) and (AValue > (MaxDouble-FIncrement))) then Result := MaxDouble else Result := AValue + FIncrement; end; function TCustomFloatSpinEditEx.SafeDec(AValue: Double): Double; begin if (AValue < 0) and ((-MaxDouble + FIncrement) > AValue) then Result := -MaxDouble else Result := AValue - FIncrement; end; procedure TCustomFloatSpinEditEx.SetDecimals(ADecimals: Integer); begin if (FDecimals = ADecimals) or (ADecimals < 0) then Exit; //if we increment DecimalPlaces first set FValue to correctly have the current DecimalPlaces decimals, //and only then do the increment (GetValue will update FValue to have FDecimalPlaces decimals) //Issue #0034370 if (ADecimals > FDecimals) then GetValue; FDecimals := ADecimals; UpdateControl; end; function TCustomFloatSpinEditEx.ValueToStr(const AValue: Double): String; begin Result := FloatToStrF(GetLimitedValue(AValue), ffFixed, 20, DecimalPlaces, FFS); end; function TCustomFloatSpinEditEx.StrToValue(const S: String): Double; var Def, D: Double; begin {$ifdef debugspinex} debugln(['TCustomFloatSpinEditEx.StrToValue: S="',S,'"']); {$endif} case FNullValueBehaviour of nvbShowTextHint: Def := FNullValue; nvbLimitedNullValue: Def := GetLimitedValue(FNullValue); nvbMinValue: Def := FMinValue; nvbMaxValue: Def := MaxValue; nvbInitialValue: Def := FInitialValue; end; try if (FNullValueBehaviour = nvbShowTextHint)then begin if TextIsNumber(S, D) then Result := D else Result := Def; end else Result := GetLimitedValue(StrToFloatDef(S, Def, FFS)); except Result := Def; end; {$ifdef debugspinex} debugln([' Result=',(Result)]); {$endif} end; constructor TCustomFloatSpinEditEx.Create(TheOwner: TComponent); begin inherited Create(TheOwner); FFS := DefaultFormatSettings; FFS.DecimalSeparator := DefDecimalSeparator; FDecimals := DefDecimals; end; { TCustomSpinEditEx } procedure TCustomSpinEditEx.EditKeyPress(var Key: char); begin inherited EditKeyPress(Key); {Disallow any key that is not a digit or - Tab, BackSpace, Cut, Paste, Copy, Undo of course should be passed onto inherited KeyPress } inherited EditKeyPress(Key); if not (Key in (Digits + AllowedControlChars + ['-'])) then Key := #0; if (Key = '-') and IsLimited and (MinValue >= 0) then Key := #0; end; function TCustomSpinEditEx.SafeInc(AValue: Int64): Int64; begin if ((AValue > 0) and (AValue > (High(Int64)-FIncrement))) then Result := High(Int64) else Result := AValue + FIncrement; end; function TCustomSpinEditEx.SafeDec(AValue: Int64): Int64; begin if (AValue < 0) and ((Low(Int64) + FIncrement) > AValue) then Result := Low(Int64) else Result := AValue - FIncrement; end; function TCustomSpinEditEx.TextIsNumber(const S: String; out ANumber: Int64 ): Boolean; var N: Int64; begin {$ifdef debugspinex} DbgOut(['TCustomSpinEditEx.TextIsNumber: S = "',S,'" Result = ']); {$endif} try Result := TryStrToInt64(S, N); ANumber := N; except Result := False; end; {$ifdef debugspinex} debugln([Result]); {$endif} end; function TCustomSpinEditEx.ValueToStr(const AValue: Int64): String; begin Result := IntToStr(AValue); end; function TCustomSpinEditEx.StrToValue(const S: String): Int64; var Def, N: Int64; begin {$ifdef debugspinex} debugln(['TCustomSpinEditEx.StrToValue: S="',S,'"']); {$endif} case FNullValueBehaviour of nvbShowTextHint: Def := FNullValue; nvbLimitedNullValue: Def := GetLimitedValue(FNullValue); nvbMinValue: Def := FMinValue; nvbMaxValue: Def := MaxValue; nvbInitialValue: Def := FInitialValue; end; try if (FNullValueBehaviour = nvbShowTextHint)then begin if TextIsNumber(S, N) then Result := N else Result := Def; end else Result := GetLimitedValue(StrToInt64Def(S, Def)); except Result := Def; end; {$ifdef debugspinex} debugln([' Result=',(Result)]); {$endif} end;