{*****************************************}
{ TeeChart LiveBindings "Grid" DataSource }
{ Copyright(c) 2012 by Steema Software    }
{*****************************************}
unit VCLTee.Bind.Chart;
{$I TeeDefs.inc}

interface

{$IFDEF FMX}
{$HPPEMIT '#pragma link "FMXTee.Bind.Chart"'}    {Do not Localize}
{$ELSE}
{$HPPEMIT '#pragma link "VCLTee.Bind.Chart"'}    {Do not Localize}
{$ENDIF}

uses System.SysUtils, System.Classes, Data.Bind.Components, Data.Bind.Grid,
     System.Types, System.Bindings.Outputs,
     Generics.Collections;

implementation

uses
  {$IFDEF FMX}
  FMXTee.Engine, FMXTee.Chart, FMXTee.Series
  {$ELSE}
  VCLTee.TeEngine, VCLTee.Chart, VCLTee.Series
  {$ENDIF}
  ;

type
  TBaseLinkChartToDataSourceControlManager = class(TInterfacedObject, ILinkGridToDataSourceControlManager)
  private
    FChart: TCustomChart;
    //FDefaultColumnWidth: Integer;
    function AddColumn(
      const ADescription: TCreateColumnDescription): Integer; overload;
    function DescribeColumn(
      AIndex: Integer; const ADescription: TCreateColumnDescription): TLinkGridColumnDescription; overload;
    procedure UpdateColumn(AIndex: Integer;
      const ACreateDescription: TCreateColumnDescription); overload;
  protected
    function FormatCellDataExpression(AIndex: Integer): string; virtual; abstract;
    function SupportsMemberType(AMemberType: TScopeMemberType; var ALiteralExpression: string): Boolean; virtual;
    function CreateColumn(
      const ADescription: TCreateColumnDescription; AChart: TCustomChart): TChartSeries; virtual;
    function CreateColumnDescription(
      AIndex: Integer; ADescription: TCreateColumnDescription): TLinkGridColumnDescription; virtual;
    function GetColumnClass(
       ADescription: TCreateColumnDescription): TComponentClass; virtual;
  protected
    procedure BeginUpdate;
    procedure EndUpdate;
    function GetDefaultColumnWidth: Integer;
    procedure SetDefaultColumnWidth(AWidth: Integer);
    function CanAddColumn(AColumn: TBaseLinkGridToDataSourceColumn): Boolean; overload;
    function CanAddColumn(ADataSource: TBaseLinkingBindSource; const AMemberName: string): Boolean; overload;
    procedure ClearColumns;
    function DescribeColumn(AIndex: Integer; AColumn: TBaseLinkGridToDataSourceColumn): TLinkGridColumnDescription; overload;
    function DescribeColumn(AIndex: Integer; ADataSource: TBaseLinkingBindSource; const AMemberName: string): TLinkGridColumnDescription; overload;
    function AddColumn(AColumn: TBaseLinkGridToDataSourceColumn): Integer; overload;
    function AddColumn(ADataSource: TBaseLinkingBindSource; const AMemberName: string): Integer; overload;
    procedure UpdateColumn(AIndex: Integer; AColumn: TBaseLinkGridToDataSourceColumn); overload;
    procedure UpdateColumn(AIndex: Integer; ADataSource: TBaseLinkingBindSource; const AMemberName: string); overload;
    function GetPositionGetter(var AGetter: string; var ABase: Integer): Boolean;
    function GetPositionSetter(var ASetter: string; var ABase: Integer): Boolean;
  public
    constructor Create(ACustomChart: TCustomChart);
  end;

  TLinkChartToDataSourceControlManager = class(TBaseLinkChartToDataSourceControlManager)
  private
  protected
    function GetColumnClass(
       ADescription: TCreateColumnDescription): TComponentClass; override;
    function CreateColumn(
      const ADescription: TCreateColumnDescription; AChart: TCustomChart): TChartSeries; override;
    function FormatCellDataExpression(AIndex: Integer): string; override;
    function SupportsMemberType(AMemberType: TScopeMemberType; var ALiteralExpression: string): Boolean; override;
  public
    constructor Create(AChart: TCustomChart);
  end;

  TBaseLinkChartToDataSourceColumnFactory = class(TLinkGridToDataSourceColumnFactory)
  public
    function UsesUnits: TArray<string>; override;
  end;

  TLinkChartToDataSourceColumnFactory = class(TBaseLinkChartToDataSourceColumnFactory)
  public
    function Supports(AIntf: TGuid; AChart: TComponent): Boolean; override;
    function CreateFactory(AIntf: TGuid;
                           AChart: TComponent): IInterface; override;
  end;

