{********************************************}
{   TeeChart VCL Library                     }
{ Copyright (c) 1995-2020 by Steema Software }
{        All Rights Reserved                 }
{********************************************}
unit TeeChartGrid;
{$I TeeDefs.inc}

interface

Uses {$IFNDEF FMX}
     {$IFNDEF LINUX}
     Windows, Messages,
     {$ENDIF}
     {$ENDIF}

     {$IFDEF FMX}
     FMX.Types, FMX.Grid, FMX.Menus,

     {$IFDEF D19}
     FMX.Graphics, // Hint
     {$ENDIF}
     
     {$IFDEF D24}
     // Required for 10.1 Berlin
     FMX.Grid.Style, FMX.Presentation.Factory, FMX.Presentation.Style,
     {$ENDIF}

     {$IFDEF D16}
     System.Rtti,
     {$ENDIF}
     
     {$IFDEF D17}
     System.UIConsts,
     {$ENDIF}

     {$IFDEF D18}
     FMX.Controls,
     {$ENDIF}

     {$IFDEF D24}
     FMX.Controls.Model,
     {$ENDIF}

     {$ELSE}
     Controls, Grids, Graphics, ExtCtrls, Menus, Forms,
     {$ENDIF}

     {$IFDEF D8}
     Types,
     Variants,
     {$ENDIF}

     Classes,

     {$IFDEF FMX}
     FMXTee.Chart, FMXTee.Engine, FMXTee.Procs, FMXTee.Canvas
     {$ELSE}
     Chart, TeEngine, TeeProcs, TeCanvas, TeeNavigator
     {$ENDIF}
     ;

type
  TChartGridNavigator=class {$IFDEF FMX}(TComponent) end{$ENDIF};

  TChartGridShow=(cgsAuto,cgsNo,cgsYes);

  TCustomChartGrid=class;

  TEditingCellEvent=procedure (Sender: TCustomChartGrid; ACol, ARow: Integer;
                               var Allow:Boolean) of object;

  TCustomChartGrid=class(TCustomGrid,ITeeEventListener)
  private
    FAllowAppend : Boolean;
    FAllowInsertSeries : Boolean;
    FChart       : TCustomChart;
    FColors      : Boolean;
    FGradientCell: TChartGradient;
    FGrid3DMode  : Boolean;
    FLabels      : Boolean;
    FSymbolClickable : Boolean;
    FVisuals     : TChartGridShow;
    FXValues     : TChartGridShow;

    FOldValue    : String;
    FOnChangeColor : TNotifyEvent;
    FOnEditing   : TEditingCellEvent;
    FOnSetCell   : {$IFDEF FMX}TOnSetValue{$ELSE}TSetEditEvent{$ENDIF};
    FShowFields  : Boolean;
    FWasNull     : Boolean;

    IHasNo       : Array of Boolean;
    INavigator   : TChartGridNavigator;
    IPopupVisual : TPopupMenu;
    FSeries      : TChartSeries;
    ISeriesChart : TCustomAxisPanel;

    Procedure AddListener(const AChart:TCustomAxisPanel);
    function AtSeriesColor(var Series:TChartSeries):Boolean;
    function CanInsertSeries:Boolean;
    Procedure CanvasChanged(Sender:TObject); virtual;

    {$IFDEF FMX}
    procedure AddColumns(const ACount:Integer);
    function Col:Integer;
    function ColCount:Integer;
    function Row:Integer;
    {$ENDIF}

    procedure DoChangeVisual(const ASeries:TChartSeries; AIndex:Integer);
    function FindHeaderText(ACol:Integer):String;
    Function GetGradientCell:TChartGradient;

    {$IFNDEF FMX}
    function GetReadOnly:Boolean;
    {$ENDIF}

    procedure MenuChangeVisual(Sender:TObject);
    procedure MenuDeleteVisual(Sender:TObject);
    procedure MenuEditVisual(Sender:TObject);
    function NewBarSeries:TChartSeries;
    procedure NotifyChange;
    procedure Regenerate;
    Procedure RemoveListener(const AChart:TCustomAxisPanel);
    Procedure SetBooleanProperty(Var Variable:Boolean; Value:Boolean);
    Procedure SetChart(const AChart:TCustomChart);
    Procedure SetGradientCell(const Value:TChartGradient);
    Procedure SetGrid3DMode(Value:Boolean);
    Procedure SetManualData(const ASeries:TChartSeries);

    {$IFNDEF FMX}
    Procedure SetNavigator(const ANavigator:TChartGridNavigator);
    procedure SetReadOnly(const Value:Boolean);
    function SymbolRect(const Rect:TRect):TRect;
    {$ENDIF}

    Procedure SetShowColors(Value:Boolean);
    Procedure SetShowFields(Value:Boolean);
    Procedure SetShowLabels(Value:Boolean);
    Procedure SetShowVisuals(const Value:TChartGridShow);
    Procedure SetShowXValues(const Value:TChartGridShow);
    procedure SetSeries(const Value: TChartSeries);
    function ValidListsOfSeries(const ASeries:TChartSeries; const AIndex:Integer):Integer;
  protected
    FActiveChanged   : TNotifyEvent;
    FSelectedChanged : {$IFDEF FMX}TNotifyEvent{$ELSE}{$IFDEF LCL}TOnSelectCellEvent{$ELSE}TSelectCellEvent{$ENDIF}{$ENDIF};
    FOnTopLeftChanged: TNotifyEvent;

    {$IFNDEF LCL}
    {$IFNDEF FMX}
    function CanEditModify: Boolean; override;
    {$ENDIF}
    function CanEditShow: Boolean; {$IFNDEF FMX}override;{$ENDIF}
    {$ENDIF}

    procedure DblClick; override;

    {$IFDEF FMX}
    {$IFNDEF D17}
    procedure ApplyStyle; override;
    {$ENDIF}

    {$IFDEF D24}
    function DefineModelClass: TDataModelClass; override;
    {$ENDIF}

    function GetValue(ACol, ARow: Integer): {$IFDEF D17}TValue{$ELSE}Variant{$ENDIF}; {$IFNDEF D24}override;{$ENDIF}

    {$ELSE}
    procedure DrawCell(ACol, ARow: Integer; ARect: TRect;
                       AState: TGridDrawState); override;
    function GetEditText(ACol, ARow: Integer): String; override;
    {$ENDIF}

    Function GetSeriesColor(out AColor:TColor):TChartSeries; overload;
    Function GetSeriesColor(out AColor:TColor; ACol,ARow:Integer):TChartSeries; overload;

    procedure EditVisual(ACol:Integer);
    Function HasPoints:Boolean;
    function HasVisuals(const ASeries:TChartSeries):Boolean;

    procedure KeyDown(var Key: Word; {$IFDEF FMX}var KeyChar: System.WideChar;{$ENDIF} Shift: TShiftState); override;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;

    {$IFNDEF FMX}
    Procedure Loaded; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: TCoordinate); override;

    procedure MouseMove(Shift: TShiftState; X, Y: TCoordinate); override;
    function SelectCell(ACol, ARow: Integer): Boolean; override;
    {$ENDIF}

    {$IFDEF FMX}
    procedure SetValue(ACol, ARow: Integer; const Value: {$IFDEF D17}TValue{$ELSE}Variant{$ENDIF}); {$IFNDEF D24}override;{$ENDIF}
    {$ELSE}
    procedure SetEditText(ACol, ARow: Integer; const Value: String); override;
    {$ENDIF}

    procedure TeeEvent(Event: TTeeEvent);

    Function IsVisualsColumn(ACol:Integer):Boolean;

    {$IFNDEF FMX}
    procedure TopLeftChanged; override;
    {$ENDIF}
  public
    { Allow changing cells even if Series.DataSource is assigned }
    AllowChanges : Boolean;

    Constructor Create(AOwner:TComponent); override;
    Destructor Destroy; override;

    Procedure AppendRow;

    Procedure ChangeColor; overload;
    Procedure ChangeColor(AColor:TColor); overload;
    Procedure ChangeColor(const ASeries:TChartSeries; AColor:TColor); overload;

    Function ColorsColumn:Integer;
    Procedure Delete;
    Function FindXYZIndex(const ASeries:TChartSeries; ARow,ACol:Integer):Integer;
    Function FirstRowNum:Integer;

    Function GetSeries(ACol:Integer):TChartSeries; overload;
    Function GetSeries(ACol:Integer; out AList:TChartValueList):TChartSeries; overload;

    Function GetXYZSeries:TChartSeries;
    Procedure Insert;
    Function LabelsColumn:Integer;

    Procedure RecalcDimensions;

    {$IFDEF TEEOCX}
    procedure SetFocus; override;
    {$ENDIF}

    Procedure StartEditing;
    Procedure StopEditing(Cancel:Boolean);

    property AllowAppend:Boolean read FAllowAppend write FAllowAppend default True;
    property AllowInsertSeries:Boolean read FAllowInsertSeries write FAllowInsertSeries default False;
    property Chart:TCustomChart read FChart write SetChart;
    property GradientCell:TChartGradient read GetGradientCell write SetGradientCell;

    {$IFNDEF FMX}
    property ReadOnly:Boolean read GetReadOnly write SetReadOnly default False;
    {$ENDIF}

    property Series:TChartSeries read FSeries write SetSeries;
    property SeriesSymbolClickable:Boolean read FSymbolClickable write FSymbolClickable default True;
    property ShowColors:Boolean read FColors write SetShowColors default False;
    property ShowFields:Boolean read FShowFields write SetShowFields default True;
    property ShowLabels:Boolean read FLabels write SetShowLabels default True;
    property ShowVisuals:TChartGridShow read FVisuals write SetShowVisuals default cgsAuto;
    property ShowXValues:TChartGridShow read FXValues write SetShowXValues default cgsAuto;

    {$IFNDEF FMX}
    property FixedCols default 1;
    property DefaultRowHeight default {$IFDEF LINUX}20{$ELSE}16{$ENDIF};
    property GridLineWidth default 1;
    {$ENDIF}

    property Grid3DMode:Boolean read FGrid3DMode write
             SetGrid3DMode default False; // for XYZ series, set x by z grid mode

    { events }
    property OnChangeColor:TNotifyEvent read FOnChangeColor write FOnChangeColor;
    property OnEditingCell:TEditingCellEvent read FOnEditing write FOnEditing;
    property OnSetEditText:{$IFDEF FMX}TOnSetValue{$ELSE}TSetEditEvent{$ENDIF} read FOnSetCell write FOnSetCell;
    property OnTopLeftChanged:TNotifyEvent read FOnTopLeftChanged write FOnTopLeftChanged;
  end;

  {$IFDEF D16}
  [ComponentPlatformsAttribute(TeeAllComponentPlatformIDs)]
  {$ENDIF}
  TChartGrid=class(TCustomChartGrid)
  public
    {$IFNDEF FMX}
    property Col;
    property ColCount;
    property ColWidths;
    property EditorMode;
    property GridHeight;
    property GridWidth;
    property LeftCol;
    property Selection;
    property Row;
    {$ENDIF}

    property RowCount;

    {$IFNDEF FMX}
    property RowHeights;
    {$IFNDEF LCL}
    property TabStops;
    {$ENDIF}
    {$ENDIF}

    property TopRow;
  published
    property Align;

    {$IFDEF FMX}

    {$IFNDEF D18}
    property Animated;  // Removed after XE3
    {$ENDIF}

    property AutoHide;
    property OnGetValue;
    property OnSetValue;
    {$ENDIF}

    {$IFDEF FMX}

    {$IFDEF D17}
    property Action;
    property Anchors;
    {$ENDIF}

    {$IFDEF D24}
    property Height;
    property Position;
    property Size;
    property StyleLookup;
    property TabOrder;
    property Width;
    {$ENDIF}

    {$ELSE}
    property Anchors;
    {$ENDIF}

    {$IFDEF FMX}
    {$IFDEF D23}
    property ParentShowHint;
    property ShowHint;
    {$ENDIF}
    {$ELSE}

    {$IFNDEF LCL}
    property BiDiMode;
    {$ENDIF}

    property BorderStyle;
    property Color;
    property Constraints;
    {$IFNDEF LCL}
    property Ctl3D;
    {$ENDIF}
    property DefaultColWidth;
    property DefaultRowHeight;
    property DefaultDrawing;

    property DragMode;
    property DragCursor;
    property DragKind;
    property Enabled;
    property FixedColor;
    property Font;
    property GridLineWidth;

    {$IFDEF FMX}
    {$IFDEF D24}
    property Height;
    property Width;
    {$ENDIF}
    {$ENDIF}

    property Options;

    {$IFNDEF LCL}
    property ParentBiDiMode;
    {$ENDIF}

    property ParentColor;
    {$IFNDEF LCL}
    property ParentCtl3D;
    {$ENDIF}
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ScrollBars;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;

    { events }
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnStartDrag;
    property OnEndDock;
    property OnEnter;
    property OnExit;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;

    {$IFDEF D10}
    property OnMouseEnter;
    property OnMouseLeave;
    {$ENDIF}

    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnStartDock;
    {$ENDIF}

    property Chart;
    property Series;
    property ShowColors;
    property ShowLabels;
    property ShowVisuals;
    property ShowXValues;

    { event }
    property OnChangeColor;
    property OnEditingCell;
    property OnSetEditText;
    property OnTopLeftChanged;
  end;

  {$IFNDEF FMX}
  {$IFDEF D16}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64)]
  {$ENDIF}
  TChartGridNavigator=class(TCustomTeeNavigator)
  private
    FGrid : TCustomChartGrid;
    procedure ActiveChanged(Sender:TObject);
    procedure EnableButtonsColRow(ACol,ARow:Integer);
    procedure SelectedChanged(Sender: TObject; ACol, ARow: Integer;
                              var CanSelect: Boolean);
    procedure SetGrid(Value:TCustomChartGrid);
  protected
    procedure BtnClick(Index: TTeeNavigateBtn); override;
    procedure Notification( AComponent: TComponent;
                            Operation: TOperation); override;
  public
    procedure EnableButtons; override;
  published
    property Grid:TCustomChartGrid read FGrid write SetGrid;
  end;
  {$ENDIF}

