{********************************************}
{ TeeChart Pro - TChartListBox class         }
{                                            }
{ Copyright (c) 2011-2020 by Steema Software }
{        All Rights Reserved                 }
{********************************************}
unit FMXTee.Chart.ListBox;
{$I TeeDefs.inc}

interface

uses
  System.Classes, System.Types, FMX.Types, System.UITypes,
  FMX.ListBox, FMX.Menus,
  FMXTee.Procs, FMXTee.Engine, FMXTee.Chart;

type
  {$IFDEF D16}
  [ComponentPlatformsAttribute(TeeAllComponentPlatformIDs)]
  {$ENDIF}
  TChartListBox=class(TListBox, ITeeEventListener)
  private
    FChart: TCustomChart;
    FGroup: TSeriesGroup;
    FColors : Boolean;
    FIcons : Boolean;
    FNames: Boolean;

    procedure ChangeSeriesColor(Sender:TObject);
    procedure CheckActiveCheck(const AItem:TListBoxItem);
    procedure CheckColor(const AItem:TListBoxItem);
    procedure CheckIcon(const AItem:TListBoxItem);
    procedure FillSeries(const OldSeries:TChartSeries);
    function GetChecks:Boolean;
    Function GetSelectedSeries:TChartSeries;
    function GetSeries(Index:Integer):TChartSeries;
    Function GetSeriesGroup:TCustomSeriesList;
    Procedure SelectSeries(AIndex:Integer);
    procedure SetChecks(const Value:Boolean);
    procedure SetColors(const Value:Boolean);
    procedure SetGroup(const Value: TSeriesGroup);
    procedure SetIcons(const Value:Boolean);
    Procedure SetSelectedSeries(const Value:TChartSeries);
    function SeriesItemText(const ASeries:TChartSeries):String;
    procedure SetChart(const Value:TCustomChart);
    procedure SetNames(const Value: Boolean);
    procedure UpdateSeries;
  protected
    function AddSeries(const ASeries:TChartSeries):TListBoxItem;
    Function AnySelected:Boolean;

    {$IFNDEF D20}
    procedure ContextMenu(const ScreenPosition: TPointF); override;
    {$ENDIF}

    function SelectionCount:Integer;
    procedure TeeEvent(Event: TTeeEvent);  { interface }
  public
    {$IFNDEF D20}
    PopupMenu : TPopupMenu;
    {$ENDIF}

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

    function DeleteSeries:Boolean;
    function RenameSeries:Boolean;

    property Series[Index:Integer]:TChartSeries read GetSeries;
  published
    property Chart:TCustomChart read FChart write SetChart;
    property SelectedSeries:TChartSeries read GetSelectedSeries
                                         write SetSelectedSeries;
    property SeriesGroup:TSeriesGroup read FGroup write SetGroup;
    property ShowActiveCheck:Boolean read GetChecks write SetChecks default True;
    property ShowSeriesColor:Boolean read FColors write SetColors default True;
    property ShowSeriesIcon:Boolean read FIcons write SetIcons default True;
    property ShowSeriesNames:Boolean read FNames write SetNames default False;
  end;

implementation

uses
  System.SysUtils, FMXTee.Constants, FMX.Objects, FMXTee.Canvas,
  FMXTee.Editor.Stroke,
  {$IFDEF D17}
  FMX.Styles,
  {$ENDIF}
  {$IFDEF D18}
  FMX.StdCtrls,
  {$ENDIF}
  FMX.Controls, FMX.Dialogs;

{$R FMXTee.Chart.ListBox.Style.res}
{$R TeeBmps.res}

{ TChartListBox }

function TChartListBox.GetSeries(Index: Integer): TChartSeries;
begin
  if (Index<>-1) and (Index<Count) then
     result:=TChartSeries(ListItems[Index].Data)
  else
     result:=nil;
end;

type
  TChartAccess=class(TCustomChart);

procedure TChartListBox.SetChart(const Value: TCustomChart);
begin
  if FChart<>Value then
  begin
    if Assigned(Chart) then
    begin
      TChartAccess(Chart).RemoveListener(Self);
      Chart.RemoveFreeNotification(Self);
    end;

    FChart:=Value;

    if Assigned(Chart) then
    begin
      Chart.FreeNotification(Self);
      TChartAccess(Chart).Listeners.Add(Self);
      FillSeries(nil);
    end
    else
      Clear;
  end;
