unit uBafGrid;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.ExtCtrls, FMX.Layouts,
  System.Contnrs, FMX.Objects, FMX.Edit, FMX.ComboEdit, uBafClientTab,
  FMX.ScrollBox, FMX.Memo, System.Math, FMX.Styles, UBafControls, FMX.TreeView,
  FMX.Menus, uStringIniFile, FMX.ListBox, uBafTypes, uBafComboHelper,
  System.StrUtils, FMX.Pickers, System.Rtti;

type
  TBafGridKind = (gkPage, gkDashHor, gkDashVert);

  TBafPage = class;
  TBafGridPrimaryDivs = class;
  TBafGridPrimaryDiv = class;
  TBafGridCategories = class;
  TBafGridCategory = class;
  TBafGridSegments = class;
  TBafGridSegment = class;
  TBafSimpleGrid = class;
  TBafSgColumns = class;
  TBafSgColumn = class;
  TBafSgRow = class;
  TBafSgCell = class;

  TBafGridParents = record
    Grid: TBafPage;
    PrimaryDivs: TBafGridPrimaryDivs;
    PrimaryDiv: TBafGridPrimaryDiv;
    Categories: TBafGridCategories;
    Category: TBafGridCategory;
    Segments: TBafGridSegments;
    Segment: TBafGridSegment;
    SegButton: TBafButton;
    SimpleGrid: TBafSimpleGrid;
    Columns: TBafSgColumns;
    Column: TBafSgColumn;
    Row: TBafSgRow;
    Cell: TBafSgCell;
    procedure CreateFrom(AParents: TBafGridParents);
  end;

  TBafGridCellPosition = record
    Prim: integer;
    Cat: integer;
    Seg: integer;
    Col: integer;
    Row: integer;
    SegmentType: TBafGridSegmentType;
    Button: integer;
    Position: TBafGridMousePosition;
    PosObject: TObject;
    x, y: integer;
    class operator Equal(a: TBafGridCellPosition; b: TBafGridCellPosition): Boolean;
    class operator NotEqual(a: TBafGridCellPosition; b: TBafGridCellPosition): Boolean;
    procedure Clear;
    function GetXY(AX, AY: integer): TBafGridCellPosition;
    function SetSegment(ASeg: integer; APosition: TBafGridMousePosition;
        APosObject: TObject; ASegmentType: TBafGridSegmentType): boolean;
  end;

  TBafGridEvent = procedure(const Sender: TBafGridParents) of object;

  TBafGridEditEvent = procedure(ASender: TBafGridParents; var AText: string;
      var AAbort: boolean; ACommand: string; AChanged: boolean) of object;

  TBafGridStatusEvent = procedure(ASender: TObject; AStatus: TBafGridStatus) of object;

  TAllSegFunction = function(ASegment: TBafGridSegment): boolean of object;

  TBafGridUtils = class
  public
//    class function CalcTextLeft(ACan: TCanvas; AText: string; AWidth, AScale, AAddScaled: integer;
//        AAlignment: TBafAlignment): integer;
    class procedure SetFont(ACan: TCanvas; AFont: TBafFont);
    class function GetButtonText(AChar: Char): string;

//    class procedure HLine(ACan: TCanvas; x1, x2, y: integer);
//    class procedure VLine(ACan: TCanvas; x, y1, y2: integer);
//    class procedure Rectangle(ACan: TCanvas; ARect: TRect);
//    class function SchiebeRect(ARect: TRect; AX, AY: integer): TRect;
//    class function ResizeRect(ARect: TRect; ALeft, ATop, ARight, ABottom: integer): TRect;
//    class function MouseInRect(ARect: TRect; AX, AY: integer): boolean;
//    class function DarkenColor(AColor: TColor; ADarken: Byte): TColor;
//    class function DoDate(AText: string; AType: TBafGridCellType): string;
//    class function yyyy2Date(AText: string): TDateTime;
  end;


  TBafCheck = class
  private
    FCheckType: TBafCheckType;
    FCondition: string;
    FCaption: string;
    FCheck: string;
    FFailed: boolean;
  public
    property CheckType: TBafCheckType read FCheckType  write FCheckType;
    property Condition: string read FCondition write FCondition;
    property Check: string read FCheck write FCheck;
    property Caption: string read FCaption write FCaption;
    property Failed: boolean read FFailed write FFailed;
  end;

  TBafSgRow = class(TObjectList)
  private
    FParents: TBafGridParents;
    FDarken: boolean;
    FRowType: TBafGridRowType;
    FIndex: integer;
    FDate: TDate;
    FStyle: string;
    FLineP: string;
    FHasCanged: boolean;
    FRowInserted: boolean;
    FDataKeyValue: string;
    FDataLinkedTable: string;
    FDataItemKey: string;
    FDataIdInTable: string;
    FLinkData: string;
    FLinkDataField: string;
    FJoinList: TStringList;
    FJoinType: TBafJoinType;
    FJoinType2: TBafJoinType;
    FJoinList2: TStringList;
    FCellCommand: string;
    function GetCell(ACol: integer): TBafSgCell;
    procedure SetHasCanged(const Value: boolean);
  public
    class procedure Create2List(ARowType: TBafGridRowType; AList: TObjectList; AParents: TBafGridParents);
    destructor Destroy; override;
    procedure AddCell;
    procedure ClearCalc;
    property Parents: TBafGridParents read FParents;
    property Cells[ACol: integer]: TBafSgCell read GetCell;
    property Darken: boolean read FDarken write FDarken;
    property RowType: TBafGridRowType read FRowType write FRowType;
    property Index: integer read FIndex write FIndex;
    property Date: TDate read FDate write FDate;
    property Style: string read FStyle write FStyle;              // for VL
    property LineP: string read FLineP write FLineP;  // for VL
    property HasChanged: boolean read FHasCanged write SetHasCanged;
    property RowInserted: boolean read FRowInserted write FRowInserted;  // for grid
  public   // for VL data
    property DataKeyValue: string read FDataKeyValue write FDataKeyValue;
    property DataLinkedTable: string read FDataLinkedTable write FDataLinkedTable;
    property DataIdInTable: string read FDataIdInTable write FDataIdInTable;
    property DataItemKey: string read FDataItemKey write FDataItemKey;
  public   // for linked data und join
    property LinkData: string read FLinkData write FLinkData;
    property LinkDataField: string read FLinkDataField write FLinkDataField;
    property JoinList: TStringList read FJoinList;
    property JoinType: TBafJoinType read FJoinType write FJoinType;
    property JoinList2: TStringList read FJoinList2;
    property JoinType2: TBafJoinType read FJoinType2 write FJoinType2;
    property CellCommand: string read FCellCommand write FCellCommand;
  end;

  TBafSgCell = class
  private
    FText: string;
    FCellType: TBafGridCellType;
    FParents: TBafGridParents;
//    FBafButtonList: TBafButtonList;
    FHasChanged: boolean;
    FShowSort: boolean;
    FSortDown: TBafListButtonStat;
    FSortUp: TBafListButtonStat;
    FColSpan: integer;           // cause by Colspan Cell will not painted
    FSpanNoPaint: boolean;
    FLookupHelper: TBafComboHelper;
    FAlignment: TBafAlignment;
    FCommand: string;
    FIsCalced: boolean;
    FReadOnly: boolean;
    FVisible: boolean;
    FDataKey: string;
    FDataKeyColValue: string;
    FCharCase: TEditCharCase;
//    FCellBrushColor: string;
//    FCellFontColor: string;
    FHint: string;
    FDisplayRect: TRectF;
    FCaptionRect: TRectF;
    FMaxLength: integer;
    FDataQuelle: TBafDataQuelle;
    FDataFieldName: string;
    FDataQIndex: integer;
    FLinkedSegment: TBafGridSegment;
    FNoData: boolean;
    FChangeCommand: string;
    FPassword: boolean;
    procedure SetCellType(const Value: TBafGridCellType);
    procedure SetText(const Value: string);
    procedure SetHasChanged(const Value: boolean);
    function ReplaceParameter(AText: string): string;
  public
    constructor Create(AParents: TBafGridParents);
    destructor Destroy; override;
    procedure ToggleBool;
    procedure ToggleTodo;
    procedure SetBool(AValue: boolean);
    procedure SetTodo(AValue: WideChar);
    procedure ClearSort(AOnlyHover: boolean);
    procedure SetSort(APos: TBafGridCellPosition; AOnlyHover: boolean);
    function GetDisplayText: string;
    function GetHintDisplayText: string;
    function GetLookupHelper(var ALookupHelper: TBafComboHelper; ALive: boolean): boolean;
    function GetCellType(AHeaderFooter: boolean): TBafGridCellType;
    procedure SetTextChange(AText: string; AAlwaysChanged: boolean = true);
    property Text: string read FText write SetText;
    property Hint: string read FHint write FHint;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property CellType: TBafGridCellType read FCellType write SetCellType;
    property ShowSort: boolean read FShowSort write FShowSort;
    property SortUp: TBafListButtonStat read FSortUp write FSortUp;
    property SortDown: TBafListButtonStat read FSortDown write FSortDown;
    property ColSpan: integer read FColSpan write FColSpan;
    property LookupHelper: TBafComboHelper read FLookupHelper write FLookupHelper;
    property Alignment: TBafAlignment read FAlignment write FAlignment;
    property Command: string read FCommand write FCommand;
    property ChangeCommand: string read FChangeCommand write FChangeCommand;
    property IsCalced: boolean read FIsCalced write FIsCalced;
    property ReadOnly: boolean read FReadOnly write FReadOnly;
    property Visible: boolean read FVisible write FVisible;
    property Parents: TBafGridParents read FParents;
    property SpanNoPaint: boolean read FSpanNoPaint write FSpanNoPaint;
    property DataQuelle: TBafDataQuelle read FDataQuelle write FDataQuelle;
    property DataQIndex: integer read FDataQIndex write FDataQIndex;
    property DataKeyValue: string read FDataKey write FDataKey;                     // Key in der Data-Table
    property DataKeyColValue: string read FDataKeyColValue write FDataKeyColValue;  // Wert für id_in_table
    property DataFieldName: string read FDataFieldName write FDataFieldName;
    property CharCase: TEditCharCase read FCharCase write FCharCase;
    property DisplayRect: TRectF read FDisplayRect write FDisplayRect;
    property CaptionRect: TRectF read FCaptionRect write FCaptionRect;
    property MaxLength: integer read FMaxLength write FMaxLength; // for VL
    property LinkedSegment: TBafGridSegment read FLinkedSegment write FLinkedSegment; // for VL
    property NoData: boolean read FNoData write FNoData;
    property Password: boolean read FPassword write FPassword;