implementation

uses
  Math, SysUtils,

  {$IFDEF FMX}
  FMX.Platform, FMXTee.Constants, FMXTee.Editor.Stroke, FMXTee.Series,
  FMX.Objects
  {$ELSE}

  {$IFDEF LCL}
  LCLType,
  {$ENDIF}

  TeeConst, TeePenDlg, Series
  {$ENDIF}
  ;

{$IFNDEF FMX}

{ TChartGridNavigator }
procedure TChartGridNavigator.Notification( AComponent: TComponent;
                                        Operation: TOperation);
begin
  inherited;
  if (Operation=opRemove) and Assigned(Grid) and (AComponent=Grid) then
     Grid:=nil;
end;

procedure TChartGridNavigator.SetGrid(Value:TCustomChartGrid);
begin
  if FGrid<>Value then
  begin
    if Assigned(FGrid) then
       FGrid.RemoveFreeNotification(Self);

    FGrid:=Value;

    if Assigned(FGrid) then
    begin
      FGrid.FreeNotification(Self);
      FGrid.FActiveChanged:=ActiveChanged;
      FGrid.FSelectedChanged:=SelectedChanged;
      FGrid.SetNavigator(Self);
    end;

    EnableButtons;
  end;
end;

procedure TChartGridNavigator.ActiveChanged(Sender:TObject);
begin
  EnableButtons;
end;

procedure TChartGridNavigator.BtnClick(Index: TTeeNavigateBtn);
begin
  if Assigned(FGrid) then
  with FGrid do
  begin
    case Index of
      nbPrior : if Row>FirstRowNum then Row:=Row-1;
      nbNext  : if Row<RowCount-1 then Row:=Row+1;
      nbFirst : if Row>FirstRowNum then Row:=FirstRowNum;
      nbLast  : if Row<RowCount-1 then Row:=RowCount-1;
      nbInsert: Insert;
      nbEdit  : begin
                  SetFocus;
                  StartEditing;
                end;
      nbCancel: StopEditing(True);
      nbPost  : StopEditing(False);
      nbDelete: Delete;
    end;
  end;
  inherited;
end;

procedure TChartGridNavigator.EnableButtons;
begin
  inherited;
  if Assigned(FGrid) then EnableButtonsColRow(FGrid.Col,FGrid.Row);
end;

procedure TChartGridNavigator.EnableButtonsColRow(ACol,ARow:Integer);
var UpEnable : Boolean;
    DnEnable : Boolean;
begin
  UpEnable := ARow>FGrid.FirstRowNum;
  DnEnable := ARow<FGrid.RowCount-1;
  Buttons[nbFirst].Enabled  := UpEnable;
  Buttons[nbPrior].Enabled  := UpEnable;
  Buttons[nbNext].Enabled   := DnEnable;
  Buttons[nbLast].Enabled   := DnEnable;
  Buttons[nbInsert].Enabled := (goEditing in FGrid.Options) and
                               (not FGrid.EditorMode) and
                               (Assigned(FGrid.Chart) and (FGrid.Chart.SeriesCount>0)) or
                               Assigned(FGrid.Series);

  Buttons[nbDelete].Enabled := (FGrid.RowCount>1) and
                               Buttons[nbInsert].Enabled and
                               FGrid.HasPoints;
  Buttons[nbPost].Enabled   := FGrid.EditorMode;
  Buttons[nbEdit].Enabled   := Buttons[nbInsert].Enabled;
  Buttons[nbCancel].Enabled := FGrid.EditorMode;
end;