{ TBaseLinkChartToDataSourceControlManager }

function TBaseLinkChartToDataSourceControlManager.AddColumn(
  ADataSource: TBaseLinkingBindSource; const AMemberName: string): Integer;
var
  LDescription: TCreateColumnDescription;
begin
  LDescription := TCreateColumnDescription.Create(ADataSource, AMemberName);
  Result := AddColumn(LDescription);
end;

procedure TBaseLinkChartToDataSourceControlManager.BeginUpdate;
begin
  {$IFDEF FMX}
  FChart.BeginUpdate;
  {$ELSE}
  FChart.AutoRepaint:=False; //BeginUpdate;
  {$ENDIF}
end;

function TBaseLinkChartToDataSourceControlManager.DescribeColumn(
  AIndex: Integer; AColumn: TBaseLinkGridToDataSourceColumn): TLinkGridColumnDescription;
begin
  Result := DescribeColumn(AIndex, TCreateColumnDescription.Create(AColumn as TLinkGridToDataSourceColumn));
end;

function TBaseLinkChartToDataSourceControlManager.DescribeColumn(
  AIndex: Integer; ADataSource: TBaseLinkingBindSource; const AMemberName: string): TLinkGridColumnDescription;
begin
  Result := DescribeColumn(AIndex, TCreateColumnDescription.Create(ADataSource, AMemberName));
end;


function TBaseLinkChartToDataSourceControlManager.AddColumn(
  AColumn: TBaseLinkGridToDataSourceColumn): Integer;
begin
  Result := AddColumn(TCreateColumnDescription.Create(AColumn as TLinkGridToDataSourceColumn));
end;

procedure TBaseLinkChartToDataSourceControlManager.UpdateColumn(
  AIndex: Integer; const ACreateDescription: TCreateColumnDescription);
var
  LColumn: TChartSeries;
begin
  LColumn := FChart[AIndex];
  if LColumn.ClassType <> GetColumnClass(ACreateDescription) then
  begin
//    // Recreate column
//    LNewColumn := CreateColumn(ACreateDescription, LColumn.Parent as TCustomGrid);

  end;
  if LColumn <> nil then
  begin
    LColumn.Title := ACreateDescription.Header;

    {
    if ACreateDescription.Width <> -1 then
      LColumn.Width := ACreateDescription.Width
    else
      LColumn.Width := GetDefaultColumnWidth;
    }

    LColumn.Visible := ACreateDescription.Visible;

    //LColumn.ReadOnly := ACreateDescription.ReadOnly;
  end;
end;

procedure TBaseLinkChartToDataSourceControlManager.UpdateColumn(
  AIndex: Integer; AColumn: TBaseLinkGridToDataSourceColumn);
var
  LCreateDescription: TCreateColumnDescription;
begin
  LCreateDescription := TCreateColumnDescription.Create(AColumn as TLinkGridToDataSourceColumn);
  UpdateColumn(AIndex, LCreateDescription);
end;

procedure TBaseLinkChartToDataSourceControlManager.UpdateColumn(AIndex: Integer; ADataSource: TBaseLinkingBindSource; const AMemberName: string);
var
  LCreateDescription: TCreateColumnDescription;