//    property CellFontColor: string read FCellFontColor write FCellFontColor;
//    property CellBrushColor: string read FCellBrushColor write FCellBrushColor;
  end;

  TBafSgColumn = class(TCollectionItem)
  protected
    FParents: TBafGridParents;
    FStretch: single;
    FCellNullValue: string;
    FCellMaxLength: integer;
    FCellCharCase: TEditCharCase;
    FCellRight: TBafRight;
    FJoinList: TStringList;
    FDataLinkedTable: string;
    FCalcedRect: TRectF;
    FCellDataQuelle: TBafDataQuelle;
    FCellNoData: boolean;
    FHintJoinList: TStringList;
    FIni: TStringIniFile;
    FSortColumn: integer;
    FCellReadOnlyFieldName: string;
    FWidth: single;
    FCellFieldName: string;
    FDataItemKey: string;
    FLineP: string;
    FCaption: string;
    FCellHintFieldName: string;
    FCellVisible: boolean;
    FVisible: boolean;
    FCalcedRow: integer;
    FCellCommand: string;
    FXGridIndex: integer;
    FCellAlignment: TBafAlignment;
    FLookupHelper: TBafComboHelper;
    FCellType: TBafGridCellType;
    FCellNullValueAction: TBafNullValueAction;
    FCellReadOnly: boolean;
    FDataKeyColField: string;
    FJoinType: TBafJoinType;
    procedure SetLinkCommand(const Value: string);
  private
    FScrollRect: TRectF;
    FCellDataQIndex: integer;
    FLinkedSegment: TBafGridSegment;
    FBafSortDirection: TBafSortDirection;
    procedure SetCellFieldName(const Value: string);
  protected
    FLinkCommand: string;
    FLinkFunc: TBafLinkFuncs;
    FLinkCol: integer;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    function SetValues(AWidth, AStretch: integer; AVisible: boolean;
        Style: string): TBafSgColumn;
    procedure Sort(AUp: boolean);
    procedure ToggleSort;
    procedure SetNullValue(ACellNullValueAction: TBafNullValueAction; ACellNullValue: string);
  published
    property Parents: TBafGridParents read FParents;
    property Width: single read FWidth write FWidth;               // minimum width
    property Stretch: single read FStretch write FStretch;         // up to this value stretch the columns
    property CalcedRect: TRectF read FCalcedRect;
    property ScrollRect: TRectF read FScrollRect;                  // to calc horz scroll
    property CalcedRow: integer read FCalcedRow;
    property Visible: boolean read FVisible write FVisible;
    property CellType: TBafGridCellType read FCellType write FCellType;
    property CellMaxLength: integer read FCellMaxLength write FCellMaxLength;
    property CellLookupHelper: TBafComboHelper read FLookupHelper write FLookupHelper;
    property CellReadOnly: boolean read FCellReadOnly write FCellReadOnly;
    property CellVisible: boolean read FCellVisible write FCellVisible;
    property CellAlignment: TBafAlignment read FCellAlignment write FCellAlignment;
    property CellCommand: string read FCellCommand write FCellCommand;
    property CellRight: TBafRight read FCellRight write FCellRight;
    property CellFieldName: string read FCellFieldName write SetCellFieldName;
    property CellHintFieldName: string read FCellHintFieldName write FCellHintFieldName;
    property CellReadOnlyFieldName: string
        read FCellReadOnlyFieldName write FCellReadOnlyFieldName;
    property CellNoData: boolean read FCellNoData write FCellNoData;
    property CellDataQuelle: TBafDataQuelle read FCellDataQuelle write FCellDataQuelle;
    property CellDataQIndex: integer read FCellDataQIndex write FCellDataQIndex;
    property CellNullValueAction: TBafNullValueAction
        read FCellNullValueAction write FCellNullValueAction;
    property CellNullValue: string read FCellNullValue write FCellNullValue;
    property CellCharCase: TEditCharCase read FCellCharCase write FCellCharCase;
    property Ini: TStringIniFile read FIni write FIni;
    property LinkCommand: string read FLinkCommand write SetLinkCommand;
    property Caption: string read FCaption write FCaption;     // only legends of charts and filter forms
    property LineP: string read FLineP write FLineP;
    property XGridIndex: integer read FXGridIndex write FXGridIndex;
    property SortColumn: integer read FSortColumn write FSortColumn;
    property SortDirection: TBafSortDirection read FBafSortDirection write FBafSortDirection;
  public   // for VL data, YGrids and JGrid
    property DataLinkedTable: string read FDataLinkedTable write FDataLinkedTable;
    property DataItemKey: string read FDataItemKey write FDataItemKey;
    property DataKeyColField: string read FDataKeyColField write FDataKeyColField;
    property JoinList: TStringList read FJoinList;
    property HintJoinList: TStringList read FHintJoinList;
    property JoinType: TBafJoinType read FJoinType write FJoinType;
    property LinkedSegment: TBafGridSegment read FLinkedSegment write FLinkedSegment;
  end;

  TBafSgColumns = class(TCollection)
  private
    FParents: TBafGridParents;
    FAllColumnWidth: integer;
    FLineType: TBafGridLineType;
    FRowCount: integer;
    FFixedColsCount: integer;
    FHasLinkedColumns: boolean;
    function GetItem(Index: Integer): TBafSgColumn;
    procedure SetItem(Index: Integer; const Value: TBafSgColumn);
  protected
    FSortStack: TStringList;
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
  public
    constructor Create(AGrid: TBafSimpleGrid);
    destructor Destroy; override;
    function Add: TBafSgColumn;
    function AddColumn(AFieldName, ACaption, AHint: string; AReadOnly: boolean;
        AWidth, AStretch: single): TBafSgColumn;
    function AddItem(Item: TBafSgColumn; Index: Integer): TBafSgColumn;
    function Insert(Index: Integer): TBafSgColumn;
    procedure GridHeaderLineBreak;
    procedure ClearJoinLists;
    procedure ClearSort;
    procedure AddSortStack(AIndex: integer; ASortDir: TBafSortDirection);
    property Parents: TBafGridParents read FParents;
    property LineType: TBafGridLineType read FLineType write FLineType;
    property RowCount: integer read FRowCount;
    property Items[Index: Integer]: TBafSgColumn read GetItem write SetItem;
    property FixedColsCount: integer read FFixedColsCount write FFixedColsCount;
    property HasLinkedColumns: boolean read FHasLinkedColumns write FHasLinkedColumns;
  end;


  TBafSimpleGrid = class(TPanel)
  private
    FHasChanged: boolean;
    FVlInsert: boolean;
    FGridCache: string;
    FFilterStatement: string;
    procedure SetHasChanged(const Value: boolean);
    function GetCell(AType: TBafGridRowType; ACol, ARow: integer): TBafSgCell;
    function GetDataRow(ARow: integer): TBafSgRow;
    function GetDataCells(ACol, ARow: integer): TBafSgCell;
    procedure SetSelectedCol(const Value: integer);
    procedure SetSelectedRow(const Value: integer);
    function GetLinkRow(ARow: integer): TBafSgRow;
    procedure SetFilterStatement(const Value: string);
  protected  // general
    FParents: TBafGridParents;
    FHorzScrollBar: TScrollBar;
    FVertScrollBar: TScrollBar;
    FPanelLeft: TPanel;
    FPanelDesk: TPanel;
    FSelectedRow, FSelectedCol: integer;
    FKeyChange: boolean; // Change the selection by key
    FHintList: TStringList;
    procedure CreateComponents;
    procedure GridPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure ScrollChange(Sender: TObject);
    procedure Resize; override;
  protected // Cols and Rows
    FColumns: TBafSgColumns;
    FDataRowList, FDataRowFilteredList, FDataRowDisplayList: TObjectList;
    FHeaderRowList: TObjectList;
    FFooterRowList: TObjectList;
    FLinkRowList: TObjectList;  // for linked data, especially memos
    FRowCount: integer;
    FColorBack, FColorSelection, FColorReadonly: TAlphaColor;
    procedure NextCell(var Key: Word);
    procedure PriorCell(var Key: Word);
    procedure NextRow(var Key: Word);
    procedure PriorRow(var Key: Word);
    procedure NextPriorRowEdit(AKey: word; AShift: TShiftState);
    function CanCellSelect(ACol, ARow: integer): boolean;
    procedure CheckChangeRow(APrior: boolean);
    procedure CheckChangeCol;
    procedure CreateColsAndRows;
    procedure PaintRows(AType: TBafGridRowType; AList: TObjectList;
        AFirst, ALast: integer; AStart: integer);
    procedure PaintCell(AType: TBafGridRowType; ACol, ARow: integer; ARect: TRectF);
    procedure LoadStyle;
  protected   // edit
    FEditVisible: TBafClientEditComp;
    FEditCell: TBafSgCell;
    FEditReadOnly: boolean;
    FEdit: TBafEdit;
    FCombo: TBafComboBox;
    FKeyPressText: Char;
    FEditCellType: TBafGridCellType;
    FEditByReturn: boolean;
    procedure CreateEditComps;
    procedure ShowEdit(AKeyPress: boolean);
    procedure ComboClosePopup(Sender: TObject);
    procedure EditKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
        Shift: TShiftState);
    procedure EditKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
        Shift: TShiftState);
  protected // mouse and key
    FHoverCell: TBafSgCell;
    procedure PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure DialogKey(var Key: Word; Shift: TShiftState); override;
    procedure KeyDown(var Key: Word; var KeyChar: WideChar; Shift: TShiftState); override;
    procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer;
        var Handled: Boolean); override;
  public
    constructor Create(ASeg: TBafGridSegment);
    destructor Destroy; override;
    procedure CalcScrollbars;
    property Cells[AType: TBafGridRowType; ACol, ARow: integer]: TBafSgCell read GetCell;
    function RowCount(AType: TBafGridRowType = rtData): integer;
    function Mouse2Cell(X, Y: Single; var ARowType: TBafGridRowType;
        var ACol, ARow: integer): boolean;
    function GetSelectedCell: TBafSgCell;
    procedure HideEdit(ASave: boolean);
    property DataRow[ARow: integer]: TBafSgRow read GetDataRow;
    property LinkRow[ARow: integer]: TBafSgRow read GetLinkRow;
    property DataCells[ACol, ARow: integer]: TBafSgCell read GetDataCells;
    property SelectedCol: integer read FSelectedCol write SetSelectedCol;
    property SelectedRow: integer read FSelectedRow write SetSelectedRow;
  public
    function GetVlCellText(AFieldName: string): string;
    property VlInsert: boolean read FVlInsert write FVlInsert;
    property FilterStatement: string read FFilterStatement write SetFilterStatement;
  published
    property Columns: TBafSgColumns read FColumns;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property GridCache: string read FGridCache write FGridCache;
  end;


  TBafGridSegment = class(TCollectionItem)
  protected  // general
    FParents: TBafGridParents;
    FSegmentType: TBafGridSegmentType;
    FPanel: TBafPanel;
    FTopAdd: integer;
    FCalcedHeight: single;
    procedure SetSegmentType(const Value: TBafGridSegmentType);
    procedure SegmentCalcHeight(AHeight: single);
  protected   // header
    FHeader: string;
    FHeaderParentPanel: TBafPanel;
    FHeaderPanel: TBafPanel;
    FHeaderLabel: TLabel;
    FButtons: string;
    FButtonList: TObjectList;
    procedure CreateHeader;
    procedure CalcHeaderHeight;
    procedure SetHeader(const Value: string);
    procedure SetButtons(const Value: string);
    procedure HeaderButtonClick(Sender: TObject);
  protected // text and memo
    FMemo: TMemo;
    procedure CreateMemo;
    procedure MemoChange(Sender: TObject);
    procedure MemoKeyDown(Sender: TObject; var Key: Word;
      var KeyChar: Char; Shift: TShiftState);
    procedure SetMemoTextUnchanged(AText: string);
  protected // buttons
    FButtonDock: TBafButtonDock;
    procedure ButtonDockHeightChange(Sender: TObject);
    procedure CreateButtonDock;
    procedure ButtonClick(Sender: TObject);
  protected // grid
    FGrid: TBafSimpleGrid;
    procedure CreateSimpleGrid;
  private
    FSegmentName: string;
    FSegRight: TBafRight;
    FHelpPath: string;
    FLineP: string;
    FOpenClose: TBafOpenCloseType;
    FMaxHeightClosed: Single;
    FMaxHeightOpened: Single;
    FReadOnly: boolean;
    FHasChanged: boolean;
    FDataKeyValue: string;
    FDataQuelle: TBafDataQuelle;
    FDataRef: string;
    FDataItem: string;
    FDataTable: TStringList;
    FDataKey: TStringList;
    FLinkedCell: TBafSgCell;
    function GetLines: TStrings;
    procedure SetOpenClose(const Value: TBafOpenCloseType);
    procedure SetReadOnly(const Value: boolean);
    procedure SetHasChanged(const Value: boolean);
    function GetDataKey(AIndex: integer): string;
    function GetDataTable(AIndex: integer): string;
    procedure SetDataKey(AIndex: integer; const Value: string);
    procedure SetDataTable(AIndex: integer; const Value: string);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure RefreshHeight;
    property SegmentType: TBafGridSegmentType
        read FSegmentType write SetSegmentType;
    property LineP: string read FLineP write FLineP;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
  public // text, memo, buttons
    procedure AddButton(ACaption, ACommand, ALineP: string;
        AWidth: integer; AReadOnly: boolean);
    procedure AddLine(AText: string);
    procedure AddLineUnchanged(AText: string);
    procedure LinesRefresh;
    property Lines: TStrings read GetLines;
    property LinkedCell: TBafSgCell read FLinkedCell write FLinkedCell;
  public // grid
    procedure GridCalcHeight;
    property Grid: TBafSimpleGrid read FGrid;
  public // data
    function GetDataTableCount: integer;
    property DataTable[AIndex: integer]: string read GetDataTable write SetDataTable;
    property DataKey[AIndex: integer]: string read GetDataKey write SetDataKey;
    property DataKeyValue: string read FDataKeyValue write FDataKeyValue;
    property DataQuelle: TBafDataQuelle read FDataQuelle write FDataQuelle;
    property DataItem: string read FDataItem write FDataItem;
    property DataRef: string read FDataRef write FDataRef;
  published
    property SegmentName: string read FSegmentName write FSegmentName;
    property SegRight: TBafRight read FSegRight write FSegRight;
    property HelpPath: string read FHelpPath write FHelpPath;
    property Header: string read FHeader write SetHeader;
    property Buttons: string read FButtons write SetButtons;
    property OpenClose: TBafOpenCloseType read FOpenClose write SetOpenClose;
    property MaxHeightOpened: Single read FMaxHeightOpened write FMaxHeightOpened;
    property MaxHeightClosed: Single read FMaxHeightClosed write FMaxHeightClosed;
    property ReadOnly: boolean read FReadOnly write SetReadOnly;
  end;

  TBafGridSegments = class(TCollection)
  private
    FParents: TBafGridParents;

    function GetItem(Index: Integer): TBafGridSegment;
    procedure SetItem(Index: Integer; const Value: TBafGridSegment);
  public
    constructor Create(ACategory: TBafGridCategory);
    destructor Destroy; override;
    function Add: TBafGridSegment;
    function AddItem(Item: TBafGridSegment; Index: Integer): TBafGridSegment;
    function Insert(Index: Integer): TBafGridSegment;
    property Items[Index: Integer]: TBafGridSegment read GetItem write SetItem;
    property Parents: TBafGridParents read FParents;
  published
  end;



  TBafGridCategory = class(TCollectionItem)
  private
    FParents: TBafGridParents;
    FIsVertical: boolean;
    FPanel: TBafPanel;
    FSplitter: TSplitter;
    FBafVertScrollBox: TBafVertScrollBox;
    FAutoSize: boolean;
    FSize: Single;
    FHasPlus: boolean;
    FLineP: string;
    FCaption: string;
    FOpened: boolean;
    FSegments: TBafGridSegments;
    procedure SetSize(const Value: Single);
    procedure SetHasPlus(const Value: boolean);
    procedure CreateComponents;
    procedure SetAutoSize(const Value: boolean);
    procedure SetCaption(const Value: string);
    procedure SetOpened(const Value: boolean);
  protected
    FHeader: TPanel;
    FHeaderLabel: TLabel;
    FHeaderButton: TButton;
    FScroll: TBafVertScrollBox;
    procedure FHeaderButtonClick(Sender: TObject);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    function CalcHeight: Single;
    property IsVertical: boolean read FIsVertical;
    property Parents: TBafGridParents read FParents;
  published
    property AutoSize: boolean read FAutoSize write SetAutoSize;
    property Size: Single read FSize write SetSize;
    property HasPlus: boolean read FHasPlus write SetHasPlus;
    property LineP: string read FLineP write FLineP;
    property Caption: string read FCaption write SetCaption;
    property Opened: boolean read FOpened write SetOpened;
    property Segments: TBafGridSegments read FSegments;
  end;

  TBafGridCategories = class(TCollection)
  private
    FParents: TBafGridParents;

    function Mouse2Position(AX, AY: integer; var APos: TBafGridCellPosition): boolean;
    function GetItem(Index: Integer): TBafGridCategory;
    procedure SetItem(Index: Integer; const Value: TBafGridCategory);
  public
    constructor Create(APrimaryDiv: TBafGridPrimaryDiv);
    destructor Destroy; override;
    function Add: TBafGridCategory;
    function AddItem(Item: TBafGridCategory; Index: Integer): TBafGridCategory;
    function Insert(Index: Integer): TBafGridCategory;
    property Items[Index: Integer]: TBafGridCategory read GetItem write SetItem;
  published
    property Parents: TBafGridParents read FParents;
  end;





  TBafGridPrimaryDiv = class(TCollectionItem)
  private
    FParents: TBafGridParents;
    FIsVertical: boolean;
    FPanel: TBafPanel;
    FAutoSize: boolean;
    FSize: Single;
    FCategories: TBafGridCategories;
    FLineP: string;
    procedure SetSize(const Value: Single);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure CalcHeight;
    property IsVertical: boolean read FIsVertical;
    property Parents: TBafGridParents read FParents;
  published
    property AutoSize: boolean read FAutoSize write FAutoSize;
    property Size: Single read FSize write SetSize;
    property Categories: TBafGridCategories read FCategories;
    property LineP: string read FLineP write FLineP;
  end;

  TBafGridPrimaryDivs = class(TCollection)
  private
    FParents: TBafGridParents;

    function Mouse2Position(AX, AY: integer; var APos: TBafGridCellPosition): boolean;
    function GetItem(Index: Integer): TBafGridPrimaryDiv;
    procedure SetItem(Index: Integer; const Value: TBafGridPrimaryDiv);
  public
    constructor Create(AGrid: TBafPage);
    destructor Destroy; override;
    function Add: TBafGridPrimaryDiv;
    function AddItem(Item: TBafGridPrimaryDiv; Index: Integer): TBafGridPrimaryDiv;
    function Insert(Index: Integer): TBafGridPrimaryDiv;
    function FindSegmentByName(ASegmentName: string): TBafGridSegment;
    property Items[Index: Integer]: TBafGridPrimaryDiv read GetItem write SetItem;
    property Parents: TBafGridParents read FParents;
  published
  end;

  TBafPage = class(TBafVertScrollBox)
  private
    FGridKind: TBafGridKind;
    FPrimaryDivs: TBafGridPrimaryDivs;
    FOnSegButtonClick: TBafGridEvent;
    FBeforeEdit: TBafGridEditEvent;
    FAfterEdit: TBafGridEditEvent;
    FOnStatus: TBafGridStatusEvent;
    FGridStatus: TBafGridStatus;
    FCheckList: TStringList;
    function SegmentBrowse(ASegment: TBafGridSegment): boolean;
    function SegmentNoChange(ASegment: TBafGridSegment): boolean;
    function SegmentSaveGridCache(ASegment: TBafGridSegment): boolean;
    function InternButtons(ASegment: TBafGridSegment): boolean;
  protected
    FTimer: TTimer;
    FTimerTaskList: TStringList;
    procedure StartTimer(ATask: string);
    procedure FTimerTimer(Sender: TObject);
    procedure VScrollChange; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    class function Place(AOwner: TComponent; AParent: TControl): TBafPage;
    procedure Clear;
    procedure SegButtonClick(const AParents: TBafGridParents);
    procedure DoEditEvent(ASender: TBafGridParents; AAfter: boolean;
        var AText: string; var AAbort: boolean; ACommand: string;
        AChanged: boolean);
    procedure SetStatus;
    procedure SaveGridCache;
    function AllSegFunction(AFunc: TAllSegFunction): boolean;
    property CheckList: TStringList read FCheckList;
  published
    property GridKind: TBafGridKind read FGridKind write FGridKind;
    property PrimaryDivs: TBafGridPrimaryDivs read FPrimaryDivs;
  published  // Events
    property OnSegButtonClick: TBafGridEvent
      read FOnSegButtonClick write FOnSegButtonClick;
    property BeforeEdit: TBafGridEditEvent read FBeforeEdit write FBeforeEdit;
    property AfterEdit: TBafGridEditEvent read FAfterEdit write FAfterEdit;
    property OnStatus: TBafGridStatusEvent read FOnStatus write FOnStatus;
  end;



implementation

uses foBafHistory, uBafDataCache, foBafGridFilter;

var
  mvColumns: TBafSgColumns;
  mvSortCell: integer;
  mvSortType: TBafSort;
  mvSortUp: boolean;

{ TBafGrid }

function TBafPage.AllSegFunction(AFunc: TAllSegFunction): boolean;
// goes through all Segments an exec the function
// return true, wenn all AFunc returns true
var
  LCat, LSeg, LCol, LPrim: integer;
  LSegment: TBafGridSegment;
  LCategory: TBafGridCategory;
begin
  result := true;
  for LPrim := 0 to PrimaryDivs.Count - 1 do begin
    for LCat := 0 to PrimaryDivs.Items[LPrim].Categories.Count - 1 do begin
      LCategory := PrimaryDivs.Items[LPrim].Categories.Items[LCat];
      for LSeg := 0 to LCategory.Segments.Count - 1 do begin
        LSegment := LCategory.Segments.Items[LSeg];
        if not AFunc(LSegment) then
          result := false;
      end; // for LSeg
    end; // for LCat
  end; // for LPrim
end;

procedure TBafPage.Clear;
begin
  FPrimaryDivs.Clear;
end;

constructor TBafPage.Create(AOwner: TComponent);
begin
  inherited;
  FPrimaryDivs := TBafGridPrimaryDivs.Create(Self);
  Tag := 1337;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled := false;
  FTimer.Interval := 100;
  FTimer.OnTimer := FTimerTimer;
  FTimerTaskList := TStringList.Create;
  FCheckList := TStringList.Create;
  FCheckList.OwnsObjects := true;
end;

