{*********************************************}
{  TeeChart Charting Library for FireMonkey   }
{  Copyright (c) 2012-2020 by Steema Software }
{  All Rights Reserved                        }
{*********************************************}
unit FMXTee.Editor.Functions;
{$I TeeDefs.inc}

interface

uses
  SysUtils, Types, Classes, Variants, FMX.Types, FMX.Controls, FMX.Forms,

  {$IFNDEF UPTOXE3}
  FMX.StdCtrls,
  {$ENDIF}

  {$IFNDEF UPTOXE7}
  FMX.Controls.Presentation,
  {$ENDIF}

  FMX.ListBox, FMX.Dialogs, FMX.TabControl, FMXTee.Editor.Source,
  FMXTee.Engine, FMXTee.Editor.SelectList;

type
  TFunctionEditor = class(TBaseSourceEditor)
    PageControl1: TTabControl;
    TabSource: TTabItem;
    PanSingle: TPanel;
    Label1: TLabel;
    LValues: TLabel;
    CBSingle: TComboBox;
    CBValues: TComboBox;
    BNone: TButton;
    TabOptions: TTabItem;
    SpeedButton1: TButton;
    procedure SpeedButton1Click(Sender: TObject);
    procedure BApplyClick(Sender: TObject);
    procedure CBSingleChange(Sender: TObject);
    procedure CBValuesChange(Sender: TObject);
    procedure BNoneClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure CBSourcesChange(Sender: TObject);
  private
   { Private declarations }
    Filling  : Boolean;
    IOptions : TForm;
    ISources : TSelectListForm;

    procedure FillSeries;
    class procedure SetValueListCombo(const Series:TChartSeries; const Combo:TComboBox);
    procedure TryCreateNewFunction;
  public
   { Public declarations }
    Series : TChartSeries;

    class procedure FillSeriesSources(const AItems:TCustomListBox; const AItems2:TComboBox;
                const TheSeries:TChartSeries; AddCurrent:Boolean);
    Function FunctionClass:TTeeFunctionClass;
    procedure Translate; override;
  end;


implementation

{$R *.fmx}

uses
  FMXTee.Chart, FMXTee.Constants, FMXTee.Procs, FMXTee.Editor.Gallery,
  FMXTee.Editor.Functions.Base, FMXTee.Editor.Functions.Period,
  FMXTee.Editor.Stroke;

type
  TClassObject=class
  public
    TheClass : TClass;
    Constructor Create(const AClass:TClass);
  end;

{ TClassObject }

Constructor TClassObject.Create(const AClass: TClass);
begin
  inherited Create;
  TheClass:=AClass;
end;

function IndexOfClass(const ACombo:TComboBox; const AClass:TClass):Integer;
var t : Integer;
    tmp : TObject;
begin
  for t:=0 to ACombo.Items.Count-1 do
  begin
    tmp:=ACombo.Items.Objects[t];

    if tmp is TClassObject then
       if TClassObject(tmp).TheClass=AClass then
          Exit(t);
  end;

  result:=-1;
end;

procedure FillTeeFunctions(const ACombo:TComboBox);

  procedure AddItem(const AText:String; const AObject:TObject);
  var item : TListBoxItem;
  begin
    item:=TListBoxItem.Create(ACombo.Owner);
    item.Text:=AText;
    item.Data:=AObject;
    ACombo.AddObject(item);
  end;

var t : Integer;
begin
  ACombo.BeginUpdate;

  ACombo.Clear;
  AddItem(TeeMsg_FunctionNone, nil);

  with TeeSeriesTypes do
  for t:=0 to Count-1 do
    With Items[t] do
    if Assigned(FunctionClass) and (not Assigned(SeriesClass)) and
       (IndexOfClass(ACombo,FunctionClass)=-1) then
           AddItem(ReplaceChar(Description {$IFNDEF CLR}^{$ENDIF},TeeLineSeparator,' '),
                   TClassObject.Create(FunctionClass));

  ACombo.EndUpdate;