begin
  LCreateDescription := TCreateColumnDescription.Create(ADataSource, AMemberName);
  UpdateColumn(AIndex, LCreateDescription);
end;

function TBaseLinkChartToDataSourceControlManager.AddColumn(
  const ADescription: TCreateColumnDescription): Integer;
var
  LColumn: TChartSeries;
begin
  LColumn := CreateColumn(ADescription, FChart);
  LColumn.Title := ADescription.Header;

  {
  if ADescription.Width <> -1 then
    LColumn.Width := ADescription.Width
  else
    LColumn.Width := GetDefaultColumnWidth;
  }

  LColumn.Visible := ADescription.Visible;

  //LColumn.ReadOnly := ADescription.ReadOnly;
//  LColumn.StyleLookup := ADescription.StyleLookup;

  FChart.AddSeries(LColumn);
  Result := FChart.SeriesList.IndexOf(LColumn);
end;

type
  TSeriesAccess=class(TChartSeries);

function TBaseLinkChartToDataSourceControlManager.CreateColumn(
  const ADescription: TCreateColumnDescription; AChart: TCustomChart): TChartSeries;
begin
  Result := TBarSeries.Create(AChart);

  TSeriesAccess(Result).DontSerialize:=True;
end;

const
  sUnknown = '''(unknown)''';
  sBlob = '''(blob)''';
  sSelf = 'Self';
  sSelectedText = 'SelectedText(Self)';

function TBaseLinkChartToDataSourceControlManager.CreateColumnDescription(
  AIndex: Integer;
  ADescription: TCreateColumnDescription): TLinkGridColumnDescription;
var
  LColumn: TLinkGridColumnDescription;
  FPairsList: TList<TLinkGridColumnExpressionPair>;
  LPair: TLinkGridColumnExpressionPair;
  LFormatColumnExpressions: TArray<TLinkGridColumnExpressionPair>;
  LFormatCellExpressions: TArray<TLinkGridColumnExpressionPair>;
  LParseCellExpressions: TArray<TLinkGridColumnExpressionPair>;
  LCellExpression: String;
  LMemberName: string;
  LMemberType: TScopeMemberType;
  LMemberGetter: string;
  LMemberSetter: string;
//  LSelectedText: string;
  LLiteralExpression: string;
begin
  LMemberName := ADescription.MemberName;
  LMemberType := ADescription.MemberType;
  LMemberGetter := ADescription.MemberGetter;
  LMemberSetter := ADescription.MemberSetter;
  FPairsList := TList<TLinkGridColumnExpressionPair>.Create;
  try
    FPairsList.Clear;
    LFormatColumnExpressions := FPairsList.ToArray;

    if not SupportsMemberType(LMemberType, LLiteralExpression) then
    begin
      Assert(LLiteralExpression <> '');
      LCellExpression := LLiteralExpression;
    end
    else
    begin
      LCellExpression := LMemberGetter;
      Assert(LMemberGetter <> '');
    end;

    if ADescription.CustomFormat <> '' then
      LCellExpression := Format(ADescription.CustomFormat, [LCellExpression]);

    FPairsList.Clear;

    if LCellExpression <> '' then
    begin
      LPair := TLinkGridColumnExpressionPair.Create(
        FormatCellDataExpression(AIndex), LCellExpression {$IFDEF D24}, ADescription.CustomFormat <> ''{$ENDIF});

      FPairsList.Add(LPair);
      LFormatCellExpressions := FPairsList.ToArray;
    end;

    if (LMemberSetter = '')   then
      LCellExpression := ''
    else
    begin
      case LMemberType of
        mtUnknown:
          LCellExpression := '';
        mtBitmap:
          LCellExpression := '';
      else
        LCellExpression := LMemberSetter;
        Assert(LCellExpression <> '');
      end;
    end;

    FPairsList.Clear;

    (*
    if LCellExpression <> '' then
    begin
      if ADescription.CustomParse <> '' then
        LSelectedText := Format(ADescription.CustomParse, [sSelectedText])
      else
        LSelectedText := sSelectedText;
      LPair := TLinkGridColumnExpressionPair.Create(
      LSelectedText, LCellExpression);   // do not localize
      FPairsList.Add(LPair);
    end;
    *)
    
    LParseCellExpressions := FPairsList.ToArray;

    LColumn := TLinkGridColumnDescription.Create(FChart[AIndex], '',
     AIndex, '', ADescription.MemberName, ADescription.ColumnStyle,
     LFormatColumnExpressions, LFormatCellExpressions, LParseCellExpressions);


    Result := LColumn;
  finally
    FPairsList.Free;
  end;