end;

procedure TChartListBox.SetChecks(const Value: Boolean);
var t : Integer;
begin
  if GetChecks<>Value then
  begin
    ShowCheckboxes:=Value;

    for t:=0 to Count-1 do
        CheckActiveCheck(ListItems[t]);
  end;
end;

procedure TChartListBox.SetColors(const Value: Boolean);
var t : Integer;
begin
  if FColors<>Value then
  begin
    FColors:=Value;

    for t:=0 to Count-1 do
        CheckColor(ListItems[t]);
  end;
end;

procedure TChartListBox.SetGroup(const Value: TSeriesGroup);
begin
  if FGroup<>Value then
  begin
    FGroup:=Value;
    UpdateSeries;
  end;
end;

procedure TChartListBox.SetIcons(const Value: Boolean);
var t : Integer;
begin
  if FIcons<>Value then
  begin
    FIcons:=Value;

    for t:=0 to Count-1 do
        CheckIcon(ListItems[t]);
  end;
end;

function TChartListBox.SeriesItemText(const ASeries:TChartSeries):String;
begin
  if FNames then
  begin
    result:=ASeries.Name;
    if result='' then
       result:=TeeMsg_Series+' '+IntToStr(Chart.SeriesList.IndexOf(ASeries));
  end
  else
    result:=SeriesTitleOrName(ASeries);
end;

procedure TChartListBox.SetNames(const Value: Boolean);
var t : Integer;
begin
  if FNames<>Value then
  begin
    FNames:=Value;

    for t := 0 to Count-1 do
        with ItemByIndex(t) do
             Text:=SeriesItemText(TChartSeries(Data));
  end;
end;

function TChartListBox.SelectionCount:Integer;
var t : Integer;
begin
  result:=0;

  for t:=0 to Count-1 do
      if ListItems[t].IsSelected then
         Inc(result);
end;

procedure TChartListBox.TeeEvent(Event:TTeeEvent);

  procedure CheckLegendBoxes;
  begin
    //if Chart.Legend.CheckBoxes then
    //   CheckStyle:=Chart.Legend.CheckBoxesStyle;
  end;

  function ItemOfSeries(ASeries:TCustomChartSeries):TListBoxItem;
  var t : Integer;
  begin
    for t := 0 to Count-1 do
    if ListItems[t].Data=ASeries then
       Exit(ListItems[t]);

    result:=nil;
  end;

var tmpItem : TListBoxItem;
    tmpColor : TFmxObject;
begin
  if not (csDestroying in ComponentState) then
  if Event is TTeeSeriesEvent then
  With TTeeSeriesEvent(Event) do
  Case Event of
    seChangeColor : begin
                      tmpItem:=ItemOfSeries(Series);
                      if Assigned(tmpItem) then
                      begin
                        tmpColor:=tmpItem.FindStyleResource('color');
                        (tmpColor as TShape).Fill.Color:=TChartSeries(Series).Color;
                      end;
                    end;

   seChangeActive : begin
                      CheckLegendBoxes;
                    end;

    seChangeTitle : begin
                     tmpItem:=ItemOfSeries(Series);
                     if Assigned(tmpItem) then
                        tmpItem.Text:=SeriesTitleOrName(Series);
                    end;

         seRemove : begin
                      tmpItem:=ItemOfSeries(Series);
                      if Assigned(tmpItem) then
                         tmpItem.Free;
                    end;

           seAdd  : UpdateSeries;
           seSwap : UpdateSeries;
  end
  else
  if Event is TTeeView3DEvent then
     Repaint;
end;

function TChartListBox.AnySelected: Boolean;
begin
  if MultiSelect then
     result:=SelectionCount>0
  else
     result:=ItemIndex<>-1;
end;

procedure TChartListBox.ChangeSeriesColor(Sender: TObject);
var tmpShape : TShape;
    tmpItem  : TListBoxItem;
begin
  tmpShape:=Sender as TShape;

  tmpShape.Fill.Color:=EditColor(Self,tmpShape.Fill.Color,[]);

  tmpItem:=TListBoxItem(tmpShape.Parent.Parent);

  (tmpItem.Data as TChartSeries).Color:=tmpShape.Fill.Color;

  tmpItem.Repaint;