procedure TChartGridNavigator.SelectedChanged(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
  EnableButtonsColRow(ACol,ARow);
end;
{$ENDIF}

{ TCustomChartGrid }
Constructor TCustomChartGrid.Create(AOwner:TComponent);
begin
  inherited;

  {$IFNDEF FMX}
  {$IFNDEF LCL}
  FSaveCellExtents:=False;
  {$ENDIF}
  {$ENDIF}

  FSymbolClickable:=True;
  FColors:=False;
  FLabels:=True;
  FXValues:=cgsAuto;
  FShowFields:=True;
  FVisuals:=cgsAuto;
  FWasNull:=False;

  {$IFNDEF FMX}
  GridLineWidth:=1;
  ColWidths[0]:=30;
  DefaultRowHeight:={$IFDEF LINUX}20{$ELSE}16{$ENDIF};
  FixedCols:=1;
  RowHeights[0]:=20;

  Options:=[ goHorzLine,
             goVertLine,
             goRangeSelect,
             goDrawFocusSelected,
             goRowSizing,
             goColSizing,
             goEditing,
             goTabs,
             goThumbTracking
           ];

  {$ENDIF}

  FAllowAppend:=True;

  {$IFDEF FMX}

  {$IFDEF D18}
  StyleLookup:= 'gridstyle';
  {$ELSE}
  FStyleLookup:= 'gridstyle';
  {$ENDIF}

  if Assigned(Owner) and (csLoading in Owner.ComponentState) then
     Exit;  // Avoid "duplicate column name" exception when loading 
  {$ENDIF}

  RecalcDimensions;
end;

Destructor TCustomChartGrid.Destroy;
begin
  Series:=nil;
  Chart:=nil;
  IHasNo:=nil;
  FGradientCell.Free;

  inherited;
end;

{$IFDEF FMX}
{$IFNDEF D17}
procedure TCustomChartGrid.ApplyStyle;
var r : TRectangle;
begin
  inherited;

  r:=FindStyleResource('background') as TRectangle;
  if Assigned(r) then
     r.Fill.Color:=claWhite;
end;
{$ENDIF}
{$ELSE}
Procedure TCustomChartGrid.Loaded;
begin
  inherited;
  RowHeights[0]:=20;
end;
{$ENDIF}

Function TCustomChartGrid.HasPoints:Boolean;
var t: Integer;
begin
  result:=False;

  if Assigned(FSeries) and (not (csDestroying in FSeries.ComponentState)) then { single series }
     result:=FSeries.Count>0
  else
  if Assigned(Chart) then { all series in chart }
  With Chart do
  for t:=0 to SeriesCount-1 do
  if (not (csDestroying in Series[t].ComponentState)) and (Series[t].Count>0) then
  begin
    result:=True;
    Exit;
  end;
end;

Function TCustomChartGrid.FirstRowNum:Integer;
begin
  if ShowFields then result:=2 else result:=1;

  {$IFDEF FMX}
  Dec(result,2);
  {$ENDIF}
end;

{$IFNDEF FMX}
function TCustomChartGrid.SelectCell(ACol, ARow: Integer): Boolean;
begin
  result:=inherited SelectCell(ACol,ARow) and (ACol>0) and (ARow>=FirstRowNum);
  // Call event
  if result and Assigned(FSelectedChanged) then
     FSelectedChanged(Self,ACol,ARow,result);
end;
{$ENDIF}

{$IFNDEF LCL}
{$IFNDEF FMX}
function TCustomChartGrid.CanEditModify: Boolean;
begin
  result:=inherited CanEditModify;

  if result then
     if Assigned(INavigator) then
        INavigator.ActiveChanged(Self);
end;
{$ENDIF}
{$ENDIF}

{$IFNDEF FMX}
procedure TCustomChartGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
                                     X, Y: TCoordinate);

  Procedure SelectColumn;
  var tmpCoord     : TGridCoord;
      tmpSelection : TGridRect;
  begin
    tmpCoord:=MouseCoord(x,y);

    if (tmpCoord.X>FixedCols) and (tmpCoord.Y<FixedRows) then
    begin
      // Select full column
      with tmpSelection do
      begin
        Left:=tmpCoord.X;
        Right:=Left;
        Top:=FixedRows;
        Bottom:=RowCount-1;
      end;

      // Unselect column if it was previously full selected
      if (tmpSelection.Left=Selection.Left) and
         (tmpSelection.Top=Selection.Top) and
         (tmpSelection.Right=Selection.Right) and
         (tmpSelection.Bottom=Selection.Bottom) then
      begin
        tmpSelection.Top:=Row;
        tmpSelection.Bottom:=Row;
      end;

      Selection:=tmpSelection;
    end;
  end;

var tmp : TChartSeries;
begin
  inherited;

  if (FGridState=gsNormal) and (Button=mbLeft) then
     if not AtSeriesColor(tmp) then
        SelectColumn;

  if Assigned(INavigator) then
     INavigator.ActiveChanged(Self);
end;

procedure TCustomChartGrid.MouseMove(Shift: TShiftState; X, Y: TCoordinate);
var tmpColor  : TColor;
    tmpCoord  : TGridCoord;
    tmpSeries : TChartSeries;
    tmpR      : TRect;
begin
  if goEditing in Options then
  begin
    tmpCoord:=MouseCoord(x,y);

    if ShowColors then
    begin
      tmpR:=CellRect(tmpCoord.X,tmpCoord.Y);

      if (GetSeriesColor(tmpColor,tmpCoord.X,tmpCoord.Y)<>nil) and
          PointInRect(tmpR,TeePoint(x,y)) then
         Cursor:=crHandPoint
      else
         Cursor:=crDefault;
    end
    else
    if tmpCoord.Y=0 then
    begin
      tmpSeries:=GetSeries(tmpCoord.X);

      if Assigned(tmpSeries) then
      begin
        tmpR:=SymbolRect(CellRect(tmpCoord.X,tmpCoord.Y));

        if PointInRect(tmpR,TeePoint(x,y)) and SeriesSymbolClickable then
           Cursor:=crHandPoint
        else
           Cursor:=crDefault;
      end;
    end;
  end;

  inherited;
end;
{$ENDIF}

Procedure TCustomChartGrid.StopEditing(Cancel:Boolean);
begin
  {$IFNDEF FMX}
  if EditorMode then
  begin
    {$IFNDEF LCL}
    if Cancel then InplaceEditor.Text:='';

    HideEditor;
    {$ENDIF}

    if Cancel and (not FWasNull) then
       SetEditText(Col,Row,FOldValue);

    NotifyChange;
  end;
  {$ENDIF}
end;

{$IFDEF TEEOCX}
procedure TCustomChartGrid.SetFocus;
begin
//  if GetParentForm(Self)<>nil then inherited
//                              else
                              Windows.SetFocus(Handle);
end;
{$ENDIF}

{$IFDEF FMX}
function TCustomChartGrid.Col:Integer;
begin
  result:=ColumnIndex;
end;

function TCustomChartGrid.ColCount:Integer;
begin
  result:=ColumnCount;
end;

function TCustomChartGrid.Row:Integer;
begin
  result:=Selected;
end;
{$ENDIF}

Procedure TCustomChartGrid.StartEditing;

  function GetCellValue:String;
  begin
    {$IFDEF FMX}
    result:=GetValue(Col,Row){$IFDEF D17}.ToString{$ENDIF}
    {$ELSE}
    result:=GetEditText(Col,Row)
    {$ENDIF}
  end;

var tmpSeries : TChartSeries;
begin
  {$IFNDEF FMX}
  if (goEditing in Options) and (not EditorMode) then
  {$ENDIF}
  begin
    if Col>0 then
    begin
      tmpSeries:=GetSeries(Col);

      if Assigned(tmpSeries) then
      with tmpSeries do
      if (DataSource=nil) or Self.AllowChanges then
      begin
        if Grid3DMode then
           FWasNull:=Row>tmpSeries.MaxZValue
        else
           FWasNull:=(Row>Count) or (IsNull(Row-1));

        if FWasNull then FOldValue:=''
                    else FOldValue:=GetCellValue;

        {$IFNDEF FMX}
        EditorMode:=True;
        {$ENDIF}

        NotifyChange;
      end;
    end;
  end;
end;

procedure TCustomChartGrid.KeyDown(var Key: Word; {$IFDEF FMX}var KeyChar: System.WideChar;{$ENDIF} Shift: TShiftState);
var tmpSeries : TChartSeries;
    tmpCol    : Integer;
    tmpRow    : Integer;
begin
  if ssCtrl in Shift then
     if (Key=TeeKey_Delete) {$IFNDEF FMX}and (not EditorMode){$ENDIF} then
        Delete
     else
        inherited
  else
  begin
    Case Key of
      TeeKey_Escape : StopEditing(True);

      TeeKey_F2     : if FColors and (Col=ColorsColumn) then
                         ChangeColor
                      else
                      if IsVisualsColumn(Col) then
                         EditVisual(Col)
                      else
                         StartEditing;

      TeeKey_Up     :
            if (Row=RowCount-1) then  // last row
            begin
              if FColors and (Col=ColorsColumn) then tmpCol:=Succ(Col)
                                                else tmpCol:=Col;

              tmpSeries:=GetSeries(tmpCol);
              tmpRow:=Row-FirstRowNum;

              if Assigned(tmpSeries) and
                 (tmpSeries.Count>tmpRow) and
                 tmpSeries.IsNull(tmpRow) and
                 (tmpSeries.Labels[tmpRow]='') then
              begin
                RowCount:=RowCount-1;
                tmpSeries.Delete(Row-FirstRowNum+1);
              end
              else
                inherited;
            end
            else
              inherited;

      TeeKey_Down :
            begin
              if Row=RowCount-1 then AppendRow
                                else inherited;
            end;

      TeeKey_Insert : Insert;

      TeeKey_Delete : if ssCtrl in Shift then Delete
                      else
                      begin
                        {$IFNDEF FMX}
                        if not EditorMode then EditorMode:=True;
                        {$ENDIF}

                        inherited;
                      end;
      TeeKey_Return,
      TeeKey_Space  : if FColors and (Col=ColorsColumn) then
                         ChangeColor
                      else
                      if IsVisualsColumn(Col) then
                         EditVisual(Col)
                      else
                         inherited;

      TeeKey_Right : if CanInsertSeries and
                        (Col=ColCount-1) and
                        Assigned(Chart) and (Chart.SeriesCount>0) then
                     begin
                       tmpSeries:=Chart.AddSeries(TChartSeriesClass(Chart.SeriesList.Last.ClassType));
                       tmpSeries.Name:=GetNewSeriesName(tmpSeries.Owner);

                       inherited;
                     end
                     else
                        inherited;

      TeeKey_Left : if CanInsertSeries and
                       (Col=ColCount-1) and
                       Assigned(Chart) and (Chart.SeriesCount>0) and
                       (Chart.SeriesList.Last.Count=0) then
                     begin
                       Chart.AutoRepaint:=False;

                       {$IFDEF AUTOREFCOUNT}
                       Chart.SeriesList.Last.ParentChart:=nil;
                       {$ELSE}
                       Chart.SeriesList.Last.Free;
                       {$ENDIF}

                       Chart.AutoRepaint:=True;
                       Chart.Invalidate;
                     end
                     else
                        inherited;
    else inherited;
    end;
  end;