end;

function TBaseLinkChartToDataSourceControlManager.DescribeColumn(
  AIndex: Integer; const ADescription: TCreateColumnDescription): TLinkGridColumnDescription;
begin
  Result := CreateColumnDescription(AIndex, ADescription);
end;

function TBaseLinkChartToDataSourceControlManager.GetColumnClass(ADescription: TCreateColumnDescription): TComponentClass;
begin
  Result := TBarSeries;
end;

function TBaseLinkChartToDataSourceControlManager.GetDefaultColumnWidth: Integer;
begin
  Result := 0; //FDefaultColumnWidth;
end;

function TBaseLinkChartToDataSourceControlManager.GetPositionGetter(
  var AGetter: string; var ABase: Integer): Boolean;
begin
  AGetter := ''; //Selected'; // Do not localize
  ABase := 0;
  Exit(False);
end;

function TBaseLinkChartToDataSourceControlManager.GetPositionSetter(
  var ASetter: string; var ABase: Integer): Boolean;
begin
  ASetter := ''; //Selected'; // Do not localize
  ABase := 0;
  Exit(False);
end;


procedure TBaseLinkChartToDataSourceControlManager.SetDefaultColumnWidth(
  AWidth: Integer);
begin
  //FDefaultColumnWidth := AWidth;
end;

function TBaseLinkChartToDataSourceControlManager.SupportsMemberType(
  AMemberType: TScopeMemberType; var ALiteralExpression: string): Boolean;
begin
  Result := False;

  case AMemberType of
    mtInteger,
    mtFloat,
    mtBCD,
    mtUInteger,
    mtDateTime,
    mtCurrency: Result := True;
  else
    //mtUnknown,tVariant,mtObject,mtText:
    ALiteralExpression := sUnknown
  end;
end;

procedure TBaseLinkChartToDataSourceControlManager.EndUpdate;
begin
  {$IFDEF FMX}
  FChart.EndUpdate;
  {$ELSE}
  FChart.AutoRepaint:=True; // EndUpdate;
  FChart.Invalidate;
  {$ENDIF}
end;

function TBaseLinkChartToDataSourceControlManager.CanAddColumn(
  AColumn: TBaseLinkGridToDataSourceColumn): Boolean;
begin
  (*
  if (AMemberName <> '') and Supports(ADataSource, IScopeMemberScripting, LScopeMemberScripting) then
    if LScopeMemberScripting.GetMemberType(AMemberName, MemberType) then
       Result:=SupportsMemberType(MemberType, Dummy);
  *)
  Result := True;
end;

function TBaseLinkChartToDataSourceControlManager.CanAddColumn(
  ADataSource: TBaseLinkingBindSource; const AMemberName: string): Boolean;
var LScopeMemberScripting : IScopeMemberScripting;
    MemberType : TScopeMemberType;
    Dummy : String;
begin
  Result := False;

  if (AMemberName <> '') and Supports(ADataSource, IScopeMemberScripting, LScopeMemberScripting) then
    if LScopeMemberScripting.GetMemberType(AMemberName, MemberType) then
       Result:=SupportsMemberType(MemberType, Dummy);