end;

procedure TFunctionEditor.FillSeries;
var t         : Integer;
    tmp       : TChartSeries;
    tmpSource : TObject;
    tmpIndex  : Integer;
    tmpItem   : TListBoxItem;
begin
  CBSingle.Clear;

  With ISources do
  begin
    FromList.Clear;

    FillSeriesSources(FromList,CBSingle,Series,False);

    With ToList do
    begin
      BeginUpdate;
      try
        Clear;

        With Series do
        if Assigned(DataSource) then
        for t:=0 to DataSources.Count-1 do
            if TComponent(DataSources[t]) is TChartSeries then
            begin
              tmp:=TChartSeries(DataSources[t]);

              tmpItem:=TListBoxItem.Create(Self);
              tmpItem.Text:=SeriesTitleOrName(tmp);
              tmpItem.Data:=tmp;
              ToList.AddObject(tmpItem);

              tmpItem:=FromList.ItemByIndex(FromList.Items.IndexOfObject(tmp));
              tmpItem.Free;
            end;
      finally
        EndUpdate;
      end;
    end;
  end;

  if PanSingle.Visible then
  begin
    // Set current selected single series
    tmpSource:=Series.DataSource;

    if Assigned(tmpSource) and
       (tmpSource is TChartSeries) then
    begin
      tmpIndex:=CBSingle.ListBox.Items.IndexOfObject(tmpSource);

      with CBSingle do
      if tmpIndex<>ItemIndex then
      begin
        ItemIndex:=tmpIndex;

        Filling:=True;
        try
          CBSingleChange(Self);
        finally
          Filling:=False;
        end;
      end;

      SetValueListCombo(Series,CBValues);
    end
    else
      CBSingle.ItemIndex:=-1;
  end;
end;

class procedure TFunctionEditor.FillSeriesSources(const AItems:TCustomListBox;
   const AItems2:TComboBox; const TheSeries:TChartSeries; AddCurrent:Boolean);

  Procedure AddSource(const ASeries: TChartSeries; const Prefix:String);
  var tmp : String;
      tmpItem : TListBoxItem;
  begin
    if TheSeries.ParentChart.IsValidDataSource(TheSeries,ASeries) then
    begin
      tmp:=SeriesTitleOrName(ASeries);

      if Prefix<>'' then
         tmp:=Prefix+' '+tmp;

      if Assigned(AItems2) then
      begin
        tmpItem:=TListBoxItem.Create(AItems2.Owner);
        tmpItem.Text:=tmp;
        tmpItem.Data:=ASeries;
        AItems2.AddObject(tmpItem);
      end;

      if AddCurrent or (TheSeries.DataSources.IndexOf(ASeries)=-1) then
         if Assigned(AItems) then
         begin
           tmpItem:=TListBoxItem.Create(AItems.Owner);
           tmpItem.Text:=tmp;
           tmpItem.Data:=ASeries;
           AItems.AddObject(tmpItem);
         end;
    end;
  end;

  procedure AddFrom(const Component:TComponent; const Prefix:String);
  var t : Integer;
  begin
    with Component do
       for t:=0 to ComponentCount-1 do
       begin
           if Components[t] is TChartSeries then
              AddSource(TChartSeries(Components[t]),Prefix);

           //recurse because series may be owned by other components as well!
           AddFrom(Components[t],'');
       end;
  end;

begin
  if Assigned(TheSeries.Owner) then
     AddFrom(TheSeries.Owner,'');
end;

procedure TFunctionEditor.FormActivate(Sender: TObject);
begin
  inherited;

  PageControl1.ActiveTab:=TabSource;
  Series:=TChartSeries(TagObject);