end;

// In "3D Grid" mode, do not add or remove series
function TCustomChartGrid.CanInsertSeries:Boolean;
begin
  result:=AllowInsertSeries and (not Grid3DMode);
end;

Procedure TCustomChartGrid.CanvasChanged(Sender:TObject);
Begin
  {$IFDEF FMX}Repaint{$ELSE}Invalidate{$ENDIF};
end;

Function TCustomChartGrid.GetGradientCell:TChartGradient;
begin
  if not Assigned(FGradientCell) then
     FGradientCell:=TChartGradient.Create(CanvasChanged);

  result:=FGradientCell;
end;

{$IFNDEF FMX}
function TCustomChartGrid.GetReadOnly:Boolean;
begin
  result:=not (goEditing in Options);
end;

procedure TCustomChartGrid.SetReadOnly(const Value:Boolean);
begin
  if Value then Options:=Options-[goEditing]
           else Options:=Options+[goEditing];
end;
{$ENDIF}

Procedure TCustomChartGrid.AppendRow;
var tmpCol    : Integer;
    tmpSeries : TChartSeries;
begin
  if (not ReadOnly) and
     (CanInsertSeries or AllowAppend) and
     (not Grid3DMode) then
  begin
    if FColors and (Col=ColorsColumn) then tmpCol:=Succ(Col)
                                      else tmpCol:=Col;

    tmpSeries:=GetSeries(tmpCol);

    if Assigned(tmpSeries) then
    begin
      tmpSeries.AddNull;
      RowCount:=RowCount+1;

      {$IFDEF FMX}
      Selected:=RowCount-1;
      {$ELSE}
      Row:=RowCount-1;
      {$ENDIF}
    end;
  end
end;

const
  TeeMsg_ItemSharp='#';  // Do not localize

function TCustomChartGrid.FindHeaderText(ACol:Integer):String;
var tmpSeries : TChartSeries;
    tmpList   : TChartValueList;
begin
  {$IFDEF FMX}
  if ACol=0 then
     result:=TeeMsg_ItemSharp
  else
  {$ENDIF}
  if FLabels and (ACol=LabelsColumn) then
     result:=TeeMsg_Text
  else
  if FColors and (ACol=ColorsColumn) then
     result:=TeeMsg_Colors
  else
  if IsVisualsColumn(ACol) then
     result:='Visuals'
  else
  if ShowFields and (ACol>LabelsColumn) then
  begin
     tmpSeries:=GetSeries(ACol,tmpList);

     if Assigned(tmpSeries) then
        if Grid3DMode then
           result:=IntToStr(Round(tmpSeries.MinXValue)+ACol-1)
        else
        if Assigned(tmpList) then
           result:=tmpList.Name;
  end;
end;

Function TCustomChartGrid.FindXYZIndex(const ASeries:TChartSeries;
                                       ARow,ACol:Integer):Integer;
var tmpList : TChartValueList;
    t       : Integer;
begin
  result:=-1;
  tmpList:=ASeries.GetYValueList('Z'); // Do not localize

  for t:=0 to ASeries.Count-1 do
  if (tmpList.Value[t]=ARow+1) and
     (ASeries.NotMandatoryValueList.Value[t]=ACol) then
  begin
    result:=t;
    break;
  end;
end;

{$IFDEF FMX}

{$IFDEF D24}
type
  TChartGridModel=class(TGridModel)
  private
    {$IFDEF AUTOREFCOUNT}[weak]{$ENDIF}
    IGrid : TChartGrid;
  protected
    function DoGetValue(ACol, ARow: Integer): TValue; override;
    procedure DoSetValue(ACol, ARow: Integer; const AValue: TValue); override;
  public
    Constructor Create(const AOwner: TComponent); override;
  end;

Constructor TChartGridModel.Create(const AOwner: TComponent);
begin
  inherited;
  IGrid:=AOwner as TChartGrid;
end;

function TChartGridModel.DoGetValue(ACol, ARow: Integer):TValue;
begin
  result:=IGrid.GetValue(ACol,ARow);
end;

procedure TChartGridModel.DoSetValue(ACol, ARow: Integer; const AValue: TValue);
begin
  IGrid.SetValue(ACol,ARow,AValue);
end;

function TCustomChartGrid.DefineModelClass: TDataModelClass;
begin
  result:=TChartGridModel;
end;
{$ENDIF}

function TCustomChartGrid.GetValue(ACol, ARow: Integer): {$IFDEF D17}TValue{$ELSE}Variant{$ENDIF};
{$ELSE}
function TCustomChartGrid.GetEditText(ACol,ARow: Integer): String;
{$ENDIF}

  Function GetALabel(Index:Integer):String;
  var t : Integer;
  begin
    result:='';

    if Assigned(FSeries) then
       result:=FSeries.Labels[Index]
    else
    // return first Label found
    for t:=0 to FChart.SeriesCount-1 do
    With FChart.Series[t] do
    if Labels.Count>Index then
    begin
      result:=Labels[Index];
      break;
    end;
  end;

var tmpSeries : TChartSeries;
    tmpList   : TChartValueList;

  Function GetAValue:String;
  var tmpValue : TDateTime;
  begin
    tmpSeries:=GetSeries(ACol,tmpList);

    if Assigned(tmpSeries) and (tmpSeries.Count>ARow) then
    begin
      if Assigned(tmpList) then
      begin
        if tmpList.DateTime then
        begin
          tmpValue:=tmpList.Value[ARow];

          if tmpValue<1 then result:=TimeToStr(tmpValue)
                        else result:=DateTimeToStr(tmpValue);
        end
        else
           result:=FormatFloat(tmpSeries.ValueFormat,tmpList.Value[ARow])
      end
      else
        result:='...';
    end
    else
       result:='';
  end;

var t : Integer;
begin
  result:={$IFDEF FMX}{$IFDEF D24}''{$ELSE}inherited{$ENDIF}{$ELSE}''{$ENDIF};

  if ACol=0 then // First column
  begin
    if ARow>=FirstRowNum then
       if Grid3DMode then
       begin
         tmpSeries:=GetXYZSeries;
         result:=IntToStr(Round(tmpSeries.MinZValue)+ARow-FirstRowNum);
       end
       else
         result:=TeeStr(ARow-FirstRowNum)

    {$IFNDEF FMX}
    else
    if (not ShowFields) or (ARow=1) then
       result:=TeeMsg_ItemSharp;
    {$ENDIF}
  end
  else
  begin
    {$IFNDEF FMX}
    if ((not ShowFields) and (ARow=0)) or
       (ShowFields and (ARow=1)) then
          result:=FindHeaderText(ACol) // Header
    else
    {$ENDIF}

    // Values
    if ARow>=FirstRowNum then
    begin
      Dec(ARow,FirstRowNum);

      if Grid3DMode then
      begin
        tmpSeries:=GetXYZSeries;

        t:=FindXYZIndex(tmpSeries,ARow,ACol);

        if t<>-1 then
           result:=FormatFloat(tmpSeries.ValueFormat,
                               tmpSeries.MandatoryValueList.Value[t]);
      end
      else
      if FLabels and (ACol=LabelsColumn) then
         result:=StringReplace(GetALabel(ARow),TeeLineSeparator,' ',[rfReplaceAll]) // Change linefeeds to spaces
      else
      if (not FColors) or (ACol<>ColorsColumn) then
         result:=GetAValue;
    end;
  end;
end;

procedure TCustomChartGrid.SetGradientCell(const Value:TChartGradient);
begin
  if Assigned(Value) then GradientCell.Assign(Value)
                     else FreeAndNil(FGradientCell);
end;

Procedure TCustomChartGrid.SetGrid3DMode(Value:Boolean);
begin
  FGrid3DMode:=Value;
  Regenerate;
  Repaint;
end;

type
  TSeriesAccess=class(TChartSeries);

Procedure TCustomChartGrid.SetManualData(const ASeries:TChartSeries);
begin
  TSeriesAccess(ASeries).ManualData:=True;
end;

function TCustomChartGrid.NewBarSeries:TChartSeries;
begin
  result:=CreateNewSeries(Chart.Owner,Chart,TBarSeries);
end;