end;

procedure TBaseLinkChartToDataSourceControlManager.ClearColumns;
var t : Integer;
begin
  t:=0;

  while t<FChart.SeriesCount do
    if TSeriesAccess(FChart[t]).DontSerialize then
       {$IFDEF AUTOREFCOUNT}
       FChart[t].DisposeOf
       {$ELSE}
       FChart[t].Free
       {$ENDIF}
    else
       Inc(t);
end;

constructor TBaseLinkChartToDataSourceControlManager.Create(ACustomChart: TCustomChart);
begin
  FChart := ACustomChart;
  //FDefaultColumnWidth := 64;
end;

{ TLinkChartToDataSourceColumnFactory }


function TLinkChartToDataSourceColumnFactory.CreateFactory(AIntf: TGuid;
                       AChart: TComponent): IInterface;
begin
  Result := TLinkChartToDataSourceControlManager.Create(TCustomChart(AChart));
end;

function TLinkChartToDataSourceColumnFactory.Supports(AIntf: TGuid;
  AChart: TComponent): Boolean;
begin
  Result := False;
  if AIntf = ILinkGridToDataSourceControlManager then
    if AChart.InheritsFrom(TCustomChart) then
      Result := True;
end;

{ TBaseLinkChartToDataSourceColumnFactory }

function TBaseLinkChartToDataSourceColumnFactory.UsesUnits: TArray<string>;
begin
  SetLength(Result, 2);
  Result[0] := {$IFDEF FMX}'FMXTee.Bind.Chart'{$ELSE}'VCLTee.Bind.Chart'{$ENDIF}; // Do not localize
  Result[1] := {$IFDEF FMX}'FMXTee.Bind.Editors'{$ELSE}'VCLTee.Bind.Editors'{$ENDIF}; // Do not localize
end;

{ TLinkChartToDataSourceControlManager }

constructor TLinkChartToDataSourceControlManager.Create(AChart: TCustomChart);
begin
  //FChart := AChart;
  inherited Create(AChart);
end;

function TLinkChartToDataSourceControlManager.CreateColumn(
  const ADescription: TCreateColumnDescription; AChart: TCustomChart): TChartSeries;
begin
  {
  case ADescription.MemberType of
    mtBitmap,
    mtObject:
      Result := TImageColumn.Create(FCustomGrid);
    mtBoolean:
      Result := TCheckColumn.Create(FCustomGrid)
  else
  }
    Result := inherited CreateColumn(ADescription, AChart);
 // end;
end;

function TLinkChartToDataSourceControlManager.FormatCellDataExpression(
  AIndex: Integer): string;
begin
  Result := Format('Series[%d]', [AIndex]);
end;

function TLinkChartToDataSourceControlManager.SupportsMemberType(
  AMemberType: TScopeMemberType; var ALiteralExpression: string): Boolean;
begin
  Result := False;

  case AMemberType of
    mtInteger,
    mtFloat,
    mtBCD,
    mtUInteger,
    mtDateTime,
    mtCurrency: Result := True;
  else
    //mtUnknown,tVariant,mtObject,mtText:
    ALiteralExpression := sUnknown
  end;
end;


function TLinkChartToDataSourceControlManager.GetColumnClass(
  ADescription: TCreateColumnDescription): TComponentClass;
begin
  {
  case ADescription.MemberType of
    mtBitmap,
    mtObject:
      Result := TImageColumn;
    mtBoolean:
      Result := TCheckColumn;
  else
  }
    Result := inherited;
  //end;
end;

initialization
  RegisterLinkGridToDataSourceColumnFactory([TLinkChartToDataSourceColumnFactory]);
  RegisterTeeStandardSeries;
finalization
  UnregisterLinkGridToDataSourceColumnFactory([TLinkChartToDataSourceColumnFactory]);
end.
