{********************************************}
{ TDragPointTool and Editor Dialog           }
{ Copyright (c) 2000-2020 by Steema Software }
{********************************************}
unit TeeDragPoint;
{$I TeeDefs.inc}

interface

uses {$IFDEF LINUX}
     //Libc,
     {$ELSE}
     {$IFNDEF FMX}
     Windows, Messages,
     {$ENDIF}
     {$ENDIF}
     SysUtils, Classes,

     {$IFDEF FMX}
     FMX.Types, System.UITypes,
     {$ELSE}
     Graphics, Controls,
     {$ENDIF}

     {$IFDEF FMX}
     FMXTee.Canvas, FMXTee.Engine, FMXTee.Tools
     {$ELSE}
     TeEngine, TeeTools, TeCanvas
     {$ENDIF}
     ;

{ DragPoint: A TeeChart Tool example.

  This tool component can be used to allow the end-user to
  drag points of any Chart Series by using the mouse.

  This unit also includes a Form that is used as the editor dialog
  for the tool. This form automatically shows at the Tools tab of the
  TeeChart editor.

  Installation / use:

  1) Add this unit to a project or to a design-time package.

  2) Then, edit a Chart1 and go to "Tools" tab and Add a DragPoint tool.

  3) Set the tool Series property to a existing series (eg: Series1 ).

  4) Fill the series with points as usually.

  5) At run-time, use the left mouse button to click and drag a
     Series point.

}

type
  TDragPointTool=class;

  TDragPointToolEvent=procedure(Sender:TDragPointTool; Index:Integer) of object;
  TDragPointStartEvent=procedure(Sender:TDragPointTool; Index:Integer; var Start:Boolean) of object;

  TDragPointStyle=(dsX,dsY,dsBoth); // Pending: dsZ for 3D series

  { Drag Point Tool }
  TDragPointTool=class(TTeeCustomToolSeries)
  private
    FDragStyle   : TDragPointStyle;
    FOnDrag      : TDragPointToolEvent;
    FOnEndDrag   : TDragPointToolEvent;
    FOnStartDrag : TDragPointStartEvent;

    IDragging : Integer;
  protected
    Procedure ChartMouseEvent( AEvent: TChartMouseEvent;
                               Button:TMouseButton;
                               Shift: TShiftState; X, Y: Integer); override;
    class Function GetEditorClass:String; override;
  public
    Constructor Create(AOwner:TComponent); override;
    class Function Description:String; override;
    class Function LongDescription:String; override;
  published
    property Active;
    property DragStyle:TDragPointStyle read FDragStyle write FDragStyle
                                       default dsBoth;
    property Series;

    { events }
    property OnDragPoint:TDragPointToolEvent read FOnDrag write FOnDrag;
    property OnEndDrag:TDragPointToolEvent read FOnEndDrag write FOnEndDrag;
    property OnStartDrag:TDragPointStartEvent read FOnStartDrag write FOnStartDrag;
  end;

implementation

uses
  {$IFDEF FMX}
  FMXTee.Chart, FMXTee.ProConstants
  {$ELSE}
  Chart, TeeProCo
  {$ENDIF}
  ;

{ TDragPointTool }
Constructor TDragPointTool.Create(AOwner: TComponent);
begin
  inherited;
  IDragging:=-1;
  FDragStyle:=dsBoth;
end;

{ When the user clicks or moves the mouse... }
procedure TDragPointTool.ChartMouseEvent(AEvent: TChartMouseEvent;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

  Function CheckAxisLimits(const Axis:TChartAxis; Position:Integer):Double;
  begin
    with Axis do
    if Maximum=Minimum then
    begin
      AutomaticMaximum:=False;
      Maximum:=Maximum+0.1*MinAxisIncrement;
    end;

    result:=Axis.CalcPosPoint(Position);
  end;

var tmpStart : Boolean;
begin
  if Assigned(Series) then
  Case AEvent of
      cmeUp: if IDragging<>-1 then
             begin
               if Assigned(FOnEndDrag) then
                  FOnEndDrag(Self,IDragging);

               IDragging:=-1;  { stop dragging on mouse up  }
             end;

    cmeMove: if IDragging<>-1 then { if dragging... }
             begin
               // Some series like TeePolar implement this interface to convert
               // mouse xy pixels to Angle & Radius values:
               if Supports(Series,ITeeScreenToPoint) then
                   (Series as ITeeScreenToPoint).ScreenToPoint(x,y,IDragging,
                                     (DragStyle=dsX) or (DragStyle=dsBoth),
                                     (DragStyle=dsY) or (DragStyle=dsBoth)
                                    )
               else
               with Series do
               begin
                 BeginUpdate;
                 try
                   { Change the Series point X and / or Y values... }
                   if (Self.DragStyle=dsX) or (Self.DragStyle=dsBoth) then
                      XValue[IDragging]:=CheckAxisLimits(GetHorizAxis,x);

                   if (Self.DragStyle=dsY) or (Self.DragStyle=dsBoth) then
                      YValue[IDragging]:=CheckAxisLimits(GetVertAxis,y);

                 finally
                   EndUpdate;
                 end;
               end;

               { Notify the point values change... }
               if Assigned(FOnDrag) then
                  FOnDrag(Self,IDragging);
             end;

    cmeDown: begin { when the mouse button is pressed... }
               IDragging:=Series.Clicked(x,y);

               { if clicked a Series point... }
               if IDragging<>-1 then
               begin
                 if Assigned(FOnStartDrag) then
                 begin
                   tmpStart:=True;
                   FOnStartDrag(Self,IDragging,tmpStart);

                   ParentChart.CancelMouse:=tmpStart;

                   if not tmpStart then
                      IDragging:=-1;
                 end
                 else
                   ParentChart.CancelMouse:=True;
               end;
             end;
  end;
end;

class function TDragPointTool.Description: String;
begin
  result:=TeeMsg_DragPoint;
end;

class function TDragPointTool.GetEditorClass: String;
begin
  result:='TDragPointToolEdit';  // Do not localize
end;

class function TDragPointTool.LongDescription: String;
begin
  result:=TeeMsg_DragPointDesc;
end;

initialization
  RegisterTeeTools([TDragPointTool]);
finalization
  UnRegisterTeeTools([TDragPointTool]);
end.