{$IFDEF FMX}
procedure TCustomChartGrid.SetValue(ACol, ARow: Integer; const Value: {$IFDEF D17}TValue{$ELSE}Variant{$ENDIF});
{$ELSE}
procedure TCustomChartGrid.SetEditText(ACol, ARow: Integer; const Value: String);
{$ENDIF}

  Procedure SetALabel(Index:Integer; Const AValue:String);

    Procedure SetLabelSeries(ASeries:TChartSeries);
    begin
      While Index>=ASeries.Count do ASeries.AddNull('');
      ASeries.Labels[Index]:=AValue;
    end;

  var t : Integer;
  begin
    if Assigned(Series) then SetLabelSeries(Series)
    else
    for t:=0 to Chart.SeriesCount-1 do SetLabelSeries(Chart[t]);
  end;

  Procedure SetAllManualData;
  var t : Integer;
  begin
    if Assigned(Series) then SetManualData(Series)
    else
    for t:=0 to Chart.SeriesCount-1 do SetManualData(Chart[t]);
  end;

var tmpSeries : TChartSeries;
    tmpList   : TChartValueList;
    tmpRow    : Integer;
begin
  inherited;

  if Assigned(FSeries) or Assigned(FChart) then
  begin
     if (not Assigned(FSeries)) and
        ((FChart.SeriesCount=0) and CanInsertSeries) then
          NewBarSeries;

     try
      SetAllManualData;

      Dec(ARow,FirstRowNum);

      if FLabels and (ACol=LabelsColumn) then // Labels
         SetALabel(ARow,Value{$IFDEF FMX}{$IFDEF D17}.ToString{$ENDIF}{$ENDIF})
      else
      if (not FColors) or (ACol<>ColorsColumn) then // not Colors
      if not IsVisualsColumn(ACol) then
      begin
        // Values

        tmpSeries:=GetSeries(ACol,tmpList);

        if Assigned(tmpSeries) then
        begin
          if Assigned(tmpList) then
          begin
            if Grid3DMode then
            begin
              tmpRow:=FindXYZIndex(tmpSeries,ARow,ACol);
              tmpList:=tmpSeries.MandatoryValueList;
            end
            else
            begin
              While ARow>=tmpSeries.Count do tmpSeries.AddNull;
              tmpRow:=ARow;
            end;

            if Trim(Value{$IFDEF FMX}{$IFDEF D17}.ToString{$ENDIF}{$ENDIF})='' then
            begin
              {$IFNDEF FMX}
              if not EditorMode then
              {$ENDIF}
              begin
                tmpSeries.ValueColor[tmpRow]:=clNone;
                {$IFDEF FMX}
                Repaint;
                {$ELSE}
                Invalidate;
                {$ENDIF}
              end;
            end
            else
            begin
              if tmpList.DateTime then
                 tmpList.Value[tmpRow]:=StrToDateTime(Value{$IFDEF FMX}{$IFDEF D17}.ToString{$ENDIF}{$ENDIF})
              else
                 tmpList.Value[tmpRow]:=StrToFloat(Value{$IFDEF FMX}{$IFDEF D17}.ToString{$ENDIF}{$ENDIF});

              tmpList.Modified:=True;

              if tmpSeries.IsNull(tmpRow) then
              begin
                tmpSeries.ValueColor[tmpRow]:=clTeeColor;
                if FColors then
                   Self.Repaint;
              end;
            end;

            tmpSeries.RefreshSeries;
          end;
        end;

        { Repaint Series and / or Chart }
        if Assigned(FSeries) then FSeries.Repaint
                             else FChart.Repaint;
      end;

      { call event }
      if Assigned(FOnSetCell) then
         FOnSetCell(Self,ACol,ARow+FirstRowNum,Value);

    except { hide exception }
      on EConvertError do ;
    end;
  end;
end;

Function TCustomChartGrid.GetXYZSeries:TChartSeries;
var t : Integer;
begin
  result:=nil;

  if Assigned(FChart) then
  begin
    for t:=0 to FChart.SeriesCount-1 do
        if FChart[t].HasZValues then
        begin
          result:=FChart[t];
          break;
        end;
  end
  else
  if Assigned(FSeries) and FSeries.HasZValues then
     result:=FSeries;
end;

Function TCustomChartGrid.GetSeries(ACol:Integer):TChartSeries;
var tmp : TChartValueList;
begin
  result:=GetSeries(ACol,tmp);
end;

Function TCustomChartGrid.IsVisualsColumn(ACol:Integer):Boolean;
var tmp : TChartValueList;
begin
  result:=(not Grid3DMode) and Assigned(GetSeries(ACol,tmp)) and (not Assigned(tmp));
end;

{$IFNDEF FMX}
procedure TCustomChartGrid.TopLeftChanged;
begin
  inherited TopLeftChanged;
  if Assigned(FOnTopLeftChanged) then FOnTopLeftChanged(Self);
end;
{$ENDIF}

procedure TCustomChartGrid.DoChangeVisual(const ASeries:TChartSeries; AIndex:Integer);
var tmpVisual : TVisualBlock;
begin
  if Assigned(TeeOnChangeVisual) then
  begin
    tmpVisual:=ASeries.Visuals[AIndex];

    tmpVisual:=TeeOnChangeVisual(Self,ASeries,tmpVisual);

    if Assigned(tmpVisual) then
       ASeries.Visuals[AIndex]:=tmpVisual;
  end;
end;

procedure TCustomChartGrid.MenuChangeVisual(Sender:TObject);
begin
  {$IFNDEF FMX}
  DoChangeVisual(TChartSeries(TMenuItem(Sender).GetParentMenu.Tag),TMenuItem(Sender).Tag);
  {$ENDIF}
end;

procedure TCustomChartGrid.MenuDeleteVisual(Sender:TObject);
{$IFNDEF FMX}
var tmpSeries : TChartSeries;
{$ENDIF}
begin
  {$IFNDEF FMX}
  tmpSeries:=TChartSeries(TMenuItem(Sender).GetParentMenu.Tag);
  tmpSeries.Visuals[TMenuItem(Sender).Tag]:=nil;
  {$ENDIF}
end;

procedure TCustomChartGrid.MenuEditVisual(Sender:TObject);
{$IFNDEF FMX}
var tmpVisual : TVisualBlock;
    tmpSeries : TChartSeries;
{$ENDIF}
begin
  {$IFNDEF FMX}
  tmpSeries:=TChartSeries(TMenuItem(Sender).GetParentMenu.Tag);
  tmpVisual:=tmpSeries.Visuals[TMenuItem(Sender).Tag];

  with tmpVisual.Editor(Self) as TCustomForm do
  try
    ShowModal;
  finally
    Free;
  end;
  {$ENDIF}
end;

{$IFDEF D17}
{$IFDEF FMX}
type
  TColumnAccess=class(TColumn);
{$ENDIF}
{$ENDIF}

procedure TCustomChartGrid.EditVisual(ACol:Integer);

  function BottomRightPosition:TPoint;
  {$IFDEF FMX}
  {$IFDEF D24}
  var tmp : TObject;
  {$ENDIF}
  {$ENDIF}
  begin
    {$IFDEF FMX}

    {$IFDEF D24}
    tmp:=TColumnAccess(Columns[Col]).{$IFDEF D25}CellObject{$ELSE}CellControl{$ENDIF};

    if tmp is TControl then
       result:=TControl(tmp).BoundsRect.BottomRight
    else
       result:=BoundsRect.BottomRight; // <-- ??
    {$ELSE}
    result:={$IFDEF D17}TColumnAccess{$ENDIF}(Columns[Col]).CellControlByRow(Row).BoundsRect.BottomRight;
    {$ENDIF}

    {$ELSE}
    result:=ClientToScreen(CellRect(ACol,Row).BottomRight);
    {$ENDIF}
  end;

var tmp : TChartValueList;
    tmpSeries : TChartSeries;
    tmpVisual : TVisualBlock;
    tmpIndex  : TeeNativeInt;
    tmpItem   : TMenuItem;
begin
  tmpSeries:=GetSeries(ACol,tmp);

  if Assigned(tmpSeries) and (not Assigned(tmp)) then
  begin
    tmpIndex:=Row-FirstRowNum;

    if tmpIndex<tmpSeries.Count then
    begin
      tmpVisual:=tmpSeries.Visuals[tmpIndex];

      if Assigned(tmpVisual) then
      begin
        if not Assigned(IPopupVisual) then
        begin
          IPopupVisual:=TPopupMenu.Create(Self);

          {$IFNDEF FMX}
          IPopupVisual.Alignment:=paRight;
          {$ENDIF}

          tmpItem:=TMenuItem.Create(Self);
          tmpItem.{$IFDEF FMX}Text{$ELSE}Caption{$ENDIF}:=TeeMsg_Edit;
          tmpItem.OnClick:=MenuEditVisual;
          IPopupVisual.{$IFDEF FMX}AddObject{$ELSE}Items.Add{$ENDIF}(tmpItem);

          tmpItem:=TMenuItem.Create(Self);
          tmpItem.{$IFDEF FMX}Text{$ELSE}Caption{$ENDIF}:=TeeMsg_Change;
          tmpItem.OnClick:=MenuChangeVisual;
          IPopupVisual.{$IFDEF FMX}AddObject{$ELSE}Items.Add{$ENDIF}(tmpItem);

          tmpItem:=TMenuItem.Create(Self);
          tmpItem.{$IFDEF FMX}Text{$ELSE}Caption{$ENDIF}:=TeeMsg_Delete;
          tmpItem.OnClick:=MenuDeleteVisual;
          IPopupVisual.{$IFDEF FMX}AddObject{$ELSE}Items.Add{$ENDIF}(tmpItem);
        end;

        IPopupVisual.Tag:=ObjectToTag(tmpSeries);
        IPopupVisual.{$IFDEF FMX}Children{$ELSE}Items{$ENDIF}[0].Tag:=tmpIndex;
        IPopupVisual.{$IFDEF FMX}Children{$ELSE}Items{$ENDIF}[1].Tag:=tmpIndex;

        with BottomRightPosition do
             IPopupVisual.Popup(X,Y);
      end
      else
        DoChangeVisual(tmpSeries,tmpIndex);
    end;
  end;