//  if Assigned(Series) and Assigned(Series.ParentChart) then
//     TTeePanelAccess(Series.ParentChart).Listeners.Add(Self);

  // Create multi-list form
  ISources:=TSelectListForm.Create(Self);

  With ISources do
  begin
    FromList.Height:=FromList.Height-5;
    ToList.Height:=ToList.Height-5;
    Height:=Height-8;
    OnChange:=CBSourcesChange;
    TagObject:=Self.Series;
  end;

  EmbeddForm(ISources,TabSource);

  TeeTranslateControl(ISources);

  if Assigned(Series) then
  begin
    FillSeries;

    // Select current at list of functions
    if Assigned(Series.FunctionType) then
       CBSources.ItemIndex:=IndexOfClass(CBSources,Series.FunctionType.ClassType)
    else
       CBSources.ItemIndex:=CBSources.ListBox.Items.IndexOf(TeeMsg_FunctionNone);

    BApply.Enabled:=(not Assigned(Series.FunctionType))
                    and (Series.DataSource=nil);
  end;

  ISources.FormActivate(Self);
end;

procedure TFunctionEditor.FormCreate(Sender: TObject);
begin
  inherited;

  FillTeeFunctions(CBSources);
end;

function TFunctionEditor.FunctionClass: TTeeFunctionClass;
var tmp : TObject;
begin
  tmp:=CBSources.Selected.Data;

  if tmp is TClassObject then
     result:=TTeeFunctionClass(TClassObject(tmp).TheClass)
  else
     result:=nil;
end;

class procedure TFunctionEditor.SetValueListCombo(const Series: TChartSeries;
  const Combo: TComboBox);
var tmpList : String;
begin
  tmpList:=Series.MandatoryValueList.ValueSource;

  if tmpList='' then
     Combo.ItemIndex:=0
  else
     Combo.ItemIndex:=Combo.ListBox.Items.IndexOf(tmpList);
end;

procedure TFunctionEditor.SpeedButton1Click(Sender: TObject);
var tmpFunc  : TTeeFunctionClass;
begin
  tmpFunc:=TChartGallery.ChooseFunction(Owner);

  if Assigned(tmpFunc) then
  begin
    CBSources.ItemIndex:=IndexOfClass(CBSources,tmpFunc);
    CBSourcesChange(Self);
  end;
end;

procedure TFunctionEditor.Translate;
begin
  TeeTranslateControl(Self,[CBSingle]) // 7.02,7.07
end;

procedure TFunctionEditor.TryCreateNewFunction;
var tmpClass : TTeeFunctionClass;
begin
  { Create a new function... }
  tmpClass:=FunctionClass;

  if Assigned(tmpClass) then
  begin
    if (not Assigned(Series.FunctionType)) or
       (Series.FunctionType.ClassType<>tmpClass) then
    begin
      Series.FunctionType:=nil;
      CreateNewTeeFunction(Series,tmpClass);

      BApply.Enabled:=False;
    end;
  end
  else
    Series.FunctionType:=nil;
end;

procedure TFunctionEditor.BApplyClick(Sender: TObject);

  procedure DoApply;
  var t : Integer;
  begin
    { Set datasources... }
    if PanSingle.Visible then
    begin
      if CBSingle.ItemIndex=-1 then
      begin
        Series.DataSource:=nil;
        Series.MandatoryValueList.ValueSource:='';
      end
      else
      begin
        Series.DataSource:=TChartSeries(CBSingle.Selected.Data);

        if CBValues.ItemIndex=-1 then
           Series.MandatoryValueList.ValueSource:=''
        else
           Series.MandatoryValueList.ValueSource:=CBValues.Selected.Text;
      end;
    end
    else
    begin
      if ISources.ToList.Count=0 then
         Series.DataSource:=nil
      else
      begin
        With ISources.ToList do
           for t:=0 to Count-1 do
               Series.CheckOtherSeries(TChartSeries(ItemByIndex(t).Data));

        With Series.DataSources do
        begin
          // for t:=0 to Count-1 do  // 6.02
          //   TSeriesAccess(Items[t]).RemoveLinkedSeries(TheSeries);

          Clear;

          With ISources.ToList do
          for t:=0 to Count-1 do
            Series.DataSources.Add(TChartSeries(ItemByIndex(t).Data));

            //TSeriesAccess(tmp).AddLinkedSeries(TheSeries); // 6.02
        end;
      end;
    end;

    TryCreateNewFunction;
  end;