destructor TBafPage.Destroy;
begin
  FreeAndNil(FCheckList);
  FreeAndNil(FTimerTaskList);
  FreeAndNil(FPrimaryDivs);
  inherited;
end;

procedure TBafPage.DoEditEvent(ASender: TBafGridParents; AAfter: boolean;
    var AText: string; var AAbort: boolean; ACommand: string; AChanged: boolean);
begin
  if not AAfter and Assigned(FBeforeEdit) then
    FBeforeEdit(ASender, AText, AAbort, ACommand, AChanged)
  else if AAfter and Assigned(FAfterEdit) then
    FAfterEdit(ASender, AText, AAbort, ACommand, AChanged);
end;

procedure TBafPage.FTimerTimer(Sender: TObject);
var
  s: string;
begin
  if FTimerTaskList.Count = 0 then begin
    FTimer.Enabled := false;
    exit;
  end;
  s := FTimerTaskList[0];
  FTimerTaskList.Delete(0);
  if s = 'repaint' then
    Repaint;
end;

function TBafPage.InternButtons(ASegment: TBafGridSegment): boolean;
begin

end;

class function TBafPage.Place(AOwner: TComponent; AParent: TControl): TBafPage;
begin
  result := TBafPage.Create(AOwner);
  result.Parent := AParent;
  result.Align := TAlignLayout.Client;
  result.Margins.Left := 8;
  result.Margins.Top := 8;
  result.Margins.Right := 8;
  result.Margins.Bottom := 8;
end;

procedure TBafPage.SaveGridCache;
begin
  AllSegFunction(SegmentSaveGridCache);
end;

procedure TBafPage.SegButtonClick(const AParents: TBafGridParents);
begin
  if Assigned(FOnSegButtonClick) then
    FOnSegButtonClick(AParents);
end;

function TBafPage.SegmentBrowse(ASegment: TBafGridSegment): boolean;
// returns true, wenn Segment is not in Edit-Mode
begin
  result := true;
  if ASegment.SegmentType in [stValueList, stGrid, stXGrid] then
    result := ASegment.FGrid.FEditVisible = ecNone;
end;

function TBafPage.SegmentNoChange(ASegment: TBafGridSegment): boolean;
begin
  result := not ASegment.HasChanged;
end;

function TBafPage.SegmentSaveGridCache(ASegment: TBafGridSegment): boolean;
begin
  if Assigned(ASegment.Grid) and (ASegment.Grid.GridCache <> '') then
    gvBafDataCache.SaveGridCache(ASegment);
  result := true;
end;

procedure TBafPage.SetStatus;
var
  LStatus: TBafGridStatus;  // (gsBrowse, gsChanged, gsEditing, gsPlausiFailed)
begin
  if AllSegFunction(SegmentBrowse) then begin
    LStatus := gsBrowse;
    if not AllSegFunction(SegmentNoChange) then
      LStatus := gsChanged;
  end
  else
    LStatus := gsEditing;

  FGridStatus := LStatus;
  AllSegFunction(InternButtons);
  if Assigned(FOnStatus) then
    FOnStatus(Self, LStatus);
end;

procedure TBafPage.StartTimer(ATask: string);
var
  s: string;
  p: integer;
begin
  s := AnsiLowerCase(ATask);
  p := FTimerTaskList.IndexOf(s);
  if p >= 0 then begin
//    FTimer.Enabled := false;
    FTimerTaskList.Delete(p);
  end;
  FTimerTaskList.Add(s);
  FTimer.Enabled := true;
end;

procedure TBafPage.VScrollChange;
begin
  inherited;
  StartTimer('repaint');
end;

{ TBafGridCellPosition }

procedure TBafGridCellPosition.Clear;
begin
  Prim := -1;
  Cat := -1;
  Seg := -1;
  Col := -1;
  Row := -1;
  SegmentType := stNone;
  Button := -1;
  Position := mpNone;
  PosObject := nil;
end;

class operator TBafGridCellPosition.Equal(a, b: TBafGridCellPosition): Boolean;
begin
  result := (a.Prim = b.Prim) and (a.Cat = b.Cat) and (a.Seg = b.Seg) and
      (a.Row = b.Row) and (a.Col = b.Col) and
      (a.SegmentType = b.SegmentType) and (a.Button = b.Button) and
      (a.Position = b.Position);
end;

function TBafGridCellPosition.GetXY(AX, AY: integer): TBafGridCellPosition;
begin
  X := AX;
  Y := AY;
  result := Self;
end;

class operator TBafGridCellPosition.NotEqual(a, b: TBafGridCellPosition): Boolean;
begin
  result := not (a = b);
end;

function TBafGridCellPosition.SetSegment(ASeg: integer;
  APosition: TBafGridMousePosition; APosObject: TObject;
  ASegmentType: TBafGridSegmentType): boolean;
begin
  result := true;
  Seg := ASeg;
  Position := APosition;
  PosObject := APosObject;
  SegmentType := ASegmentType;
end;

{ TBafGridPrimaryDivs }

function TBafGridPrimaryDivs.Add: TBafGridPrimaryDiv;
begin
  Result := AddItem(nil, -1);
end;

function TBafGridPrimaryDivs.AddItem(Item: TBafGridPrimaryDiv;
  Index: Integer): TBafGridPrimaryDiv;
begin
  if Item = nil then
    Result := TBafGridPrimaryDiv.Create(Self)
  else
  begin
    Result := Item;
    if Assigned(Item) then
    begin
      Result.Collection := Self;
      if Index < Count then
        Index := Count - 1;
      Result.Index := Index;
    end;
  end;
end;

constructor TBafGridPrimaryDivs.Create(AGrid: TBafPage);
begin
  inherited Create(TBafGridPrimaryDiv);
  FParents.Grid := AGrid;
  FParents.PrimaryDivs := Self;
end;

destructor TBafGridPrimaryDivs.Destroy;
begin

  inherited;
end;

function TBafGridPrimaryDivs.FindSegmentByName(
    ASegmentName: string): TBafGridSegment;
var
  LPrim, LCat, LSeg: integer;
  LCategory: TBafGridCategory;
  LSegment: TBafGridSegment;
begin
  result := nil;
  for LPrim := 0 to Count - 1 do begin
    for LCat := 0 to Items[LPrim].Categories.Count - 1 do begin
      LCategory := Items[LPrim].Categories.Items[LCat];
      for LSeg := 0 to LCategory.Segments.Count - 1 do begin
        LSegment := LCategory.Segments.Items[LSeg];
        if AnsiCompareText(ASegmentName, LSegment.SegmentName) = 0 then begin
          result := LSegment;
          exit;
        end;
      end;
    end;
  end;
end;

function TBafGridPrimaryDivs.GetItem(Index: Integer): TBafGridPrimaryDiv;
begin
  Result := TBafGridPrimaryDiv(inherited GetItem(Index));
end;

function TBafGridPrimaryDivs.Insert(Index: Integer): TBafGridPrimaryDiv;
begin
  Result := AddItem(nil, Index);
end;

function TBafGridPrimaryDivs.Mouse2Position(AX, AY: integer;
  var APos: TBafGridCellPosition): boolean;
begin

end;

procedure TBafGridPrimaryDivs.SetItem(Index: Integer;
  const Value: TBafGridPrimaryDiv);
begin
  inherited SetItem(Index, Value);
end;

{ TBafGridParents }

procedure TBafGridParents.CreateFrom(AParents: TBafGridParents);
begin
  Grid := AParents.Grid;
  PrimaryDivs := AParents.PrimaryDivs;
  PrimaryDiv := AParents.PrimaryDiv;
  Categories := AParents.Categories;
  Category := AParents.Category;
  Segments := AParents.Segments;
  Segment := AParents.Segment;
  SimpleGrid := AParents.SimpleGrid;
  Columns := AParents.Columns;
  Column := AParents.Column;
  Row := AParents.Row;
  Cell := AParents.Cell;
end;

{ TBafGridPrimaryDiv }

procedure TBafGridPrimaryDiv.CalcHeight;
var
  i: integer;
  LHeight: single;
begin
  if FAutoSize and (FParents.Grid.GridKind = gkPage) then begin
    LHeight := 0;
    for i := 0 to FCategories.Count - 1 do
      LHeight := System.Math.Max(LHeight, FCategories.Items[i].CalcHeight);
    Size := System.Math.Max(LHeight, 38);
  end;
end;

constructor TBafGridPrimaryDiv.Create(Collection: TCollection);
begin
  inherited;
  FParents.CreateFrom((Collection as TBafGridPrimaryDivs).FParents);
  FParents.PrimaryDiv := Self;
  FIsVertical := FParents.Grid.GridKind in [gkDashVert];
  if FIsVertical then begin
    FPanel := TBafPanel.Place(FParents.Grid, TAlignLayout.Left);
  end
  else begin
    FPanel := TBafPanel.Place(FParents.Grid, TAlignLayout.Top);
    FPanel.Margins.Right := 2;
    FPanel.Height := 300;
    FPanel.StyleLookup := 'pushpanel';
  end;
  FCategories := TBafGridCategories.Create(Self);
end;

destructor TBafGridPrimaryDiv.Destroy;
begin
  FreeAndNil(FCategories);
  FreeAndNil(FPanel);

  inherited;
end;

procedure TBafGridPrimaryDiv.SetSize(const Value: Single);
begin
  FSize := Value;
  if FIsVertical then
    FPanel.Width := Value
  else
    FPanel.Height := Value;
end;

{ TBafGridCategory }

function TBafGridCategory.CalcHeight: Single;
var
  i: integer;
begin
  result := FHeader.Height + 8;
  for i := 0 to Segments.Count - 1 do begin
    if Segments.Items[i].FPanel.Visible then
      result := result + Segments.Items[i].FPanel.Height;
  end;
end;

constructor TBafGridCategory.Create(Collection: TCollection);
var
  LParent: TPanel;
begin
  inherited;
  FParents.CreateFrom((Collection as TBafGridCategories).FParents);
  FParents.Category := Self;
  FIsVertical := not FParents.PrimaryDiv.IsVertical;
  LParent := FParents.PrimaryDiv.FPanel;
  if FIsVertical then begin
    if Index > 0 then
      FSplitter := TBafSplitterBar.Place(LParent, false);
    FPanel := TBafPanel.Place(LParent, TAlignLayout.Left);
  end
  else begin
    if Index > 0 then
      FSplitter := TBafSplitterBar.Place(LParent, true);
    FPanel := TBafPanel.Place(LParent, TAlignLayout.Top);
  end;
  CreateComponents;
  FSegments := TBafGridSegments.Create(Self);
end;

procedure TBafGridCategory.CreateComponents;
begin
  FHeader := TPanel.Create(FPanel);
  FHeader.Parent := FPanel;
  FHeader.Align := TAlignLayout.Top;
  FHeader.Height := 30;
  FHeaderLabel := TLabel.Create(FPanel);
  FHeaderLabel.Parent := FHeader;
  FHeaderLabel.Align := TAlignLayout.Client;
  FHeaderLabel.Margins.Left := 2;
  FHeaderLabel.Text := 'Category';
  FHeaderLabel.StyledSettings := [TStyledSetting.FontColor];
  FHeaderLabel.TextSettings.Font.Style := [TFontStyle.fsBold];
  FHeaderButton := TButton.Create(FPanel);
  FHeaderButton.Parent := FHeader;
  FHeaderButton.Align := TAlignLayout.Left;
  FHeaderButton.Width := 30;
  FHeaderButton.Text := '+';
  FHeaderButton.StyledSettings := [TStyledSetting.FontColor];
  FHeaderButton.TextSettings.Font.Family := 'Font Awesome 5 Free';
  FHeaderButton.StyleLookup := 'donetoolbutton';
  FHeaderButton.OnClick := FHeaderButtonClick;

  FScroll := TBafVertScrollBox.Create(FPanel);
  FScroll.Parent := FPanel;
  FScroll.Align := TAlignLayout.Client;
  FScroll.Margins.Bottom := 8;
end;

destructor TBafGridCategory.Destroy;
begin
  FreeAndNil(FSegments);
  FreeAndNil(FPanel);
  inherited;
end;

procedure TBafGridCategory.FHeaderButtonClick(Sender: TObject);
begin
  Opened := not Opened;
end;

procedure TBafGridCategory.SetAutoSize(const Value: boolean);
begin
  FAutoSize := Value;
  if Value then
    FPanel.Align := TAlignLayout.Client;
end;

procedure TBafGridCategory.SetCaption(const Value: string);
begin
  FCaption := Value;
  FHeaderLabel.Text := Value;
end;

procedure TBafGridCategory.SetHasPlus(const Value: boolean);
begin
  FHasPlus := Value;
end;

procedure TBafGridCategory.SetOpened(const Value: boolean);
var
  i: integer;
  LSeg: TBafGridSegment;
begin
  FOpened := Value;
  if FOpened then
    FHeaderButton.Text := #$f146
  else
    FHeaderButton.Text := #$f0fe;
  for i := 0 to Segments.Count - 1 do begin
    LSeg := Segments.Items[i];
    if FOpened then
      LSeg.FPanel.Visible := LSeg.OpenClose in [ocOnlyOpen, ocOpenAndClose]
    else
      LSeg.FPanel.Visible := LSeg.OpenClose in [ocOnlyClose, ocOpenAndClose];
    LSeg.RefreshHeight;
  end;
  FParents.PrimaryDiv.CalcHeight;
  FParents.Grid.StartTimer('repaint');
end;

procedure TBafGridCategory.SetSize(const Value: Single);
begin
  FSize := Value;
  if FIsVertical then
    FPanel.Width := Value
  else
    FPanel.Height := Value;
end;

{ TBafGridCategories }

function TBafGridCategories.Add: TBafGridCategory;
begin
  Result := AddItem(nil, -1);
end;

function TBafGridCategories.AddItem(Item: TBafGridCategory;
  Index: Integer): TBafGridCategory;
begin
  if Item = nil then
    Result := TBafGridCategory.Create(Self)
  else
  begin
    Result := Item;
    if Assigned(Item) then
    begin
      Result.Collection := Self;
      if Index < Count then
        Index := Count - 1;
      Result.Index := Index;
    end;
  end;
end;

constructor TBafGridCategories.Create(APrimaryDiv: TBafGridPrimaryDiv);
begin
  inherited Create(TBafGridCategory);
  FParents.CreateFrom(APrimaryDiv.FParents);
  FParents.Categories := Self;
end;

destructor TBafGridCategories.Destroy;
begin

  inherited;
end;

function TBafGridCategories.GetItem(Index: Integer): TBafGridCategory;
begin
  Result := TBafGridCategory(inherited GetItem(Index));
end;

function TBafGridCategories.Insert(Index: Integer): TBafGridCategory;
begin
  Result := AddItem(nil, Index);
end;

function TBafGridCategories.Mouse2Position(AX, AY: integer;
  var APos: TBafGridCellPosition): boolean;
begin

end;

procedure TBafGridCategories.SetItem(Index: Integer; const Value: TBafGridCategory);
begin
  inherited SetItem(Index, Value);
end;

{ TBafGridSegment }

procedure TBafGridSegment.AddButton(ACaption, ACommand, ALineP: string;
    AWidth: integer; AReadOnly: boolean);
var
  LButton: TBafButton;
begin
  LButton := TBafButton.Create(FButtonDock);
  LButton.Parent := FButtonDock;
  LButton.Text := ACaption;
  LButton.Width := AWidth;
  LButton.Enabled := not AReadOnly;
  LButton.LineP := ALineP;
  LButton.OnClick := ButtonClick;
  FButtonDock.PlaceControls;
  FParents.PrimaryDiv.CalcHeight;
end;

procedure TBafGridSegment.AddLine(AText: string);
begin
  FMemo.Lines.Add(AText);
  MemoChange(FMemo);
end;

procedure TBafGridSegment.AddLineUnchanged(AText: string);
begin
  FMemo.OnChangeTracking := nil;
  try
    FMemo.Lines.Add(AText);
    SegmentCalcHeight(Max(FMemo.Lines.Count, 1) * 16 + 34 + FHeaderPanel.Height);
  finally
    FMemo.OnChangeTracking := MemoChange;
  end;
end;

procedure TBafGridSegment.ButtonClick(Sender: TObject);
var
  LParents: TBafGridParents;
  b: boolean;
begin
  LParents := FParents;
  LParents.SegButton := Sender as TBafButton;
  b := LParents.SegButton.Enabled;
  LParents.SegButton.Enabled := false;
  try
    FParents.Grid.SegButtonClick(LParents);
  finally
    LParents.SegButton.Enabled := b;
  end;
end;

procedure TBafGridSegment.ButtonDockHeightChange(Sender: TObject);
begin
  FPanel.Height := FHeaderPanel.Height + FButtonDock.Height;
end;