end;

Function TCustomChartGrid.HasVisuals(const ASeries:TChartSeries):Boolean;
begin
  result:=(FVisuals=cgsYes) or
         ((FVisuals=cgsAuto) and
           ((ASeries.Visuals.Count>0) or Assigned(ASeries.Visuals.Template)));
end;

type
  TValueListAccess=class(TChartValueList);

// Returns the number of non-internal ValueLists if ASeries, excluding the
// non-mandatory (X) values of ASeries according to the ShowXValues property.
function TCustomChartGrid.ValidListsOfSeries(const ASeries:TChartSeries; const AIndex:Integer):Integer;
var t: Integer;
begin
  result:=0;

  for t:=0 to ASeries.ValuesList.Count-1 do
      if not TValueListAccess(ASeries.ValuesList[t]).Internal then
         Inc(result);

  if not IHasNo[AIndex] then
     if not TValueListAccess(ASeries.NotMandatoryValueList).Internal then
        Dec(result);
end;

// Returns series and series valuelist for the ACol column parameter
Function TCustomChartGrid.GetSeries(ACol:Integer; out AList:TChartValueList):TChartSeries;

  procedure GetAList(Index:Integer);

    procedure ListNotInternal(AIndex:Integer);
    var t, tmp : Integer;
    begin
      tmp:=-1;

      for t:=0 to result.ValuesList.Count-1 do
      begin
        if not TValueListAccess(result.ValuesList[t]).Internal then
        begin
          Inc(tmp);

          if tmp=AIndex then
          begin
            AList:=result.ValuesList[t];
            Exit;
          end;
        end;
      end;

      AList:=nil;
    end;

  begin
    if IHasNo[Index] then
       if ACol>0 then
          AList:=result.ValuesList[ACol-1]
       else
          AList:=result.ValuesList[ACol]
    else
    if result.YMandatory or (ACol=0) then
       AList:=result.ValuesList[ACol]
    else
       if ACol>0 then
          ListNotInternal(ACol-1)
       else
          ListNotInternal(ACol);
  end;

var tmp : Integer;
    t   : Integer;
begin
  AList:=nil;

  if Grid3DMode then
     result:=GetXYZSeries
  else
  if Length(IHasNo)=0 then // <-- FMX special case to avoid AV
     result:=nil
  else
  begin
    if FLabels then
       Dec(ACol);

    if FColors then
       Dec(ACol);

    if ACol>=0 then
    begin
      if Assigned(FSeries) then
      begin
        tmp:=ValidListsOfSeries(FSeries,0);

        if (tmp>=ACol) then
        begin
          result:=FSeries;
          GetAList(0);
          Exit;
        end;
      end
      else
      if Assigned(FChart) then
      for t:=0 to FChart.SeriesCount-1 do
      begin
        tmp:=ValidListsOfSeries(FChart.Series[t],t);

        if HasVisuals(FChart.Series[t]) and (tmp=ACol-1) then
        begin
          result:=FChart.Series[t];
          AList:=nil;
          Exit;
        end
        else
        if (tmp>=ACol) then
        begin
          result:=FChart.Series[t];
          GetAList(t);
          Exit;
        end;

        Dec(ACol,tmp);
      end;
    end;

    result:=nil;
  end;
end;

{$IFNDEF FMX}
function TCustomChartGrid.SymbolRect(const Rect:TRect):TRect;
begin
  with Rect do
       result:=TeeRect(Left+4,Top+4,Left+16,Top+16);
end;

procedure TCustomChartGrid.DrawCell(ACol, ARow: Integer; ARect: TRect;
  AState: TGridDrawState);

  function FieldsPos:Integer;
  begin
    result:=ARect.Top+3;
  end;
  
  Function GetSeriesCol(ACol:Integer):TChartSeries;
  var tmp : Integer;
      t   : Integer;
  begin
    if FLabels then tmp:=LabelsColumn
    else
    if FColors then tmp:=ColorsColumn
    else
       tmp:=0;

    if Assigned(FSeries) then
    begin
      result:=FSeries;
      exit;
    end
    else
    for t:=0 to FChart.SeriesCount-1 do
    begin
      Inc(tmp);

      if tmp=ACol then
      begin
        result:=FChart.Series[t];
        exit;
      end;

      Inc(tmp,ValidListsOfSeries(FChart.Series[t],t)-1);
    end;

    result:=nil;
  end;

  Procedure FillWithColor(const AColor:TColor);
  begin
    Canvas.Brush.Color:=AColor;
    Canvas.Brush.Style:=bsSolid;
    Canvas.FillRect(ARect);
  end;

  procedure CheckGradientCell;
  var tmpCanvas :TTeeCanvas;
  begin
    if Assigned(Self.FGradientCell) and Self.FGradientCell.Visible then
    begin
      GradientCell.Assign(Self.FGradientCell);

      tmpCanvas:=TTeeCanvas3D.Create;
      try
        tmpCanvas.ReferenceCanvas:=Canvas;
        FGradientCell.Draw(tmpCanvas,ARect);
      finally
        tmpCanvas.Free;
      end;
      Canvas.Brush.Style := bsClear;
    end
    else
      GradientCell.Visible:=False;
  end;

  procedure DrawValueText(TopOffset:Integer);  // Values and # symbol
  var tmpSt : String;
  begin
    {$IFDEF MSWINDOWS}
    Windows.SetTextAlign(Canvas.Handle,TA_RIGHT);
    {$ENDIF}

    tmpSt:=GetEditText(ACol, ARow);

    CheckGradientCell;

    Canvas.TextRect(ARect,ARect.Right-2,ARect.Top+TopOffset, tmpSt);

    {$IFDEF MSWINDOWS}
    Windows.SetTextAlign(Canvas.Handle,TA_LEFT);
    {$ENDIF}
  end;

var tmp       : Integer;
    tmpSeries : TChartSeries;
    AList     : TChartValueList;
    tmpSt     : String;
    tmpColor  : TColor;
begin
  if Assigned(FChart) or Assigned(FSeries) then
  begin
    CheckGradientCell;

    tmp:=0;

    if FLabels then Inc(tmp);
    if FColors then Inc(tmp);

    if (ARow=0) and (ACol>tmp) then // Series first Header
    begin
      tmpSeries:=GetSeriesCol(ACol);

      if Assigned(FSeries) and (not ShowFields) then
      begin
        GetSeries(ACol,AList);
        Canvas.TextRect(ARect, ARect.Left+18, ARect.Top+4, AList.Name);
      end
      else
      if Assigned(tmpSeries) then
      if (not Grid3DMode) or ((ACol-tmp)=1) then // only once for 3D mode
      begin
        PaintSeriesLegend(tmpSeries,Canvas,SymbolRect(ARect));

        Canvas.Brush.Style:=bsClear;
        Canvas.Pen.Style:=psSolid;
        Canvas.Font.Color:=Self.Font.Color;

        {$IFDEF MSWINDOWS}
        SetBkMode(Canvas.Handle,Transparent);
        {$ENDIF}

        tmpSt:=SeriesTitleOrName(tmpSeries,True);
        tmpSt:=ReplaceChar(tmpSt,TeeColumnSeparator,' ');

        Canvas.TextRect(ARect, ARect.Left+18, ARect.Top+4,tmpSt);
      end;
    end
    else
    begin
      if (ACol=0) or (ShowFields and (ARow=1)) then // Point number (#) column
         FillWithColor(FixedColor);

      if FColors and (ACol=ColorsColumn) then // Colors
      begin
        if ARow=FirstRowNum-1 then
           Canvas.TextRect(ARect, ARect.Left+2, FieldsPos, TeeMsg_Colors)
        else
        if ARow>=FirstRowNum then
        begin
          tmpSeries:=GetSeries(ACol+1);

          if Assigned(tmpSeries) and (tmpSeries.Count>(ARow-FirstRowNum)) then
          begin
            tmpColor:=tmpSeries.ValueColor[ARow-FirstRowNum];

            if tmpColor=clNone then
               tmpColor:=ColorToRGB(Self.Color);
          end
          else tmpColor:=ColorToRGB(Self.Color);

          FillWithColor(TTeeCanvas.ColorFrom(tmpColor,0)); // For GDI canvas-> 0 alpha
        end;
      end
      else
      if FLabels and (ACol=LabelsColumn) then // Labels
      begin
         CheckGradientCell;
         Canvas.TextRect(ARect, ARect.Left+2, FieldsPos, GetEditText(ACol, ARow))
      end
      else
      if (ARow=0) and (ACol=0) then // # symbol
         DrawValueText(4)
      else
         DrawValueText(3);
    end;
  end;

end;
{$ENDIF}

procedure TCustomChartGrid.Notification( AComponent: TComponent;
                                   Operation: TOperation);