var tmp : Boolean;
begin
  inherited;

  if Assigned(IOptions) and Assigned(IOptions.OnCloseQuery) then
  begin
    tmp:=True;
    IOptions.OnCloseQuery(IOptions,tmp);
  end;

  Series.BeginUpdate;
  DoApply;
  Series.EndUpdate;
  Series.CheckDataSource;

  BApply.Enabled:=False;
end;

procedure TFunctionEditor.CBSingleChange(Sender: TObject);
var tmp : TChartSeries;
    t   : Integer;
    item : TListBoxItem;
begin
  BApply.Enabled:=not Filling;

  with CBValues do
  begin
    Clear;
    Enabled:=CBSingle.ItemIndex<>-1;
    BNone.Enabled:=Enabled;

    if Enabled then
    begin
      tmp:=TChartSeries(CBSingle.Selected.Data);

      for t:=1 to tmp.ValuesList.Count-1 do
      begin
        item:=TListBoxItem.Create(Owner);
        item.Text:=tmp.ValuesList[t].Name;
        AddObject(item);
      end;

      CBValues.ItemIndex:=0;
    end;
  end;
end;

type
  TBaseFunctionEditorClass=class of TBaseFunctionEditor;
  TFunctionAccess=class(TTeeFunction);

procedure TFunctionEditor.CBSourcesChange(Sender: TObject);

  Procedure CreateEditorForm(const EditorClass:String);
  var tmpF : TBaseFunctionEditorClass;
  begin
    FreeAndNil(IOptions);

    tmpF:=TBaseFunctionEditorClass(GetClass(EditorClass));

    if Assigned(tmpF) then
    begin
      IOptions:=tmpF.Create(Self);
      TeeTranslateControl(IOptions);

      IOptions.TagObject:=Series.FunctionType;

      TBaseFunctionEditor(IOptions).ActivateFunction;
      EmbeddForm(IOptions,TabOptions);
    end;
  end;

var tmpEdit : String;
    tmp     : TTeeFunction;
    tmpNoSource : Boolean;
begin
  inherited;

  TabOptions.Visible:=FunctionClass<>nil;

  if TabOptions.Visible then
  begin
    TryCreateNewFunction;

    tmp:=FunctionClass.Create(nil);  // temporary function
    try
      tmpEdit:=TFunctionAccess(tmp).GetEditorClass;
      tmpNoSource:=TFunctionAccess(tmp).NoSourceRequired;

      PanSingle.Visible:=(not tmpNoSource) and TFunctionAccess(tmp).SingleSource;

      if PanSingle.Visible then
      begin
        LValues.Visible:=not TFunctionAccess(tmp).HideSourceList;
        CBValues.Visible:=LValues.Visible;
      end;

      if tmpEdit='' then TabOptions.Visible:=False
                    else CreateEditorForm(tmpEdit);

      if tmpNoSource then
      begin
        TabOptions.Visible:=True;
        PageControl1.ActiveTab:=TabOptions;
      end;

      TabSource.Visible:=not tmpNoSource;
    finally
      tmp.Free;
    end;

    BApply.Enabled:=True;
  end
  else
  begin
    TabSource.Visible:=True;
    PanSingle.Visible:=False;
  end;

  //ISources.Visible:=not PanSingle.Visible;
end;

procedure TFunctionEditor.CBValuesChange(Sender: TObject);
begin
  BApply.Enabled:=True;
end;

procedure TFunctionEditor.BNoneClick(Sender: TObject);
begin
  CBSingle.ItemIndex:=-1;
  CBSingleChange(Self);
end;

end.
