{********************************************}
{ TeeChart Data Import Component             }
{ Copyright (c) 2013-2020 by Steema Software }
{ All Rights Reserved                        }
{********************************************}
unit TeeImportEditor;
{$I TeeDefs.inc}

interface

uses
  {$IFDEF MSWINDOWS}
  Windows, Messages,
  {$ENDIF}

  SysUtils, Classes,

  {$IFDEF D16}
  Types, // Inline hint
  {$ENDIF}

  Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls,
  TeeImport, Chart, Buttons,

  {$IFDEF MSWINDOWS}
  TeeGDIPlus,
  {$ENDIF}

  TeeProcs, TeEngine, CheckLst, TeeLisB;

type
  TImportEditor = class(TForm)
    PageControl1: TPageControl;
    TabOrigin: TTabSheet;
    PanelButtons: TPanel;
    RGOrigin: TRadioGroup;
    Panel2: TPanel;
    BImport: TButton;
    BPrevious: TSpeedButton;
    BNext: TSpeedButton;
    TabOptions: TTabSheet;
    TabPreview: TTabSheet;
    PreviewChart: TChart;
    OpenDialog1: TOpenDialog;
    PanelFile: TPanel;
    LFile: TLabel;
    CBFile: TComboBox;
    ChooseFile: TSpeedButton;
    PanelWeb: TPanel;
    LURL: TLabel;
    CBURL: TComboBox;
    PanelComponent: TPanel;
    LComponent: TLabel;
    Tree: TTreeView;
    PanelOptions: TPanel;
    BAnalyze: TButton;
    PanelSelected: TPanel;
    CBSeries: TChartListBox;
    Panel1: TPanel;
    Panel3: TPanel;
    Label1: TLabel;
    Panel4: TPanel;
    FormatOptions: TPanel;
    procedure FormResize(Sender: TObject);
    procedure BNextClick(Sender: TObject);
    procedure PageControl1Change(Sender: TObject);
    procedure BPreviousClick(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure BImportClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure CBFileChange(Sender: TObject);
    procedure CBURLChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ChooseFileClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RGOriginClick(Sender: TObject);
    procedure TreeChange(Sender: TObject; Node: TTreeNode);
    procedure BAnalyzeClick(Sender: TObject);
    procedure CBSeriesChangeActive(Sender: TChartListBox;
      Series: TCustomChartSeries);
  private
    { Private declarations }

    ShouldSaveHistory,
    LoadedHistory : Boolean;

    ChartEditor : TCustomForm;

    FImport : TDataImport;

    Origin_Component:Integer;

    procedure FillOptions;
    procedure LoadComponents;
    procedure LoadHistory;
    procedure SaveHistory;

    function Origin_Clipboard:Integer;
    function SelectedComponent:TComponent;
    procedure ShowHideTabs;
    procedure TryAddHistory(const ACombo:TComboBox);
  public
    { Public declarations }

    // When True, enable "Component" import source at runtime.
    // Default is False. Allow importing at design-time only.
    ComponentRuntime : Boolean;

    Chart : TCustomChart;
  end;

implementation

{$IFNDEF LCL}
{$R *.dfm}
{$ELSE}
{$R *.lfm}
{$ENDIF}

uses
  Clipbrd, Registry, Math,
  TeePenDlg, TeeEditCha, TeCanvas, TeeProCo;

const
  Origin_File=0;
  Origin_URL=1;

function TImportEditor.Origin_Clipboard:Integer;
begin
  if csDesigning in Chart.ComponentState then
     result:=3
  else
     result:=2;
end;

procedure TImportEditor.FormResize(Sender: TObject);
begin
  // Resize RadioGroup
  RGOrigin.SetBounds(RGOrigin.Left,RGOrigin.Top,Min(220,TabOrigin.Width-32),160);

  CBFile.Width:=TabOptions.Width-51;
  ChooseFile.Left:=CBFile.Left+CBFile.Width+2;

  CBURL.Width:=CBFile.Width;

  Tree.Height:=TabOptions.Height-56;
  Tree.Width:=CBURL.Width;
end;

procedure TImportEditor.TryAddHistory(const ACombo:TComboBox);

  function ExistsItem(const AText:String):Boolean;
  var t : Integer;
  begin
    for t:=0 to ACombo.Items.Count-1 do
        if AText=UpperCase(Trim(ACombo.Items[t])) then
        begin
          result:=True;
          Exit;
        end;

    result:=False;
  end;

var S : String;
begin
  S:=UpperCase(Trim(ACombo.Text));

  if (S<>'') and (not ExistsItem(S)) then
  begin
    ACombo.Items.Insert(0, ACombo.Text);
    ShouldSaveHistory:=True;
  end;
end;

procedure TImportEditor.BNextClick(Sender: TObject);
begin
  if PageControl1.ActivePage=TabOptions then
  begin
    if PanelFile.Visible then
       TryAddHistory(CBFile)
    else
    if PanelWeb.Visible then
       TryAddHistory(CBURL);
  end;

  PageControl1.ActivePageIndex:=PageControl1.ActivePageIndex+1;
  PageControl1Change(Self);
end;

type
  TImportAccess=class(TDataImport);

procedure TImportEditor.PageControl1Change(Sender: TObject);

  function CanPreview:Boolean;
  begin
    result:=(CBFile.Visible and (Trim(CBFile.Text)<>'')) or
            (CBURL.Visible and (Trim(CBURL.Text)<>'')) or
            (Tree.Visible and (SelectedComponent<>nil)) or
            ((RGOrigin.ItemIndex=Origin_Clipboard) and Clipboard.HasFormat(CF_TEXT));
  end;

begin
  BPrevious.Enabled:=PageControl1.ActivePageIndex>0;
  BNext.Enabled:=PageControl1.ActivePageIndex<PageControl1.PageCount-1;

  case PageControl1.ActivePageIndex of
    0: FormActivate(Self);

    1: begin
         PanelFile.Visible:=RGOrigin.ItemIndex=Origin_File;
         PanelWeb.Visible:=RGOrigin.ItemIndex=Origin_URL;
         PanelComponent.Visible:=RGOrigin.ItemIndex=Origin_Component;

         if RGOrigin.ItemIndex=Origin_Clipboard then
            BAnalyze.Enabled:=True;
            
         if (not LoadedHistory) and
            ( CBFile.Visible or CBURL.Visible ) and
            ( (CBFile.Items.Count=0) or (CBURL.Items.Count=0) ) then
              LoadHistory;

         if PanelComponent.Visible and (Tree.Items.Count=0) then
            LoadComponents;

         if PanelFile.Visible then
            BNext.Enabled:=Trim(CBFile.Text)<>'';

         if PanelWeb.Visible then
            BNext.Enabled:=Trim(CBURL.Text)<>'';

         if PanelComponent.Visible then
         begin
           Tree.Height:=TabOptions.Height-56;
           BNext.Enabled:=SelectedComponent<>nil;
         end;
       end;

    2: if CanPreview then
       begin
         FImport.ImportToChart(TImportAccess(FImport).IFormat,PreviewChart);
         BImport.Enabled:=Assigned(Chart);
       end;
  end;

  ShowHideTabs;
end;

procedure TImportEditor.BPreviousClick(Sender: TObject);
begin
  PageControl1.ActivePageIndex:=PageControl1.ActivePageIndex-1;
  PageControl1Change(Self);
end;

Procedure TeeImportShowEditor(AOwner:TComponent; APanel:TCustomTeePanel;
                              ATab:TTabSheet; APageControl:TPageControl);

var tmpForm : TImportEditor;

    {$IFDEF TEEOCX}
    tmpHidden : TChartEditorHiddenTabs;
    {$ENDIF}
begin
  if Assigned(APanel) then
  begin
    {$IFDEF TEEOCX}
    if AOwner is TChartEditForm then
       tmpHidden:=TChartEditForm(AOwner).TheHiddenTabs
    else
       tmpHidden:=[];
    {$ENDIF}

    if Assigned(APanel) then
    begin
      if not Assigned(ATab) then
      begin
        ATab:=TTabSheet.Create(AOwner);
        ATab.Caption:=TeeMsg_Import;
        ATab.PageControl:=APageControl;
      end
      else
      if (ATab.ControlCount=0) and (ATab.Caption=TeeMsg_Import) then
      begin
        tmpForm:=TImportEditor.Create(AOwner);
        tmpForm.Align:=alClient;

        if AOwner is TChartEditForm then
           tmpForm.ChartEditor:=TCustomForm(AOwner);

        TeeTranslateControl(tmpForm);
        AddFormTo(tmpForm,ATab,APanel);
      end;
    end;
  end;
end;

procedure InstallHook;
begin
  TeeOnShowEditor.Add(@TeeImportShowEditor);
end;

procedure RemoveHook;
begin
  TeeOnShowEditor.Remove(@TeeImportShowEditor);
end;

procedure TImportEditor.FormActivate(Sender: TObject);
var tmp : Boolean;
begin
  tmp:=Clipboard.HasFormat(CF_TEXT) or Clipboard.HasFormat(CF_PICTURE);

  if tmp then
  begin
    if RGOrigin.Items.Count=Origin_Clipboard then
       RGOrigin.Items.Add('Clipboard');
  end
  else
    if RGOrigin.Items.Count>Origin_Clipboard then
       RGOrigin.Items.Delete(Origin_Clipboard);
end;

type
  TChartEditFormAccess=class(TChartEditForm);

procedure TImportEditor.BImportClick(Sender: TObject);
begin
  FImport.ImportToChart(TImportAccess(FImport).IFormat,Chart);

  if Assigned(ChartEditor) then
  begin
    TChartEditFormAccess(ChartEditor).RefreshSeriesNodes;
    TChartEditFormAccess(ChartEditor).ShowHideGroups;

    // Select just create SeriesGroup at Chart Editor
  end
  else
    ModalResult:=mrOk;
end;

procedure TImportEditor.FormShow(Sender: TObject);
begin
  if Tag<>0 then
     Chart:=TCustomChart(Tag);

  if Origin_Component<>-1 then
  if (not Assigned(Chart)) or
     ((not (csDesigning in Chart.ComponentState)) and (not ComponentRuntime) ) then
  begin
    RGOrigin.Items.Delete(Origin_Component);
    Origin_Component:=-1;
  end;
end;

procedure TImportEditor.CBFileChange(Sender: TObject);
begin
  PanelSelected.Visible:=False;
  BAnalyze.Enabled:=CBFile.Text<>'';
end;

procedure TImportEditor.CBURLChange(Sender: TObject);
begin
  PanelSelected.Visible:=False;
  BAnalyze.Enabled:=CBURL.Text<>'';
end;

procedure TImportEditor.LoadComponents;

  function ComponentTitle(const AComponent:TComponent):String;
  begin
    if AComponent is TCustomForm then
       result:=TCustomForm(AComponent).Caption
    else
       result:='';

    if result='' then
       result:=AComponent.Name;

    if result='' then
       result:=AComponent.ClassName;
  end;

  procedure AddComponents(const ANode:TTreeNode);
  var t : Integer;
      AOwner : TComponent;
      tmp : TComponent;
  begin
    AOwner:=TComponent(ANode.Data);

    for t:=0 to AOwner.ComponentCount-1 do
    begin
      tmp:=AOwner.Components[t];

      if TDataImport.CanImport(tmp) then
         AddComponents(Tree.Items.AddChildObject(ANode,ComponentTitle(tmp),tmp));
    end;
  end;

begin
  Tree.Items.Clear;

  if Assigned(Chart) and Assigned(Chart.Owner) then
     AddComponents(Tree.Items.AddChildObject(nil,ComponentTitle(Chart.Owner),Chart.Owner));

  if Tree.Items.Count>0 then
     Tree.Items[0].Expanded:=True;
end;

const
  HistoryKey='Software\Steema Software\TeeChart Pro\Import';

procedure TImportEditor.LoadHistory;
var R : TRegistry;

  procedure LoadItems(const Key:String; const AItems:TStrings);
  var t, n : Integer;
  begin
    AItems.Clear;

    if R.OpenKeyReadOnly(HistoryKey+'\'+Key) then
    try
      if R.ValueExists('Count') then
      begin
        n:=R.ReadInteger('Count');

        if n>0 then
        begin
          AItems.BeginUpdate;
          try
            for t:=0 to n-1 do
                AItems.Add(R.ReadString('Item'+IntToStr(t)));
          finally
            AItems.EndUpdate;
          end;
        end;
      end;
      
    finally
      R.CloseKey;
    end;
  end;

begin
  R:=TRegistry.Create(KEY_READ);
  try
    LoadItems('File_History',CBFile.Items);
    LoadItems('URL_History',CBURL.Items);

    LoadedHistory:=True;
  finally
    R.Free;
  end;
end;

procedure TImportEditor.SaveHistory;
var R : TRegistry;

  procedure SaveItems(const Key:String; const AItems:TStrings);
  var t : Integer;
  begin
    if R.OpenKey(HistoryKey+'\'+Key,True) then
    begin
      for t:=0 to AItems.Count-1 do
          R.WriteString('Item'+IntToStr(t),AItems[t]);

      R.WriteInteger('Count',AItems.Count);
    end;
  end;

begin
  R:=TRegistry.Create;
  try
    SaveItems('File_History',CBFile.Items);
    SaveItems('URL_History',CBURL.Items);
  finally
    R.Free;
  end;
end;

function TImportEditor.SelectedComponent: TComponent;
begin
  if Assigned(Tree.Selected) and Assigned(Tree.Selected.Data) then
     result:=TComponent(Tree.Selected.Data)
  else
     result:=nil;
end;

procedure TImportEditor.ShowHideTabs;
var Old,
    t : Integer;
begin
  Old:=PageControl1.ActivePageIndex;

  for t:=0 to PageControl1.PageCount-1 do
      PageControl1.Pages[t].TabVisible:=t=Old;

  PageControl1.ActivePageIndex:=Old;
end;

procedure TImportEditor.FormCreate(Sender: TObject);
begin
  Origin_Component:=2;

  PageControl1.ActivePageIndex:=0;

  ShowHideTabs;
end;

procedure TImportEditor.ChooseFileClick(Sender: TObject);
begin
  OpenDialog1.FileName:=CBFile.Text;

  if OpenDialog1.Execute then
  begin
    CBFile.Text:=OpenDialog1.FileName;
    TryAddHistory(CBFile);

    CBFileChange(Self);
  end;
end;

procedure TImportEditor.FormDestroy(Sender: TObject);
begin
  FImport.Free;

  if ShouldSaveHistory then
     SaveHistory;
end;

procedure TImportEditor.RGOriginClick(Sender: TObject);
begin
  PreviewChart.FreeAllSeries;
  PreviewChart.Title.Caption:='';

  BImport.Enabled:=False;
end;

procedure TImportEditor.TreeChange(Sender: TObject; Node: TTreeNode);
begin
  BAnalyze.Enabled:=PanelComponent.Visible and (SelectedComponent<>nil);
end;

type
  TDataFormatAccess=class(TDataFormat);

procedure TImportEditor.FillOptions;
var
  tmpY : Integer;

  procedure AddOption(const AIndex:Integer; const S:String);
  var tmpTitle : String;
      tmpType  : String;
      tmpLabel : TLabel;
      tmpCombo : TComboFlat;
  begin
    tmpTitle:=TeeExtractField(S,1,',');

    if tmpTitle='' then
       tmpLabel:=nil
    else
    begin
      tmpLabel:=TLabel.Create(Self);
      tmpLabel.Caption:=tmpTitle;
      tmpLabel.Left:=14;
      tmpLabel.Top:=tmpY;
      tmpLabel.Parent:=FormatOptions;

      Inc(tmpY,tmpLabel.Height+4);
    end;

    tmpType:=UpperCase(Trim(TeeExtractField(S,2,',')));

    if tmpType='COMBO' then
    begin
      tmpCombo:=TComboFlat.Create(Self);
      tmpCombo.Left:=14;
      tmpCombo.Top:=tmpY;

      tmpCombo.Parent:=FormatOptions;

      // Do this after tmpCombo.Parent is set:
      TDataFormatAccess(TImportAccess(FImport).IFormat).PrepareOption(tmpCombo,AIndex);

      if Assigned(tmpLabel) then
         tmpLabel.FocusControl:=tmpCombo;

      Inc(tmpY,tmpCombo.Height+4);
    end;
  end;

  procedure AddFormatOptions;
  var tmp : TStrings;
      t : Integer;
  begin
    tmp:=TDataFormatAccess(TImportAccess(FImport).IFormat).Options;
    try
      tmpY:=20;

      for t:=0 to tmp.Count-1 do
          AddOption(t,tmp[t]);
    finally
      tmp.Free;
    end;
  end;

begin
  PanelSelected.Visible:=True;
  CBSeries.Chart:=TImportAccess(FImport).IFormat;

  while FormatOptions.ControlCount>0 do
        FormatOptions.Controls[0].Free;

  if CBSeries.Chart<>nil then
     AddFormatOptions;
end;

procedure TImportEditor.BAnalyzeClick(Sender: TObject);
begin
  // Analyze

  FImport.Free;

  FImport:=TDataImport.Create(Self);
  FImport.AnalyzeMode:=True;

  if RGOrigin.ItemIndex=Origin_File then
     FImport.ImportFromFile(CBFile.Text)
  else
  if RGOrigin.ItemIndex=Origin_URL then
     FImport.ImportFromURL(CBURL.Text)
  else
  if RGOrigin.ItemIndex=Origin_Component then
     FImport.Import(SelectedComponent)
  else
  begin
    // Origin_Clipboard:

    if Clipboard.HasFormat(CF_TEXT) then
       FImport.Import(Clipboard.AsText);
  end;

  FillOptions;

  BAnalyze.Enabled:=False;
  BNext.Enabled:=True;
end;

procedure TImportEditor.CBSeriesChangeActive(Sender: TChartListBox;
  Series: TCustomChartSeries);
begin
  BNext.Enabled:=TImportAccess(FImport).IFormat.CountActiveSeries>0;
  BImport.Enabled:=BNext.Enabled;
end;

initialization
  TeeActivateGroup;
  RegisterClass(TImportEditor);
  InstallHook;
finalization
  RemoveHook;
end.