begin
  inherited;

  if Operation=opRemove then
  begin
    if Assigned(FChart) and (AComponent=FChart) then
        Chart:=nil;

    if Assigned(INavigator) and (AComponent=INavigator) then
        INavigator:=nil;

    if Assigned(FSeries) and (AComponent=FSeries) then
        Series:=nil;
  end;
end;

// Deletes current row, also deletes series points
Procedure TCustomChartGrid.Delete;

  Procedure TryDelete(ASeries:TChartSeries; AIndex:Integer);
  begin
    With ASeries do
    if (Row-FirstRowNum)<Count then
    begin
      Delete(Row-FirstRowNum);

      if not IHasNo[AIndex] then
         NotMandatoryValueList.FillSequence;
    end;
  end;

var t : Integer;
begin
  {$IFNDEF FMX}
  if goEditing in Options then
  {$ENDIF}
  begin
    if Assigned(FSeries) then
       TryDelete(FSeries,0)
    else
    With FChart do
    for t:=0 to SeriesCount-1 do
        TryDelete(Series[t],t);

    Repaint;

    if RowCount>(1+FirstRowNum) then
    begin
      RowCount:=RowCount-1;
      NotifyChange;
    end;
  end;
end;

// Inserts a new row to the end (appends),
// also inserts (appends) new series points
Procedure TCustomChartGrid.Insert;
var tmpInc : Boolean;

  Procedure TryInsert(ASeries:TChartSeries);
  begin
    With ASeries do
    begin
      tmpInc:=Count>0;

      if YMandatory then
         AddNullXY(Count,0)
      else
         AddNullXY(0,Count);
    end;
  end;

Var t : Integer;
begin
  {$IFNDEF FMX}
  if goEditing in Options then
  {$ENDIF}
  begin
    tmpInc:=False;

    if Assigned(FSeries) then
       TryInsert(FSeries)
    else
    With FChart do
    for t:=0 to SeriesCount-1 do
       TryInsert(Series[t]);

    if tmpInc then
       RowCount:=RowCount+1;

    {$IFDEF FMX}
    Selected:=RowCount-1;
    {$ELSE}
    Row:=RowCount-1; // Go to last row
    {$ENDIF}
  end;
end;

type
  TTeePanelAccess=class(TCustomAxisPanel);

Procedure TCustomChartGrid.AddListener(const AChart:TCustomAxisPanel);
begin
  if Assigned(AChart) then
  begin
    AChart.FreeNotification(Self);
    TTeePanelAccess(AChart).AddListener(Self);
  end;
end;

Procedure TCustomChartGrid.RemoveListener(const AChart:TCustomAxisPanel);
begin
  if Assigned(AChart) then
  begin
    AChart.RemoveFreeNotification(Self);
    TTeePanelAccess(AChart).RemoveListener(Self);
  end;
end;

Procedure TCustomChartGrid.SetChart(const AChart:TCustomChart);
begin
  if FChart<>AChart then
  begin
    RemoveListener(FChart);
    FChart:=AChart;
    AddListener(FChart);
  end;

  Regenerate;
  Repaint;
end;

{$IFNDEF FMX}
Procedure TCustomChartGrid.SetNavigator(const ANavigator:TChartGridNavigator);
begin
  INavigator:=ANavigator;

  if Assigned(INavigator) then
     INavigator.FreeNotification(Self);
end;
{$ENDIF}

{$IFDEF FMX}
procedure TCustomChartGrid.AddColumns(const ACount:Integer);
var tmp : TColumn;
begin
  BeginUpdate;
  try
    while ColumnCount>0 do
    begin
      {$IFDEF AUTOREFCOUNT}
      Columns[0].Parent:=nil;
      {$ELSE}
      Columns[0].Free;
      {$ENDIF}
    end;

    while ColCount<ACount do
    begin
      tmp:=TColumn.Create(Owner);
      tmp.Stored:=False;
      tmp.Name:=TeeGetUniqueName(Owner,'Column');
      tmp.Header:=FindHeaderText(ColCount);
      AddObject(tmp);
    end;

  finally
    EndUpdate;
  end;
end;
{$ENDIF}

// Main procedure to calculate how many rows and columns should the grid have
Procedure TCustomChartGrid.RecalcDimensions;

  Function MaxNumPoints:Integer;
  var t : Integer;
  begin
    result:=0;

    if Assigned(FSeries) and (not (csDestroying in ComponentState)) and
       (not (csDestroying in FSeries.ComponentState)) then
          result:=FSeries.Count
    else
    for t:=0 to FChart.SeriesCount-1 do
    with FChart[t] do
    if not (csDestroying in ComponentState) then
       if (t=0) or (Count>result) then
          result:=Count;
  end;

var tmpCol : Integer;

  Procedure CalcParams(ASeries:TChartSeries; AIndex:Integer);
  var t : Integer;
  begin
    if not (csDestroying in ASeries.ComponentState) then
    begin
      if FXValues=cgsAuto then
         IHasNo[AIndex]:=(not TValueListAccess(ASeries.NotMandatoryValueList).Internal) and HasNoMandatoryValues(ASeries)
      else
         IHasNo[AIndex]:=FXValues=cgsYes;

      if IHasNo[AIndex] then
         Inc(tmpCol);

      for t:=1 to ASeries.ValuesList.Count-1 do
      if not TValueListAccess(ASeries.ValuesList[t]).Internal then
         Inc(tmpCol);

      if HasVisuals(ASeries) then
         Inc(tmpCol)
    end;
  end;

  Procedure SetXYZDimensions;
  var Values : Array of TChartValue;

    Function Find(Const Value:TChartValue):Boolean;
    var t : Integer;
    begin
      result:=False;

      for t:=0 to Length(Values)-1 do
          if Values[t]=Value then
          begin
            result:=True;
            exit;
          end;
    end;

  var tmpSeries : TChartSeries;
      t         : Integer;
      tmp       : Double;
  begin
    tmpSeries:=GetXYZSeries;

    if Assigned(tmpSeries) then
    begin
      for t:=0 to tmpSeries.Count-1 do
      begin
        tmp:=tmpSeries.ValuesList[2].Value[t];

        if not Find(tmp) then
        begin
          SetLength(Values,Length(Values)+1);
          Values[Length(Values)-1]:=tmp;
        end;
      end;

      {$IFDEF FMX}
      AddColumns(1+Length(Values));
      {$ELSE}
      ColCount:=1+Length(Values);
      {$ENDIF}

      Values:=nil;

      for t:=0 to tmpSeries.Count-1 do
      begin
        tmp:=tmpSeries.NotMandatoryValueList.Value[t];

        if not Find(tmp) then
        begin
          SetLength(Values,Length(Values)+1);
          Values[Length(Values)-1]:=tmp;
        end;
      end;

      RowCount:=2+Length(Values);
    end;

    FLabels:=False;
    FColors:=False;

    {$IFNDEF FMX}
    FixedRows:=2;
    ColWidths[1]:=ColWidths[2]; // TV52012110
    {$ENDIF}
  end;

  function ColumnWidth(Index:Integer):TCoordinate;
  var tmp : String;
  begin
    result:={$IFDEF FMX}Columns[Index].Width{$ELSE}ColWidths[Index]{$ENDIF};

    tmp:=FindHeaderText(Index);

    if (tmp<>'') and Assigned(Canvas) then
    begin
      {$IFNDEF FMX}
      Canvas.Font.Assign(Self.Font);
      {$ENDIF}

      result:=Max(result,4+Canvas.TextWidth(tmp));
    end;
  end;

  procedure AutoColumnWidths(AFirstCol:Integer);
  var t : Integer;
      tmpWidth : TCoordinate;
  begin
    {$IFNDEF FMX}
    if HandleAllocated then
    {$ENDIF}

    for t:=AFirstCol to ColCount-1 do
    begin
      tmpWidth:=ColumnWidth(t);

      {$IFDEF FMX}
      if tmpWidth>Columns[t].Width then
         Columns[t].Width:=tmpWidth;
      {$ELSE}
      if tmpWidth>ColWidths[t] then
         ColWidths[t]:=tmpWidth;
      {$ENDIF}
    end;
  end;

var t : Integer;
    tmpColIndex : Integer;
begin
  {$IFNDEF FMX}
  if csLoading in ComponentState then
     Exit;
  {$ENDIF}
  
  if Assigned(FChart) then
     SetLength(IHasNo,FChart.SeriesCount)
  else
  if Assigned(FSeries) then
     SetLength(IHasNo,1);

  if Grid3DMode then
     SetXYZDimensions
  else
  begin
    tmpCol:=1;

    if Assigned(FChart) or Assigned(FSeries) then
    begin
      if Assigned(FSeries) then
         CalcParams(FSeries,0)
      else
      for t:=0 to FChart.SeriesCount-1 do
         CalcParams(FChart[t],t);

      if FLabels then Inc(tmpCol);
      if FColors then Inc(tmpCol);

      RowCount:=Math.Max(1+FirstRowNum,MaxNumPoints+FirstRowNum);
    end
    else
      RowCount:=1+FirstRowNum;

    tmpColIndex:=1;

    {$IFDEF FMX}
    AddColumns(tmpCol);

    Columns[0].Width:=30;
    Columns[0].Enabled:=False;

    if FColors and (ColCount>1) then
    begin
      Columns[1].Width:=40;
      Inc(tmpColIndex);
    end;

    {$ELSE}
    ColCount:=tmpCol;
    {$ENDIF}

    {$IFNDEF FMX}
    ColWidths[0]:=30;

    if FColors and (ColCount>1) then
    begin
      ColWidths[1]:=40;
      Inc(tmpColIndex);
    end;

    if ShowFields then FixedRows:=2
                  else FixedRows:=1;
    {$ENDIF}

    AutoColumnWidths(tmpColIndex);
  end;

  if (Col=0) and (ColCount>1) then
      {$IFDEF FMX}
      if Assigned(HScrollBar) then
      {$ENDIF}
         {$IFDEF FMX}ColumnIndex{$ELSE}Col{$ENDIF}:=1;

  NotifyChange;