procedure TBafGridSegment.CalcHeaderHeight;
begin
  if FHeader = '' then
    FHeaderParentPanel.Height := FTopAdd
  else
    FHeaderParentPanel.Height := 28 + FTopAdd;
  LinesRefresh;
end;

constructor TBafGridSegment.Create(Collection: TCollection);
begin
  inherited;
  FDataTable := TStringList.Create;
  FDataKey := TStringList.Create;
  FParents.CreateFrom((Collection as TBafGridSegments).FParents);
  FParents.Segment := Self;
  FPanel := TBafPanel.Place(FParents.Category.FScroll, TAlignLayout.Top);
  if Index = 0 then
    FTopAdd := 4;
  CreateHeader;
  FButtonList := TObjectList.Create(true);
//  FPanel.Opacity := 0;
end;

procedure TBafGridSegment.CreateButtonDock;
begin
  FButtonDock := TBafButtonDock.Place(FPanel, FPanel);
  FButtonDock.OnHeightChange := ButtonDockHeightChange;
  FButtonDock.Margins.Left := 16;
end;

procedure TBafGridSegment.CreateHeader;
begin
  FHeaderParentPanel := TBafPanel.Place(FPanel, TAlignLayout.Top);
//  FHeaderParentPanel.Height := 30;
  FHeaderPanel := TBafPanel.Place(FHeaderParentPanel, TAlignLayout.Left);
  FHeaderPanel.Width := 300;
  FHeaderLabel := TLabel.Create(FPanel);
  FHeaderLabel.Parent := FHeaderPanel;
  FHeaderLabel.Align := TAlignLayout.Client;
  FHeaderLabel.Margins.Rect := Rect(24, 7 + FTopAdd, 4, 1);
  FHeaderLabel.Text := 'Header';
  FHeaderLabel.StyledSettings := [TStyledSetting.FontColor];
//  FHeaderLabel.TextSettings.Font.Style := [TFontStyle.fsBold];
end;

procedure TBafGridSegment.CreateMemo;
begin
  FMemo := TMemo.Create(FPanel);
  FMemo.Parent := FPanel;
  FMemo.Align := TAlignLayout.Client;
  FMemo.Margins.Rect := Rect(24, 4, 4, 4);
  case FSegmentType of
    stText: begin
      FMemo.ReadOnly := true;
      FMemo.StyleLookup := 'memolabelstyle';
      FMemo.ShowScrollBars := false;
    end;
    stMemo: begin
      FMemo.StyleLookup := 'memotextstyle';
      FMemo.OnChangeTracking := MemoChange;
      FMemo.OnKeyDown := MemoKeyDown;
    end;
  end;
end;

procedure TBafGridSegment.CreateSimpleGrid;
begin
  FGrid := TBafSimpleGrid.Create(Self);
  FGrid.Parent := FPanel;
  FGrid.Align := TAlignLayout.Client;
  FGrid.Margins.Rect := Rect(24, 4, 4, 4);
end;

destructor TBafGridSegment.Destroy;
begin
  FreeAndNil(FButtonList);
  FreeAndNil(FDataKey);
  FreeAndNil(FDataTable);
  inherited;
end;

function TBafGridSegment.GetDataKey(AIndex: integer): string;
begin
  while FDataKey.Count <= AIndex do
    FDataKey.Add('');
  result := FDataKey[AIndex];
end;

function TBafGridSegment.GetDataTable(AIndex: integer): string;
begin
  while FDataTable.Count <= AIndex do
    FDataTable.Add('');
  result := FDataTable[AIndex];
end;

function TBafGridSegment.GetDataTableCount: integer;
begin
  result := FDataTable.Count;
end;

function TBafGridSegment.GetLines: TStrings;
begin
  result := FMemo.Lines;
end;

procedure TBafGridSegment.GridCalcHeight;
var
  LCount: integer;
begin
  LCount := (Grid.FHeaderRowList.Count + Grid.FDataRowDisplayList.Count
      + Grid.FFooterRowList.Count) * Grid.Columns.RowCount;
  SegmentCalcHeight(Max(LCount, 1) * 22 + 34 + FHeaderPanel.Height);
  FGrid.CalcScrollbars;
end;

procedure TBafGridSegment.HeaderButtonClick(Sender: TObject);
var
  LChar: Char;
begin
  LChar := ((Sender as TButton).TagString + ' ')[1];
  case LChar of
    'H': TFrmBafHistory.ShowSegment(Self);
    'F': TFrmBafFilter.ShowSegment(Self);
  end;
end;

procedure TBafGridSegment.LinesRefresh;
begin
  if Assigned(FMemo) then begin
    SegmentCalcHeight(Max(FMemo.Lines.Count, 1) * 16 + 34 + FHeaderPanel.Height);
    FHeaderPanel.Align := TAlignLayout.Client;
  end;
end;

procedure TBafGridSegment.MemoChange(Sender: TObject);
begin
  SegmentCalcHeight(Max(FMemo.Lines.Count, 1) * 16 + 34 + FHeaderPanel.Height);
  HasChanged := true;
  if Assigned(LinkedCell) then
    LinkedCell.HasChanged := true;
  FParents.Grid.SetStatus;
end;

procedure TBafGridSegment.MemoKeyDown(Sender: TObject; var Key: Word;
  var KeyChar: Char; Shift: TShiftState);
var
  LValue, LPos, LNewPos, LTop, LBottom, LMemoTop: single;
  LScroll: TBafVertScrollBox;
begin
  LScroll := FParents.Category.FScroll;
  LMemoTop := FPanel.Position.Y + FMemo.Position.Y;
  LPos := FMemo.Caret.Pos.Y;
  case Key of
    vkReturn, vkDown: LPos := LPos + 16;
    vkUp: LPos := LPos - 16;
  end;
  LTop := LScroll.ViewportPosition.Y - LMemoTop;
  LBottom := LScroll.ViewportPosition.Y + LScroll.Height - LMemoTop - 16;
  LNewPos := LScroll.ViewportPosition.Y;
  if LPos < LTop then
    LNewPos := LPos + LMemoTop - 30;
  if LPos > LBottom then
    LNewPos := LPos + LMemoTop - LScroll.Height + 30;
  LValue := LScroll.ViewportPosition.Y - LNewPos;
  if Abs(LValue) > 3 then
    LScroll.ScrollBy(0, LValue);
end;

procedure TBafGridSegment.RefreshHeight;
var
  LHeight: single;
begin
  case SegmentType of
    stButtons: FPanel.Height := FHeaderPanel.Height + FButtonDock.Height;
    else begin
      LHeight := FCalcedHeight;
      if FParents.Category.Opened and (FMaxHeightOpened > 0)
          and (LHeight > FMaxHeightOpened) then
        LHeight := FMaxHeightOpened;
      if not FParents.Category.Opened and (FMaxHeightClosed > 0)
          and (LHeight > FMaxHeightClosed) then
        LHeight := FMaxHeightClosed;
      FPanel.Height := LHeight;
    end;
  end;
  if Assigned(FGrid) then
    FGrid.CalcScrollbars;
end;

procedure TBafGridSegment.SegmentCalcHeight(AHeight: single);
begin
  FCalcedHeight := AHeight;
  if FPanel.Height <> AHeight then begin
    if FParents.Category.Opened and (FMaxHeightOpened > 0)
        and (AHeight > FMaxHeightOpened) then
      AHeight := FMaxHeightOpened;
    if not FParents.Category.Opened and (FMaxHeightClosed > 0)
        and (AHeight > FMaxHeightClosed) then
      AHeight := FMaxHeightClosed;
    FPanel.Height := AHeight;
    FParents.PrimaryDiv.CalcHeight;
  end;
end;

procedure TBafGridSegment.SetButtons(const Value: string);
var
  i, LRight: integer;
  s: string;
  LButton: TButton;
begin
  FButtons := AnsiUpperCase(Value);
  FButtonList.Clear;  // deletes all old buttons
  for i := Length(FButtons) downto 1 do begin
    s := TBafGridUtils.GetButtonText(FButtons[i]);
    if s <> '' then begin
      LButton := TButton.Create(FHeaderPanel);
      LButton.Parent := FHeaderPanel;
      LButton.Width := 22;
      LButton.Position.X := 0;
      LButton.Align := TAlignLayout.Right;
      LButton.StyledSettings := [TStyledSetting.FontColor];
      LButton.TextSettings.Font.Family := 'Font Awesome 5 Free';
      LButton.StyleLookup := 'donetoolbutton';
      LButton.Text := s;
      LButton.TagString := FButtons[i];
      LRight := IfThen(i = Length(FButtons), 5, 1);
      LButton.Margins.Rect := Rect(1, 6 + FTopAdd, LRight, 0);
      LButton.OnClick := HeaderButtonClick;
      FButtonList.Add(LButton);
    end;
  end;
end;

procedure TBafGridSegment.SetDataKey(AIndex: integer; const Value: string);
begin
  while FDataKey.Count <= AIndex do
    FDataKey.Add('');
  FDataKey[AIndex] := AnsiLowerCase(Value);
end;

procedure TBafGridSegment.SetDataTable(AIndex: integer; const Value: string);
begin
  while FDataTable.Count <= AIndex do
    FDataTable.Add('');
  FDataTable[AIndex] := AnsiLowerCase(Value);
end;

procedure TBafGridSegment.SetHasChanged(const Value: boolean);
begin
  FHasChanged := Value;
  if not Value and Assigned(FGrid) then
    FGrid.HasChanged := false;
end;

procedure TBafGridSegment.SetHeader(const Value: string);
begin
  FHeader := Value;
  FHeaderLabel.Text := Value;
  CalcHeaderHeight;
end;

procedure TBafGridSegment.SetMemoTextUnchanged(AText: string);
begin
  FMemo.OnChangeTracking := nil;
  try
    FMemo.Lines.Text := AText;
    SegmentCalcHeight(Max(FMemo.Lines.Count, 1) * 16 + 34 + FHeaderPanel.Height);
  finally
    FMemo.OnChangeTracking := MemoChange;
  end;
end;

procedure TBafGridSegment.SetOpenClose(const Value: TBafOpenCloseType);
begin
  FOpenClose := Value;
  if FParents.Category.Opened then
    FPanel.Visible := OpenClose in [ocOnlyOpen, ocOpenAndClose]
  else
    FPanel.Visible := OpenClose in [ocOnlyClose, ocOpenAndClose];
end;

procedure TBafGridSegment.SetReadOnly(const Value: boolean);
begin
  FReadOnly := Value;
  case FSegmentType of
    stText, stMemo: FMemo.ReadOnly := Value;

  end;
end;

procedure TBafGridSegment.SetSegmentType(const Value: TBafGridSegmentType);
begin
  FSegmentType := Value;
  case Value of
    stText, stMemo: CreateMemo;
    stButtons: CreateButtonDock;
    stValueList, stGrid, stXGrid: CreateSimpleGrid;


  end;
end;

{ TBafGridSegments }

function TBafGridSegments.Add: TBafGridSegment;
begin
  Result := AddItem(nil, -1);
end;

function TBafGridSegments.AddItem(Item: TBafGridSegment;
  Index: Integer): TBafGridSegment;
begin
  if Item = nil then
    Result := TBafGridSegment.Create(Self)
  else
  begin
    Result := Item;
    if Assigned(Item) then
    begin
      Result.Collection := Self;
      if Index < Count then
        Index := Count - 1;
      Result.Index := Index;
    end;
  end;

end;

constructor TBafGridSegments.Create(ACategory: TBafGridCategory);
begin
  inherited Create(TBafGridSegment);
  FParents.CreateFrom(ACategory.FParents);
  FParents.Segments := Self;
end;

destructor TBafGridSegments.Destroy;
begin

  inherited;
end;

function TBafGridSegments.GetItem(Index: Integer): TBafGridSegment;
begin
  Result := TBafGridSegment(inherited GetItem(Index));
end;

function TBafGridSegments.Insert(Index: Integer): TBafGridSegment;
begin
  Result := AddItem(nil, Index);
end;

procedure TBafGridSegments.SetItem(Index: Integer;
  const Value: TBafGridSegment);
begin
  inherited SetItem(Index, Value);
end;

{ TBafSimpleGrid }

procedure TBafSimpleGrid.CalcScrollbars;
var
  LAllCount, LCount, LScroll: integer;
begin
  LAllCount := FHeaderRowList.Count + FDataRowDisplayList.Count
      + FFooterRowList.Count;
  FRowCount := Trunc(FPanelDesk.Height / (22 * Columns.RowCount));
  FVertScrollBar.Visible := FRowCount < LAllCount;
  if FVertScrollBar.Visible then begin
    FVertScrollBar.Max := LAllCount;
    FVertScrollBar.ViewportSize := FRowCount;
    FVertScrollBar.Repaint;
  end;
  LScroll := System.Math.Max(0, LAllCount - FRowCount);
  FVertScrollBar.Value := System.Math.Min(FVertScrollBar.Value, LScroll);
end;

function TBafSimpleGrid.CanCellSelect(ACol, ARow: integer): boolean;
var
  LCell: TBafSgCell;
begin
  LCell := GetCell(rtDisplayData, ACol, ARow);
  result := (LCell.Parents.Column.Width > 0) and LCell.Parents.Column.Visible;
end;

procedure TBafSimpleGrid.CheckChangeCol;
var
  LCol: TBafSgColumn;
  LCompWidth, LFixed, LScroll: single;
begin
  LCol := Columns.Items[FSelectedCol];
  LCompWidth := FParents.SimpleGrid.Width;
  LFixed := 0;
  if Columns.FixedColsCount > 0 then
    LFixed := Columns.Items[Columns.FixedColsCount - 1].FCalcedRect.Right;
  LScroll := FHorzScrollBar.Value;
  if LCol.ScrollRect.Right > LCompWidth then
    LScroll := LCol.ScrollRect.Right - LCompWidth;
  if LCol.ScrollRect.Left < LFixed then
    LScroll := LScroll + (LCol.ScrollRect.Left - LFixed);
  if LScroll < 0 then
    LScroll := 0;
  if Abs(LScroll - FHorzScrollBar.Value) > 1 then begin
    FHorzScrollBar.Value := LScroll;
    Columns.GridHeaderLineBreak
  end;
end;

procedure TBafSimpleGrid.CheckChangeRow(APrior: boolean);
var
  LRect: TRectF;
  LPos, LRow: single;
  LCell: TBafSgCell;
begin
  if not FVertScrollBar.Visible then begin
    LCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
    LRect := LCell.DisplayRect;
    LRow := LCell.FParents.Column.CalcedRow * 22;
    LPos := LRect.Top - FParents.Grid.ViewportPosition.Y;
    if APrior then begin
      if (LPos - LRow) < (FParents.Grid.Height * 0.2) then
        FParents.Grid.ScrollBy(0, 22 * Columns.RowCount);
    end
    else begin
      if (LPos + LRow) > (FParents.Grid.Height * 0.8) then
        FParents.Grid.ScrollBy(0, -22 * Columns.RowCount);
    end;
  end;
  Repaint;
end;

procedure TBafSimpleGrid.ComboClosePopup(Sender: TObject);
begin
  HideEdit(true);
end;

constructor TBafSimpleGrid.Create(ASeg: TBafGridSegment);
begin
  inherited Create(ASeg.FParents.Grid);
  FSelectedRow := -1;
  FSelectedCol := -1;
  FParents.CreateFrom(ASeg.FParents);
  FParents.SimpleGrid := Self;
  CreateComponents;
  CreateColsAndRows;
  CreateEditComps;
  FHintList := TStringList.Create;
  CanFocus := true;
  LoadStyle;
end;

procedure TBafSimpleGrid.CreateColsAndRows;
begin
  FDataRowList := TObjectList.Create(true);
  FDataRowDisplayList := FDataRowList;
  FDataRowFilteredList := TObjectList.Create(false);
  FHeaderRowList := TObjectList.Create(true);
  FFooterRowList := TObjectList.Create(true);
  FLinkRowList := TObjectList.Create(true);
  FColumns := TBafSgColumns.Create(Self);
end;

procedure TBafSimpleGrid.CreateComponents;
begin
  FVertScrollBar := TScrollBar.Create(Self);
  FVertScrollBar.Parent := Self;
  FVertScrollBar.Orientation := TOrientation.Vertical;
  FVertScrollBar.Align := TAlignLayout.Right;
  FVertScrollBar.OnChange := ScrollChange;
  FPanelLeft := TPanel.Create(Self);
  FPanelLeft.Parent := Self;
  FPanelLeft.Align := TAlignLayout.Client;
  FPanelLeft.StyleLookup := 'pushpanel';
  FHorzScrollBar := TScrollBar.Create(Self);
  FHorzScrollBar.Parent := FPanelLeft;
  FHorzScrollBar.Align := TAlignLayout.Bottom;
  FHorzScrollBar.OnChange := ScrollChange;
  FPanelDesk := TPanel.Create(Self);
  FPanelDesk.Parent := FPanelLeft;
  FPanelDesk.Align := TAlignLayout.Client;
  FPanelDesk.OnPaint := GridPaint;
  FPanelDesk.ClipChildren := true;
  FPanelDesk.OnMouseUp := PanelDeskMouseUp;
  FPanelDesk.OnMouseDown := PanelDeskMouseDown;
  FPanelDesk.OnMouseMove := PanelDeskMouseMove;
  FPanelDesk.StyleLookup := 'pushpanel';
