mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-26 22:03:48 +02:00
431 lines
11 KiB
ObjectPascal
431 lines
11 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Authors: Alexander Klenin
|
|
|
|
}
|
|
|
|
unit TANavigation;
|
|
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, Controls, Graphics, StdCtrls, TAChartUtils, TAGraph;
|
|
|
|
type
|
|
|
|
{ TChartNavScrollBar }
|
|
|
|
TChartNavScrollBar = class (TCustomScrollBar)
|
|
private
|
|
FAutoPageSize: Boolean;
|
|
FChart: TChart;
|
|
FListener: TListener;
|
|
procedure ChartExtentChanged(ASender: TObject);
|
|
procedure SetAutoPageSize(AValue: Boolean);
|
|
procedure SetChart(AValue: TChart);
|
|
protected
|
|
procedure Scroll(
|
|
AScrollCode: TScrollCode; var AScrollPos: Integer); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
published
|
|
property AutoPageSize: Boolean
|
|
read FAutoPageSize write SetAutoPageSize default false;
|
|
property Chart: TChart read FChart write SetChart;
|
|
published
|
|
property Align;
|
|
property Anchors;
|
|
property BidiMode;
|
|
property BorderSpacing;
|
|
property Constraints;
|
|
property DragCursor;
|
|
property DragKind;
|
|
property DragMode;
|
|
property Enabled;
|
|
property Kind;
|
|
property LargeChange;
|
|
property Max;
|
|
property Min;
|
|
property PageSize;
|
|
property ParentBidiMode;
|
|
property ParentShowHint;
|
|
property PopupMenu;
|
|
property Position;
|
|
property ShowHint;
|
|
property SmallChange;
|
|
property TabOrder;
|
|
property TabStop;
|
|
property Visible;
|
|
published
|
|
property OnChange;
|
|
property OnContextPopup;
|
|
property OnDragDrop;
|
|
property OnDragOver;
|
|
property OnEndDrag;
|
|
property OnEnter;
|
|
property OnExit;
|
|
property OnKeyDown;
|
|
property OnKeyPress;
|
|
property OnKeyUp;
|
|
property OnScroll;
|
|
property OnStartDrag;
|
|
property OnUTF8KeyPress;
|
|
end;
|
|
|
|
{ TChartNavPanel }
|
|
|
|
TChartNavPanel = class(TCustomControl)
|
|
private
|
|
FIsDragging: Boolean;
|
|
FLogicalExtentRect: TRect;
|
|
FOffset: TDoublePoint;
|
|
FOldCursor: TCursor;
|
|
FPrevPoint: TDoublePoint;
|
|
FScale: TDoublePoint;
|
|
procedure ChartExtentChanged(ASender: TObject);
|
|
private
|
|
FAllowDragNavigation: Boolean;
|
|
FChart: TChart;
|
|
FDragCursor: TCursor;
|
|
FFullExtentPen: TPen;
|
|
FListener: TListener;
|
|
FLogicalExtentPen: TPen;
|
|
FMiniMap: Boolean;
|
|
FProportional: Boolean;
|
|
FShift: TShiftState;
|
|
procedure SetChart(AValue: TChart);
|
|
procedure SetDragCursor(AValue: TCursor);
|
|
procedure SetFullExtentPen(AValue: TPen);
|
|
procedure SetLogicalExtentPen(AValue: TPen);
|
|
procedure SetMiniMap(AValue: Boolean);
|
|
procedure SetProportional(AValue: Boolean);
|
|
protected
|
|
procedure MouseDown(
|
|
AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer); override;
|
|
procedure MouseMove(AShift: TShiftState; AX, AY: Integer); override;
|
|
procedure MouseUp(
|
|
AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer); override;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure Paint; override;
|
|
published
|
|
property AllowDragNavigation: Boolean
|
|
read FAllowDragNavigation write FAllowDragNavigation default true;
|
|
property Chart: TChart read FChart write SetChart;
|
|
property DragCursor: TCursor read FDragCursor write SetDragCursor default crSizeAll;
|
|
property FullExtentPen: TPen read FFullExtentPen write SetFullExtentPen;
|
|
property LogicalExtentPen: TPen read FLogicalExtentPen write SetLogicalExtentPen;
|
|
property MiniMap: Boolean read FMiniMap write SetMiniMap default false;
|
|
property Proportional: Boolean read FProportional write SetProportional default false;
|
|
property Shift: TShiftState read FShift write FShift default [ssLeft];
|
|
published
|
|
property Align;
|
|
property BorderSpacing;
|
|
property Visible;
|
|
end;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Forms, SysUtils, TAGeometry;
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents(
|
|
CHART_COMPONENT_IDE_PAGE, [TChartNavScrollBar, TChartNavPanel]);
|
|
end;
|
|
|
|
{ TChartNavScrollBar }
|
|
|
|
procedure TChartNavScrollBar.ChartExtentChanged(ASender: TObject);
|
|
var
|
|
fe, le: TDoubleRect;
|
|
fw, lw: Double;
|
|
begin
|
|
Unused(ASender);
|
|
if Chart = nil then exit;
|
|
fe := Chart.GetFullExtent;
|
|
le := Chart.LogicalExtent;
|
|
if le = EmptyExtent then
|
|
le := fe;
|
|
case Kind of
|
|
sbHorizontal: begin
|
|
fw := fe.b.X - fe.a.X;
|
|
if fw <= 0 then
|
|
Position := 0
|
|
else
|
|
Position := Round(WeightedAverage(Min, Max, (le.a.X - fe.a.X) / fw));
|
|
lw := le.b.X - le.a.X;
|
|
end;
|
|
sbVertical: begin
|
|
fw := fe.b.Y - fe.a.Y;
|
|
if fw <= 0 then
|
|
Position := 0
|
|
else
|
|
Position := Round(WeightedAverage(Max, Min, (le.a.Y - fe.a.Y) / fw));
|
|
lw := le.b.Y - le.a.Y;
|
|
end;
|
|
end;
|
|
if AutoPageSize and not (csDesigning in ComponentState) then
|
|
PageSize := Round(lw / fw * (Max - Min));
|
|
end;
|
|
|
|
constructor TChartNavScrollBar.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FListener := TListener.Create(@FChart, @ChartExtentChanged);
|
|
end;
|
|
|
|
destructor TChartNavScrollBar.Destroy;
|
|
begin
|
|
FreeAndNil(FListener);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TChartNavScrollBar.Scroll(
|
|
AScrollCode: TScrollCode; var AScrollPos: Integer);
|
|
var
|
|
fe, le: TDoubleRect;
|
|
d, w: Double;
|
|
begin
|
|
inherited Scroll(AScrollCode, AScrollPos);
|
|
if Chart = nil then exit;
|
|
w := Max - Min;
|
|
if w = 0 then exit;
|
|
fe := Chart.GetFullExtent;
|
|
le := Chart.LogicalExtent;
|
|
if le = EmptyExtent then
|
|
le := fe;
|
|
case Kind of
|
|
sbHorizontal: begin
|
|
d := WeightedAverage(fe.a.X, fe.b.X, Position / w);
|
|
le.b.X += d - le.a.X;
|
|
le.a.X := d;
|
|
end;
|
|
sbVertical: begin
|
|
d := WeightedAverage(fe.b.Y, fe.a.Y, Position / w);
|
|
le.b.Y += d - le.a.Y;
|
|
le.a.Y := d;
|
|
end;
|
|
end;
|
|
Chart.LogicalExtent := le;
|
|
// Focused ScrollBar is glitchy under Win32, especially after PageSize change.
|
|
if (GetParentForm(Chart) <> nil) and GetParentForm(Chart).Active then
|
|
Chart.SetFocus;
|
|
end;
|
|
|
|
procedure TChartNavScrollBar.SetAutoPageSize(AValue: Boolean);
|
|
begin
|
|
if FAutoPageSize = AValue then exit;
|
|
FAutoPageSize := AValue;
|
|
ChartExtentChanged(Self);
|
|
end;
|
|
|
|
procedure TChartNavScrollBar.SetChart(AValue: TChart);
|
|
begin
|
|
if FChart = AValue then exit;
|
|
|
|
if FListener.IsListening then
|
|
FChart.ExtentBroadcaster.Unsubscribe(FListener);
|
|
FChart := AValue;
|
|
if FChart <> nil then
|
|
FChart.ExtentBroadcaster.Subscribe(FListener);
|
|
ChartExtentChanged(Self);
|
|
end;
|
|
|
|
{ TChartNavPanel }
|
|
|
|
procedure TChartNavPanel.ChartExtentChanged(ASender: TObject);
|
|
begin
|
|
Unused(ASender);
|
|
Invalidate;
|
|
end;
|
|
|
|
constructor TChartNavPanel.Create(AOwner: TComponent);
|
|
const
|
|
DEF_WIDTH = 40;
|
|
DEF_HEIGHT = 20;
|
|
begin
|
|
inherited Create(AOwner);
|
|
FListener := TListener.Create(@FChart, @ChartExtentChanged);
|
|
FFullExtentPen := TPen.Create;
|
|
FFullExtentPen.OnChange := @ChartExtentChanged;
|
|
FLogicalExtentPen := TPen.Create;
|
|
FLogicalExtentPen.OnChange := @ChartExtentChanged;
|
|
FLogicalExtentRect := ZeroRect;
|
|
Width := DEF_WIDTH;
|
|
Height := DEF_HEIGHT;
|
|
FAllowDragNavigation := true;
|
|
FDragCursor := crSizeAll;
|
|
FShift := [ssLeft];
|
|
end;
|
|
|
|
destructor TChartNavPanel.Destroy;
|
|
begin
|
|
FreeAndNil(FListener);
|
|
FreeAndNil(FFullExtentPen);
|
|
FreeAndNil(FLogicalExtentPen);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TChartNavPanel.MouseDown(
|
|
AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer);
|
|
begin
|
|
if (Chart <> nil) and AllowDragNavigation then begin
|
|
FPrevPoint := (DoublePoint(AX, Height - AY) - FOffset) / FScale;
|
|
FIsDragging :=
|
|
(AShift = Shift) and IsPointInRect(Point(AX, AY), FLogicalExtentRect);
|
|
if FIsDragging then begin
|
|
FOldCursor := Cursor;
|
|
Cursor := DragCursor;
|
|
end;
|
|
end;
|
|
inherited MouseDown(AButton, AShift, AX, AY);
|
|
end;
|
|
|
|
procedure TChartNavPanel.MouseMove(AShift: TShiftState; AX, AY: Integer);
|
|
var
|
|
p: TDoublePoint;
|
|
le: TDoubleRect;
|
|
begin
|
|
if (Chart <> nil) and FIsDragging then begin
|
|
p := (DoublePoint(AX, Height - AY) - FOffset) / FScale;
|
|
le := Chart.LogicalExtent;
|
|
le.a += p - FPrevPoint;
|
|
le.b += p - FPrevPoint;
|
|
Chart.LogicalExtent := le;
|
|
FPrevPoint := p;
|
|
end;
|
|
inherited MouseMove(AShift, AX, AY);
|
|
end;
|
|
|
|
procedure TChartNavPanel.MouseUp(
|
|
AButton: TMouseButton; AShift: TShiftState; AX, AY: Integer);
|
|
begin
|
|
if FIsDragging then
|
|
Cursor := FOldCursor;
|
|
FIsDragging := false;
|
|
inherited MouseUp(AButton, AShift, AX, AY);
|
|
end;
|
|
|
|
procedure TChartNavPanel.Paint;
|
|
|
|
function GraphRect(ARect: TDoubleRect): TRect;
|
|
begin
|
|
ARect.a := ARect.a * FScale + FOffset;
|
|
ARect.b := ARect.b * FScale + FOffset;
|
|
Result := Rect(
|
|
Round(ARect.a.X), Height - Round(ARect.b.Y), Round(ARect.b.X), Height - Round(ARect.a.Y));
|
|
end;
|
|
|
|
var
|
|
fe, le, ext: TDoubleRect;
|
|
sz: TDoublePoint;
|
|
oldAxisVisible: Boolean;
|
|
feRect: TRect;
|
|
begin
|
|
if Chart = nil then exit;
|
|
fe := Chart.GetFullExtent;
|
|
le := Chart.LogicalExtent;
|
|
if le = EmptyExtent then
|
|
le := fe;
|
|
ext := fe;
|
|
ExpandRect(ext, le.a);
|
|
ExpandRect(ext, le.b);
|
|
sz := ext.b - ext.a;
|
|
if (sz.X <= 0) or (sz.Y <= 0) then exit;
|
|
FScale := DoublePoint(Width, Height) / sz;
|
|
FOffset := ZeroDoublePoint;
|
|
if Proportional then begin
|
|
if FScale.X < FScale.Y then begin
|
|
FScale.Y := FScale.X;
|
|
FOffset.Y := (Height - sz.Y * FScale.Y) / 2;
|
|
end
|
|
else begin
|
|
FScale.X := FScale.Y;
|
|
FOffset.X := (Width - sz.X * FScale.X) / 2;
|
|
end;
|
|
end;
|
|
FOffset -= ext.a * FScale;
|
|
|
|
feRect := GraphRect(fe);
|
|
if MiniMap then begin
|
|
oldAxisVisible := Chart.AxisVisible;
|
|
Chart.AxisVisible := false;
|
|
Chart.PaintOnAuxCanvas(Canvas, feRect);
|
|
Chart.AxisVisible := oldAxisVisible;
|
|
end
|
|
else begin
|
|
Canvas.Brush.Color := Chart.BackColor;
|
|
Canvas.Brush.Style := bsSolid;
|
|
Canvas.FillRect(ClientRect);
|
|
end;
|
|
Canvas.Brush.Style := bsClear;
|
|
Canvas.Pen := FullExtentPen;
|
|
Canvas.Rectangle(feRect);
|
|
Canvas.Pen := LogicalExtentPen;
|
|
FLogicalExtentRect := GraphRect(le);
|
|
Canvas.Rectangle(FLogicalExtentRect);
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetChart(AValue: TChart);
|
|
begin
|
|
if FChart = AValue then exit;
|
|
|
|
if FListener.IsListening then
|
|
FChart.ExtentBroadcaster.Unsubscribe(FListener);
|
|
FChart := AValue;
|
|
if FChart <> nil then
|
|
FChart.ExtentBroadcaster.Subscribe(FListener);
|
|
ChartExtentChanged(Self);
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetDragCursor(AValue: TCursor);
|
|
begin
|
|
if FDragCursor = AValue then exit;
|
|
FDragCursor := AValue;
|
|
if MouseCapture then
|
|
Cursor := FDragCursor;
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetFullExtentPen(AValue: TPen);
|
|
begin
|
|
if FFullExtentPen = AValue then exit;
|
|
FFullExtentPen.Assign(AValue);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetLogicalExtentPen(AValue: TPen);
|
|
begin
|
|
if FLogicalExtentPen = AValue then exit;
|
|
FLogicalExtentPen.Assign(AValue);
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetMiniMap(AValue: Boolean);
|
|
begin
|
|
if FMiniMap = AValue then exit;
|
|
FMiniMap := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
procedure TChartNavPanel.SetProportional(AValue: Boolean);
|
|
begin
|
|
if FProportional = AValue then exit;
|
|
FProportional := AValue;
|
|
Invalidate;
|
|
end;
|
|
|
|
end.
|
|
|