end;

{$IFNDEF D20}
procedure TChartListBox.ContextMenu(const ScreenPosition: TPointF);
begin
  inherited;

  if (csDesigning in ComponentState) or (PopupMenu=nil) then
    Exit;

//  HideOther1.Enabled:=ItemIndex<>-1;
//  ShowAll1.Enabled:=not Chart.SeriesList.AllActive;

  PopupMenu.PopupComponent:=Self;
  PopupMenu.Popup(Round(ScreenPosition.X), Round(ScreenPosition.Y));
end;
{$ENDIF}

constructor TChartListBox.Create(AOwner: TComponent);
begin
  inherited;

  FColors:=True;
  FIcons:=True;

  ItemHeight:=24;
  MultiSelect:=True;
  ShowCheckboxes:=True;
end;

Destructor TChartListBox.Destroy;
begin
  Chart:=nil;
  inherited;
end;

function TChartListBox.DeleteSeries: Boolean;

  Procedure DoDelete;
  var t,
      Old : Integer;
  begin
    Old:=ItemIndex;

    if MultiSelect then
    begin
      t:=0;

      while t<Count do
      if ListItems[t].IsSelected then
         Series[t].{$IFDEF AUTOREFCOUNT}ParentChart:=nil{$ELSE}Free{$ENDIF}
      else
         Inc(t);
    end
    else
    if ItemIndex<>-1 then
       Series[ItemIndex].{$IFDEF AUTOREFCOUNT}ParentChart:=nil{$ELSE}Free{$ENDIF};

    if Count>Old then
       SelectSeries(Old)
    else
    if Count>0 then
       SelectSeries(0);
  end;

var tmpSt : String;
    Old : Integer;
begin
  result:=False;

  if AnySelected then { 5.01 }
  begin
    if (not MultiSelect) or (SelectionCount=1) then  { 5.01 }
       tmpSt:=SeriesTitleOrName(SelectedSeries)
    else
       tmpSt:=TeeMsg_SelectedSeries;

    if YesNo(Format(TeeMsg_SureToDelete, [tmpSt])) then
    begin
      Old:=ItemIndex;

      DoDelete;

      if Old<Count then
         ItemIndex:=Old
      else
         ItemIndex:=Count-1;

      UpdateSelection;

      result:=True;
    end;
  end;
end;

const
  {$IFDEF D23}
  TeeListStyle='chartlistbox';
  {$ELSE}
  TeeListStyle='chartlistbox';
  {$ENDIF}

function TChartListBox.AddSeries(const ASeries:TChartSeries):TListBoxItem;
var tmp : TFMXObject;
begin
  result:=TListBoxItem.Create(Owner);
  result.Name:=TeeGetUniqueName(Owner,'SeriesItem');
  result.Text:=SeriesItemText(ASeries);

  result.Data:=ASeries;
  result.IsChecked:=ASeries.Visible;
  AddObject(result);

  {$IFDEF D21}
  // This is necessary again in XE7. (Bug?)
  result.StyleLookup:=TeeListStyle;

  {$ELSE}

  {$IFNDEF D19}
  // Change StyleLookup *after* adding to ListSeries:
  result.StyleLookup:=TeeListStyle;
  {$ENDIF}

  {$ENDIF}

  result.ApplyStyleLookup;

  // Disable text autosize to reset text align to client, as we need
  // centered vertical alignment:
  tmp:=result.FindStyleResource('text');

  if Assigned(tmp) then
     (tmp as TText).AutoSize:=False;

  CheckActiveCheck(result);
  CheckColor(result);
  CheckIcon(result);
end;

procedure TChartListBox.CheckActiveCheck(const AItem:TListBoxItem);
var tmp : TFMXObject;
begin
  tmp:=AItem.FindStyleResource('check');

  if Assigned(tmp) then
     TCheckBox(tmp).Visible:=ShowCheckboxes;
end;