end;

procedure TCustomChartGrid.NotifyChange;
begin
  if Assigned(FActiveChanged) then
     FActiveChanged(Self);
end;

Procedure TCustomChartGrid.SetBooleanProperty(Var Variable:Boolean; Value:Boolean);
begin
  if Variable<>Value then
  begin
    Variable:=Value;
    RecalcDimensions;
    Repaint;
  end;
end;

Procedure TCustomChartGrid.SetShowFields(Value:Boolean);
{$IFNDEF FMX}
var tmpChanged:Boolean;
{$ENDIF}
begin
  {$IFNDEF FMX}
  tmpChanged:=FShowFields<>Value;
  {$ENDIF}

  SetBooleanProperty(FShowFields,Value);

  if Row<FirstRowNum then
     {$IFDEF FMX}Selected{$ELSE}Row{$ENDIF}:=FirstRowNum;

  {$IFNDEF FMX}
  if tmpChanged then TopLeftChanged;
  {$ENDIF}
end;

procedure TCustomChartGrid.SetShowLabels(Value: Boolean);
begin
  SetBooleanProperty(FLabels,Value);
end;

Procedure TCustomChartGrid.SetShowVisuals(const Value:TChartGridShow);
begin
  FVisuals:=Value;
  RecalcDimensions;
  Repaint;
end;

Procedure TCustomChartGrid.SetShowColors(Value:Boolean);
begin
  SetBooleanProperty(FColors,Value);
end;

Procedure TCustomChartGrid.SetShowXValues(const Value:TChartGridShow);
begin
  FXValues:=Value;
  RecalcDimensions;
  Repaint;
end;

procedure TCustomChartGrid.TeeEvent(Event: TTeeEvent);
begin
  if not (csDestroying in ComponentState) then
  if Event is TTeeSeriesEvent then
  Case TTeeSeriesEvent(Event).Event of
    seChangeTitle,
    seChangeColor,
    seChangeActive: {$IFDEF FMX}Repaint{$ELSE}Invalidate{$ENDIF};

  {$IFNDEF FMX}
  else
  begin
    RecalcDimensions;
    Invalidate;
  end;
  {$ENDIF}
  end;
end;

Function TCustomChartGrid.GetSeriesColor(out AColor:TColor; ACol,ARow:Integer):TChartSeries;
begin
  AColor:=clNone;

  if FColors and (ACol=ColorsColumn) and (ARow>=FirstRowNum) then
  begin
    result:=GetSeries(ACol+1);

    if Assigned(result) then
    begin
      if result.Count>(ARow-FirstRowNum) then
         AColor:=result.ValueColor[ARow-FirstRowNum]
      else
         result:=nil;
    end;
  end
  else
     result:=nil;
end;

Function TCustomChartGrid.GetSeriesColor(out AColor:TColor):TChartSeries;
begin
  result:=GetSeriesColor(AColor,Col,Row);
end;

Procedure TCustomChartGrid.ChangeColor(AColor:TColor);
var tmpColor  : TColor;
    tmpSeries : TChartSeries;
begin
  tmpSeries:=GetSeriesColor(tmpColor);
  if Assigned(tmpSeries) then
     ChangeColor(tmpSeries,AColor);
end;

Procedure TCustomChartGrid.ChangeColor(const ASeries:TChartSeries; AColor:TColor);
begin
  ASeries.ValueColor[Row-FirstRowNum]:=AColor;
  SetManualData(ASeries);

  {$IFNDEF FMX}
  DrawCell(Col,Row,CellRect(Col,Row),[gdSelected,gdFocused]);
  {$ENDIF}

  if Assigned(FOnChangeColor) then
     FOnChangeColor(Self);
end;

Procedure TCustomChartGrid.ChangeColor;
var tmpSeries : TChartSeries;
    tmpColor  : TColor;
begin
  tmpSeries:=GetSeriesColor(tmpColor);

  if Assigned(tmpSeries) then
  begin
    if EditColorDialog(Self,tmpColor) then
       ChangeColor(tmpSeries,tmpColor);
  end
  else
  if CanInsertSeries then
  begin
    if EditColorDialog(Self,tmpColor) then
    begin
      tmpSeries:=NewBarSeries;

      while tmpSeries.Count<=(Row-FirstRowNum) do
            tmpSeries.AddNull;

      ChangeColor(tmpSeries,tmpColor);

      {$IFDEF FMX}
      Repaint;
      {$ELSE}
      Invalidate;
      {$ENDIF}
    end;
  end;
end;

function TCustomChartGrid.AtSeriesColor(var Series:TChartSeries):Boolean;
{$IFNDEF FMX}
var tmpCoord : TGridCoord;
    tmpPoint : TPoint;
    tmp      : TChartSeries;
    tmpR     : TRect;
{$ENDIF}
begin
  result:=False;

  {$IFNDEF FMX}

  {$IFDEF FMX}
  tmpPoint:=Platform.GetMousePos;
  {$ELSE}
  tmpPoint:=ScreenToClient(Mouse.CursorPos);
  {$ENDIF}

  tmpCoord:=MouseCoord(tmpPoint.X,tmpPoint.Y);

  if tmpCoord.Y=0 then
  begin
    tmp:=GetSeries(tmpCoord.X);

    if Assigned(tmp) then
    begin
      tmpR:=SymbolRect(CellRect(tmpCoord.X,tmpCoord.Y));

      if PointInRect(tmpR,tmpPoint) then
      begin
        Series:=tmp;
        result:=True;
      end;
    end;
  end;
  {$ENDIF}
end;

// Use double-click mouse event to show the color editor dialog
// to change a series point color
procedure TCustomChartGrid.DblClick;
var tmpSeries : TChartSeries;
    tmpColor  : TColor;
begin
  if FColors and (Col=ColorsColumn) {$IFNDEF FMX}and (goEditing in Options){$ENDIF} then
     ChangeColor
  else
  if IsVisualsColumn(Col) then
     EditVisual(Col)
  else
  if AtSeriesColor(tmpSeries) and SeriesSymbolClickable then
  begin
    tmpColor:=tmpSeries.Color;

    if EditColorDialog(Self,tmpColor) then
       tmpSeries.Color:=tmpColor;
  end
  else
     inherited;
end;

{$IFNDEF LCL}
function TCustomChartGrid.CanEditShow: Boolean;
begin
  result:={$IFDEF FMX}True{$ELSE}inherited CanEditShow{$ENDIF};

  if result then
  begin
    if Assigned(FOnEditing) then
       FOnEditing(Self,Col,Row,result);

    if result then
       if FColors and (Col=ColorsColumn) then
          result:=False
       else
       if IsVisualsColumn(Col) then
          result:=False
       else
          FOldValue:={$IFDEF FMX}GetValue(Col,Row){$IFDEF FMX}{$IFDEF D17}.ToString{$ENDIF}{$ENDIF}{$ELSE}GetEditText(Col,Row){$ENDIF};
  end;
end;
{$ENDIF}

procedure TCustomChartGrid.Regenerate;
begin
  if not (csDestroying in ComponentState) then
  begin
    RecalcDimensions;

    {$IFNDEF FMX}
    {$IFNDEF LCL} // Same method in LCL does a different thing
    MoveColRow(Math.Min(ColCount-1,1), FirstRowNum, True, True);
    {$ENDIF}
    {$ENDIF}
  end;
end;

procedure TCustomChartGrid.SetSeries(const Value: TChartSeries);
begin
  if FSeries<>Value then
  begin
    if Assigned(FSeries) then
    begin
      FSeries.RemoveFreeNotification(Self);

      if Assigned(FSeries.ParentChart) then
         RemoveListener(FSeries.ParentChart)
      else
         RemoveListener(ISeriesChart);
    end;

    FSeries:=Value;

    if Assigned(FSeries) then
    begin
      FSeries.FreeNotification(Self);
      Chart:=nil;

      ISeriesChart:=FSeries.ParentChart;

      Chart:=TCustomChart(ISeriesChart);
      AddListener(ISeriesChart);
    end;
  end;

  Regenerate;
  Repaint;
end;

// Returns column number for series "Labels"
Function TCustomChartGrid.LabelsColumn:Integer;
begin
  if FLabels then
  begin
    if FColors then result:=2
               else result:=1;
  end
  else result:=-1;
end;

// Returns column number for series "Colors"
Function TCustomChartGrid.ColorsColumn:Integer;
begin
  if FColors then result:=1
             else result:=-1;
end;

{$IFDEF FMX}
{$IFDEF D24}
initialization
  TPresentationProxyFactory.Current.Register(TChartGrid, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
finalization
  TPresentationProxyFactory.Current.Unregister(TChartGrid, TControlType.Styled, TStyledPresentationProxy<TStyledGrid>);
{$ENDIF}
{$ENDIF}
end.