end;

procedure TBafSimpleGrid.CreateEditComps;
begin
  FEdit := TBafEdit.Create(Self);
  if not (csDesigning in ComponentState) then
    FEdit.Parent := Self;
  FEdit.Visible := false;
  FEdit.Tab2Return := true;
  FEdit.OnKeyUp := EditKeyUp;
  FEdit.OnKeyDown := EditKeyDown;

  FCombo := TBafComboBox.Create(Self);
  if not (csDesigning in ComponentState) then
    FCombo.Parent := Self;
  FCombo.Visible := false;
  FCombo.DropDownKind := TDropDownKind.Custom;
  FCombo.OnClosePopup := ComboClosePopup;
  FCombo.TabCloses := true;
end;

destructor TBafSimpleGrid.Destroy;
begin
  FreeAndNil(FHintList);
  FreeAndNil(FColumns);
  FreeAndNil(FLinkRowList);
  FreeAndNil(FFooterRowList);
  FreeAndNil(FHeaderRowList);
  FreeAndNil(FDataRowFilteredList);
  FreeAndNil(FDataRowList);
  inherited;
end;

procedure TBafSimpleGrid.DialogKey(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if IsFocused and (Key > 0) then begin
    case Key of
      vkTab: if ssShift in Shift then PriorCell(Key) else NextCell(Key);
      vkLeft: PriorCell(Key);
      vkRight: NextCell(Key);
      vkUp: begin
        if ssCtrl in Shift then begin
          NextPriorRowEdit(Key, Shift);
          Key := 0;
        end
        else
          PriorRow(Key);
      end;
      vkDown: begin
        if ssCtrl in Shift then begin
          NextPriorRowEdit(Key, Shift);
          Key := 0;
        end
        else
          NextRow(Key);
      end;
      BAF_VK_PLUS, BAF_VK_MINUS: if ssCtrl in Shift then begin
        NextPriorRowEdit(Key, Shift);
        Key := 0;
      end;
    end;
  end;
end;

procedure TBafSimpleGrid.EditKeyDown(Sender: TObject; var Key: Word;
  var KeyChar: Char; Shift: TShiftState);

  procedure lokDate(ADiff: integer);
  begin
    if FEditVisible = ecNone then begin
      FEditCell.SetTextChange(FormatDateTime('dd.mm.yyyy', trunc(now) + ADiff));
      FEditCell.Parents.Grid.Repaint;
    end
    else
      FEdit.Text := FormatDateTime('dd.mm.yyyy', trunc(now) + ADiff);
    KeyChar := #0;
  end;

begin
  if KeyChar <> #0 then begin
    case FEditCellType of
      ctInt: if not (KeyChar in ['0'..'9', #8]) then
        KeyChar := #0;
      ctCurr, ctCurr4: if not (KeyChar in ['0'..'9', ',', #8]) then
        KeyChar := #0;
      ctDate, ctDateMin, ctDateSek: begin
        case KeyChar of
          'h', 't', 'H', 'T': lokDate(0);
          'm', 'M': lokDate(1);
          'y', 'g', 'Y', 'G': lokDate(-1);
          'ü', 'Ü': lokDate(2);
          'v', 'V': lokDate(-2);
          '0'..'9', '.': ;
          ':': if FEditCellType = ctDate then
            KeyChar := #0;
          else
            KeyChar := #0;
        end;
      end;
    end;
  end;
end;

procedure TBafSimpleGrid.EditKeyUp(Sender: TObject; var Key: Word;
  var KeyChar: Char; Shift: TShiftState);
begin
  case key of
    vkReturn: begin
      if FEditByReturn then
        FEditByReturn := false
      else begin
        HideEdit(true);
        NextRow(Key);
      end;
    end;
    vkTab: HideEdit(true);
    vkEscape: HideEdit(false);
    vkDown, vkUp: if (ssCtrl in Shift) then begin
      HideEdit(true);
      NextPriorRowEdit(Key, Shift);
    end;
    BAF_VK_PLUS, BAF_VK_MINUS: begin
      HideEdit(true);
      NextPriorRowEdit(Key, Shift);
    end;
  end;
end;

function TBafSimpleGrid.GetCell(AType: TBafGridRowType; ACol, ARow: integer): TBafSgCell;
var
  LRow: TBafSgRow;
  LList: TObjectList;
begin
  LList := nil;
  result := nil;
  case AType of
    rtHeader: LList := FHeaderRowList;
    rtData: LList := FDataRowList;
    rtDisplayData: LList := FDataRowDisplayList;
    rtFooter: LList := FFooterRowList;
    rtLink: LList := FLinkRowList;
    else
      exit;
  end;
  while ARow >= LList.Count do
    TBafSgRow.Create2List(AType, LList, FParents);
  LRow := (LList[ARow] as TBafSgRow);
  result := LRow.Cells[ACol];
end;

function TBafSimpleGrid.GetDataCells(ACol, ARow: integer): TBafSgCell;
begin
  result := GetDataRow(ARow).Cells[ACol];
end;

function TBafSimpleGrid.GetDataRow(ARow: integer): TBafSgRow;
begin
  while ARow >= FDataRowList.Count do
    TBafSgRow.Create2List(rtData, FDataRowList, FParents);
  result := (FDataRowList[ARow] as TBafSgRow);
end;

function TBafSimpleGrid.GetLinkRow(ARow: integer): TBafSgRow;
begin
  while ARow >= FLinkRowList.Count do
    TBafSgRow.Create2List(rtLink, FLinkRowList, FParents);
  result := (FLinkRowList[ARow] as TBafSgRow);
end;

function TBafSimpleGrid.GetSelectedCell: TBafSgCell;
begin
  result := nil;
  if (FSelectedRow >= 0) and (FSelectedCol >= 0) then
    result := Cells[rtDisplayData, FSelectedCol, FSelectedRow];
end;

function TBafSimpleGrid.GetVlCellText(AFieldName: string): string;
var
  LRow, LCol: integer;
  LCell: TBafSgCell;
begin
  for LRow := 0 to RowCount(rtData) - 1 do begin
    for LCol := 0 to Columns.Count - 1 do begin
      LCell := Cells[rtData, LCol, LRow];
      if LCell.DataFieldName = AFieldName then begin
        result := LCell.Text;
        exit;
      end;
    end;
  end;
end;

procedure TBafSimpleGrid.GridPaint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
var
  LStart, LCount, LScroll, LCol, LRow: integer;
  LPoint: TPointF;
  LRect: TRectF;

  procedure lokCheckInView;
  begin
    if FVertScrollBar.Visible then begin
      if FSelectedRow < LScroll then begin
        FVertScrollBar.Value := FSelectedRow;
        LScroll := trunc(FVertScrollBar.Value);
      end
      else if FSelectedRow > (LCount + LScroll - 1) then begin
        FVertScrollBar.Value := FSelectedRow - LCount + 1;
        LScroll := trunc(FVertScrollBar.Value);
      end;
    end;
  end; // procedure lokCheckInView;

  procedure lokPaintHint;
  var
    i: integer;
    LHeight, LWidth, LTick: Single;
  begin
    LWidth := 0;
    LHeight := Canvas.TextHeight('Wy');
    LTick := 0.2 * LHeight;
    FHintList.Text := FHoverCell.Hint;
    for i := 0 to FHintList.Count - 1 do
      LWidth := Max(LWidth, Canvas.TextWidth(FHintList[i]));
    LRect := Rectf(0, 0, LWidth + 2 * LTick,
        LHeight * FHintList.Count + 2 * LTick);
    LRect.Offset(FHoverCell.CaptionRect.Right,
        FHoverCell.DisplayRect.Bottom - LTick);
    if LRect.Right > Width then begin
      LRect := Rectf(0, 0, LWidth + 2 * LTick,
          LHeight * FHintList.Count + 2 * LTick);
      LRect.Offset(FHoverCell.CaptionRect.Left + LTick - LWidth,
          FHoverCell.DisplayRect.Bottom - LTick);
    end;
    if LRect.Bottom > Height then
      LRect.Offset(0, - LRect.Height - 22);
    Canvas.Fill.Color := TAlphaColorRec.Yellow;
    Canvas.FillRect(LRect, 0, 0, [], 1);
    Canvas.Fill.Color := TAlphaColorRec.Black;
    LRect.Inflate(-LTick, -LTick);
    Canvas.FillText(LRect, FHoverCell.Hint, false, 1, [], TTextAlign.Leading);
  end; // procedure lokPaintHint;

begin
  for LRow := 0 to FDataRowList.Count - 1 do
    for LCol := 0 to FColumns.Count - 1 do
      GetCell(rtData, LCol, LRow).DisplayRect := RectF(0, 0, 0, 0);
  Canvas.BeginScene;
  try
    LRect := ARect;
    LRect.Inflate(1, 1);
    Canvas.Fill.Color := FColorReadonly;
    Canvas.FillRect(LRect, 0, 0, [], 1);

    LCount := Min(FRowCount, FHeaderRowList.Count);
    PaintRows(rtHeader, FHeaderRowList, 0, LCount - 1, 0);
    LStart := LCount;
    LCount := Min(FDataRowDisplayList.Count, FRowCount - FHeaderRowList.Count
        - FFooterRowList.Count);
    LScroll := trunc(FVertScrollBar.Value);
    if FKeyChange then
      lokCheckInView;
    PaintRows(rtDisplayData, FDataRowDisplayList, 0 + LScroll, LCount + LScroll - 1,
        LStart);
    LStart := FHeaderRowList.Count + LCount;
    LCount := Min(FFooterRowList.Count, FRowCount - FHeaderRowList.Count);
    PaintRows(rtFooter, FFooterRowList, 0, LCount - 1, LStart);
    if Assigned(FHoverCell) and (FHoverCell.Hint <> '') then
      lokPaintHint;
  finally
    Canvas.EndScene;
    FKeyChange := false;
  end;
// procedure TBafSimpleGrid.GridPaint
end;

procedure TBafSimpleGrid.HideEdit(ASave: boolean);
var
  LAbort, LChanged, LComboTab: boolean;
  LText: string;
  LKey: word;

  procedure lokEdit;
  var
    LLookupHelper: TBafComboHelper;
  begin
    if ASave and (FEditReadOnly = false) then begin
      case FEditVisible of
        ecEdit: LText := FEdit.Text;
        ecCombo: if FEditCell.GetLookupHelper(LLookupHelper, false) then
          LText := LLookupHelper.GetGuid(FCombo);
      end;
      FEditCell.Parents.Grid.DoEditEvent(FEditCell.Parents, false, LText,
          LAbort, '', LText <> FEditCell.Text);
      LChanged := LText <> FEditCell.Text;
      FEditCell.SetTextChange(LText, false);
      FEditCell.Parents.Grid.DoEditEvent(FEditCell.Parents, true, LText,
          LAbort, '', LChanged);
    end;
  end; // procedure lokEdit;

begin
  LAbort := false;
  case FEditVisible of
    ecEdit, ecCombo: lokEdit;
  end;
  FEdit.Visible := false;
  FCombo.Visible := false;
  if not (csDesigning in ComponentState) and CanFocus then
    SetFocus;
  LComboTab := (FEditVisible = ecCombo) and FCombo.WithTabClosed;
  FCombo.WithTabClosed := false;
  FEditVisible := ecNone;
  FEditByReturn := false;
  Paint;
  FParents.Grid.SetStatus;
  LKey := vkTab;
  if FEdit.HasTab2Return or LComboTab then
    NextCell(LKey);
//  if Assigned(LCell) then
//    LCell.Parents.Segment.FEditScrollPos := 0;
end;

procedure TBafSimpleGrid.KeyDown(var Key: Word; var KeyChar: WideChar;
  Shift: TShiftState);
var
  LCell: TBafSgCell;

  procedure lokBool;
  begin
    if KeyChar in [' '] then
      LCell.ToggleBool
    else if KeyChar in BAFYESCHARS then
      LCell.SetBool(true)
    else if KeyChar in BAFNOCHARS then
      LCell.SetBool(false);
    LCell.Parents.SimpleGrid.Repaint;
  end; // procedure lokBool

  procedure lokTodo;
  begin
    case KeyChar of
      'A', 'a': LCell.SetTodo('A');
      ' ': LCell.SetTodo(' ');
      'C', 'c': LCell.SetTodo('A');
      'R', 'r': LCell.SetTodo('R');
    end;
    LCell.Parents.SimpleGrid.Repaint;
  end; // procedure lokBool

  procedure lokEdit;
  var
    LKey: word;
  begin
    if Key in [vkReturn] then begin
      FEditByReturn := true;
      ShowEdit(false)
    end
    else if KeyChar >= '0' then begin
      FKeyPressText := KeyChar;
      case FParents.Segment.SegmentType of
        stValueList: FEditCellType := LCell.CellType;
        stGrid, stXGrid: FEditCellType := LCell.FParents.Column.CellType;
      end;
      FEditCell := LCell;
      EditKeyDown(nil, LKey, KeyChar, []);
      if KeyChar <> #0 then
        ShowEdit(true);
    end;
  end; // procedure lokEdit

begin
  inherited;
  LCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
  case LCell.Parents.Column.CellType of
    ctBool, ctBool2: lokBool;
    ctTodo: lokTodo;
    else
      lokEdit;
  end;
// procedure TBafSimpleGrid.KeyDown
end;

procedure TBafSimpleGrid.LoadStyle;
var
  LObject, LObject2: TFmxObject;
  LRectangle: TRectangle;
  i: integer;
  s: string;

  function lokFindObject(AParent: TFmxObject; AName: string): TFmxObject;
  var
    i: integer;
    LChild: TFmxObject;
  begin
    result := nil;
    for i := 0 to AParent.ChildrenCount - 1 do begin
      LChild := AParent.Children.Items[i];
      if AnsiCompareText(LChild.StyleName, AName) = 0 then begin
        result := LChild;
        exit;
      end
      else begin
        result := lokFindObject(LChild, AName);
        if Assigned(result) then
          exit;
      end;
    end;
  end;

begin
  LObject := FMX.Types.FindStyleResource('gridstyle.background');
  if Assigned(LObject) and (LObject is TRectangle) then begin
    LRectangle := TRectangle(LObject);
    FColorBack := LRectangle.Fill.Color;
    LObject2 := lokFindObject(LObject, 'selection');
    if Assigned(LObject2) and (LObject2 is TRectangle) then begin
      LRectangle := TRectangle(LObject2);
      FColorSelection := LRectangle.Fill.Color;
    end;
  end;
  LObject := FMX.Types.FindStyleResource('pushpanel');
  if Assigned(LObject) and (LObject is TRectangle) then begin
    LRectangle := TRectangle(LObject);
    FColorReadonly := LRectangle.Fill.Color;
  end;
end;

function TBafSimpleGrid.Mouse2Cell(X, Y: Single; var ARowType: TBafGridRowType;
    var ACol, ARow: integer): boolean;
var
  i, LHDCnt: integer;
  LValue: single;
  LRect: TRectF;
  LCell: TBafSgCell;
begin
  result := false;
  ARowType := rtNone;
  LHDCnt := FHeaderRowList.Count + FDataRowDisplayList.Count;
  LValue := (Y / (22 * Columns.RowCount)) ;
  if (LValue >= 0) and (LValue < FHeaderRowList.Count) then begin
    ARowType := rtHeader;
    ARow := Trunc(LValue);
  end
  else if (LValue >= FHeaderRowList.Count) and (LValue < LHDCnt) then begin
    ARowType := rtDisplayData;
    ARow := Trunc(LValue) - FHeaderRowList.Count + Trunc(FVertScrollBar.Value);
  end
//  else if ((LValue + Trunc(FVertScrollBar.Value)) >= FHeaderRowList.Count)
//      and ((LValue + Trunc(FVertScrollBar.Value)) < LHDCnt) then begin
//    ARowType := rtDisplayData;
//    ARow := Trunc(LValue) - FHeaderRowList.Count;
//  end
  else if (LValue >= LHDCnt)
      and (LValue < (LHDCnt + FFooterRowList.Count)) then begin
    ARowType := rtFooter;
    ARow := Trunc(LValue) - LHDCnt;
  end;

  for i := 0 to Columns.Count - 1 do begin
    LCell := Cells[ARowType, i, ARow];
    if Assigned(LCell) then begin
      LRect := LCell.DisplayRect;
      if (LRect.Left <= x) and (x <= LRect.Right)
        and (LRect.Top <= y) and (y <= LRect.Bottom) then begin  // need for multiline rows
        ACol := i;
        result := true;
        exit;
      end;
    end;
  end;
end;

procedure TBafSimpleGrid.MouseWheel(Shift: TShiftState; WheelDelta: Integer;
  var Handled: Boolean);
begin
  inherited;
  Handled := false;
  if FVertScrollBar.Visible then begin
    if (WheelDelta < 0) and (FVertScrollBar.Value < FVertScrollBar.ValueRange.Max) then begin
      FVertScrollBar.Value := FVertScrollBar.Value + 1;
      Handled := true;
    end
    else if (WheelDelta > 0) and (FVertScrollBar.Value > FVertScrollBar.ValueRange.Min) then begin
      FVertScrollBar.Value := FVertScrollBar.Value - 1;
      Handled := true;
    end
  end;
end;

procedure TBafSimpleGrid.NextCell(var Key: Word);
var
  LCol: integer;
begin
  if (FSelectedRow = (FDataRowDisplayList.Count - 1))
      and (FSelectedCol = (FColumns.Count - 1)) then

  else begin
    if FSelectedRow = (FDataRowDisplayList.Count - 1) then begin  // last row
      LCol := FSelectedCol;
      inc(FSelectedCol);
      if not CanCellSelect(FSelectedCol, FSelectedRow) then
        NextCell(Key);
      if not CanCellSelect(FSelectedCol, FSelectedRow) then
        FSelectedCol := LCol;
    end
    else begin
      inc(FSelectedCol);
      if FSelectedCol = FColumns.Count then begin
        inc(FSelectedRow);
        FSelectedCol := 0;
        CheckChangeRow(false);
      end;
      if not CanCellSelect(FSelectedCol, FSelectedRow) then
        NextCell(Key);
    end;
    Key := 0;
    FKeyChange := true;
    CheckChangeCol;
    Repaint;
  end;
end;

procedure TBafSimpleGrid.NextPriorRowEdit(AKey: word; AShift: TShiftState);
var
  LValue: string;
  LKey: word;
  LCell: TBafSgCell;
  LCellType: TBafGridCellType;

  procedure lokMultiRowDown(AAdd: integer);
  var
    LRow, LRowCount: integer;
  begin
    LRowCount := LCell.Parents.SimpleGrid.RowCount(rtDisplayData);
    for LRow := FSelectedRow to LRowCount - 1 do begin
      if AAdd <> 0 then begin
        case LCellType of
          ctInt: LValue := IntToStr(StrToIntDef(LValue, 0) + AAdd);

        end;
      end;
      GetCell(rtDisplayData, FSelectedCol, LRow).SetTextChange(LValue);
    end
  end; // procedure lokMultiRowDown

  procedure lokMultiRowUp(AAdd: integer);
  var
    LRow: integer;
  begin
    for LRow := FSelectedRow - 1 downto 0 do begin
      if AAdd <> 0 then begin
        case LCellType of
          ctInt: LValue := IntToStr(StrToIntDef(LValue, 0) + AAdd);

        end;
      end;
      GetCell(rtDisplayData, FSelectedCol, LRow).SetTextChange(LValue);
    end
  end; // procedure lokMultiRowUp

begin
  LCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
  LCellType := LCell.Parents.Column.CellType;
  LValue := LCell.Text;
  if ssAlt in AShift then begin
    if (AKey = vkDown) then
      lokMultiRowDown(0)
    else if (AKey = BAF_VK_PLUS) and not (ssShift in AShift) then
      lokMultiRowDown(1)
    else if (AKey = BAF_VK_MINUS) and not (ssShift in AShift) then
      lokMultiRowDown(-1)
    else if (AKey = vkUp) then
      lokMultiRowUp(0)
    else if (AKey = BAF_VK_PLUS) and (ssShift in AShift) then
      lokMultiRowUp(1)
    else if (AKey = BAF_VK_MINUS) and (ssShift in AShift) then
      lokMultiRowUp(-1);
  end
  else begin
    if AKey = vkDown then
      NextRow(LKey)
    else if AKey = vkUp then
      PriorRow(LKey)
    else if AKey in [BAF_VK_PLUS, BAF_VK_MINUS] then begin
      if ssShift in AShift then
        PriorRow(LKey)
      else
        NextRow(LKey);
      case LCellType of
        ctInt: LValue := IntToStr(StrToIntDef(LValue, 0)
            + IfThen(AKey = BAF_VK_PLUS, 1, -1));

      end;
    end;
    GetCell(rtDisplayData, FSelectedCol, FSelectedRow).SetTextChange(LValue);
  end;
end;

procedure TBafSimpleGrid.NextRow(var Key: Word);
begin
  if (FSelectedRow = (FDataRowDisplayList.Count - 1)) then

  else begin
    SelectedRow := SelectedRow + 1;
    Key := 0;
    FKeyChange := true;
    CheckChangeRow(false);
  end;
end;

procedure TBafSimpleGrid.PaintCell(AType: TBafGridRowType; ACol, ARow: integer;
    ARect: TRectF);
var
  LText, LIcon: string;
  LCell: TBafSgCell;
  LAlign: TTextAlign;
  LColumn: TBafSgColumn;
  LCellType: TBafGridCellType;
  LSegmentType: TBafGridSegmentType;
  LVisible: boolean;

  procedure lokSetColor;
  begin
//    if (ACol = FSelectedCol) and (ARow = FSelectedRow) and (AType = rtData) then begin
//      if IsFocused then
//        Canvas.Fill.Color := TAlphaColorRec.Skyblue
//      else
//        Canvas.Fill.Color := TAlphaColorRec.Silver;
//    end
//    if (ACol = FSelectedCol) and (ARow = FSelectedRow)
//        and (AType = rtDisplayData) and IsFocused then
//      Canvas.Fill.Color := $FF0886CB
//    else if (ACol <> FSelectedCol) and (ARow = FSelectedRow)
//        and (AType = rtData) and IsFocused
//        and (FParents.Segment.SegmentType in [stGrid, stXGrid]) then
//      Canvas.Fill.Color := $FF0886CB
    if (ACol = FSelectedCol) and (ARow = FSelectedRow)
        and (AType in [rtData, rtDisplayData]) and IsFocused then
      Canvas.Fill.Color := FColorSelection
    else begin
      if (AType in [rtFooter, rtHeader]) then
        Canvas.Fill.Color := $FF0886CB
      else if (LCell.ReadOnly or LColumn.CellReadOnly) then
        Canvas.Fill.Color := $FF252525
      else
        Canvas.Fill.Color := FColorBack;
    end;
//    Canvas.Stroke.Color := $FFFFFFFF;
  end; // procedure lokSetColor;

  function lokCheckBoolean: boolean;
  begin
    result := true;
    if LCellType in [ctBool, ctBool2] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := TTextAlign.Center;
      if BafIsYesChar(LText) then
        LText :=  #$f14a
      else
        LText := IfThen(LColumn.CellType = ctBool, #$f0c8, '');
    end
    else if LCellType in [ctTodo] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := TTextAlign.Center;
      case AnsiUpperCase(LText + ' ')[1] of
        'A': LText := #$f192;
        'C': LText := #$f058;
        'R': LText := #$f057;
        else
          LText := '';
      end;
    end
    else
      Canvas.Font.Family := '';
  end; // function lokCheckBoolean;

  procedure lokCheckSort;
  begin
    if LCell.ShowSort and (LColumn.SortDirection <> sdNone) then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      case LColumn.SortDirection of
        sdDown: LIcon := #$f358;
        sdUp: LIcon := #$f35b;
      end;
      ARect.Right := ARect.Right - 2;
      Canvas.FillText(ARect, LIcon, false, 1, [], TTextAlign.Trailing);
      ARect.Right := ARect.Right - ARect.Height + 2;
      Canvas.Font.Family := '';
    end;
  end;

begin
  LAlign := TTextAlign.Leading;
  LCell := Cells[AType, ACol, ARow];
  LColumn := LCell.Parents.Column;
  LText := LCell.GetDisplayText;
  LSegmentType := LCell.Parents.Segment.SegmentType;
  if LSegmentType in [stGrid, stXGrid] then
    LCellType := LCell.Parents.Column.CellType
  else
    LCellType := LCell.CellType;
  LVisible := LCell.Visible or (LSegmentType in [stGrid, stXGrid]);
  LCell.DisplayRect := ARect;
  LCell.CaptionRect := ARect;
  LCell.CaptionRect.Width := Canvas.TextWidth(LText) + 2;
  Canvas.DrawRect(ARect, 0, 0, [], 1);
  ARect.Inflate(-1, -1);
  lokSetColor;
  if (AType = rtDisplayData) and LVisible then
    lokCheckBoolean;
  Canvas.FillRect(ARect, 0, 0, [], 1);
  ARect.Left := ARect.Left + 2;
  Canvas.Fill.Color := TAlphaColorRec.White;
  if AType = rtHeader then
    lokCheckSort;
  if LVisible then
    Canvas.FillText(ARect, LText, false, 1, [], LAlign);
// procedure TBafSimpleGrid.PaintCell
end;

procedure TBafSimpleGrid.PaintRows(AType: TBafGridRowType; AList: TObjectList;
    AFirst, ALast: integer; AStart: integer);
type
  TSpanType = (stVL, stHeader, stFooter);
var
  LRow, LCol, i: integer;
  LPosY, LWidth, LRight: single;
  LRectF: TRectF;
  LCell, LSpanCell: TBafSgCell;
begin
  for LRow := AFirst to ALast do begin
    for LCol := 0 to Columns.Count - 1do begin
      LCell := Cells[AType, LCol, LRow];
      LWidth := LCell.Parents.Column.Width;
      LPosY := (LRow - AFirst + AStart) * 22 * Columns.RowCount
        + 22 * LCell.FParents.Column.CalcedRow;
      LRectF := LCell.FParents.Column.CalcedRect;
      if (LRectF.Width > 1) and (LRectF.Right > 0)
          and (LRectF.Left < FPanelDesk.Width)
          and (LPosY < FPanelDesk.Height) and ((LPosY + LWidth) >= 0) then begin
        LRectF.Top := LPosY;
        LRectF.Bottom := LPosY + 22;
        if LCell.ColSpan = 1 then
          PaintCell(AType, LCol, LRow, LRectF)
        else if LCell.ColSpan > 1 then begin
          for i := 1 to LCell.ColSpan - 1 do begin
            LSpanCell := Cells[AType, LCol + i, LRow];
            LSpanCell.ColSpan := 0;
            LRight := LSpanCell.Parents.Column.CalcedRect.Right;
          end;
          LRectF.Right := LRight;
          PaintCell(AType, LCol, LRow, LRectF);
        end;
      end;
    end;
  end;
// procedure TBafSimpleGrid.PaintRows
end;

procedure TBafSimpleGrid.PanelDeskMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Single);
var
  LCol, LRow: integer;
  LRowType: TBafGridRowType;
begin
  if ssDouble in Shift then begin
    if Mouse2Cell(X, Y, LRowType, LCol, LRow) and (LRowType = rtDisplayData) then
      ShowEdit(false);
  end;
end;

procedure TBafSimpleGrid.PanelDeskMouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Single);
var
  LCol, LRow: integer;
  LCell: TBafSgCell;
  LRowType: TBafGridRowType;
begin
  if Mouse2Cell(X, Y, LRowType, LCol, LRow) then begin
    LCell := Cells[LRowType, LCol, LRow];
    if LCell <> FHoverCell then begin
      if LCell.Hint <> '' then
        FHoverCell := LCell
      else
        FHoverCell := nil;
      FPanelDesk.Repaint;
    end;
  end
  else begin
    FHoverCell := nil;
    FPanelDesk.Repaint;
  end;
end;

procedure TBafSimpleGrid.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LCol, LRow: integer;
  LCell: TBafSgCell;
  LRowType: TBafGridRowType;
begin
  if not IsFocused then
    SetFocus;
  if FEditVisible <> ecNone then    // if we can click the panel, we're outside the edit control
    HideEdit(true);

  if Mouse2Cell(X, Y, LRowType, LCol, LRow) then begin
    if LRowType = rtDisplayData then begin
      SelectedRow := LRow;
      SelectedCol := LCol;
      LCell := Cells[rtDisplayData, LCol, LRow];
      if LCell.CellType in [ctBool, ctBool2] then
          LCell.ToggleBool;
      if LCell.CellType = ctTodo then
          LCell.ToggleTodo;
      FPanelDesk.Repaint;
    end;
    if LRowType = rtHeader then begin
      Columns.Items[LCol].ToggleSort;
      FPanelDesk.Repaint;
    end;
  end;
end;

procedure TBafSimpleGrid.PriorCell(var Key: Word);
begin
  if (FSelectedRow = 0) and (FSelectedCol = 0) then

  else begin
    dec(FSelectedCol);
    if FSelectedCol = -1 then begin
      dec(FSelectedRow);
      FSelectedCol := FColumns.Count - 1;
      CheckChangeRow(true);
    end;
    if not CanCellSelect(FSelectedCol, FSelectedRow) then
      PriorCell(Key);
    Key := 0;
    FKeyChange := true;
    Repaint;
  end;
end;

procedure TBafSimpleGrid.PriorRow(var Key: Word);
begin
  if (FSelectedRow = 0) then

  else begin
    SelectedRow := SelectedRow - 1;
    Key := 0;
    FKeyChange := true;
    CheckChangeRow(true);
  end;
end;

procedure TBafSimpleGrid.Resize;
begin
  inherited;
  Columns.GridHeaderLineBreak;
end;

function TBafSimpleGrid.RowCount(AType: TBafGridRowType): integer;
begin
  case AType of
    rtHeader: result := FHeaderRowList.Count;
    rtFooter: result := FFooterRowList.Count;
    rtLink: result := FLinkRowList.Count;
//    rtMap: result := FMapRowList.Count;
    rtDisplayData: result := FDataRowDisplayList.Count;
  else
    result := FDataRowList.Count;
  end;
end;

procedure TBafSimpleGrid.ScrollChange(Sender: TObject);
begin
  if Sender = FHorzScrollBar then
    Columns.GridHeaderLineBreak;
  Repaint;
end;

procedure TBafSimpleGrid.SetFilterStatement(const Value: string);
var
  LIni: TStringIniFile;
  LRow, i, LCol: integer;
  LFilter, LWert1, LWert2, LValue: string;

  function lokCheckString: boolean;
  begin
    if LFilter = 'Equal' then
      result := LValue = LWert1
    else if LFilter = 'EqualCi' then
      result := AnsiCompareText(LValue, LWert1) = 0
    else if LFilter = 'List' then
      result := Pos(LValue, LWert1) > 0
    else if LFilter = 'ListCi' then
      result := Pos(AnsiLowerCase(LValue), AnsiLowerCase(LWert1)) > 0
    else if LFilter = 'Start' then
      result := Pos(LWert1, LValue) = 1
    else if LFilter = 'StartCi' then
      result := Pos(AnsiLowerCase(LWert1), AnsiLowerCase(LValue)) = 1
    else if LFilter = 'Part' then
      result := Pos(LWert1, LValue) > 0
    else if LFilter = 'PartCi' then
      result := Pos(AnsiLowerCase(LWert1), AnsiLowerCase(LValue)) > 0

    else
      result := false;
  end; // function lokCheckString

  function lokCheckCurr: boolean;
  var
    LCurValue, LCurWert1, LCurWert2: currency;
  begin
    LCurValue := StrToCurrDef(LValue, -MaxInt);
    LCurWert1 := StrToCurrDef(LWert1, -MaxInt);
    LCurWert2 := StrToCurrDef(LWert2, -MaxInt);
    if (LCurValue = -MaxInt) or (LCurWert1 = -MaxInt) then
      result := false
    else if LFilter = 'Equal' then
      result := LCurValue = LCurWert1
    else if LFilter = 'EqualCi' then
      result := LCurValue = LCurWert1
    else if LFilter = '>' then
      result := LCurValue > LCurWert1
    else if LFilter = '>=' then
      result := LCurValue >= LCurWert1
    else if LFilter = '<' then
      result := LCurValue < LCurWert1
    else if LFilter = '<=' then
      result := LCurValue <= LCurWert1
    else if (LFilter = 'Between') and ((LCurWert2 <> -MaxInt)) then
      result := (LCurValue >= LCurWert1) and (LCurValue <= LCurWert2)

    else
      result := false;
  end; // function lokCheckString

  function lokInFilter: boolean;
  var
    i: integer;
    LCat: string;
  begin
    result := true;
    for i := 0 to Columns.Count - 1 do begin
      LCat := Cells[rtHeader, i, 0].Text;
      LFilter := LIni.ReadString(LCat, 'filter', '');
      if LFilter <> '' then begin
        LWert1 := LIni.ReadString(LCat, 'wert_1', '');
        LWert2 := LIni.ReadString(LCat, 'wert_2', '');
        if Columns.Items[i].CellType in [ctLookup] then
          LValue := (FDataRowList[LRow] as TBafSgRow).Cells[i].GetDisplayText
        else
          LValue := (FDataRowList[LRow] as TBafSgRow).Cells[i].Text;
        case Columns.Items[i].CellType of
          ctText, ctLookup, ctGuid, ctIBAN: result := lokCheckString;
          ctInt, ctCurr, ctCurr4: result := lokCheckCurr;
        end; // case
        if not result then
          exit;
      end;
    end;
  end; // function lokInFilter

begin
  FFilterStatement := Value;
  LIni := TStringIniFile.Create('');
  try
    LIni.AsString := Value;
    if Trim(Value) <> '' then begin
      FDataRowFilteredList.Clear;

      for LRow := 0 to FDataRowList.Count - 1 do begin
        if lokInFilter then
          FDataRowFilteredList.Add(FDataRowList[LRow]);
      end;

      FDataRowDisplayList := FDataRowFilteredList;
      Repaint;
//      FParents.Grid.RefreshCalcedCells;
//      FParents.Grid.Resize;
    end
    else begin
      FDataRowDisplayList := FDataRowList;
    end;

    for i := 0 to FDataRowDisplayList.Count - 1 do
      (FDataRowDisplayList[i] as TBafSgRow).Index := i;

//    for i := 0 to FParents.Segment.FHeaderRowList.Count - 1 do
//      (FParents.Segment.FHeaderRowList[i] as TBafGridRow).ClearCalc;
//    for i := 0 to FParents.Segment.FFooterRowList.Count - 1 do
//      (FParents.Segment.FFooterRowList[i] as TBafGridRow).ClearCalc;

  finally
    LIni.Free;
  end;
  Columns.GridHeaderLineBreak;
  FParents.Segment.RefreshHeight;
// procedure TBafSimpleGrid.SetFilterStatement
end;

procedure TBafSimpleGrid.SetHasChanged(const Value: boolean);
begin
  FHasChanged := Value;
  if Value then
    FParents.Segment.HasChanged := true;
end;

procedure TBafSimpleGrid.SetSelectedCol(const Value: integer);
begin
  FSelectedCol := Value;
end;

procedure TBafSimpleGrid.SetSelectedRow(const Value: integer);
var
  LCol: integer;
  LGridColumn: TBafSgColumn;
  LCell: TBafSgCell;
begin
  if FSelectedRow <> Value then begin
    if Columns.HasLinkedColumns then begin
      for LCol := 0 to Columns.Count - 1 do begin
        LGridColumn := Columns.Items[LCol];
        if LGridColumn.LinkedSegment <> nil then begin
          if LGridColumn.LinkedSegment.HasChanged then
            Cells[rtData, LCol, FSelectedRow].SetTextChange(
                LGridColumn.LinkedSegment.Lines.Text);
          if Value >= 0 then begin
            LCell := Cells[rtData, LCol, Value];
            LGridColumn.LinkedSegment.SetMemoTextUnchanged(LCell.Text);
            LGridColumn.LinkedSegment.LinkedCell := LCell;
            LGridColumn.LinkedSegment.HasChanged := false;
          end
          else begin
            LGridColumn.LinkedSegment.SetMemoTextUnchanged('');
            LGridColumn.LinkedSegment.LinkedCell := nil;
            LGridColumn.LinkedSegment.HasChanged := false;
          end;
        end;
      end;
      FParents.Grid.SetStatus;
    end;
    FSelectedRow := Value;
  end;
end;

procedure TBafSimpleGrid.ShowEdit(AKeyPress: boolean);
var
  LRect: TRectF;
  LKey: word;

  procedure lokEdit(AEdit: TEdit; AEditVisible: TBafClientEditComp);
  begin
    AEdit.ReadOnly := FEditReadOnly;
//    AEdit.Color := IfThen(FEditReadOnly, clSilver, clWindow);
//    AEdit.Font.Size := gvSty.GSC.GetScaled(10);
    AEdit.Visible := true;
    FEditVisible := AEditVisible;
    AEdit.SetBounds(LRect.Left, LRect.Top, LRect.Width, LRect.Height);
    if AKeyPress then
      AEdit.Text := FKeyPressText
    else
      AEdit.Text := FEditCell.Text;
    case FParents.Segment.SegmentType of
      stValueList: begin
        AEdit.MaxLength := FEditCell.MaxLength;
        FEditCellType := FEditCell.CellType;
        AEdit.CharCase := FEditCell.CharCase;
        AEdit.Password := FEditCell.Password;
      end;
      stGrid, stXGrid: begin
        AEdit.MaxLength := FEditCell.FParents.Column.CellMaxLength;
        FEditCellType := FEditCell.FParents.Column.CellType;
        AEdit.CharCase := FEditCell.FParents.Column.CellCharCase;
      end;
    end;
    if AEdit.CanFocus then
      AEdit.SetFocus;
    if AKeyPress then begin
      AEdit.SelLength := 0;
      AEdit.SelStart := MaxInt;
    end
    else
      AEdit.SelectAll;
    if AEdit is TBafEdit then
      TBafedit(AEdit).HasTab2Return := false;
  end; // procedure lokEdit

  procedure lokLookup;
  var
    LLookupHelper: TBafComboHelper;
  begin
    if FEditCell.GetLookupHelper(LLookupHelper, true) then
      LLookupHelper.FillAndSet(FCombo, FEditCell.Text);
//    FCombo.Color := IfThen(FEditReadOnly, clSilver, clWindow);
//    FCombo.Font.Size := gvSty.GSC.GetScaled(10);
    FCombo.Visible := true;
    FEditVisible := ecCombo;
    FCombo.SetBounds(LRect.Left, LRect.Top, LRect.Width, LRect.Height);
    FCombo.DropDown;
    if FCombo.CanFocus then
      FCombo.SetFocus;
//    FEditCellType := FEditCell.FParents.Column.CellType;
  end; // procedure lokLookup

begin
  if (FEditVisible <> ecNone) then
    exit;

  FEditCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
  if FEditCell.CellType in [ctBool, ctBool2, ctTodo] then
    exit;
  case FEditCell.Parents.Segment.SegmentType of
    stValueList: if not FEditCell.Visible then
      exit;
    stGrid, stXGrid: if not FEditCell.Parents.Column.CellVisible then
      exit;
  end;
  LRect := FEditCell.DisplayRect;
  if (LRect.Width < 5) or (LRect.Height < 5) then
    exit;    // cell isn't really visible
  if FEditCell.Parents.Column.CellType in [ctBool, ctBool2, ctTodo] then
    exit;   // we don't edit boolean
  FEditReadOnly := FEditCell.FParents.Column.CellReadOnly or FEditCell.ReadOnly
    or FEditCell.FParents.Segment.ReadOnly;
  case FEditCell.CellType of
    ctLookup, ctLookupStatus, ctLookupLive: lokLookup;
    else
      lokEdit(FEdit, ecEdit);
  end;
  FParents.Grid.SetStatus;
// procedure TBafSimpleGrid.ShowEdit
end;

{ TBafSgColumns }

function TBafSgColumns.Add: TBafSgColumn;
begin
  Result := AddItem(nil, -1);
end;

function TBafSgColumns.AddColumn(AFieldName, ACaption, AHint: string;
  AReadOnly: boolean; AWidth, AStretch: single): TBafSgColumn;
var
  LCell: TBafSgCell;
begin
  result := Add;
  result.CellFieldName := AFieldName;
  LCell := Parents.SimpleGrid.Cells[rtHeader, result.Index, 0];
  LCell.Text := ACaption;
  LCell.Hint := AHint;
  result.CellReadOnly := AReadOnly;
  result.Width := AWidth;
  result.Stretch := AStretch;
  result.XGridIndex := -1;
  result.CellDataQuelle := dqSQL;
end;

function TBafSgColumns.AddItem(Item: TBafSgColumn;
  Index: Integer): TBafSgColumn;
begin
  if Item = nil then
    Result := TBafSgColumn.Create(Self)
  else
  begin
    Result := Item;
    if Assigned(Item) then
    begin
      Result.Collection := Self;
      if Index < Count then
        Index := Count - 1;
      Result.Index := Index;
    end;
  end;
end;

function BafGridColumnSort(Item1, Item2: Pointer): Integer;
var
  LRow1, LRow2: TBafSgRow;
  LText1, LText2, LDir: string;
  LDiff: double;
  LCurr1, LCurr2: currency;
  LSSP, LCol: integer;
  LColumn: TBafSgColumn;

  procedure lokSort;
  begin
    case LColumn.CellType of
      ctInt, ctCurr, ctCurr4: begin
        LCurr1 := StrToCurrDef(LText1, 0);
        LCurr2 := StrToCurrDef(LText2, 0);
        result := round(LCurr1 - LCurr2);
        if result = 0 then
          result := round(10000 * (LCurr1 - LCurr2));
      end;
      ctDate, ctDateMin, ctDateSek: begin
        LDiff := StrToDateTimeDef(LText1, 0) - StrToDateTimeDef(LText2, 0);
        if Abs(LDiff) < 2 then
          result := round(24 * 3600 * LDiff)
        else
          result := round(LDiff);
      end;
      else
        result := AnsiCompareText(LText1, LText2);
    end;
  end; // procedure lokSort;

begin
  result := 0;
  LSSP := 0;

  while (result = 0) and (LSSP < mvColumns.FSortStack.Count) do begin
    LRow1 := TObject(Item1) as TBafSgRow;
    LRow2 := TObject(Item2) as TBafSgRow;
    LCol := StrToIntDef(mvColumns.FSortStack.Names[LSSP], -1);
    LDir := copy(mvColumns.FSortStack.ValueFromIndex[LSSP], 1, 1);
    if (LCol >= 0) and ((LDir = 'U') or (LDir = 'D')) then begin
      LText1 := LRow1.Cells[LCol].Text;
      LText2 := LRow2.Cells[LCol].Text;
    end;

    LColumn := mvColumns.Items[LCol];
    lokSort;             // <-------
    if LDir = 'U' then
      result := result * -1;

    inc(LSSP);
  end;
end;


procedure TBafSgColumns.AddSortStack(AIndex: integer; ASortDir: TBafSortDirection);
var
  s: string;
  ix: integer;
begin
  s := IntToStr(AIndex);
  ix := FSortStack.IndexOfName(s);
  if ix >= 0 then
    FSortStack.Delete(ix);
  if ASortDir <> sdNone then
    FSortStack.Insert(0, s + '=' + IfThen(ASortDir = sdUp, 'U', 'D'));

  mvColumns := Self;
  // we sort both lists
  FParents.SimpleGrid.FDataRowList.Sort(BafGridColumnSort);
  FParents.SimpleGrid.FDataRowFilteredList.Sort(BafGridColumnSort);
  for ix := 0 to FParents.SimpleGrid.FDataRowDisplayList.Count - 1 do
    (FParents.SimpleGrid.FDataRowDisplayList[ix] as TBafSgRow).Index := ix;
end;

procedure TBafSgColumns.ClearJoinLists;
var
  LCol: integer;
begin
  for LCol := 0 to Count - 1 do begin
    Items[LCol].JoinList.Clear;
    Items[LCol].HintJoinList.Clear;
  end;
end;

procedure TBafSgColumns.ClearSort;
var
  LCol: integer;
begin
  for LCol := 0 to Count - 1 do
    Items[LCol].SortDirection := sdNone;
end;

constructor TBafSgColumns.Create(AGrid: TBafSimpleGrid);
begin
  inherited Create(TBafSgColumn);
  FParents := AGrid.FParents;
  FParents.Columns := Self;
  FRowCount := 1;
  FSortStack := TStringList.Create;
end;

destructor TBafSgColumns.Destroy;
begin
  FreeAndNil(FSortStack);

  inherited;
end;

function TBafSgColumns.GetItem(Index: Integer): TBafSgColumn;
begin
  Result := TBafSgColumn(inherited GetItem(Index));
end;

procedure TBafSgColumns.GridHeaderLineBreak;
var
  LCompWidth, LLeft, LCellWidth, LStretchWidth, LNeeded, LScroll,
      LRowHeight, LTop, LFixedLeft, LLeftScroll, LMaxWidth: single;
  i, LIndexInRow, LRow: integer;
  LColumn: TBafSgColumn;
  LScrollBar: TScrollBar;

  procedure lokStretch;
  // distributes LStretchWidth to the space of the current row
  var
    i, LCol2: integer;
    LDiff, LLeft2, LCellWidth2, LFixedLeft2: single;
    LColumn2: TBafSgColumn;
    LAddScale: double;
  begin
    LDiff := (LCompWidth - 5 - LLeft);
    if (LDiff < 3) or (LStretchWidth < 3) then
      exit;
    LAddScale := IfThen(LStretchWidth < LDiff, 1, LDiff / LStretchWidth);
    LLeft2 := - LScroll;
    LFixedLeft2 := 0;
    for i := 0 to Count - 1 do begin
      LCol2 := i;
      LColumn2 := Items[LCol2];
      if LColumn2.FCalcedRow = LRow then begin
        LCellWidth2 := LColumn2.Width + LColumn2.Stretch * LAddScale;
        LTop := LRow * LRowHeight;
        if i < FixedColsCount then begin
          LColumn2.FCalcedRect := RectF(LFixedLeft2, LTop,
              LFixedLeft2 + LCellWidth2 + 1, LTop + LRowHeight);
          LFixedLeft2 := LFixedLeft2 + LCellWidth2;
        end
        else
          LColumn2.FCalcedRect := RectF(Max(LFixedLeft2, LLeft2), LTop,
              Max(LFixedLeft2, LLeft2 + LCellWidth2 + 1), LTop + LRowHeight);
        LLeft2 := LLeft2 + LCellWidth2;
      end;
    LMaxWidth := Max(LMaxWidth, LColumn2.FCalcedRect.Right);
    end;
    LStretchWidth := 0;
  end; // procedure lokStretch

  procedure lokCalc;
  var
    i: integer;
  begin
    for i := 0 to Count - 1 do begin
      LColumn := Items[i];
      LCellWidth := LColumn.Width;
      if LineType in [ltSingle, ltSingleStretch] then
        LNeeded := LNeeded + LCellWidth
      else
        LNeeded := Max(LNeeded, LCellWidth);
      if (LineType in [ltMulti, ltMultiStretch])
          and ((LLeft + LCellWidth) > LCompWidth)
          and (LIndexInRow > 0) then begin
        if LineType in [ltSingleStretch, ltMultiStretch] then
          lokStretch;
        inc(LRow);
        LLeft := - LScroll;
        LIndexInRow := 0;
      end;
      LStretchWidth := LStretchWidth + LColumn.Stretch;
      LCellWidth := Min(LCellWidth, LCompWidth - LLeft);
      LTop := LRow * LRowHeight;
      if (i < FixedColsCount) and (LineType in [ltSingle, ltSingleStretch]) then begin
        LColumn.FCalcedRect := RectF(LFixedLeft, LTop,
            LFixedLeft + LCellWidth + 1, LTop + LRowHeight);
        LFixedLeft := LFixedLeft + LCellWidth;
      end
      else
        LColumn.FCalcedRect := RectF(Max(LFixedLeft, LLeft), LTop,
            Max(LFixedLeft, LLeft + LCellWidth + 1), LTop + LRowHeight);
      LColumn.FScrollRect := RectF(LLeftScroll, LTop,
            LLeftScroll + LColumn.Width, LTop + LRowHeight);
      LColumn.FCalcedRow := LRow;
      LLeft := LLeft + LCellWidth;
      LLeftScroll := LLeftScroll + LColumn.Width;
      LMaxWidth := Max(LMaxWidth, LColumn.FCalcedRect.Right);
      inc(LIndexInRow);
    end;
  end; // procedure lokCalc

begin
  LCompWidth := FParents.SimpleGrid.Width;
  LScrollBar := FParents.SimpleGrid.FHorzScrollBar;
  LScroll := LScrollBar.Value;
  LLeft := - LScroll;
  LLeftScroll := - LScroll;
  LRow := 0;
  LNeeded := 0;
  LStretchWidth := 0;
  LIndexInRow := 0;
  LFixedLeft := 0;
  LRowHeight := 22;
  LMaxWidth := 0;
  lokCalc;
  if LineType in [ltSingleStretch, ltMultiStretch] then
    lokStretch;
  LScrollBar.Visible := (LNeeded > LCompWidth);
  if LScrollBar.Visible then begin
    LScrollBar.Max := LNeeded;
    LScrollBar.ViewportSize := LCompWidth;
    LScrollBar.Repaint;
  end
  else
    FParents.SimpleGrid.FHorzScrollBar.Value := 0;
  FRowCount := LRow + 1;
  FParents.Segment.GridCalcHeight;
  FParents.Segment.FHeaderPanel.Width := LMaxWidth + 26;
// procedure TBafSgColumns.GridHeaderLineBreak
end;

function TBafSgColumns.Insert(Index: Integer): TBafSgColumn;
begin
  Result := AddItem(nil, Index);
end;

procedure TBafSgColumns.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited;
  FAllColumnWidth := -1;
end;

procedure TBafSgColumns.SetItem(Index: Integer; const Value: TBafSgColumn);
begin
  inherited SetItem(Index, Value);
end;

{ TBafSgColumn }

constructor TBafSgColumn.Create(Collection: TCollection);
begin
  inherited;
  FParents := (Collection as TBafSgColumns).Parents;
  FParents.Column := Self;
  FSortColumn := -1;
  FJoinList := TStringList.Create;
  FJoinList.Sorted := true;
  FJoinList.Duplicates := dupIgnore;
  FHintJoinList := TStringList.Create;
  FHintJoinList.Sorted := true;
  FHintJoinList.Duplicates := dupIgnore;
end;

destructor TBafSgColumn.Destroy;
begin
  FreeandNil(FHintJoinList);
  FreeandNil(FJoinList);
  inherited;
end;

procedure TBafSgColumn.SetCellFieldName(const Value: string);
begin
  FCellFieldName := AnsiLowerCase(Value);
end;

procedure TBafSgColumn.SetLinkCommand(const Value: string);
var
  p1, p2: integer;
  s: string;
begin
  FLinkCommand := Value;
  p1 := Pos('(', FLinkCommand);
  p2 := Pos(')', FLinkCommand);
  s := copy(FLinkCommand, p1 + 1, p2 - p1 - 1);
  FLinkCol := StrToInt(s);

  FLinkCommand := copy(FLinkCommand, 1, p1 - 1);
  if FLinkCommand = 'sum' then
    FLinkFunc := lfSum
  else if FLinkCommand = 'count' then
    FLinkFunc := lfCount
  else if FLinkCommand = 'min' then
    FLinkFunc := lfMin
  else if FLinkCommand = 'max' then
    FLinkFunc := lfMax
  else
    FLinkFunc := lfNone;
end;

procedure TBafSgColumn.SetNullValue(ACellNullValueAction: TBafNullValueAction;
  ACellNullValue: string);
begin
  FCellNullValueAction := ACellNullValueAction;
  FCellNullValue := ACellNullValue;
end;

function TBafSgColumn.SetValues(AWidth, AStretch: integer; AVisible: boolean;
  Style: string): TBafSgColumn;
begin
  FWidth := AWidth;
  FStretch := AStretch;
  FVisible := AVisible;
  result := Self;
end;


procedure TBafSgColumn.Sort(AUp: boolean);
var
  i: integer;
begin
  mvSortCell := Index;
  mvSortUp := AUp;
  case CellType of
    ctCurr, ctCurr4, ctInt: mvSortType := soNumber;
    ctDate, ctDateMin, ctDateSek: mvSortType := soDate;
    else
      mvSortType := soText;
  end;

  // Wenn wir eine berechnete Spalte sortieren, dann muss zunächst sichergestellt
  // werden, dass überall berechnete Werte vorhanden sind...
//  if FCellCommand <> '' then begin
//    for i := 0 to FParents.Segment.FDataRowList.Count - 1 do
//      FParents.Segment.DataCells[Index, i].GetDisplayText;
//  end;
//
//  // Wir sortieren beide Listen
//  FParents.Segment.FDataRowList.Sort(BafGridColumnSort);
//  FParents.Segment.FDataRowFilteredList.Sort(BafGridColumnSort);
//  for i := 0 to FParents.Segment.FDataRowDisplayList.Count - 1 do
//    (FParents.Segment.FDataRowDisplayList[i] as TBafGridRow).Index := i;
end;

procedure TBafSgColumn.ToggleSort;
var
  LCol, ix: integer;
  LSortDir: TBafSortDirection;
begin
  LSortDir := SortDirection;
  Parents.Columns.ClearSort;
  if LSortDir = sdDown then
    SortDirection := sdUp
  else
    SortDirection := sdDown;
  Parents.Columns.AddSortStack(Index, SortDirection);

end;

{ TBafSgRow }

procedure TBafSgRow.AddCell;
var
  LCell: TBafSgCell;
begin
  LCell := TBafSgCell.Create(FParents);
  LCell.FParents.Column := FParents.Columns.Items[Count];
  if (LCell.Parents.Segment.SegmentType in [stGrid, stXGrid]) and (RowType = rtData) then
    LCell.CellType := LCell.Parents.Column.CellType;
  if (LCell.Parents.Segment.SegmentType in [stValueList])
      and (LCell.Parents.Column.CellType = ctButton) then
    LCell.CellType := ctButton;
  Add(LCell);
end;

procedure TBafSgRow.ClearCalc;
var
  i: integer;
begin
  for i := 0 to Count - 1 do
    Cells[i].FIsCalced := false;
end;

class procedure TBafSgRow.Create2List(ARowType: TBafGridRowType;
  AList: TObjectList; AParents: TBafGridParents);
var
  LRow: TBafSgRow;
begin
  LRow := TBafSgRow.Create(true);
  LRow.RowType := ARowType;
  LRow.FParents.CreateFrom(AParents);
  LRow.FParents.Row := LRow;
  LRow.FParents.Columns := AParents.SimpleGrid.Columns;
  LRow.Index := AList.Add(LRow);
  LRow.FJoinList := TStringList.Create;
  LRow.FJoinList.Sorted := true;
  LRow.FJoinList.Duplicates := dupIgnore;
  LRow.FJoinList2 := TStringList.Create;
  LRow.FJoinList2.Sorted := true;
  LRow.FJoinList2.Duplicates := dupIgnore;
end;

destructor TBafSgRow.Destroy;
begin
  FreeAndNil(FJoinList2);
  FreeAndNil(FJoinList);
  inherited;
end;

function TBafSgRow.GetCell(ACol: integer): TBafSgCell;
begin
  while ACol >= Count do
    AddCell;
  result := (Items[ACol] as TBafSgCell);
end;

procedure TBafSgRow.SetHasCanged(const Value: boolean);
begin
  FHasCanged := Value;
  if Value then
    FParents.SimpleGrid.HasChanged := true;
end;

{ TBafSgCell }

procedure TBafSgCell.ClearSort(AOnlyHover: boolean);
begin
  FSortDown := bsNone;
  FSortUp := bsNone;
end;

constructor TBafSgCell.Create(AParents: TBafGridParents);
begin
  inherited Create;
  FParents := AParents;
  FParents.Cell := Self;
  FColSpan := 1;
end;

destructor TBafSgCell.Destroy;
begin

  inherited;
end;

function TBafSgCell.GetCellType(AHeaderFooter: boolean): TBafGridCellType;
begin
  case FParents.Segment.SegmentType of
    stValueList: result := CellType;
    stGrid, stXGrid: begin
      if AHeaderFooter and (CellType <> ctText) then
        result := CellType
      else
        result := FParents.Column.CellType;
    end;
  else
    result := ctText;
  end;
end;

function TBafSgCell.GetDisplayText: string;
var
  LLookupHelper: TBafComboHelper;
  i: integer;
begin
  if CellType = ctGuid then
    result := '°°°'
  else if Password then
    for i := 1 to Length(FText) do
      result := result + '*'
  else begin
    if FCommand <> '' then begin
      if FIsCalced then
        result := FText
      else begin
        result := '';
//        result := FParents.Grid.FBafFormelnCalc.Calc(FCommand,
//            GetCellType(FParents.Row.RowType <> rtData), FParents.Column.Index,
//            FParents.Row.Index,
//            FParents.Segment.FDataRowDisplayList.Count, FParents.Segment.Columns.Count,
//            FParents.Segment.GetCellValue);
        FText := result;
        FIsCalced := true;
      end;
    end
    else
      result := FText;
    if GetLookupHelper(LLookupHelper, false) then
      result := LLookupHelper.GetCaptionGuid(FText);
  end;
end;

function TBafSgCell.GetHintDisplayText: string;
var
  LLookupHelper: TBafComboHelper;
begin
  if (Parents.Column.Width > 0) then
    result := Hint
  else begin
    if FCommand <> '' then begin
      if FIsCalced then
        result := FText
      else begin
        result := '';
//        result := FParents.Grid.FBafFormelnCalc.Calc(FCommand,
//            GetCellType(FParents.Row.RowType <> rtData), FParents.Column.Index,
//            FParents.Row.Index,
//            FParents.Segment.FDataRowDisplayList.Count, FParents.Segment.Columns.Count,
//            FParents.Segment.GetCellValue);
        FText := result;
        FIsCalced := true;
      end;
    end
    else
      result := FHint;
    if GetLookupHelper(LLookupHelper, false) then
      result := LLookupHelper.GetCaptionGuid(FHint);
    if Parents.Column.CellType in [ctBool, ctBool2] then
      result := IfThen(BafIsYesChar(FHint), 'x', '');
  end;
end;

function TBafSgCell.GetLookupHelper(var ALookupHelper: TBafComboHelper;
  ALive: boolean): boolean;
var
  LFill: boolean;
  LLineP: string;
begin
  result := false;
  LFill := false;
  if (CellType in [ctLookup, ctLookupStatus])
      and Assigned(LookupHelper) then begin
    ALookupHelper := LookupHelper;
    result := true;
    if CellType = ctLookupLive then begin
      LFill := true;
      LLineP := FParents.Row.LineP;
    end;
  end
  else if (FParents.Column.CellType in [ctLookup, ctLookupStatus])
      and (FParents.Row.RowType = rtData)
      and Assigned(FParents.Column.CellLookupHelper) then begin
    ALookupHelper := FParents.Column.CellLookupHelper;
    result := true;
  end
  else if (FParents.Column.CellType in [ctLookupLive])
      and (FParents.Row.RowType = rtData) and Assigned(LookupHelper) then begin
    ALookupHelper := LookupHelper;
    result := true;
    LFill := true;
    LLineP := FParents.Column.LineP;
  end;
//  if LFill and (ALive or (ALookupHelper.Count = 0)) and Assigned(FParents.Grid.FOnFillLookupLiveEvent) then
//    FParents.Grid.FOnFillLookupLiveEvent(FParents.Grid, ALookupHelper, LLineP, Self);

end;

function TBafSgCell.ReplaceParameter(AText: string): string;
begin
  result := StringReplace(AText, '$CELL()', Text, [rfIgnoreCase, rfReplaceAll]);
//  if Assigned(FParents.Grid.FOnReplaceParameter) then
//    FParents.Grid.FOnReplaceParameter(FParents.Grid, result, result);
end;

procedure TBafSgCell.SetBool(AValue: boolean);
begin
  if not ReadOnly and not Parents.Column.CellReadOnly then
    SetTextChange(IfThen(AValue, 'Y', 'N'));
end;

procedure TBafSgCell.SetCellType(const Value: TBafGridCellType);
begin
  if FCellType <> Value then begin
    FCellType := Value;
//    if Assigned(FBafButtonList) then
//      FreeAndNil(FBafButtonList);
//    if FCellType = ctButton then begin
//      FBafButtonList := TBafButtonList.Create(FParents);
//      FBafButtonList.SetButtons(FText);
//    end
//    else if FCellType = ctLink then begin
//      FBafButtonList := TBafButtonList.Create(FParents);
//      FBafButtonList.SetButtons('L');
//    end;
  end;
end;

procedure TBafSgCell.SetHasChanged(const Value: boolean);
begin
  if not (Parents.Column.CellNoData or NoData) then begin
    FHasChanged := Value;
    if Value then begin
      FParents.SimpleGrid.HasChanged := true;
      FParents.Row.FHasCanged := true;
    end;
  end;
end;

procedure TBafSgCell.SetSort(APos: TBafGridCellPosition; AOnlyHover: boolean);
var
  LCol: integer;
begin
//  if AOnlyHover then begin
//    if (APos.Position = mpGridSortUp) and (FSortUp = bsNone) then
//      FSortUp := bsHover
//    else if (APos.Position = mpGridSortDown) and (FSortDown = bsNone) then
//      FSortDown := bsHover;
//    FParents.Grid.FSortHoverPos := APos;
//  end
//  else begin   // Sort click
//    if FParents.Grid.FSortClickPos.PosObject is TBafGridCell then
//      TBafGridCell(FParents.Grid.FSortClickPos.PosObject).ClearSort(false);
//    if (APos.Position = mpGridSortUp) then
//      FSortUp := bsPressed
//    else
//      FSortDown := bsPressed;
//    FParents.Grid.FSortClickPos := APos;
//    LCol := FParents.Column.SortColumn;
//    if LCol = -1 then
//      FParents.Column.Sort(APos.Position = mpGridSortUp)
//    else
//      FParents.Segment.Columns.Items[LCol].Sort(APos.Position = mpGridSortUp);
//  end;

end;

procedure TBafSgCell.SetText(const Value: string);
begin
  FText := Value;
//  if Assigned(FBafButtonList) and (CellType <> ctLink) then
//    FBafButtonList.SetButtons(FText);
end;

procedure TBafSgCell.SetTextChange(AText: string; AAlwaysChanged: boolean);
// write text like an edit
var
  LAbort, LChanged: boolean;
  i: integer;
  s: string;
begin
  LAbort := false;
  LChanged := Text <> AText;
//  Parents.Grid.DoCellEvent(false, AText, LAbort, '', LChanged);
  if not LAbort then begin
    if Text <> AText then begin
      case CellType of
        ctCurr, ctCurr4: begin
          for i := 1 to Length(AText) do begin
            if CharInSet(AText[i], ['0'..'9', ',']) then
              s := s + AText[i];
          end;
          AText := s;
        end;
        ctInt: begin
          for i := 1 to Length(AText) do begin
            if CharInSet(AText[i], ['0'..'9']) then
              s := s + AText[i];
          end;
          AText := s;
        end;
        ctBool, ctBool2: AText := IfThen(BafIsYesChar((AText + ' ')[1]), 'Y', 'N');
      end;
      Text := AText;
//      Parents.Segment.RefreshLinkedSegments;
//      Parents.Grid.Categories.RefreshLinkedData;
//      Parents.Grid.RefreshCalcedCells;
    end;
    HasChanged := AAlwaysChanged or LChanged;
//    Parents.Grid.DoCellEvent(true, AText, LAbort, '', LChanged);
  end;
  Parents.Grid.SetStatus;
end;

procedure TBafSgCell.SetTodo(AValue: WideChar);
begin
  if not ReadOnly and not Parents.Column.CellReadOnly then
    SetTextChange(AValue);
end;

procedure TBafSgCell.ToggleBool;
begin
  if not ReadOnly and not Parents.Column.CellReadOnly then
    SetTextChange(IfThen(BafIsYesChar(FText), 'N', 'Y'));
end;

procedure TBafSgCell.ToggleTodo;
begin
  case AnsiUpperCase(FText + ' ')[1] of
    'A': SetTextChange('C');
    'C': SetTextChange('R');
    'R': SetTextChange('');
    else
      SetTextChange('A');
  end;
end;

{ TBafGridUtils }

class function TBafGridUtils.GetButtonText(AChar: Char): string;
begin
  case AChar of
    'A', 'a': result := #$f14a;   // square cheched
    'C', 'c': result := #$f0c5;   // copy
    'D', 'd': result := #$f07c;   // folder open
    'E', 'e': result := #$f06e;   // eye - Aussehen verändern
    'F', 'f': result := #$f022;   // Filter - na ja...
    'H', 'h': result := #$f017;   // clock - history
    'I', 'i': result := #$f0c8;   // square
    'L', 'l': result := #$f14d;   // link
    'O', 'o': result := #$f06e;   // eye
    'P', 'p': result := #$f328;   // clipboard Paste
    'X', 'x': result := #$f1c3;   // Excel
    'Y', 'y': result := #$f1c1;   // PDF
    '+': result := #$f0fe;   // plus-square
    '-': result := #$f146;   // minus-square
    '?': result := #$f059;        // question-circle
  end;
end;

class procedure TBafGridUtils.SetFont(ACan: TCanvas; AFont: TBafFont);
var
  s: string;
begin
  case AFont of
    fnNormal: s := 'Arial';
    fnIcons: s := 'Font Awesome 5 Free Regular';
    fnFixed: s := 'Courier New';
  end;
  if s <> ACan.Font.Family then
    ACan.Font.Family := s;
end;

end.