procedure TChartListBox.CheckIcon(const AItem:TListBoxItem);
var tmp : TFMXObject;
begin
  tmp:=AItem.FindStyleResource('icon');

  if Assigned(tmp) then
    if ShowSeriesIcon then
    begin
      TeeLoadBitmap(TImage(tmp).Bitmap,
                    TChartSeries(AItem.Data).ClassName,
                    TChartSeries(AItem.Data).ClassParent.ClassName);

      TImage(tmp).Visible:=True;
    end
    else
      TImage(tmp).Visible:=False;
end;

procedure TChartListBox.CheckColor(const AItem:TListBoxItem);
var tmpColor : TFMXObject;
begin
  tmpColor:=AItem.FindStyleResource('color');

  if Assigned(tmpColor) then
    if ShowSeriesColor then
    begin
      TShape(tmpColor).Width:=AItem.Height;
      TShape(tmpColor).Fill.Color:=TChartSeries(AItem.Data).Color;
      TShape(tmpColor).OnClick:=ChangeSeriesColor;
    end
    else
      TShape(tmpColor).Visible:=False;
end;

procedure TChartListBox.FillSeries(const OldSeries: TChartSeries);
var t, tmp : Integer;
    tmpItems : TStrings;
begin
  tmpItems:=TStringList.Create;
  try
    FillSeriesItems(tmpItems, GetSeriesGroup, not FNames);

    tmp:=tmpItems.IndexOfObject(OldSeries);

    BeginUpdate;
    Clear;

    //{$IFNDEF D23}  // https://quality.embarcadero.com/browse/RSB-978

    // PENDING XE2
    {$IFDEF D17}
    if not Assigned(FMX.Types.FindStyleResource(TeeListStyle)) then
       FMX.Types.AddResource(
          {$IFDEF D20}TStyleStreaming{$ELSE}TStyleManager{$ENDIF}.LoadFromResource(HInstance,TeeListStyle,RT_RCDATA));
    {$ENDIF}

    {$IFDEF D18}
    DefaultItemStyles.ItemStyle:=TeeListStyle;
    {$ENDIF}

    //{$ENDIF}

    for t := 0 to tmpItems.Count-1 do
        AddSeries(tmpItems.Objects[t] as TChartSeries);

    EndUpdate;
  finally
    tmpItems.Free;
  end;

  if (tmp=-1) and (Count>0) then tmp:=0;

  if (tmp<>-1) and (Count>tmp) then
     ItemIndex:=tmp;

  // QC102821: TCommonCustomForm.MouseDown  (BeginAutoDrag instead of MouseDown)
  DragMode:=TDragMode.dmManual;
end;

Procedure TChartListBox.SelectSeries(AIndex:Integer); { 5.01 }
begin
  if MultiSelect then ListItems[AIndex].IsSelected:=True
                 else ItemIndex:=AIndex;
end;

function TChartListBox.GetChecks: Boolean;
begin
  result:=ShowCheckBoxes;
end;

function TChartListBox.GetSelectedSeries: TChartSeries;
begin
  result:=Series[ItemIndex];
end;

procedure TChartListBox.SetSelectedSeries(const Value: TChartSeries);
var
  t: Integer;
begin
  ClearSelection;

  for t := 0 to Count-1 do
    if ListItems[t].Data=Value then
    begin
      ItemIndex:=t;
      Exit;
    end;

  ItemIndex:=-1;
end;

procedure TChartListBox.UpdateSeries;
begin
  FillSeries(SelectedSeries);
end;

Function TChartListBox.GetSeriesGroup:TCustomSeriesList;
begin
  if Assigned(FGroup) then result:=FGroup.Series
                      else result:=Chart.SeriesList;
end;

function TChartListBox.RenameSeries: Boolean;
var S : String;
begin
  S:=SeriesItemText(SelectedSeries);

  result:=TeeInputQuery(TeeMsg_ChangeSeriesTitle, TeeMsg_NewSeriesTitle, S);

  if result then
  begin
    if FNames then
       SelectedSeries.Name:=S
    else
       SelectedSeries.Title:=S;

    Selected.Text:=SeriesItemText(SelectedSeries);
  end;
end;

procedure FreeListResource;
var tmp : TFmxObject;
begin
  tmp:=FMX.Types.FindStyleResource(TeeListStyle);
  if Assigned(tmp) then
     tmp.Free;
end;

initialization
finalization
  FreeListResource;
end.
