Files
lazarus-ccr/components/jvcllaz/run/JvHMI/jvdialbutton.pas
2019-06-01 21:15:10 +00:00

1349 lines
35 KiB
ObjectPascal

{-----------------------------------------------------------------------------
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/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvRadioGroup.PAS, released on 2002-07-16.
The Initial Developer of the Original Code is Rudolph Velthuis
Portions created by Rudolph Velthuis are Copyright (C) 1997 drs. Rudolph Velthuis.
All Rights Reserved.
Contributor(s):
marcelb - renaming TJvDialButton, adding on/off state and on/off color for pointer.
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org
Description:
TJvDialButton component, a button like the dial on a radio.
Known Issues:
-----------------------------------------------------------------------------}
// $Id$
unit JvDialButton;
{$mode objfpc}{$H+}
interface
uses
LCLType, LCLIntf, LMessages,
//Windows, Messages,
Classes, Graphics, Controls, Forms, ExtCtrls, ComCtrls,
JvComponent;
type
TJvDialPointerShape = (psLine, psTriangle, psDot, psOwnerDraw);
TJvTickLength = (tlShort, tlMiddle, tlLong);
TJvDialAngle = 0..3600; // 0.0 - 360.0 deg // in decidegrees (use 100 for 10 degrees)
TJvRepeatValue = 10..1000; // mouse repeat values
TJvCustomDialButton = class;
TJvDialDrawEvent = procedure(Sender: TJvCustomDialButton; ARect: TRect) of object;
TJvDialComputeTicks = procedure(Sender: TJvCustomDialButton) of object;
PTick = ^TTick;
TTick = record
Value: Integer;
Length: Integer;
Color: TColor;
Changed: Boolean;
end;
TJvCustomDialButton = class(TJvCustomControl)
private
FBitmap: TBitmap;
FBitmapRect: TRect;
FBitmapInvalid: Boolean;
// FBorderStyle: TBorderStyle;
FButtonEdge: Integer;
FDefaultPos: Integer;
FFrequency: Integer;
FLargeChange: Integer;
FMax: Integer;
FMaxAngle: TJvDialAngle;
FMin: Integer;
FMinAngle: TJvDialAngle;
FPointerRect: TRect;
FPointerColor: TColor;
FPointerColorOff: TColor;
FPointerPenWidth: Integer;
FPointerSize: Integer;
FPointerShape: TJvDialPointerShape;
FPosition: Integer;
FRadius: Integer;
FSize: Integer;
FState: Boolean;
FSmallChange: Integer;
FTicks: TList;
FTickStyle: TTickStyle;
FIncrementing: Boolean;
FRepeatTimer: TTimer;
FRepeatRate: TJvRepeatValue;
FRepeatDelay: TJvRepeatValue;
FOnChange: TNotifyEvent;
FOnDrawPointer: TJvDialDrawEvent;
FOnComputeTicks: TJvDialComputeTicks;
function CalcBounds(var AWidth, AHeight: Integer): Boolean;
function GetAngle: TJvDialAngle;
function GetCenter: TPoint;
procedure SetAngle(Value: TJvDialAngle);
procedure SetButtonEdge(Value: Integer);
procedure SetDefaultPos(Value: Integer);
procedure SetFrequency(Value: Integer);
procedure SetLargeChange(Value: Integer);
procedure SetMin(Value: Integer);
procedure SetMinAngle(Value: TJvDialAngle);
procedure SetMax(Value: Integer);
procedure SetMaxAngle(Value: TJvDialAngle);
procedure SetPointerColor(Value: TColor);
procedure SetPointerColorOff(Value: TColor);
procedure SetPointerPenWidth(Value: Integer);
procedure SetPointerSize(Value: Integer);
procedure SetPointerShape(Value: TJvDialPointerShape);
procedure SetPosition(Value: Integer);
procedure SetRadius(Value: Integer);
procedure SetSmallChange(Value: Integer);
procedure SetState(Value: Boolean);
procedure SetTickStyle(Value: TTickStyle);
procedure UpdateSize;
procedure TimerExpired(Sender: TObject);
procedure ComputeTicks;
protected
function AngleToPos(AnAngle: TJvDialAngle): Integer;
procedure BitmapNeeded; dynamic;
procedure Change; dynamic;
procedure ClearTicks;
// procedure Click; override;
// procedure CreateParams(var Params: TCreateParams); override;
// procedure CMCtl3DChanged(var Msg: TLMessage); message CM_CTL3DCHANGED;
// procedure WMSysColorChange(var Msg: TLMessage); message LM_SYSCOLORCHANGE;
// procedure WndProc(var Msg: TLMessage); override;
// procedure FocusSet(PrevWnd: THandle); override;
// procedure FocusKilled(NextWnd: THandle); override;
procedure ColorChanged; override;
procedure ParentColorChanged; override;
procedure DrawBorder; dynamic;
procedure DrawButton; dynamic;
procedure DrawPointer; dynamic;
procedure DrawTick(ACanvas: TCanvas; var Tick: TTick); dynamic;
procedure DrawTicks; dynamic;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure Loaded; override;
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
procedure Paint; override;
function PosToAngle(Pos: Integer): TJvDialAngle;
// procedure SetBorderStyle(Value: TBorderStyle); override;
procedure SetTicks(Value: TTickStyle); virtual;
procedure IncPos(Shift: TShiftState); dynamic;
procedure DecPos(Shift: TShiftState); dynamic;
property Ticks: TList read FTicks write FTicks stored True;
// to be published later:
property Angle: TJvDialAngle read GetAngle write SetAngle stored False; // in decidegrees (use 100 for 10 degrees)
property BorderStyle default bsNone;
property ButtonEdge: Integer read FButtonEdge write SetButtonEdge default 2;
property DefaultPos: Integer read FDefaultPos write SetDefaultPos;
property Frequency: Integer read FFrequency write SetFrequency default 10;
property LargeChange: Integer read FLargeChange write SetLargeChange default 2;
property Max: Integer read FMax write SetMax default 100;
property MaxAngle: TJvDialAngle read FMaxAngle write SetMaxAngle default 3300; // in decidegrees (use 100 for 10 degrees)
property Min: Integer read FMin write SetMin default 0;
property MinAngle: TJvDialAngle read FMinAngle write SetMinAngle default 300; // in decidegrees (use 100 for 10 degrees)
property PointerColorOn: TColor read FPointerColor write SetPointerColor default clBtnText;
property PointerColorOff: TColor read FPointerColorOff write SetPointerColorOff default clGrayText;
property PointerPenWidth: Integer read FPointerPenWidth write SetPointerPenWidth default 3;
property PointerSize: Integer read FPointerSize write SetPointerSize default 33;
property PointerShape: TJvDialPointerShape read FPointerShape write SetPointerShape default psLine;
property Position: Integer read FPosition write SetPosition default 0;
property Radius: Integer read FRadius write SetRadius;
property RepeatDelay: TJvRepeatValue read FRepeatDelay write FRepeatDelay default 400;
property RepeatRate: TJvRepeatValue read FRepeatRate write FRepeatRate default 100;
property SmallChange: Integer read FSmallChange write SetSmallChange default 1;
property State: Boolean read FState write SetState default True;
property TickStyle: TTickStyle read FTickStyle write SetTickStyle stored True;
property TabStop default True;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
property OnDrawPointer: TJvDialDrawEvent read FOnDrawPointer write FOnDrawPointer;
property OnComputeTicks: TJvDialComputeTicks read FOnComputeTicks write FOnComputeTicks;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AngleToPoint(AnAngle: TJvDialAngle; ACenter: TPoint; ARadius: Integer): TPoint;
procedure SetAngleParams(AnAngle, AMin, AMax: TJvDialAngle); virtual;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
procedure SetParams(APosition, AMin, AMax: Integer); virtual;
procedure SetTick(Value: Integer; Length: TJvTickLength); virtual;
function RadToAngle(const Radian: Double): TJvDialAngle;
function AngleToRad(AnAngle: TJvDialAngle): Double;
property Bitmap: TBitmap read FBitmap;
property Center: TPoint read GetCenter;
end;
TJvDialButton = class(TJvCustomDialButton)
published
// properties
property Align;
property Angle;
property BorderSpacing;
property BorderStyle;
property ButtonEdge;
property Color;
property Cursor;
property DefaultPos;
property DragCursor;
property DragMode;
property Enabled;
property Frequency;
property LargeChange;
property Max;
property MaxAngle;
property Min;
property MinAngle;
property ParentColor;
property ParentShowHint;
property PointerColorOn;
property PointerColorOff;
property PointerPenWidth;
property PointerSize;
property PointerShape;
property PopupMenu;
property Position;
property Radius;
property RepeatDelay;
property RepeatRate;
property ShowHint;
property SmallChange;
property State;
property TickStyle;
property TabOrder;
property TabStop;
property Visible;
// events
property OnChange;
property OnClick;
property OnComputeTicks;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnDrawPointer;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
{$IFDEF RTL180_UP}
property OnMouseActivate;
property OnMouseEnter;
property OnMouseLeave;
{$ENDIF RTL180_UP}
property OnMouseMove;
property OnMouseUp;
property OnMouseWheel;
property OnMouseWheelDown;
property OnMouseWheelUp;
property OnStartDrag;
end;
implementation
uses
lclstrconsts,
//Consts,
Math,
JvResources, // wp: instead of Consts
JvThemes;
const
dAngleToRadian = Pi / 1800;
dRadianToAngle = 1800 / Pi;
rcMaxEdge = 100;
rcMinEdge = 0;
rcMinRadius = 15;
tlLongLen = 10;
tlMiddleLen = 6;
tlShortLen = 4;
MinBorder = 1;
TickBorder = tlLongLen;
function GetShiftState: TShiftState;
begin
Result := [];
if GetKeyState(VK_SHIFT) < 0 then
Include(Result, ssShift);
if GetKeyState(VK_CONTROL) < 0 then
Include(Result, ssCtrl);
if GetKeyState(VK_MENU) < 0 then
Include(Result, ssAlt);
end;
constructor TJvCustomDialButton.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csClickEvents, csCaptureMouse];
IncludeThemeStyle(Self, [csParentBackground]);
FTicks := TList.Create;
// FBorderStyle := bsNone;
FButtonEdge := 5;
FDefaultPos := 0;
FFrequency := 10;
FLargeChange := 2;
FMax := 100;
FMaxAngle := 3300;
FMin := 0;
FMinAngle := 300;
FPointerColor := clBtnText;
FPointerColorOff := clGrayText;
FPointerPenWidth := 3;
FPointerSize := 33;
FRadius := rcMinRadius;
FSmallChange := 1;
FState := True;
TabStop := True;
FTickStyle := tsAuto;
FBitmapInvalid := True;
FPointerRect.Left := -1; // Only on start up
Width := 120;
Height := 120;
FRepeatDelay := 400;
FRepeatRate := 100;
SetTicks(FTickStyle);
Position := 0;
end;
destructor TJvCustomDialButton.Destroy;
begin
FBitmap.Free;
ClearTicks;
FTicks.Free;
FRepeatTimer.Free;
inherited Destroy;
end;
// Convert position Pos to an angle.
function TJvCustomDialButton.PosToAngle(Pos: Integer): TJvDialAngle;
begin
Result := FMinAngle + ((FMaxAngle - FMinAngle) * (Pos - FMin) div (FMax - FMin));
end;
// Convert angle AnAngle to a position.
function TJvCustomDialButton.AngleToPos(AnAngle: TJvDialAngle): Integer;
begin
Result := FMin + ((FMax - FMin) * (AnAngle - FMinAngle) div (FMaxAngle - FMinAngle));
end;
// Convert polar coordinates defined by AnAngle, ACenter and ARadius to a TPoint.
function TJvCustomDialButton.AngleToPoint(AnAngle: TJvDialAngle; ACenter: TPoint;
ARadius: Integer): TPoint;
var
RadAngle: Double;
begin
RadAngle := AngleToRad(AnAngle);
Result.X := ACenter.X - Round(ARadius * Sin(RadAngle));
Result.Y := ACenter.Y + Round(ARadius * Cos(RadAngle));
end;
// Convert a APoint to an angle (relative to ACenter) in radians, where
// bottom is 0, left is Pi/2, top is Pi and so on.
function PointToRad(const APoint, ACenter: TPoint): Double;
var
N: Integer;
begin
N := APoint.X - ACenter.X;
if N = 0 then
Result := 0.5 * Pi
else
Result := ArcTan((ACenter.Y - APoint.Y) / N);
if N < 0 then
Result := Result + Pi;
Result := 1.5 * Pi - Result;
end;
// Get current angle (from position).
function TJvCustomDialButton.GetAngle: TJvDialAngle;
begin
Result := PosToAngle(FPosition);
end;
// Set current angle. Sets Position.
procedure TJvCustomDialButton.SetAngle(Value: TJvDialAngle);
begin
SetAngleParams(Value, FMinAngle, FMaxAngle);
end;
// Set border style. Redraw if necessary.
(*
procedure TJvCustomDialButton.SetBorderStyle(Value: TBorderStyle);
begin
if Value <> inherited BorderStyle then
begin
FBorderStyle := Value;
if HandleAllocated then
begin
{ wp
RecreateWnd;
}
DrawBorder;
end;
end;
end; *)
// Set positional (Cartesian) parameters, value checked and invalidate if
// necessary.
procedure TJvCustomDialButton.SetParams(APosition, AMin, AMax: Integer);
var
Invalid: Boolean;
InvalidTicks: Boolean;
lChanged: Boolean;
begin
lChanged := False;
// Ensure minimum and maximum in right order.
if AMax < AMin then
raise EInvalidOperation.CreateResFmt(@RsPropertyOutOfRange, [ClassName]);
// Limit Position to Min and Max.
if APosition < AMin then
APosition := AMin;
if APosition > AMax then
APosition := AMax;
Invalid := False;
InvalidTicks := False;
// Change Min if necessary and flag redrawing if so.
if FMin <> AMin then
begin
FMin := AMin;
InvalidTicks := True;
end;
// Change Max if necessary and flag redrawing if so.
if FMax <> AMax then
begin
FMax := AMax;
InvalidTicks := True;
end;
if InvalidTicks then
begin
ComputeTicks;
Invalid := True;
end;
// Change Position if necessary and draw pointer accordingly.
if APosition <> FPosition then
begin
FPosition := APosition;
BitmapNeeded;
Invalid := true;
//DrawPointer;
lChanged := True;
end;
// If redrawing flagged, cause a redraw, redoing the bitmap too.
if Invalid then
begin
FBitmapInvalid := True;
lChanged := True;
Invalidate;
end;
if lChanged then
// Notify the user of changes.
Change;
end;
// Set all angle parameters at once.
procedure TJvCustomDialButton.SetAngleParams(AnAngle, AMin, AMax: TJvDialAngle);
var
Invalid: Boolean;
InvalidTicks: Boolean;
Pos: Integer;
begin
// Error if AMax < AMin
if AMax < AMin then
raise EInvalidOperation.CreateResFmt(@RsPropertyOutOfRange, [ClassName]);
// Confine AnAngle to limits.
if AnAngle < AMin then
AnAngle := AMin;
if AnAngle > AMax then
AnAngle := AMax;
Invalid := False;
InvalidTicks := False;
// Set MinAngle.
if FMinAngle <> AMin then
begin
FMinAngle := AMin;
InvalidTicks := True;
end;
// Set MaxAngle.
if FMaxAngle <> AMax then
begin
FMaxAngle := AMax;
InvalidTicks := True;
end;
if InvalidTicks then
begin
ComputeTicks;
Invalid := True;
end;
// Redraw if necessary
if Invalid then
begin
FBitmapInvalid := True;
Invalidate;
end;
// Set Position.
Pos := AngleToPos(AnAngle);
if Pos <> FPosition then
SetParams(Pos, FMin, FMax);
end;
procedure TJvCustomDialButton.SetDefaultPos(Value: Integer);
begin
// Change this if side effects are needed, e.g. to show a default pos marker.
if Value <> FDefaultPos then
FDefaultPos := Value;
end;
procedure TJvCustomDialButton.SetFrequency(Value: Integer);
begin
if Value <> FFrequency then
begin
FFrequency := Value;
if FFrequency < 1 then
FFrequency := 1;
if FTickStyle = tsAuto then
begin
ClearTicks;
SetTicks(FTickStyle);
end;
FBitmapInvalid := True;
Invalidate;
end;
end;
procedure TJvCustomDialButton.SetMin(Value: Integer);
begin
SetParams(FPosition, Value, FMax);
end;
procedure TJvCustomDialButton.SetMinAngle(Value: TJvDialAngle);
begin
SetAngleParams(PosToAngle(FPosition), Value, FMaxAngle);
end;
procedure TJvCustomDialButton.SetMax(Value: Integer);
begin
SetParams(FPosition, FMin, Value);
end;
procedure TJvCustomDialButton.SetMaxAngle(Value: TJvDialAngle);
begin
SetAngleParams(PosToAngle(FPosition), FMinAngle, Value);
end;
procedure TJvCustomDialButton.SetPosition(Value: Integer);
begin
SetParams(Value, FMin, FMax);
end;
function TJvCustomDialButton.CalcBounds(var AWidth, AHeight: Integer): Boolean;
var
ASize: Integer;
begin
Result := False;
ASize := rcMinRadius + MinBorder + TickBorder;
if BorderStyle = bsSingle then
Inc(ASize, GetSystemMetrics(SM_CXBORDER));
ASize := 2 * ASize + 1;
if AWidth < ASize then
begin
AWidth := ASize;
Result := True;
end;
if AHeight < ASize then
begin
AHeight := ASize;
Result := True;
end;
end;
procedure TJvCustomDialButton.SetRadius(Value: Integer);
var
MaxRadius: Integer;
begin
if Width <= Height then
MaxRadius := (Width - 1) div 2 - MinBorder - TickBorder
else
MaxRadius := (Height - 1) div 2 - MinBorder - TickBorder;
if BorderStyle = bsSingle then
Dec(MaxRadius, GetSystemMetrics(SM_CXBORDER));
if Value > MaxRadius then
Value := MaxRadius;
if Value < rcMinRadius then
Value := rcMinRadius;
if Value <> FRadius then
begin
FRadius := Value;
FBitmapInvalid := True;
Invalidate;
end;
UpdateSize;
end;
procedure TJvCustomDialButton.SetTicks(Value: TTickStyle);
var
L: TJvTickLength;
I: Integer;
begin
if Value <> tsNone then
begin
SetTick(FMin, tlLong);
SetTick(FMax, tlLong);
end;
if Value = tsAuto then
begin
I := FMin + FFrequency;
L := tlMiddle;
while I < FMax do
begin
SetTick(I, L);
if L = tlMiddle then
L := tlLong
else
L := tlMiddle;
Inc(I, FFrequency);
end;
end;
end;
procedure TJvCustomDialButton.SetState(Value: Boolean);
begin
if Value <> FState then
begin
FState := Value;
DrawPointer;
end;
end;
procedure TJvCustomDialButton.SetTickStyle(Value: TTickStyle);
begin
if FTickStyle <> Value then
begin
FTickStyle := Value;
ComputeTicks;
FBitmapInvalid := True;
Invalidate;
end;
end;
procedure TJvCustomDialButton.Change;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
procedure TJvCustomDialButton.SetSmallChange(Value: Integer);
begin
if Value > FLargeChange then
Value := FLargeChange div 2;
if Value < 1 then
Value := 1;
FSmallChange := Value;
end;
procedure TJvCustomDialButton.SetLargeChange(Value: Integer);
begin
if Value <= FSmallChange + 1 then
Value := FSmallChange + 1;
FLargeChange := Value;
end;
procedure TJvCustomDialButton.SetTick(Value: Integer; Length: TJvTickLength);
const
Lengths: array [TJvTickLength] of Byte =
(tlShortLen, tlMiddleLen, tlLongLen);
var
P: PTick;
I: Integer;
begin
if (Value < FMin) or (Value > FMax) then
raise EInvalidOperation.CreateResFmt(@RsOutOfRange, [FMin, FMax]);
for I := 0 to FTicks.Count - 1 do
begin
P := FTicks.Items[I];
if P^.Value = Value then
begin
if P^.Length <> Lengths[Length] then
begin
P^.Length := Lengths[Length];
P^.Changed := True;
Invalidate;
end;
Exit;
end;
end;
New(P);
P^.Value := Value;
P^.Length := Lengths[Length];
P^.Changed := True;
P^.Color := clBtnText;
FTicks.Add(P);
if HandleAllocated then
begin
DrawTick(FBitmap.Canvas, P^);
DrawTick(Canvas, P^);
end;
end;
procedure TJvCustomDialButton.DrawTick(ACanvas: TCanvas; var Tick: TTick);
var
Pt: TPoint;
ValueAngle: Integer;
begin
ValueAngle := PosToAngle(Tick.Value);
ACanvas.Pen.Color := Tick.Color;
Pt := AngleToPoint(ValueAngle, Center, FRadius);
ACanvas.MoveTo(Pt.X, Pt.Y);
Pt := AngleToPoint(ValueAngle, GetCenter, FRadius + Tick.Length);
ACanvas.LineTo(Pt.X, Pt.Y);
Tick.Changed := False;
end;
procedure TJvCustomDialButton.Paint;
begin
if csCreating in ControlState then
Exit;
Canvas.Brush.Color := Parent.Brush.Color;
DrawThemedBackground(Self, Canvas, ClientRect);
BitmapNeeded;
Canvas.Draw(0, 0, FBitmap);
DrawBorder;
DrawPointer;
end;
procedure TJvCustomDialButton.DrawPointer;
var
Outer, Inner, Extra: TPoint;
InnerRadius, DotRadius: Integer;
Region: HRgn;
SmallRadius: Integer;
function Lowest(A, B, C: Integer): Integer;
begin
if A < B then
if A < C then
Result := A
else
Result := C
else
if B < C then
Result := B
else
Result := C
end;
function Highest(A, B, C: Integer): Integer;
begin
if A > B then
if A > C then
Result := A
else
Result := C
else
if B > C then
Result := B
else
Result := C;
end;
begin
if not HandleAllocated then
Exit;
InnerRadius := (100 - FButtonEdge) * FRadius div 100 - 1;
if FPointerRect.Left < 0 then
FPointerRect := Rect(Center.X - InnerRadius - FPointerPenWidth,
Center.Y - InnerRadius - FPointerPenWidth,
Center.X + InnerRadius + 1 + FPointerPenWidth,
Center.Y + InnerRadius + 1 + FPointerPenWidth);
Canvas.CopyRect(FPointerRect, FBitmap.Canvas, FPointerRect);
// This is for a solid dot. I'd also like to make a Ctl3D type of dot or
// an open type of dot. We'd also have to make a disabled type of dot.
if State then
begin
Canvas.Pen.Color := FPointerColor;
Canvas.Brush.Color := FPointerColor;
end
else
begin
Canvas.Pen.Color := FPointerColorOff;
Canvas.Brush.Color := FPointerColorOff;
end;
case FPointerShape of
psLine:
begin
Outer := AngleToPoint(Angle, Center, InnerRadius);
Canvas.MoveTo(Outer.X, Outer.Y);
Inner := AngleToPoint(Angle, Center, (101 - FPointerSize) * InnerRadius div 100);
Canvas.Pen.Width := FPointerPenWidth;
Canvas.LineTo(Inner.X, Inner.Y);
FPointerRect := Rect(Math.Min(Inner.X, Outer.X),
Math.Min(Inner.Y, Outer.Y),
Math.Max(Inner.X, Outer.X),
Math.Max(Inner.Y, Outer.Y));
InflateRect(FPointerRect, FPointerPenWidth, FPointerPenWidth);
Canvas.Pen.Width := 1;
end;
psTriangle:
begin
SmallRadius := FPointerSize * InnerRadius div 100;
Outer := AngleToPoint(Angle, Center, InnerRadius);
Inner := AngleToPoint(Angle - 150, Center, InnerRadius - SmallRadius);
Extra := AngleToPoint(Angle + 150, Center, InnerRadius - SmallRadius);
Canvas.Polygon([Outer, Inner, Extra]);
FPointerRect := Rect(Lowest(Outer.X, Inner.X, Extra.X),
Lowest(Outer.Y, Inner.Y, Extra.Y),
Highest(Outer.X, Inner.X, Extra.X),
Highest(Outer.Y, Inner.Y, Extra.Y));
end;
psDot:
begin
DotRadius := FPointerSize * InnerRadius div 200;
Inner := AngleToPoint(Angle, Center, InnerRadius - DotRadius);
if Inner.X > Center.X then
Inc(Inner.X);
if Inner.Y > Center.Y then
Inc(Inner.Y);
FPointerRect := Rect(Inner.X - DotRadius,
Inner.Y - DotRadius,
Inner.X + DotRadius,
Inner.Y + DotRadius);
Canvas.Ellipse(FPointerRect.Left, FPointerRect.Top, FPointerRect.Right, FPointerRect.Bottom);
end;
psOwnerDraw:
if Assigned(FOnDrawPointer) then
begin
DotRadius := FPointerSize * InnerRadius div 200;
Outer := AngleToPoint(Angle, Center, InnerRadius - DotRadius);
if Outer.X > Center.X then
Inc(Outer.X);
if Outer.Y > Center.Y then
Inc(Outer.Y);
FPointerRect := Rect(Outer.X - DotRadius,
Outer.Y - DotRadius,
Outer.X + DotRadius,
Outer.Y + DotRadius);
// Create a clipping region to protect the area outside the button
// face.
Region := CreateEllipticRgn(FPointerRect.Left - 1, FPointerRect.Top - 1,
FPointerRect.Right + 1, FPointerRect.Bottom + 1);
SelectClipRgn(Canvas.Handle, Region);
try
FOnDrawPointer(Self, FPointerRect);
except
DeleteObject(Region);
SelectClipRgn(Canvas.Handle, 0);
raise;
end;
end;
end;
InflateRect(FPointerRect, 1, 1);
end;
procedure TJvCustomDialButton.BitmapNeeded;
begin
if FBitmap = nil then
begin
FBitmap := TBitmap.Create;
FBitmapInvalid := True;
end;
{$IFDEF JVCLThemesEnabled}
if FBitmapInvalid or StyleServices.Enabled then
{$ELSE}
if FBitmapInvalid then
{$ENDIF JVCLThemesEnabled}
begin
if FBitmap.Width <> FSize + 1 then
begin
FBitmap.Width := FSize + 1;
FBitmap.Height := FSize + 1;
FBitmapRect := Bounds(0, 0, FSize + 1, FSize + 1);
end;
{$IFDEF JVCLThemesEnabled}
if StyleServices.Enabled then
FBitmap.Canvas.CopyRect(FBitmapRect, Canvas, FBitmapRect);
{$ENDIF JVCLThemesEnabled}
// Draw on bitmap.
DrawButton;
DrawTicks;
end;
end;
function Blend(const Factor: Double; const Color1, Color2: TColor): TColor;
var
Factor2: Double;
begin
Factor2 := 1.0 - Factor;
with TRGBQuad(Result) do
begin
rgbBlue := Trunc(Factor * TRGBQuad(Color1).rgbBlue + Factor2 * TRGBQuad(Color2).rgbBlue);
rgbGreen := Trunc(Factor * TRGBQuad(Color1).rgbGreen + Factor2 * TRGBQuad(Color2).rgbGreen);
rgbRed := Trunc(Factor * TRGBQuad(Color1).rgbRed + Factor2 * TRGBQuad(Color2).rgbRed);
rgbReserved := 0;
end;
end;
procedure TJvCustomDialButton.DrawButton;
const
HalfPi = 1.57079632679489661923;
var
Edge: Integer;
FaceClr, HighlightClr, ShadowClr: TColor;
Size: Integer;
lCanvas: TCanvas;
I: Integer;
lColor: TColor;
xL, yT, xR, yB: Integer;
begin
Size := 2 * FRadius + 1;
lCanvas := FBitmap.Canvas;
lCanvas.Brush.Color := Parent.Brush.Color;
lCanvas.Brush.Style := bsSolid;
{$IFDEF JVCLThemesEnabled}
if not StyleServices.Enabled then
{$ENDIF JVCLThemesEnabled}
lCanvas.FillRect(FBitmapRect);
xL := FSize div 2 - FRadius;
xR := xL + 2*FRadius;
yT := FSize div 2 - FRadius;
yB := yT + 2*FRadius;
lCanvas.Pen.Style := psClear;
HighlightClr := ColorToRGB(clBtnHighlight);
if Color = clDefault then
lColor := clGray
else
lColor := Color;
FaceClr := ColorToRGB(lColor);
// darking the color by halving each color part value
ShadowClr := (ColorToRGB(lColor) and $00FEFEFE) shr 1;
for I := 0 to Size do
begin
lCanvas.Brush.Color := Blend(Cos(I * HalfPi / Size), HighlightClr, FaceClr);
lCanvas.Pie(xL, yT, xR, yB, I+1, 0, I-1, 0);
lCanvas.Pie(xL, yT, xR, yB, 0, I-1, 0, I+1);
end;
for I := 0 to Size do
begin
lCanvas.Brush.Color := Blend(1.0 - Sin(I * HalfPi / Size), FaceClr, ShadowClr);
lCanvas.Pie(xL, yT, xR, yB, Size, I+1, Size, I-1);
lCanvas.Pie(xL, yT, xR, yB, I-1, Size, I+1, Size);
end;
// Draw top of disk.
lCanvas.Pen.Style := psSolid;
lCanvas.Pen.Color := lColor;
lCanvas.Brush.Color := lColor;
Edge := FButtonEdge * FRadius div 100 + 1;
lCanvas.Ellipse(xL + Edge, yT + Edge, xR - Edge, yB - Edge);
// Draw bounding circle.
lCanvas.Pen.Color := clBtnText;
lCanvas.Brush.Style := bsClear;
lCanvas.Ellipse(xL, yT, xR, yB);
FBitmapInvalid := False;
end;
procedure TJvCustomDialButton.SetPointerShape(Value: TJvDialPointerShape);
begin
if Value <> FPointerShape then
begin
FPointerShape := Value;
Invalidate;
end;
end;
procedure TJvCustomDialButton.DrawBorder;
var
ARect: TRect;
begin
ARect := ClientRect;
InflateRect(ARect, -1, -1);
Canvas.Brush.Style := bsClear;
{$IFDEF JVCLThemesEnabled}
if StyleServices.Enabled then
begin
BitmapNeeded;
Canvas.Pen.Color := FBitmap.Canvas.Pixels[0, 0]
end
else
{$ENDIF JVCLThemesEnabled}
Canvas.Pen.Color := Parent.Brush.Color;
Canvas.Rectangle(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom);
Canvas.Brush.Style := bsSolid;
if Focused then
Canvas.DrawFocusRect(ARect);
end;
procedure TJvCustomDialButton.DrawTicks;
var
I: Integer;
begin
if (FTickStyle = tsNone) or (FTicks = nil) or (FTicks.Count = 0) then
Exit;
for I := 0 to FTicks.Count - 1 do
DrawTick(FBitmap.Canvas, PTick(FTicks[I])^);
end;
procedure TJvCustomDialButton.UpdateSize;
begin
FSize := 2 * (MinBorder + FRadius + TickBorder) + 1;
end;
procedure TJvCustomDialButton.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if CalcBounds(AWidth, AHeight) then
FBitmapInvalid := True;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
SetRadius(AWidth + AHeight);
end;
procedure TJvCustomDialButton.ParentColorChanged;
begin
FBitmapInvalid := True;
inherited ParentColorChanged;
end;
// Set button edge in percent (0 - 100).
procedure TJvCustomDialButton.SetButtonEdge(Value: Integer);
begin
if Value < rcMinEdge then
Value := rcMinEdge;
if Value > rcMaxEdge then
Value := rcMaxEdge;
if Value <> FButtonEdge then
begin
FButtonEdge := Value;
if not FBitmapInvalid then
begin
FBitmapInvalid := True;
Invalidate;
end;
end;
end;
(*
{ - strange: does not compile in qt with these methods...
procedure TJvCustomDialButton.FocusKilled(NextWnd: THandle);
begin
inherited FocusKilled(NextWnd);
if HandleAllocated then
DrawBorder;
end;
procedure TJvCustomDialButton.FocusSet(PrevWnd: THandle);
begin
inherited FocusSet(PrevWnd);
if HandleAllocated then
DrawBorder;
end; *)
procedure TJvCustomDialButton.MouseDown(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
A: TJvDialAngle;
begin
inherited MouseDown(Button, Shift, X, Y);
if not Focused then
begin
SetFocus;
Invalidate;
end;
if PtInRect(FPointerRect, Point(X, Y)) then
MouseCapture := True
else
begin
A := RadToAngle(PointToRad(Point(X, Y), GetCenter));
if A < Angle then
begin
DecPos(Shift);
FIncrementing := False;
end
else
begin
IncPos(Shift);
FIncrementing := True;
end;
if FRepeatTimer = nil then
FRepeatTimer := TTimer.Create(Self);
FRepeatTimer.OnTimer := @TimerExpired;
FRepeatTimer.Interval := FRepeatDelay;
FRepeatTimer.Enabled := True;
end;
end;
procedure TJvCustomDialButton.TimerExpired(Sender: TObject);
begin
FRepeatTimer.Enabled := False;
FRepeatTimer.Interval := FRepeatRate;
if FIncrementing then
IncPos(GetShiftState)
else
DecPos(GetShiftState);
FRepeatTimer.Enabled := True;
end;
procedure TJvCustomDialButton.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
inherited MouseMove(Shift, X, Y);
if MouseCapture then
SetAngle(RadToAngle(PointToRad(Point(X, Y), GetCenter)));
end;
procedure TJvCustomDialButton.MouseUp(Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
inherited MouseUp(Button, Shift, X, Y);
if FRepeatTimer <> nil then
FRepeatTimer.Enabled := False;
MouseCapture := False;
end;
function TJvCustomDialButton.GetCenter: TPoint;
begin
Result.X := FSize div 2;
Result.Y := Result.X;
end;
procedure TJvCustomDialButton.ClearTicks;
var
I: Integer;
begin
if FTicks <> nil then
with FTicks do
begin
for I := 0 to Count - 1 do
if Items[I] <> nil then
Dispose(PTick(Items[I]));
Clear;
end;
end;
{
procedure TJvCustomDialButton.Click;
begin
inherited Click;
FState := not FState;
Invalidate;
end;
}
(*
procedure TJvCustomDialButton.CreateParams(var Params: TCreateParams);
const
BorderStyles: array [TBorderStyle] of Cardinal = (0, WS_BORDER);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or BorderStyles[FBorderStyle];
{ wp: no Ctrl3D
if Ctl3D and (FBorderStyle = bsSingle) then
begin
Params.Style := Params.Style and not WS_BORDER;
Params.ExStyle := Params.ExStyle or WS_EX_STATICEDGE;
end;
}
end;
*)
procedure TJvCustomDialButton.SetPointerColor(Value: TColor);
begin
if Value <> FPointerColor then
begin
FPointerColor := Value;
if State then
DrawPointer;
end;
end;
procedure TJvCustomDialButton.SetPointerColorOff(Value: TColor);
begin
if Value <> FPointerColorOff then
begin
FPointerColorOff := Value;
if not State then
DrawPointer;
end;
end;
procedure TJvCustomDialButton.SetPointerPenWidth(Value: Integer);
begin
if (Value <> FPointerPenWidth) and (FPointerShape = psLine) then begin
FPointerPenWidth := Value;
DrawPointer;
end;
end;
procedure TJvCustomDialButton.IncPos(Shift: TShiftState);
begin
if ssShift in Shift then
Position := Position + FLargeChange
else
if ssCtrl in Shift then
Position := FMax
else
Position := Position + FSmallChange;
end;
procedure TJvCustomDialButton.DecPos(Shift: TShiftState);
begin
if ssShift in Shift then
Position := Position - FLargeChange
else
if ssCtrl in Shift then
Position := FMin
else
Position := Position - FSmallChange;
end;
procedure TJvCustomDialButton.KeyDown(var Key: Word; Shift: TShiftState);
begin
case Key of
VK_UP, VK_RIGHT:
IncPos(Shift);
VK_DOWN, VK_LEFT:
DecPos(Shift);
VK_PRIOR:
IncPos(Shift + [ssShift]);
VK_NEXT:
DecPos(Shift + [ssShift]);
VK_HOME:
Position := FMin;
VK_END:
Position := FMax;
else
inherited KeyDown(Key, Shift);
Exit;
end;
Key := 0;
inherited KeyDown(Key, Shift);
end;
{ wp: no Ctl3D
procedure TJvCustomDialButton.CMCtl3DChanged(var Msg: TLMessage);
begin
inherited;
FBitmapInvalid := True;
RecreateWnd;
end;
}
{ wp: cannot convert...
procedure TJvCustomDialButton.WndProc(var Msg: TLMessage);
begin
if Msg.Msg = CN_KEYDOWN then
DoKeyDown(TLMKey(Msg));
inherited WndProc(Msg);
end;
}
{
procedure TJvCustomDialButton.WMSysColorChange(var Msg: TMessage);
begin
FBitmapInvalid := True;
Invalidate;
end;
}
procedure TJvCustomDialButton.SetPointerSize(Value: Integer);
begin
if Value > 100 then
Value := 100
else
if Value < 1 then
Value := 1;
if Value <> FPointerSize then
begin
FPointerSize := Value;
DrawPointer;
end;
end;
function TJvCustomDialButton.AngleToRad(AnAngle: TJvDialAngle): Double;
begin
Result := dAngleToRadian * AnAngle;
end;
procedure TJvCustomDialButton.ColorChanged;
begin
FBitmapInvalid := True;
inherited ColorChanged;
end;
procedure TJvCustomDialButton.ComputeTicks;
begin
if csLoading in ComponentState then
Exit;
ClearTicks;
case FTickStyle of
tsNone:
;
tsAuto:
SetTicks(FTickStyle);
tsManual:
if Assigned(FOnComputeTicks) then
FOnComputeTicks(Self);
end;
end;
procedure TJvCustomDialButton.Loaded;
begin
inherited Loaded;
ComputeTicks;
Change;
end;
function TJvCustomDialButton.RadToAngle(const Radian: Double): TJvDialAngle;
begin
Result := Round(dRadianToAngle * Radian);
end;
end.