unit uBafPage;

// this code is under the BAF fair use license (BFUL) - https://bafbal.de/index.php?title=Bful
// the BAF page

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, Winapi.Windows, uOsStuff,
  System.DateUtils, FMX.TextLayout;

type
  TBafPageKind = (pkPage, pkDashHor, pkDashVert);

  TBafCellColor = (ccNone, ccGreen, ccYellow, ccRed, ccHeader, ccIndex);

  TBafPage = class;
  TBafPagePrimaryDivs = class;
  TBafPagePrimaryDiv = class;
  TBafPageCategories = class;
  TBafPageCategory = class;
  TBafPageSegments = class;
  TBafPageSegment = class;
  TBafSimpleGrid = class;
  TBafSgColumns = class;
  TBafSgColumn = class;
  TBafSgRow = class;
  TBafSgCell = class;
  TBafDiagram = class;
  TBafDiaColumns = class;
  TBafDiaColumn = class;
  TBafMap = class;
  TBafPic = class;

  TBafPageParents = record
    Page: TBafPage;
    PrimaryDivs: TBafPagePrimaryDivs;
    PrimaryDiv: TBafPagePrimaryDiv;
    Categories: TBafPageCategories;
    Category: TBafPageCategory;
    Segments: TBafPageSegments;
    Segment: TBafPageSegment;
    SegButton: TBafButton;
    SimpleGrid: TBafSimpleGrid;
    Columns: TBafSgColumns;
    Column: TBafSgColumn;
    Row: TBafSgRow;
    Cell: TBafSgCell;
    Dia: TBafDiagram;
    DiaColumns: TBafDiaColumns;
    DiaColumn: TBafDiaColumn;
    Map: TBafMap;
    Pic: TBafPic;
    procedure CreateFrom(AParents: TBafPageParents);
  end;

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

  TBafPageEvent = procedure(const Sender: TBafPageParents) of object;

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

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

  TBafPageLinkEvent = procedure(const Sender: TBafPageParents;
      ACommand: string) of object;

  TBafGridFillLookupLiveEvent = procedure(ASender: TObject;
      ALookupHelper: TBafComboHelper; ACell: TBafSgCell) of object;

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

  TBafHeaderButtonClickedEvent = procedure(const Sender: TBafPageParents;
      AButton: Char) of object;

  TBafMapSaveRectEvent = procedure(Sender: TBafPageParents; ARect: TRectF;
      ADefinition: string; AHorzVal, AHorzVP, AVertVal, AVertVP, FStretch: single) of object;

  TBafPageDragDropCellEvent = procedure(ADrag, ADrop: TBafPageParents) of object;


  TBafPageUtils = class
  public
    class procedure SetFont(ACan: TCanvas; AFont: TBafFont);
    class function GetButtonText(AChar: Char; ASegmentType: TBafPageSegmentType): string;
  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: TBafPageParents;
    FDarken: boolean;
    FRowType: TBafGridRowType;
    FRowIndex: integer;
    FDate: TDate;
    FStyle: string;
    FLineP: string;
    FHasCanged: boolean;
    FRowInserted: boolean;
    FDataKeyValue: string;
    FSGridZeile: string;
    FDataItemKey: string;
    FDataIdInTable: string;
    FLinkData: string;
    FLinkDataField: string;
    FJoinList: TStringList;
    FJoinType: TBafJoinType;
    FJoinType2: TBafJoinType;
    FJoinList2: TStringList;
    FCellCommand: string;
    FIsAdditionalRow: boolean;
    function GetCell(ACol: integer): TBafSgCell;
    procedure SetHasCanged(const Value: boolean);
    procedure SetMapCaption(const Value: string);
    procedure SetMapCanCheck(const Value: boolean);
    procedure SetMapCaptionPos(const Value: TBafDiaLegendPos);
    procedure SetMapFontBold(const Value: boolean);
    procedure SetMapFontColor(const Value: TAlphaColor);
    procedure SetMapFontSize(const Value: integer);
    procedure SetMapHint(const Value: string);
    procedure SetMapIsChecked(const Value: boolean);
  protected // maps
    FFontSize: integer;
    FFontBold: boolean;
    FCaption: string;
    FFontColor: TAlphaColor;
    FIsChecked: boolean;
    FCanCheck: boolean;
    FPos: TPoint;
    FCaptionPos: TBafDiaLegendPos;
    FHasChanged: boolean;
    FCells: TStringList;
    FHint: string;
    FCheckboxRect: TRectF;
  public
    class procedure Create2List(ARowType: TBafGridRowType; AList: TObjectList; AParents: TBafPageParents);
    destructor Destroy; override;
    procedure AddCell;
    procedure ClearCalc;
    function GetCellByFieldName(AFieldName: string): TBafSgCell;
    property Parents: TBafPageParents read FParents;
    property Cells[ACol: integer]: TBafSgCell read GetCell;
    property Darken: boolean read FDarken write FDarken;
    property RowType: TBafGridRowType read FRowType write FRowType;
    property RowIndex: integer read FRowIndex write FRowIndex;
    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
    property IsAdditionalRow: boolean read FIsAdditionalRow write FIsAdditionalRow;
  public   // for VL and SGrid data
    property DataKeyValue: string read FDataKeyValue write FDataKeyValue;
    property SGridZeile: string read FSGridZeile write FSGridZeile;
//    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;       // also map month calendar
    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;
  public  // for map
    property Caption: string read FCaption write SetMapCaption;
    property FontSize: integer read FFontSize write SetMapFontSize;
    property FontBold: boolean read FFontBold write SetMapFontBold;
    property FontColor: TAlphaColor read FFontColor write SetMapFontColor;
    property CanCheck: boolean read FCanCheck write SetMapCanCheck;
    property IsChecked: boolean read FIsChecked write SetMapIsChecked;
    property Pos: TPoint read FPos write FPos;
    property CaptionPos: TBafDiaLegendPos read FCaptionPos write SetMapCaptionPos;
    property Hint: string read FHint write SetMapHint;
    property CheckboxRect: TRectF read FCheckboxRect write FCheckboxRect;
  end;

  TBafSgCell = class
  private
    FText: string;
    FCellType: TBafPageCellType;
    FParents: TBafPageParents;
    FHasChanged: boolean;
    FShowSort: boolean;
    FSortDown: TBafListButtonStat;
    FSortUp: TBafListButtonStat;
    FColSpan: integer;           // cause by Colspan Cell will not painted
    FSpanNoPaint: boolean;
    FLookupHelper: TBafComboHelper;
    FLookupCmd: string;
    FAlignment: TBafAlignment;
    FCommand: string;
    FCellColorCommand: string;
    FIsCalced: boolean;
    FReadOnly: boolean;
    FVisible: boolean;
    FDataKey: string;
    FDataKeyColValue: string;
    FCharCase: TEditCharCase;
    FHint: string;
    FDisplayRect: TRectF;
    FCaptionRect: TRectF;
    FMaxLength: integer;
    FDataQuelle: TBafDataQuelle;
    FDataFieldName: string;
    FDataHintFieldName: string;
    FDataQIndex: integer;
    FLinkedSegment: TBafPageSegment;
    FNoData: boolean;
    FChangeCommand: string;
    FPassword: boolean;
    FYFieldName: string;
    FInserted: boolean;
    FLLException: boolean;
    FCharsIgnore: string;
    FNullValueAction: TBafNullValueAction;
    FNullValue: string;
    FCellColor: TBafCellColor;
    FCellAlphaColor: TAlphaColor;
    FCellArp: single;
    FReadOnlyFieldName: string;
    FCharsAllowed: string;
    procedure SetCellType(const Value: TBafPageCellType);
    procedure SetText(const Value: string);
    procedure SetHasChanged(const Value: boolean);
    function ReplaceParameter(AText: string): string;
    function GetYFieldName: string;
    function GetReadOnly: boolean;
  public
    constructor Create(AParents: TBafPageParents);
    destructor Destroy; override;
    procedure ToggleBool;
    procedure ToggleRadio;
    procedure ToggleTodo;
    procedure ToggleTriPlus;
    procedure ToggleTriEx;
    procedure SetBool(AValue: boolean);
    procedure SetTodo(AValue: WideChar);
    procedure ClearSort(AOnlyHover: boolean);
    function GetDisplayText(ANumFormat: boolean = false): string;
    function GetHintDisplayText: string;
    function GetLookupHelper(var ALookupHelper: TBafComboHelper; ALive: boolean): boolean;
    function GetCellType(AHeaderFooter: boolean): TBafPageCellType;
    procedure SetTextChange(AText: string; AAlwaysChanged: boolean = true;
        AChangeWhileLoading: boolean = false; ALookupAsText: boolean = false);
    procedure SetCellColorString(ACellColor: string);
    property Text: string read FText write SetText;
    property Hint: string read FHint write FHint;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property CellType: TBafPageCellType 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 LookupCmd: string read FLookupCmd write FLookupCmd;
    property Alignment: TBafAlignment read FAlignment write FAlignment;
    property Command: string read FCommand write FCommand;
    property CellColorCommand: string read FCellColorCommand write FCellColorCommand;
    property ChangeCommand: string read FChangeCommand write FChangeCommand;
    property IsCalced: boolean read FIsCalced write FIsCalced;
    property ReadOnly: boolean read GetReadOnly write FReadOnly;
    property ReadOnlyFieldName: string
        read FReadOnlyFieldName write FReadOnlyFieldName;
    property Visible: boolean read FVisible write FVisible;
    property Parents: TBafPageParents 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 iin data table
    property DataKeyColValue: string read FDataKeyColValue write FDataKeyColValue;  // value for id in table
    property DataFieldName: string read FDataFieldName write FDataFieldName;
    property DataHintFieldName: string read FDataHintFieldName write FDataHintFieldName;
    property YFieldName: string read GetYFieldName write FYFieldName;
    property CharCase: TEditCharCase read FCharCase write FCharCase;
    property CharsIgnore: string read FCharsIgnore write FCharsIgnore;
    property CharsAllowed: string read FCharsAllowed write FCharsAllowed;
    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: TBafPageSegment read FLinkedSegment write FLinkedSegment; // for VL
    property NoData: boolean read FNoData write FNoData;
    property Password: boolean read FPassword write FPassword;
    property Inserted: boolean read FInserted write FInserted;
    property LLException: boolean read FLLException write FLLException;
    property NullValueAction: TBafNullValueAction
        read FNullValueAction write FNullValueAction;
    property NullValue: string read FNullValue write FNullValue;
    property CellColor: TBafCellColor read FCellColor write FCellColor;
    property CellAlphaColor: TAlphaColor read FCellAlphaColor write FCellAlphaColor;
    property CellArp: single read FCellArp write FCellArp;
  end;

  TBafSgColumn = class(TCollectionItem)
  protected
    FParents: TBafPageParents;
    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;
    FOpenClose: TBafOpenCloseType;
    FCalcedRow: integer;
    FCellCommand: string;
    FCellLoadCommand: string;
    FCellColorCommand: string;
    FXGridIndex, FXXGridIndex: integer;
    FCellAlignment: TBafAlignment;
    FLookupHelper: TBafComboHelper;
    FCellLookupCmd: string;
    FCellType: TBafPageCellType;
    FCellNullValueAction: TBafNullValueAction;
    FCellReadOnly: boolean;
    FDataKeyColField: string;
    FJoinType: TBafJoinType;
    FColumnGroups: string;
    FIsMapSpecCol: boolean;
    FCellChangeCommand: string;
    FCellArp: single;
    procedure SetLinkCommand(const Value: string);
  private
    FScrollRect: TRectF;
    FCellDataQIndex: integer;
    FLinkedSegment: TBafPageSegment;
    FBafSortDirection: TBafSortDirection;
    FCellYFieldName: string;
    FCellCharsIgnore: string;
    FCellCharsAllowed: string;
    FJoinReadOnly: boolean;
    procedure SetCellFieldName(const Value: string);
    function GetCellYFieldName: string;
    procedure SetCellType(const Value: TBafPageCellType);
    function GetStretch: single;
    function GetWidth: single;
  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);
    function GetWidthValue: single;
    property IsMapSpecCol: boolean read FIsMapSpecCol write FIsMapSpecCol;
  published
    property Parents: TBafPageParents read FParents;
    property Width: single read GetWidth write FWidth;               // minimum width
    property Stretch: single read GetStretch 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 OpenClose: TBafOpenCloseType read FOpenClose write FOpenClose;
    property CellType: TBafPageCellType read FCellType write SetCellType;
    property CellMaxLength: integer read FCellMaxLength write FCellMaxLength;
    property CellLookupHelper: TBafComboHelper read FLookupHelper write FLookupHelper;
    property CellLookupCmd: string read FCellLookupCmd write FCellLookupCmd;
    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 CellLoadCommand: string read FCellLoadCommand write FCellLoadCommand;
    property CellColorCommand: string read FCellColorCommand write FCellColorCommand;
    property CellRight: TBafRight read FCellRight write FCellRight;
    property CellFieldName: string read FCellFieldName write SetCellFieldName;
    property CellYFieldName: string read GetCellYFieldName write FCellYFieldName;
    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 CellCharsIgnore: string read FCellCharsIgnore write FCellCharsIgnore;
    property CellCharsAllowed: string read FCellCharsAllowed write FCellCharsAllowed;
    property CellChangeCommand: string read FCellChangeCommand write FCellChangeCommand;
    property CellArp: single read FCellArp write FCellArp;
    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 XXGridIndex: integer read FXXGridIndex write FXXGridIndex;
    property SortColumn: integer read FSortColumn write FSortColumn;
    property SortDirection: TBafSortDirection read FBafSortDirection write FBafSortDirection;
    property ColumnGroups: string read FColumnGroups write FColumnGroups;
  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 JoinReadOnly: boolean read FJoinReadOnly write FJoinReadOnly;
    property JoinType: TBafJoinType read FJoinType write FJoinType;
    property LinkedSegment: TBafPageSegment read FLinkedSegment write FLinkedSegment;
  end;

  TBafSgColumns = class(TCollection)
  private
    FParents: TBafPageParents;
    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;
    FFieldNameList: TStringList;
    procedure Notify(Item: TCollectionItem; Action: TCollectionNotification); override;
  public
    constructor Create(AGrid: TObject);
    destructor Destroy; override;
    function Add: TBafSgColumn;
    function AddColumn(AFieldName, ACaption, AHint: string; AReadOnly, AVisible: boolean;
        AWidth, AStretch: single): TBafSgColumn;
    function AddItem(Item: TBafSgColumn; Index: Integer): TBafSgColumn;
    function Insert(Index: Integer): TBafSgColumn;
    function FieldName2Col(AFieldName: string): TBafSgColumn;
    function FieldName2Ix(AFieldName: string): integer;
    procedure GridHeaderLineBreak;
    procedure ClearJoinLists;
    procedure ClearSort;
    procedure AddSortStack(AIndex: integer; ASortDir: TBafSortDirection);
    property Parents: TBafPageParents 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;
    FGridJson: string;
    FWithoutHorizontalScrollBar: boolean;
    FFilterStatement: string;
    FLoopRow: integer;
    FSelectionColumn: integer;
    FCheckRow: integer;
    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);
    procedure SetFilterStatement(const Value: string);
    function GetRadioRow: integer;
    function GetRow(AType: TBafGridRowType; ARow: integer): TBafSgRow;
    procedure SetWithoutHorizontalScrollBar(const Value: boolean);
  protected  // general
    FParents: TBafPageParents;
    FHorzScrollBar: TScrollBar;
    FVertScrollBar: TScrollBar;
    FPanelLeft: TPanel;
    FPanelDesk: TPanel;
    FSelectedRow, FSelectedCol: integer;
    FKeyChange: boolean; // Change the selection by key
    FHintList: TStringList;
    FDec2, FDec4: single;
    FWidthResizeOld: single;
    FAddCommand: string;
    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;
    FRowCount: integer;
    FColorBack, FColorSelection, FColorReadonly, FColorHeader, FColorFont, FColorEdit,
        FColorGreen, FColorYellow, FColorRed: TAlphaColor;
    FIgnoreNextRow: boolean;
    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;
    procedure DebugColRects;
  protected   // edit
    FEditVisible: TBafClientEditComp;
    FEditCell: TBafSgCell;
    FEditReadOnly: boolean;
    FEdit: TBafEdit;
    FCombo: TBafComboBox;
    FKeyPressText: Char;
    FEditCellType: TBafPageCellType;
    FEditByReturn: boolean;
    FEditCharsIgnore: string;
    FEditCharsAllowed: string;
    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, FMouseDownCell: TBafSgCell;
    FMouseDownPoint: TPointF;
    FColumnSizing: boolean;
    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: TBafPageSegment);
    destructor Destroy; override;
    procedure CalcScrollbars;
    function RowCount(AType: TBafGridRowType = rtData): integer;
    function Mouse2Cell(X, Y: Single; var ARowType: TBafGridRowType;
        var ACol, ARow: integer): boolean;
    function GetSelectedCell: TBafSgCell;
    function InsertRow(APos: integer): TBafSgRow;
    procedure DeleteRow(AIndex: integer);
    procedure HideEdit(ASave: boolean);
    procedure SetRowCount(AType: TBafGridRowType; ACount: integer);
    property Cells[AType: TBafGridRowType; ACol, ARow: integer]: TBafSgCell read GetCell;
    property Row[AType: TBafGridRowType; ARow: integer]: TBafSgRow read GetRow;
    property DataRow[ARow: integer]: TBafSgRow read GetDataRow;
    property DataCells[ACol, ARow: integer]: TBafSgCell read GetDataCells;
    property SelectedCol: integer read FSelectedCol write SetSelectedCol;
    property SelectedRow: integer read FSelectedRow write SetSelectedRow;
    property RadioRow: integer read GetRadioRow;
    property Parents: TBafPageParents read FParents;
    property AddCommand: string read FAddCommand write FAddCommand;
    property IgnoreNextRow: boolean read FIgnoreNextRow write FIgnoreNextRow;
  public
    function GetVlCellText(AFieldName: string): string;
    function GetVlCellIndexFieldName(AQIndex: integer): string;
    procedure RecalcCells;
    property VlInsert: boolean read FVlInsert write FVlInsert;
    property FilterStatement: string read FFilterStatement write SetFilterStatement;
    property LoopRow: integer read FLoopRow write FLoopRow;
    property CheckRow: integer read FCheckRow write FCheckRow;
    property SelectionColumn: integer read FSelectionColumn write FSelectionColumn;
    property HoverCell: TBafSgCell read FHoverCell;
  published
    property Columns: TBafSgColumns read FColumns;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property GridCache: string read FGridCache write FGridCache;
    property GridJson: string read FGridJson write FGridJson;
    property WithoutHorizontalScrollBar: boolean read FWithoutHorizontalScrollBar
        write SetWithoutHorizontalScrollBar;
  end;

  TBafDiaColumn = class(TCollectionItem)
  protected
    FParents: TBafPageParents;
    FCaption: string;
    FFieldName: string;
    FColor: TAlphaColor;
    FParamLine: string;
    FColIx: integer;
    FColumnType: TBafDiaColumnType;
    FAlpha: single;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    property Caption: string read FCaption write FCaption;
    property FieldName: string read FFieldName write FFieldName;
    property Color: TAlphaColor read FColor write FColor;
    property ParamLine: string read FParamLine write FParamLine;
    property ColIx: integer read FColIx write FColIx;
    property ColumnType: TBafDiaColumnType read FColumnType write FColumnType;
    property Alpha: single read FAlpha write FALpha;
  end;

  TBafDiaColumns = class(TCollection)
  private
    FParents: TBafPageParents;
    function GetItem(Index: Integer): TBafDiaColumn;
    procedure SetItem(Index: Integer; const Value: TBafDiaColumn);
  public
    constructor Create(ADia: TBafDiagram);
    destructor Destroy; override;
    function Add: TBafDiaColumn;
    function AddColumn(AFieldName, ACaption: string; AColor: TAlphaColor): TBafDiaColumn;
    function AddItem(Item: TBafDiaColumn; Index: Integer): TBafDiaColumn;
    function Insert(Index: Integer): TBafDiaColumn;
    property Parents: TBafPageParents read FParents;
    property Items[Index: Integer]: TBafDiaColumn read GetItem write SetItem;
  end;

  TBafDiaRow = class
    Cells: array of double;
  end;

  TBafDiagram = class(TPanel)
  private
    FScrollHorzVisible: boolean;
    FScrollVertVisible: boolean;
    function GetLegendPosName: string;
    procedure SetLegendPosName(const Value: string);
    function GetDiaTypeName: string;
    procedure SetDiaTypeName(const Value: string);
    procedure SetScrollHorzVisible(const Value: boolean);
    procedure SetScrollVertVisible(const Value: boolean);
  protected  // general
    FParents: TBafPageParents;
    FHorzScrollBar: TScrollBar;
    FVertScrollBar: TScrollBar;
    FPanelLeft: TPanel;
    FPanelDesk: TPanel;
    FColumns: TBafDiaColumns;
    FLegendPos: TBafDiaLegendPos;
    FDiaType: TBafGridDiaType;
    FRectDia: TRectF;
    FFontColor: TAlphaColor;
    FFontHeight: single;
    procedure CreateComponents;
    procedure DiaPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure ScrollChangeVert(Sender: TObject);
    procedure ScrollChangeHorz(Sender: TObject);
    procedure Resize; override;
    procedure LoadStyle;
  protected
    FLinkedDiagram: TBafDiagram;
    procedure LinkValues(ASource: TBafDiagram);
  protected // PaintData
    procedure PaintLine(Canvas: TCanvas);
    procedure PaintBar(Canvas: TCanvas);
    procedure PaintSbs(Canvas: TCanvas);
    procedure PaintStack(Canvas: TCanvas);
  protected // legend
    FDiaLeft, FLinkedDiaDiaLeft, FLegendHeight: single;
    FScaleVert, FScaleHorz: double;
    FMouseDownX, FMouseDownY: single;
    FScrollVertOld, FScrollHorzOld: single;
    FColumnWidth, FLinkedDiaColumnWidth: single;
    FHasMouse: boolean;
    FMouseX, FMouseY: single;
    procedure PaintLegend(Canvas: TCanvas; const ARect: TRectF);
    procedure PaintAxlesVert(Canvas: TCanvas);
    procedure PaintAxlesHorz(Canvas: TCanvas);
    function CalcY(AValue: double): single;
    function CalcYVal(APos: single): double;
    function CalcXDate(AValue: TDateTime): single;
    function CalcXDateVal(APos: single): TDateTime;
    procedure PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskDblClick(Sender: TObject);
    procedure PanelMouseEnter(Sender: TObject);
    procedure PanelMouseLeave(Sender: TObject);
    procedure PanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
  protected // Cells
    FRows: TStringList;
    FMinValue, FMaxValue, FMinValueZoom, FMaxValueZoom: double;
    FMinDate, FMaxDate, FMinDateZoom, FMaxDateZoom: TDateTime;
    function GetCells(ACol, ARow: integer): double;
    procedure SetCells(ACol, ARow: integer; const Value: double);
  public
    constructor Create(ASeg: TBafPageSegment);
    destructor Destroy; override;
    procedure Prepare;
    procedure Zoom0;
    function AddRow(ACol0: string): integer;
    function AddRowSl(ACol0: string): TBafDiaRow;
    property Columns: TBafDiaColumns read FColumns;
    property LegendPos: TBafDiaLegendPos read FLegendPos write FLegendPos;
    property LegendPosName: string read GetLegendPosName write SetLegendPosName;
    property DiaType: TBafGridDiaType read FDiaType write FDiaType;
    property DiaTypeName: string read GetDiaTypeName write SetDiaTypeName;
    property Rows: TStringList read FRows;
    property Cells[ACol, ARow: integer]: double read GetCells write SetCells;
    property ScrollVertVisible: boolean read FScrollVertVisible write SetScrollVertVisible;
    property ScrollHorzVisible: boolean read FScrollHorzVisible write SetScrollHorzVisible;
    property JoinDiagram: TBafDiagram read FLinkedDiagram write FLinkedDiagram;
  end; // TBafDiagram


  TBafMap = class(TPanel)
  private
    FScrollHorzVisible: boolean;
    FScrollVertVisible: boolean;
    FPictureFileName: string;
    FHasChanged: boolean;
    FField_Caption: string;
    FField_FontSize: string;
    FField_FontBold: string;
    FField_FontColor: string;
    FField_CanCheck: string;
    FField_IsChecked: string;
    FField_PosX: string;
    FField_PosY: string;
    FField_CaptionPos: string;
    FField_Hint: string;
    FDefinition: string;
    FClickRow: integer;
    FOnSaveRect: TBafMapSaveRectEvent;
    FRectLoaded: boolean;
    procedure SetScrollHorzVisible(const Value: boolean);
    procedure SetScrollVertVisible(const Value: boolean);
    procedure SetPictureFileName(const Value: string);
    function GetDisplayRow(ARow: integer): TBafSgRow;
    procedure SetHasChanged(const Value: boolean);
    function GetDataRowCount: integer;
    function GetMapType: string;
    procedure SetMapType(const Value: string);
    function GetMonthColors(ACol, ARow: integer): TAlphaColor;
    procedure SetMonthColors(ACol, ARow: integer; const Value: TAlphaColor);
  protected  // general
    FParents: TBafPageParents;
    FHorzScrollBar: TScrollBar;
    FVertScrollBar: TScrollBar;
    FPanelLeft: TPanel;
    FPanelDesk: TPanel;
    FMouseDownX, FMouseDownY: single;
    FScrollVertOld, FScrollHorzOld: single;
    FMapType: TBafMapType;
    procedure CreateComponents;
    procedure MapPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure ScrollChangeVert(Sender: TObject);
    procedure ScrollChangeHorz(Sender: TObject);
    procedure Resize; override;
    function CalcY(AValue: integer): single;
    function CalcYVal(APos: single): integer;
    function CalcX(AValue: integer): single;
    function CalcXVal(APos: single): integer;
    procedure PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskDblClick(Sender: TObject);
    procedure PanelMouseEnter(Sender: TObject);
    procedure PanelMouseLeave(Sender: TObject);
    procedure PanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
  protected // Bmp
    FBmpBack, FBmpPaint: FMX.Graphics.TBitmap;
    FSrcRect, FDestRect: TRectF;
    FStretch: single;
    FFontSizeScale: single;
    FMonthCalendarSize: single;
    FMonthColors: array[0..6, 0..1] of TAlphaColor;
    procedure InitMonthColors;
  protected // Cols and Rows
    FColumns: TBafSgColumns;
    FDataRowList, FDataRowFilteredList, FDataRowDisplayList: TObjectList;
    FHeaderRowList: TObjectList;
    FHintRow: integer;
    function GetCell(AType: TBafGridRowType; ACol, ARow: integer): TBafSgCell;
    function GetDataRow(ARow: integer): TBafSgRow;
    function Mouse2Row(X, Y: single; var ARow: integer): boolean;
    procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer;
        var Handled: Boolean); override;
  public
    constructor Create(ASeg: TBafPageSegment);
    destructor Destroy; override;
    function RowCount(AType: TBafGridRowType = rtData): integer;
    procedure MapRefresh;
    procedure Zoom0;
    procedure ZoomInOut(APercent: single);
    procedure SetDestRect(ALeft, ATop, ARight, ABottom, AHorzVal, AHorzVP,
        AVertVal, AVertVP, AStretch: single);
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property ScrollVertVisible: boolean read FScrollVertVisible write SetScrollVertVisible;
    property ScrollHorzVisible: boolean read FScrollHorzVisible write SetScrollHorzVisible;
    property PictureFileName: string read FPictureFileName write SetPictureFileName;
    property Columns: TBafSgColumns read FColumns;
    property Cells[AType: TBafGridRowType; ACol, ARow: integer]: TBafSgCell read GetCell;
    property FontSizeScale: single read FFontSizeScale write FFontSizeScale;
    property MonthCalendarSize: single read FMonthCalendarSize write FMonthCalendarSize;
    property DataRow[ARow: integer]: TBafSgRow read GetDataRow;
    property DisplayRow[ARow: integer]: TBafSgRow read GetDisplayRow;
    property DataRowCount: integer read GetDataRowCount;
    property MapType: TBafMapType read FMapType write FMapType;
    property MapTypeName: string read GetMapType write SetMapType;
    property MonthColors[ACol, ARow: integer]: TAlphaColor
        read GetMonthColors write SetMonthColors;
    property Definition: string read FDefinition write FDefinition;
    property ClickRow: integer read FClickRow;
  public
    property Field_Caption: string read FField_Caption write FField_Caption;
    property Field_FontSize: string read FField_FontSize write FField_FontSize;
    property Field_FontBold: string read FField_FontBold write FField_FontBold;
    property Field_FontColor: string read FField_FontColor write FField_FontColor;
    property Field_CanCheck: string read FField_CanCheck write FField_CanCheck;
    property Field_IsChecked: string read FField_IsChecked write FField_IsChecked;
    property Field_PosX: string read FField_PosX write FField_PosX;
    property Field_PosY: string read FField_PosY write FField_PosY;
    property Field_CaptionPos: string read FField_CaptionPos write FField_CaptionPos;
    property Field_Hint: string read FField_Hint write FField_Hint;
    property OnSaveRect: TBafMapSaveRectEvent read FOnSaveRect write FOnSaveRect;
  end; // TBafMap

  TBafPic = class(TPanel)
  private
    FScrollHorzVisible: boolean;
    FScrollVertVisible: boolean;
    FDataQuelle: TBafDataQuelle;
    FPictureFileName: string;
    FDefinition: string;
    FOnSaveRect: TBafMapSaveRectEvent;
    procedure SetScrollHorzVisible(const Value: boolean);
    procedure SetScrollVertVisible(const Value: boolean);
    procedure SetPictureFileName(const Value: string);
  protected  // general
    FParents: TBafPageParents;
    FHorzScrollBar: TScrollBar;
    FVertScrollBar: TScrollBar;
    FPanelLeft: TPanel;
    FPanelDesk: TPanel;
    FBmpPaint: FMX.Graphics.TBitmap;
    FSrcRect, FDestRect: TRectF;
    FMouseDownX, FMouseDownY: single;
    FScrollVertOld, FScrollHorzOld: single;
    FStretch: single;
    FRectLoaded: boolean;
    function CalcY(AValue: integer): single;
    function CalcYVal(APos: single): integer;
    function CalcX(AValue: integer): single;
    function CalcXVal(APos: single): integer;
    procedure CreateComponents;
    procedure ScrollChangeVert(Sender: TObject);
    procedure ScrollChangeHorz(Sender: TObject);
    procedure PicPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskDblClick(Sender: TObject);
    procedure Resize; override;

  public
    constructor Create(ASeg: TBafPageSegment);
    destructor Destroy; override;
    procedure Zoom0;
    procedure LoadFromHttp(AUrl: string; AIgnoreServerCertificate: boolean;
        AInter: TObject);
    procedure SetDestRect(ALeft, ATop, ARight, ABottom, AHorzVal, AHorzVP,
        AVertVal, AVertVP, AStretch: single);
    property ScrollVertVisible: boolean read FScrollVertVisible write SetScrollVertVisible;
    property ScrollHorzVisible: boolean read FScrollHorzVisible write SetScrollHorzVisible;
    property DataQuelle: TBafDataQuelle read FDataQuelle write FDataQuelle;
    property PictureFileName: string read FPictureFileName write SetPictureFileName;
    property Definition: string read FDefinition write FDefinition;
    property OnSaveRect: TBafMapSaveRectEvent read FOnSaveRect write FOnSaveRect;
  end; // TBafPic


  TBafPageSegment = class(TCollectionItem)
  protected  // general
    FParents: TBafPageParents;
    FSegmentType: TBafPageSegmentType;
    FPanel: TBafPanel;
    FTopAdd: integer;
    FCalcedHeight: single;
    procedure SetSegmentType(const Value: TBafPageSegmentType);
    procedure SegmentCalcHeight(AHeight: single);
    function SegmentCalcHeightOpenedClosed(AHeight: single): 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;
    FWordWrap: boolean;
    procedure CreateMemo;
    procedure MemoChange(Sender: TObject);
    procedure MemoKeyDown(Sender: TObject; var Key: Word;
      var KeyChar: Char; Shift: TShiftState);
    procedure SetMemoTextUnchanged(AText: string);
    procedure SetPic(AFilename: string);
  protected // buttons
    FButtonDock: TBafButtonDock;
    procedure ButtonDockHeightChange(Sender: TObject);
    procedure CreateButtonDock;
    procedure ButtonClick(Sender: TObject);
  protected // grid, dia, map, pic
    FGrid: TBafSimpleGrid;
    FDia: TBafDiagram;
    FMap: TBafMap;
    FMapStartDate: TDate;
    FPic: TBafPic;
    procedure CreateSimpleGrid;
    procedure CreateDia;
    procedure CreateMap;
    procedure CreatePic;
  private
    FSegmentName: string;
    FSegRight: TBafRight;
    FLineP: string;
    FDataLineP: string;
    FOpenClose: TBafOpenCloseType;
    FMaxHeightClosed: Single;
    FMaxHeightOpened: Single;
    FHeightClosed: Single;
    FHeightOpened: Single;
    FReadOnly: boolean;
    FHasChanged: boolean;
    FDataKeyValue: string;
    FDataQuelle: TBafDataQuelle;
    FDataRef: string;
    FDataItem: string;
    FDataTable: TStringList;
    FDataKey: TStringList;
    FDataConnection: TStringList;
    FLinkedCell: TBafSgCell;
    FAutoSizeClosed: boolean;
    FAutoSizeOpened: boolean;
    FBafConName: string;
    FXlsCommand: string;
    FPdfCommand: string;
    FIsDataLoading: boolean;
    FCountRow: integer;
//    FFilterColumnsRow: integer;
    FHistoryNoShowFields: TStringList;
    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);
    procedure SetSegRight(const Value: TBafRight);
    procedure SetWordWrap(const Value: boolean);
    function GetHistoryNoShowFields: TStrings;
    function GetBafConName(AIndex: integer): string;
    procedure SetBafConName(AIndex: integer; const Value: string);
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure RefreshHeight;
    procedure SetColumnsOpenClose(AOpen: boolean);
    property SegmentType: TBafPageSegmentType
        read FSegmentType write SetSegmentType;
    property LineP: string read FLineP write FLineP;
    property DataLineP: string read FDataLineP write FDataLineP;
    property HasChanged: boolean read FHasChanged write SetHasChanged;
    property IsDataLoading: boolean read FIsDataLoading write FIsDataLoading;
    property Parents: TBafPageParents read FParents;
  public // text, memo, buttons
    procedure AddButton(ACaption, AHint, ALineP, AStatusEnabled: string;
        AWidth: integer; AReadOnly, ANewLine: boolean; ARight: TBafRight);
    procedure AddLine(AText: string);
    procedure AddLineUnchanged(AText: string);
    procedure LinesRefresh;
    function MemoContentHeight: single;
    property Lines: TStrings read GetLines;
    property LinkedCell: TBafSgCell read FLinkedCell write FLinkedCell;
    property WordWrap: boolean read FWordWrap write SetWordWrap;
  public // grid, dia, map
    procedure GridCalcHeight;
    procedure GridSellectAll(ASelect: boolean);
    property Grid: TBafSimpleGrid read FGrid;
    property Dia: TBafDiagram read FDia;
    property Map: TBafMap read FMap;
    property Pic: TBafPic read FPic;
    property MapStartDate: TDate read FMapStartDate write FMapStartDate;
    property CountRow: integer read FCountRow write FCountRow;
//    property FilterColumnsRow: integer read FFilterColumnsRow write FFilterColumnsRow;
  public // data
    function GetDataTableCount: integer;
    property DataTable[AIndex: integer]: string read GetDataTable write SetDataTable;
    property DataKey[AIndex: integer]: string read GetDataKey write SetDataKey;
    property BafConName[AIndex: integer]: string read GetBafConName write SetBafConName;
    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;
    property HistoryNoShowFields: TStrings read GetHistoryNoShowFields;
  published
    property SegmentName: string read FSegmentName write FSegmentName;
    property SegRight: TBafRight read FSegRight write SetSegRight;
    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 HeightOpened: Single read FHeightOpened write FHeightOpened;
    property HeightClosed: Single read FHeightClosed write FHeightClosed;
    property AutoSizeOpened: boolean read FAutoSizeOpened write FAutoSizeOpened;
    property AutoSizeClosed: boolean read FAutoSizeClosed write FAutoSizeClosed;
    property ReadOnly: boolean read FReadOnly write SetReadOnly;
    property PdfCommand: string read FPdfCommand write FPdfCommand;
    property XlsCommand: string read FXlsCommand write FXlsCommand;
  end;

  TBafPageSegments = class(TCollection)
  private
    FParents: TBafPageParents;

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



  TBafPageCategory = class(TCollectionItem)
  private
    FParents: TBafPageParents;
    FIsVertical: boolean;
    FPanel: TBafPanel;
    FSplitter: TSplitter;
    FAutoSize: boolean;
    FSize: Single;
    FHasPlus: boolean;
    FLineP: string;
    FCaption: string;
    FOpened: boolean;
    FOpenCloseIni: string;
    FSegments: TBafPageSegments;
    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);
    procedure PanelResize(Sender: TObject);
  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: TBafPageParents 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 OpenCloseIni: string read FOpenCloseIni write FOpenCloseIni;
    property Segments: TBafPageSegments read FSegments;
  end;

  TBafPageCategories = class(TCollection)
  private
    FParents: TBafPageParents;
    FDoAutoSize: boolean;

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





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

  TBafPagePrimaryDivs = class(TCollection)
  private
    FParents: TBafPageParents;

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

  TBafPage = class(TBafVertScrollBox)
  private
    FPageKind: TBafPageKind;
    FPrimaryDivs: TBafPagePrimaryDivs;
    FOnSegButtonClick: TBafPageEvent;
    FBeforeEdit: TBafPageEditEvent;
    FAfterEdit: TBafPageEditEvent;
    FOnStatus: TBafPageStatusEvent;
    FGridStatus: TBafGridStatus;
    FCheckList: TStringList;
    FOnCellCalc: TBafPageEditEvent;
    FOnLinkClick: TBafPageLinkEvent;
    FLinkValue: string;
    FLinkParents: TBafPageParents;
    FLinkCommand: string;
    FOnHintMouseEnter: TNotifyEvent;
    FOnHintMouseLeave: TNotifyEvent;
    FOnFillLookupLiveEvent: TBafGridFillLookupLiveEvent;
    FOnHeaderButtonClickedEvent: TBafHeaderButtonClickedEvent;
    FOnCellButton: TBafPageEvent;
    FOnPageDragDropCell: TBafPageDragDropCellEvent;
    FOnSelectCell: TBafPageEvent;
    FOnResizeCell: TBafPageEvent;
    FOnOpenClose: TBafPageEvent;
    function SegmentBrowse(ASegment: TBafPageSegment): boolean;
    function SegmentNoChange(ASegment: TBafPageSegment): boolean;
    function SegmentSaveGridCache(ASegment: TBafPageSegment): boolean;
    function SegmentRecalcCells(ASegment: TBafPageSegment): boolean;
    function InternButtons(ASegment: TBafPageSegment): boolean;
    function SegmentResize(ASegment: TBafPageSegment): boolean;
    function SegmentHideEdit(ASegment: TBafPageSegment): boolean;
    function SegmentRefreshOpenClose(ASegment: TBafPageSegment): boolean;
  protected
    FTimer: TTimer;
    FTimerTaskList: TStringList;
    FLastCatOpenClose: TBafPageCategory;
    FPageButtonList: TObjectList;
    procedure StartTimer(ATask: string);
    procedure FTimerTimer(Sender: TObject);
    procedure VScrollChange; override;
    procedure HintMouseEnter(Sender: TObject);
    procedure HintMouseLeave(Sender: TObject);
    procedure PagePaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    class function Place(AOwner: TComponent; AParent: TControl): TBafPage;
    procedure Resize; override;
    procedure Clear;
    procedure SegButtonClick(const AParents: TBafPageParents);
    procedure LinkClick(AParents: TBafPageParents; ACommand, ALinkValue: string);
    procedure DoEditEvent(ASender: TBafPageParents; AAfter: boolean;
        var AText: string; var AAbort: boolean; ACommand: string;
        AChanged: boolean);
    procedure SetStatus;
    procedure SaveGridCache;
    procedure RecalcCells;
    procedure RefreshOpenClose;
    procedure SetAutoSize;
    procedure HideAllEdits;
    function AllSegFunction(AFunc: TAllSegFunction): boolean;
    function GetDebugInfos: string;
    property CheckList: TStringList read FCheckList;
    property LinkValue: string read FLinkValue;
    property PageButtonList: TObjectList read FPageButtonList;
  published
    property PageKind: TBafPageKind read FPageKind write FPageKind;
    property PrimaryDivs: TBafPagePrimaryDivs read FPrimaryDivs;
  published  // Events
    property OnSegButtonClick: TBafPageEvent
      read FOnSegButtonClick write FOnSegButtonClick;
    property OnLinkClick: TBafPageLinkEvent read FOnLinkClick write FOnLinkClick;
    property BeforeEdit: TBafPageEditEvent read FBeforeEdit write FBeforeEdit;
    property AfterEdit: TBafPageEditEvent read FAfterEdit write FAfterEdit;
    property OnCellCalc: TBafPageEditEvent read FOnCellCalc write FOnCellCalc;
    property OnStatus: TBafPageStatusEvent read FOnStatus write FOnStatus;
    property OnHintMouseEnter: TNotifyEvent
      read FOnHintMouseEnter write FOnHintMouseEnter;
    property OnHintMouseLeave: TNotifyEvent
      read FOnHintMouseLeave write FOnHintMouseLeave;
    property OnFillLookupLiveEvent: TBafGridFillLookupLiveEvent
      read FOnFillLookupLiveEvent write FOnFillLookupLiveEvent;
    property OnHeaderButtonClickedEvent: TBafHeaderButtonClickedEvent
      read FOnHeaderButtonClickedEvent write FOnHeaderButtonClickedEvent;
    property OnCellButton: TBafPageEvent read FOnCellButton write FOnCellButton;
    property OnPageDragDropCell: TBafPageDragDropCellEvent
      read FOnPageDragDropCell write FOnPageDragDropCell;
    property OnSelectCell: TBafPageEvent read FOnSelectCell write FOnSelectCell;
    property OnResizeCell: TBafPageEvent read FOnResizeCell write FOnResizeCell;
    property OnOpenClose: TBafPageEvent read FOnOpenClose write FOnOpenClose;
  end;



implementation

uses foBafHistory, uBafDataCache, foBafGridFilter, uBafWebModule, foBafDbDebug;

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: TBafPageSegment;
  LCategory: TBafPageCategory;
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;
  PageButtonList.Clear;
end;

constructor TBafPage.Create(AOwner: TComponent);
begin
  inherited;
  FPrimaryDivs := TBafPagePrimaryDivs.Create(Self);
  Tag := 1337;
  FTimer := TTimer.Create(Self);
  FTimer.Enabled := false;
  FTimer.Interval := 20;
  FTimer.OnTimer := FTimerTimer;
  FTimerTaskList := TStringList.Create;
  FCheckList := TStringList.Create;
  FCheckList.OwnsObjects := true;
  FPageButtonList := TObjectList.Create(false);
  Application.ShowHint := false;
end;

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

procedure TBafPage.DoEditEvent(ASender: TBafPageParents; 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.Resize;
var
  i: integer;
begin
  inherited;
  case FPageKind of
    pkPage: begin
      AllSegFunction(SegmentResize);
      for i := 0 to FPrimaryDivs.Count - 1 do
        FPrimaryDivs.Items[i].CalcHeight;
    end;
    pkDashHor: begin
      FPrimaryDivs.ResizeHorz;
      AllSegFunction(SegmentResize);
    end;

  end;

end;

procedure TBafPage.FTimerTimer(Sender: TObject);
var
  s: string;
  i: integer;
begin
  if FTimerTaskList.Count = 0 then begin
    FTimer.Enabled := false;
    exit;
  end;
  s := FTimerTaskList[0];
  FTimerTaskList.Delete(0);
  if s = 'repaint' then
    Repaint
  else if s = 'dia_zoom0' then begin
    if Assigned(FLastCatOpenClose.Segments) then begin
      for i := 0 to FLastCatOpenClose.Segments.Count - 1 do begin
        if Assigned(FLastCatOpenClose.Segments.Items[i].Dia) then
          FLastCatOpenClose.Segments.Items[i].Dia.PanelDeskDblClick(nil);
      end;
    end;
  end
  else if s = 'link' then begin
    if Assigned(FOnLinkClick) then
      FOnLinkClick(FLinkParents, FLinkCommand);
  end;
end;

function TBafPage.GetDebugInfos: string;
var
  sl: TStringList;
  LPrim, LCat, LSeg, LCol: integer;
  LPrimaryDiv: TBafPagePrimaryDiv;
  LPageCategory: TBafPageCategory;
  LPageSegment: TBafPageSegment;
  LColumn: TBafSgColumn;
begin
  sl := TStringList.Create;
  try
    sl.Add(Format('Prim Count: %d', [FPrimaryDivs.Count]));
    for LPrim := 0 to FPrimaryDivs.Count - 1 do begin
      sl.Add(Format('Prim: %d', [LPrim]));
      LPrimaryDiv := FPrimaryDivs.Items[LPrim];
      sl.Add(Format('Size: %8.2f', [LPrimaryDiv.Size]));
      sl.Add(Format('Cat Count: %d', [LPrimaryDiv.Categories.Count]));
      for LCat := 0 to LPrimaryDiv.Categories.Count - 1 do begin
        sl.Add(Format('Cat: %d', [LPrim]));
        LPageCategory := LPrimaryDiv.Categories.Items[LCat];
        sl.Add(Format('Size: %8.2f', [LPageCategory.Size]));
        sl.Add(Format('Seg Count: %d', [LPageCategory.Segments.Count]));
        for LSeg := 0 to LPageCategory.Segments.Count - 1 do begin
          sl.Add(Format('Seg: %d', [LSeg]));
          LPageSegment := LPageCategory.Segments.Items[LSeg];
          sl.Add(Format('CalcedHeight: %8.2f', [LPageSegment.FCalcedHeight]));
          if LPageSegment.SegmentType in [stGrid, stXGrid, stXXGrid, stValueList] then begin
            sl.Add(Format('FPanelDesk.Height: %8.2f', [LPageSegment.Grid.FPanelDesk.Height]));
            for LCol := 0 to LPageSegment.Grid.Columns.Count - 1 do begin
              sl.Add(Format('Col: %d', [LCol]));
              LColumn := LPageSegment.Grid.Columns.Items[LCol];
              sl.Add(Format('Fieldname: %s', [LColumn.CellFieldName]));
              sl.Add(Format('CalcedRect.Left: %8.2f', [LColumn.CalcedRect.Left]));
              sl.Add(Format('CalcedRect.Width: %8.2f', [LColumn.CalcedRect.Width]));
              sl.Add(Format('CalcedRect.Top: %8.2f', [LColumn.CalcedRect.Top]));
              sl.Add(Format('CalcedRect.Height: %8.2f', [LColumn.CalcedRect.Height]));



            end;
          end;
        end;
      end;
    end;
  finally
    result := sl.Text;
    sl.Free;
  end;
//   sl.Add(Format('', []));
end;

procedure TBafPage.HideAllEdits;
begin
  AllSegFunction(SegmentHideEdit)
end;

procedure TBafPage.HintMouseEnter(Sender: TObject);
begin
  if Assigned(FOnHintMouseEnter) then
    FOnHintMouseEnter(Sender);
end;

procedure TBafPage.HintMouseLeave(Sender: TObject);
begin
  if Assigned(FOnHintMouseLeave) then
    FOnHintMouseLeave(Sender);
end;

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

end;

procedure TBafPage.LinkClick(AParents: TBafPageParents;
    ACommand, ALinkValue: string);
begin
  FLinkValue := ALinkValue;
  FLinkParents := AParents;
  FLinkCommand := ACommand;
  StartTimer('link');
end;

procedure TBafPage.PagePaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
begin

end;

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

end;

procedure TBafPage.RecalcCells;
begin
  AllSegFunction(SegmentRecalcCells);
end;

procedure TBafPage.RefreshOpenClose;
begin
  AllSegFunction(SegmentRefreshOpenClose);
end;

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

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

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

function TBafPage.SegmentHideEdit(ASegment: TBafPageSegment): boolean;
begin
  if Assigned(ASegment.Grid) then
    ASegment.Grid.HideEdit(true);
end;

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

function TBafPage.SegmentRecalcCells(ASegment: TBafPageSegment): boolean;
begin
  if Assigned(ASegment.Grid) then
    ASegment.Grid.RecalcCells;
end;

function TBafPage.SegmentRefreshOpenClose(ASegment: TBafPageSegment): boolean;
begin
  ASegment.SetColumnsOpenClose(ASegment.FParents.Category.Opened);
  result := true;
end;

function TBafPage.SegmentResize(ASegment: TBafPageSegment): boolean;
begin
  ASegment.RefreshHeight;
end;

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

procedure TBafPage.SetAutoSize;
var
  LDiv: integer;
begin
  for LDiv := 0 to PrimaryDivs.Count - 1 do begin
    PrimaryDivs.Items[LDiv].Categories.FDoAutoSize := true;
    PrimaryDivs.Items[LDiv].PanelResize(nil);
  end;
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 TBafPageCellPosition.Clear;
begin
  Prim := -1;
  Cat := -1;
  Seg := -1;
  Col := -1;
  Row := -1;
  SegmentType := stNone;
  Button := -1;
  Position := mpNone;
  PosObject := nil;
end;

class operator TBafPageCellPosition.Equal(a, b: TBafPageCellPosition): 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 TBafPageCellPosition.GetXY(AX, AY: integer): TBafPageCellPosition;
begin
  X := AX;
  Y := AY;
  result := Self;
end;

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

function TBafPageCellPosition.SetSegment(ASeg: integer;
  APosition: TBafPageMousePosition; APosObject: TObject;
  ASegmentType: TBafPageSegmentType): boolean;
begin
  result := true;
  Seg := ASeg;
  Position := APosition;
  PosObject := APosObject;
  SegmentType := ASegmentType;
end;

{ TBafGridPrimaryDivs }

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

function TBafPagePrimaryDivs.AddItem(Item: TBafPagePrimaryDiv;
  Index: Integer): TBafPagePrimaryDiv;
begin
  if Item = nil then
    Result := TBafPagePrimaryDiv.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 TBafPagePrimaryDivs.Create(AGrid: TBafPage);
begin
  inherited Create(TBafPagePrimaryDiv);
  FParents.Page := AGrid;
  FParents.PrimaryDivs := Self;
end;

destructor TBafPagePrimaryDivs.Destroy;
begin

  inherited;
end;

function TBafPagePrimaryDivs.FindSegmentByName(
    ASegmentName: string): TBafPageSegment;
var
  LPrim, LCat, LSeg: integer;
  LCategory: TBafPageCategory;
  LSegment: TBafPageSegment;
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 TBafPagePrimaryDivs.GetItem(Index: Integer): TBafPagePrimaryDiv;
begin
  Result := TBafPagePrimaryDiv(inherited GetItem(Index));
end;

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

function TBafPagePrimaryDivs.Mouse2Position(AX, AY: integer;
  var APos: TBafPageCellPosition): boolean;
begin

end;

procedure TBafPagePrimaryDivs.ResizeHorz;
var
  i, LCountAuto, LCount: integer;
  LHeightFixed, LHeightAuto, LHeightAutoAll, LHeightFixedAll: Single;
begin
  LCount := 0;
  LCountAuto := 0;
  LHeightFixed := 0;
  LHeightFixedAll := 0;
  for i := 0 to Count - 1 do begin
    inc(LCount);
    if Items[i].AutoSize then
      inc(LCountAuto)
    else
      LHeightFixed := LHeightFixed + Items[i].Size;
    LHeightFixedAll := LHeightFixedAll + Items[i].Size;
  end;
  LHeightAutoAll := FParents.Page.Height - LHeightFixed;
  LHeightAuto := LHeightAutoAll / LCountAuto;
  for i := 0 to Count - 1 do begin
    if LCount =  LCountAuto then
      Items[i].Size := Items[i].Size * LHeightAutoAll / LHeightFixedAll
    else if Items[i].AutoSize then
      Items[i].Size := LHeightAuto;
  end;
end;

procedure TBafPagePrimaryDivs.SetItem(Index: Integer;
  const Value: TBafPagePrimaryDiv);
begin
  inherited SetItem(Index, Value);
end;

{ TBafGridParents }

procedure TBafPageParents.CreateFrom(AParents: TBafPageParents);
begin
  Page := AParents.Page;
  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;
  Dia := AParents.Dia;
  DiaColumns := AParents.DiaColumns;
  DiaColumn := AParents.DiaColumn;
  Map := AParents.Map;
  Pic := AParents.Pic;
end;

{ TBafGridPrimaryDiv }

procedure TBafPagePrimaryDiv.CalcHeight;
var
  i: integer;
  LHeight: single;
begin
  if FAutoSize and (FParents.Page.PageKind = pkPage) 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 TBafPagePrimaryDiv.Create(Collection: TCollection);
begin
  inherited;
  FParents.CreateFrom((Collection as TBafPagePrimaryDivs).FParents);
  FParents.PrimaryDiv := Self;
  FIsVertical := FParents.Page.PageKind in [pkDashVert];
  if FIsVertical then begin
    FPanel := TBafPanel.Place(FParents.Page, TAlignLayout.Left);
  end
  else begin
    FPanel := TBafPanel.Place(FParents.Page, TAlignLayout.Top);
    FPanel.Margins.Right := 2;
    FPanel.Height := 300;
    FPanel.StyleLookup := 'pushpanel';
  end;
  FPanel.OnResize := PanelResize;
  FCategories := TBafPageCategories.Create(Self);
end;

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

  inherited;
end;

procedure TBafPagePrimaryDiv.PanelResize(Sender: TObject);
begin
  FCategories.SetAutoSize;
end;

procedure TBafPagePrimaryDiv.SetSize(const Value: Single);
begin
  if FSize <> Value then begin
    FSize := Value;
    if FIsVertical then
      FPanel.Width := Value
    else
      FPanel.Height := Value;
    FCategories.SetAutoSize;
  end;
end;

{ TBafGridCategory }

function TBafPageCategory.CalcHeight: Single;
var
  i: integer;
  LSeg: TBafPageSegment;
begin
  result := FHeader.Height + 8;
  for i := 0 to Segments.Count - 1 do begin
    if Segments.Items[i].FPanel.Visible then begin
      LSeg := Segments.Items[i];
      if LSeg.FSegmentType in [stDia, stMap] then begin
        if LSeg.FParents.Category.Opened then
          LSeg.FPanel.Height := LSeg.MaxHeightOpened
        else
          LSeg.FPanel.Height := LSeg.MaxHeightClosed;
      end;
      result := result + LSeg.FPanel.Height;
    end;
  end;
end;

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

procedure TBafPageCategory.CreateComponents;
begin
  FPanel.OnResize := PanelResize;
  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.Top := 4;
  FScroll.Margins.Bottom := 8;
end;

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

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

procedure TBafPageCategory.PanelResize(Sender: TObject);
var
  i, LCnt, LCntAutosize: integer;
  LSegment: TBafPageSegment;
begin
(*  if FAutoSize then begin
    LCnt := 0;
    LCntAutoSize := 0;
    for i := 0 to Segments.Count - 1 do begin
      LSegment := Segments.Items[i];
      if (Opened and (LSegment.OpenClose in [ocOnlyOpen, ocOpenAndClose]))
          or (not Opened and (LSegment.OpenClose in [ocOnlyClose, ocOpenAndClose])) then begin
        inc(LCnt);
        if (Opened and LSegment.AutoSizeOpened)
            or (not Opened and LSegment.FAutoSizeClosed) then begin
          inc(LCntAutosize);

        end;
      end;
    end;
  end;  *)
end;

procedure TBafPageCategory.SetAutoSize(const Value: boolean);
begin
  FAutoSize := Value;
end;

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

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

procedure TBafPageCategory.SetOpened(const Value: boolean);
var
  i: integer;
  LSeg: TBafPageSegment;
  LValue: single;
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];
    LSeg.FPanel.Align := TAlignLayout.None;
    if FOpened then
      LSeg.FPanel.Visible := LSeg.OpenClose in [ocOnlyOpen, ocOpenAndClose]
    else
      LSeg.FPanel.Visible := LSeg.OpenClose in [ocOnlyClose, ocOpenAndClose];
    LSeg.SetColumnsOpenClose(FOpened);
  end;
  for i := Segments.Count - 1 downto 0 do begin
    LSeg := Segments.Items[i];
    LSeg.FPanel.Align := TAlignLayout.Top;
  end;
  if (OpenCloseIni <> '') and Assigned(FParents.Page.FOnOpenClose) then
    FParents.Page.FOnOpenClose(FParents);
  FParents.PrimaryDiv.CalcHeight;
  FParents.Page.StartTimer('repaint');
  FParents.Page.FLastCatOpenClose := Self;
  FParents.Page.StartTimer('dia_zoom0');
end;

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

{ TBafGridCategories }

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

function TBafPageCategories.AddItem(Item: TBafPageCategory;
  Index: Integer): TBafPageCategory;
begin
  if Item = nil then
    Result := TBafPageCategory.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 TBafPageCategories.Create(APrimaryDiv: TBafPagePrimaryDiv);
begin
  inherited Create(TBafPageCategory);
  FParents.CreateFrom(APrimaryDiv.FParents);
  FParents.Categories := Self;
end;

destructor TBafPageCategories.Destroy;
begin

  inherited;
end;

function TBafPageCategories.GetItem(Index: Integer): TBafPageCategory;
begin
  Result := TBafPageCategory(inherited GetItem(Index));
end;

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

function TBafPageCategories.Mouse2Position(AX, AY: integer;
  var APos: TBafPageCellPosition): boolean;
begin

end;

procedure TBafPageCategories.SetAutoSize;
var
  LCat, LCntAuto: integer;
  LSizeAuto, LSizeFixed, LRest: single;
  LCate: TBafPageCategory;
begin
  if not FDoAutoSize then
    exit;
  LSizeAuto := 0;
  LSizeFixed := 0;
  LCntAuto := 0;
  for LCat := 0 to Count - 1 do begin
    LCate := Items[LCat];
    if LCate.AutoSize then begin
      inc(LCntAuto);
      LSizeAuto := LSizeAuto + LCate.Size;
    end
    else
      LSizeFixed := LSizeFixed + LCate.Size;
  end;
  if LCntAuto = 0 then
    exit;
  for LCat := Count - 1 downto 0 do begin
    LCate := Items[LCat];
    if LCate.AutoSize then begin
      LCate.FPanel.Align := TAlignLayout.Client;
      Break;
    end;
  end;
  LRest := FParents.PrimaryDiv.FPanel.Width - LSizeFixed;
  if LSizeAuto < 0 then
    exit;
  for LCat := 0 to Count - 1 do begin
    LCate := Items[LCat];
    if LCate.AutoSize then
      LCate.FPanel.Width := LRest * LCate.Size / LSizeAuto;
  end;
end;

procedure TBafPageCategories.SetItem(Index: Integer; const Value: TBafPageCategory);
begin
  inherited SetItem(Index, Value);
end;

{ TBafGridSegment }

procedure TBafPageSegment.AddButton(ACaption, AHint, ALineP, AStatusEnabled: string;
    AWidth: integer; AReadOnly, ANewLine: boolean; ARight: TBafRight);
var
  LButton: TBafButton;
begin
  LButton := TBafButton.Create(FButtonDock);
  LButton.Parent := FButtonDock;
  LButton.Text := ACaption;
  LButton.Hint := AHint;
//  LButton.ShowHint := false;
  LButton.Width := AWidth;
  LButton.Enabled := not AReadOnly;
  LButton.StatusEnabled := AStatusEnabled;
  LButton.Right := ARight;
  LButton.LineP := ALineP;
  if ANewLine then
    LButton.Tag := 1337;
  LButton.OnClick := ButtonClick;
  LButton.OnMouseEnter := FParents.Page.HintMouseEnter;
  LButton.OnMouseLeave := FParents.Page.HintMouseLeave;
  FParents.Page.PageButtonList.Add(LButton);
  FButtonDock.PlaceControls;
  FParents.PrimaryDiv.CalcHeight;
end;

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

procedure TBafPageSegment.AddLineUnchanged(AText: string);
begin
  FMemo.OnChangeTracking := nil;
  try
    FMemo.Lines.Add(AText);
    SegmentCalcHeight(MemoContentHeight + 34 + FHeaderPanel.Height);
  finally
    FMemo.OnChangeTracking := MemoChange;
  end;
end;

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

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

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

constructor TBafPageSegment.Create(Collection: TCollection);
begin
  inherited;
  FDataTable := TStringList.Create;
  FDataKey := TStringList.Create;
  FDataConnection := TStringList.Create;
  FParents.CreateFrom((Collection as TBafPageSegments).FParents);
  FParents.Segment := Self;
  FPanel := TBafPanel.Place(FParents.Category.FScroll, TAlignLayout.Top);
  if Index = 0 then
    FTopAdd := 4;
  CreateHeader;
  FButtonList := TObjectList.Create(true);
  FCountRow := -1;
  FHistoryNoShowFields := TStringList.Create;
end;

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

procedure TBafPageSegment.CreateHeader;
begin
  FHeaderParentPanel := TBafPanel.Place(FPanel, TAlignLayout.Top);
  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];
end;

procedure TBafPageSegment.CreateMap;
begin
  FMap := TBafMap.Create(Self);
  FMap.Parent := FPanel;
  FMap.Align := TAlignLayout.Client;
  FMap.Margins.Rect := Rect(24, 4, 4, 4);
end;

procedure TBafPageSegment.CreateMemo;
begin
  FMemo := TMemo.Create(FPanel);
  FMemo.Parent := FPanel;
  FMemo.Align := TAlignLayout.Client;
  FMemo.Margins.Rect := Rect(24, 4, 4, 4);
  FMemo.WordWrap := true;
  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 TBafPageSegment.CreatePic;
begin
  FPic := TBafPic.Create(Self);
  FPic.Parent := FPanel;
  FPic.Align := TAlignLayout.Client;
  FPic.Margins.Rect := Rect(24, 4, 4, 4);
end;

procedure TBafPageSegment.CreateDia;
begin
  FDia := TBafDiagram.Create(Self);
  FDia.Parent := FPanel;
  FDia.Align := TAlignLayout.Client;
  FDia.Margins.Rect := Rect(24, 4, 4, 4);
end;

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

destructor TBafPageSegment.Destroy;
begin
  FreeAndNil(FHistoryNoShowFields);
  FreeAndNil(FButtonList);
  FreeAndNil(FDataKey);
  FreeAndNil(FDataConnection);
  FreeAndNil(FDataTable);
  inherited;
end;

function TBafPageSegment.GetBafConName(AIndex: integer): string;
begin
  while FDataConnection.Count <= AIndex do
    FDataConnection.Add('');
  result := FDataConnection[AIndex];
end;

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

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

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

function TBafPageSegment.GetHistoryNoShowFields: TStrings;
begin
  result := FHistoryNoShowFields;
end;

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

procedure TBafPageSegment.GridCalcHeight;
var
  LCount: integer;
begin
  case SegmentType of
    stGrid, stXGrid, stXXGrid, stValueList, stSGrid: begin
      LCount := (Grid.FHeaderRowList.Count + Grid.FDataRowDisplayList.Count
          + Grid.FFooterRowList.Count) * Grid.Columns.RowCount;
      SegmentCalcHeight(Max(LCount, 1) * 22
          + IfThen(Grid.WithoutHorizontalScrollBar, 14, 34) + FHeaderPanel.Height);
//      FGrid.CalcScrollbars;
    end;
    stDia, stMap, stPic: begin
      if FParents.Category.Opened then
        SegmentCalcHeight(MaxHeightOpened)
      else
        SegmentCalcHeight(MaxHeightClosed);
    end;
  end;
end;

procedure TBafPageSegment.GridSellectAll(ASelect: boolean);
var
  i: integer;
begin
  if Assigned(Grid) and (Grid.SelectionColumn >= 0) then begin
    if ASelect then begin
      for i := 0 to Grid.RowCount(rtDisplayData) - 1 do
        Grid.Cells[rtDisplayData, Grid.SelectionColumn, i].SetTextChange(BAFYESCHAR);
    end
    else begin
      for i := 0 to Grid.RowCount(rtData) - 1 do
        Grid.Cells[rtData, Grid.SelectionColumn, i].SetTextChange(BAFNOCHAR);
    end;
    Grid.Repaint;
  end;
end;

procedure TBafPageSegment.HeaderButtonClick(Sender: TObject);
var
  LChar: Char;
begin
  LChar := ((Sender as TButton).TagString + ' ')[1];
  case LChar of
    'A': GridSellectAll(true);
    'F': TFrmBafFilter.ShowSegment(Self);
    'H': TFrmBafHistory.ShowSegment(Self);
    'I': GridSellectAll(false);
    '+': if SegmentType in [stMap] then
      Map.ZoomInOut(125)
    else if Assigned(FParents.Page.FOnHeaderButtonClickedEvent) then
      FParents.Page.FOnHeaderButtonClickedEvent(FParents, LChar);
    '-': Map.ZoomInOut(80);
    'X', 'Y': if Assigned(FParents.Page.FOnHeaderButtonClickedEvent) then
      FParents.Page.FOnHeaderButtonClickedEvent(FParents, LChar);
  end;
end;

procedure TBafPageSegment.LinesRefresh;
begin
  if Assigned(FMemo) then begin
    SegmentCalcHeight(MemoContentHeight + 34 + FHeaderPanel.Height);
    FHeaderPanel.Align := TAlignLayout.Client;
  end;
end;

procedure TBafPageSegment.MemoChange(Sender: TObject);
begin
  SegmentCalcHeight(MemoContentHeight + 34 + FHeaderPanel.Height);
  if DataQuelle in [dqFile, dqData, dqLink] then begin
    HasChanged := true;
    if Assigned(LinkedCell) then
      LinkedCell.HasChanged := true;
    FParents.Page.SetStatus;
  end;
end;

function TBafPageSegment.MemoContentHeight: single;
var
  LLayout: TTextLayout;
  i: integer;
begin
//  result := 0;
//  LLayout := TTextLayoutManager.DefaultTextLayout.Create;
//  try
//    for i := 0 to FMemo.Lines.Count - 1 do begin
//      LLayout.BeginUpdate;
//      try
//        LLayout.MaxSize := PointF(FMemo.Width, 10000);
//        LLayout.WordWrap := true;
//        LLayout.Font.Assign(FMemo.Font);
//        LLayout.Text := FMemo.Lines[i];
//      finally
//        LLayout.EndUpdate;
//      end;
//      result := result + LLayout.TextHeight;
//    end;
//  finally
//    LLayout.Free;
//  end;

//  FMemo.ContentBounds
  result := Max(FMemo.Lines.Count, 1) * 16;
end;

procedure TBafPageSegment.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 TBafPageSegment.RefreshHeight;
begin
  case SegmentType of
    stButtons: FPanel.Height := FHeaderPanel.Height + FButtonDock.Height;
    else
      FPanel.Height := SegmentCalcHeightOpenedClosed(FCalcedHeight);
  end;
  if Assigned(FGrid) then
    FGrid.CalcScrollbars;
end;

procedure TBafPageSegment.SegmentCalcHeight(AHeight: single);
begin
  FCalcedHeight := AHeight;
  if FPanel.Height <> AHeight then begin
    RefreshHeight;
    FPanel.Height := SegmentCalcHeightOpenedClosed(AHeight);
    FParents.PrimaryDiv.CalcHeight;
  end;
end;

function TBafPageSegment.SegmentCalcHeightOpenedClosed(AHeight: single): single;

  function lokAutoSizeHeight: single;
  var
    LCatHeight: single;
    i: integer;
    LSegm: TBafPageSegment;
  begin
    result := FParents.Category.FScroll.Height;
    for i := 0 to FParents.Category.Segments.Count - 1 do begin
      LSegm := FParents.Category.Segments.Items[i];
      if LSegm <> Self then
        result := result - LSegm.FPanel.Height;
    end;
//    result := Max(result - 1, 42);
    result := Max(result, 42);
  end; // function lokAutoSizeHeight

begin
  result := AHeight;
  if (FParents.Category.Opened and AutoSizeOpened)
      or (not FParents.Category.Opened and AutoSizeClosed) then
    result := lokAutoSizeHeight
  else if FParents.Category.Opened and (SegmentType in [stDia, stMap, stPic]) then
    result := HeightOpened
  else if not FParents.Category.Opened and (SegmentType in [stDia, stMap, stPic]) then
    result := HeightClosed
  else if FParents.Category.Opened and (FMaxHeightOpened > 0)
      and (AHeight > FMaxHeightOpened) then
    result := FMaxHeightOpened
  else if not FParents.Category.Opened and (FMaxHeightClosed > 0)
      and (AHeight > FMaxHeightClosed) then
    result := FMaxHeightClosed;

// function TBafPageSegment.SegmentCalcHeightOpenedClosed
end;

procedure TBafPageSegment.SetBafConName(AIndex: integer; const Value: string);
begin
  while FDataConnection.Count <= AIndex do
    FDataConnection.Add('');
  FDataConnection[AIndex] := AnsiLowerCase(Value);
end;

procedure TBafPageSegment.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 := TBafPageUtils.GetButtonText(FButtons[i], SegmentType);
    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 TBafPageSegment.SetColumnsOpenClose(AOpen: boolean);
var
  LCol: integer;
  LColumn: TBafSgColumn;
begin
  if SegmentType in [stGrid, stXGrid, stXXGrid] then begin
    for LCol := 0 to FGrid.Columns.Count - 1 do begin
      LColumn := FGrid.Columns.Items[LCol];
      case LColumn.OpenClose of
        ocOnlyOpen: LColumn.CellVisible := (LColumn.GetWidthValue > 0) and AOpen;
        ocOnlyClose: LColumn.CellVisible := (LColumn.GetWidthValue > 0) and not AOpen;
        ocOpenAndClose: LColumn.CellVisible := (LColumn.GetWidthValue > 0) ;
      end;
    end;
  end;
end;

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

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

procedure TBafPageSegment.SetHasChanged(const Value: boolean);
var
  LCol, LRow: integer;
begin
  FHasChanged := Value;
  if not Value and Assigned(FGrid) then begin
    FGrid.HasChanged := false;
    for LRow := 0 to FGrid.RowCount(rtData) - 1 do
      for LCol := 0 to FGrid.Columns.Count - 1 do
        FGrid.Cells[rtData, LCol, LRow].HasChanged := false;
  end
  else if not Value and Assigned(FMap) then begin
    FMap.HasChanged := false;
    for LRow := 0 to FMap.FDataRowList.Count - 1 do
      FMap.DataRow[LRow].HasChanged := false;
  end;
end;

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

procedure TBafPageSegment.SetMemoTextUnchanged(AText: string);
begin
  FMemo.OnChangeTracking := nil;
  try
    FMemo.Lines.Text := AText;
    SegmentCalcHeight(MemoContentHeight + 34 + FHeaderPanel.Height);
  finally
    FMemo.OnChangeTracking := MemoChange;
  end;
end;

procedure TBafPageSegment.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 TBafPageSegment.SetPic(AFilename: string);
begin
  if Pic.PictureFileName <> AFileName then begin
    Pic.PictureFileName := AFileName;
    Pic.Repaint;
  end;
end;

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

  end;
end;

procedure TBafPageSegment.SetSegmentType(const Value: TBafPageSegmentType);
begin
  FSegmentType := Value;
  case Value of
    stText, stMemo: CreateMemo;
    stButtons: CreateButtonDock;
    stValueList, stGrid, stXGrid, stXXGrid, stSGrid: CreateSimpleGrid;
    stDia: CreateDia;
    stMap: CreateMap;
    stPic: CreatePic;
  end;
end;

procedure TBafPageSegment.SetSegRight(const Value: TBafRight);
begin
  FSegRight := Value;
  if FSegmentType in [stText, stMemo] then begin
    case FSegRight of
      brNone: FMemo.Visible := false;
      brRead: begin
        FMemo.Visible := true;
        FMemo.ReadOnly := true;
      end;
      brWrite: begin
        FMemo.Visible := true;
        FMemo.ReadOnly := false;
      end;
    end;
  end;

end;

procedure TBafPageSegment.SetWordWrap(const Value: boolean);
begin
  FWordWrap := Value;
  FMemo.WordWrap := Value;
end;

{ TBafGridSegments }

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

function TBafPageSegments.AddItem(Item: TBafPageSegment;
  Index: Integer): TBafPageSegment;
begin
  if Item = nil then
    Result := TBafPageSegment.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 TBafPageSegments.Create(ACategory: TBafPageCategory);
begin
  inherited Create(TBafPageSegment);
  FParents.CreateFrom(ACategory.FParents);
  FParents.Segments := Self;
end;

destructor TBafPageSegments.Destroy;
begin

  inherited;
end;

function TBafPageSegments.GetItem(Index: Integer): TBafPageSegment;
begin
  Result := TBafPageSegment(inherited GetItem(Index));
end;

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

procedure TBafPageSegments.SetItem(Index: Integer;
  const Value: TBafPageSegment);
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);
  if LCell.Parents.Segment.SegmentType = stValueList then
    result := (LCell.Parents.Column.Width > 0) and LCell.Visible
       and (LCell.DisplayRect.Right > 0)
  else
    result := (LCell.Parents.Column.Width > 0) and LCell.Parents.Column.CellVisible;
end;

procedure TBafSimpleGrid.CheckChangeCol;
var
  LCol: TBafSgColumn;
  LCompWidth, LFixed, LScroll: single;
begin
//  DebugColRects;
  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 := 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;
  if (FSelectedRow >= 0) and (FSelectedCol >= 0) then begin
    if Assigned(Parents.Page.FOnSelectCell) then
      Parents.Page.FOnSelectCell(Cells[rtDisplayData, FSelectedCol, FSelectedRow].Parents);
  end;
end;

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

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

constructor TBafSimpleGrid.Create(ASeg: TBafPageSegment);
begin
  inherited Create(ASeg.FParents.Page);
  FSelectedRow := -1;
  FSelectedCol := -1;
  FParents.CreateFrom(ASeg.FParents);
  FParents.SimpleGrid := Self;
  CreateComponents;
  CreateColsAndRows;
  CreateEditComps;
  FHintList := TStringList.Create;
  CanFocus := true;
  LoadStyle;
  ClipChildren := true;
  FLoopRow := -1;
  FSelectionColumn := -1;

end;

procedure TBafSimpleGrid.CreateColsAndRows;
begin
  FDataRowList := TObjectList.Create(true);
  FDataRowDisplayList := FDataRowList;
  FDataRowFilteredList := TObjectList.Create(false);
  FHeaderRowList := TObjectList.Create(true);
  FFooterRowList := 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 := 'pushpanel2';
  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 := 'pushpanel2';
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.DropDownCount := 12;
  FCombo.DropDownKind := TDropDownKind.Custom;
  FCombo.OnClosePopup := ComboClosePopup;
  FCombo.TabCloses := true;
end;

procedure TBafSimpleGrid.DebugColRects;
var
  sl: TStringList;
  i: integer;
  LColumn: TBafSgColumn;

  function lokRect2String(ARect: TRectF): string;
  begin
    result := Format('Left %8.2f - Right %8.2f - Width %8.2f',
      [ARect.Left, ARect.Right, ARect.Width]);
  end;

begin
  sl := TStringList.Create;
  try
    for i := 0 to Columns.Count - 1 do begin
      LColumn := Columns.Items[i];
      sl.Add('Col ' + IntToStr(i));
      sl.Add('CalcedRect ' + lokRect2String(LColumn.CalcedRect));
      sl.Add('ScrollRect ' + lokRect2String(LColumn.ScrollRect));
    end;
    Clipboard.AsText := sl.Text;
  finally
    sl.Free;
  end;
end;

procedure TBafSimpleGrid.DeleteRow(AIndex: integer);
var
  LObject: TObject;
  i: integer;
begin
  LObject := FDataRowList.Items[AIndex];
  FDataRowList.Remove(LObject);
  FDataRowFilteredList.Remove(LObject);
  FDataRowDisplayList.Remove(LObject);
  for i := 0 to FDataRowList.Count - 1 do
    (FDataRowList.Items[i] as TBafSgRow).RowIndex := i;
end;

destructor TBafSimpleGrid.Destroy;
begin
  FreeAndNil(FHintList);
  FreeAndNil(FColumns);
  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;
      vkHome: begin
        if ssCtrl in Shift then begin
          if FVertScrollBar.Visible then
            FVertScrollBar.Value := 0
          else
            FParents.Page.VScrollBar.Value := 0;
        end
        else
          FHorzScrollBar.Value := 0;
      end;
      vkEnd: begin
        if ssCtrl in Shift then begin
          if FVertScrollBar.Visible then
            FVertScrollBar.Value := MaxInt
          else
            FParents.Page.VScrollBar.Value := MaxInt;
        end
        else
          FHorzScrollBar.Value := MaxInt;
      end;
      BAF_VK_PLUS, BAF_VK_MINUS: if ssCtrl in Shift then begin
        NextPriorRowEdit(Key, Shift);
        Key := 0;
      end;
    end; // case
  end
  else if FEdit.IsFocused and (Key = vkTab) then begin
    HideEdit(true);
    if ssShift in Shift then PriorCell(Key) else NextCell(Key);
  end;
// procedure TBafSimpleGrid.DialogKey
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.Page.Repaint;
    end
    else
      FEdit.Text := FormatDateTime('dd.mm.yyyy', trunc(now) + ADiff);
    KeyChar := #0;
  end;

  procedure lokCharsIgnore;
  begin
    if Pos(KeyChar, FEditCharsIgnore) > 0 then begin
      KeyChar := #0;
      exit;
    end;
    if (FEditCharsAllowed <> '')
        and not (Pos(KeyChar, FEditCharsAllowed) > 0) then begin
      KeyChar := #0;
      exit;
    end;
  end; // procedure lokCharsIgnore

begin
  if KeyChar <> #0 then begin
    lokCharsIgnore;

    case FEditCellType of
      ctInt: if not (KeyChar in ['0'..'9', '-', #8]) then
        KeyChar := #0;
      ctCurr, ctCurr4, ctCurrInt: if not (KeyChar in ['0'..'9', ',', '-', #8]) then
        KeyChar := #0;
      ctDate: 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', '.': ;
          else
            KeyChar := #0;
        end;
      end;
      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', '.', ' ', ':': ;
        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;
    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.GetRadioRow: integer;
begin
  for result := 0 to FDataRowDisplayList.Count - 1 do begin
    if BafIsYesChar(Cells[rtDisplayData, SelectionColumn, result].Text) then
      Exit;
  end;
  result := -1;
end;

function TBafSimpleGrid.GetRow(AType: TBafGridRowType; ARow: integer): TBafSgRow;
var
  LList: TObjectList;
begin
  LList := nil;
  result := nil;
  case AType of
    rtHeader: LList := FHeaderRowList;
    rtData: LList := FDataRowList;
    rtDisplayData: LList := FDataRowDisplayList;
    rtFooter: LList := FFooterRowList;
    else
      exit;
  end;
  while ARow >= LList.Count do
    TBafSgRow.Create2List(AType, LList, FParents);
  result := (LList[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.GetVlCellIndexFieldName(AQIndex: integer): string;
var
  LKeyName: string;
  LRow, LCol: integer;
  LCell: TBafSgCell;
begin
  LKeyName := FParents.Segment.DataKey[AQIndex];
  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 = LKeyName)
          and (LCell.DataQIndex = AQIndex) then begin
        result := LCell.YFieldName;
        exit;
      end;
    end;
  end;
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;
  t, t1, t2, c: int64;

  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
  if frmBafDbDebug.Visible then     // avoids myterical error
    exit;
  QueryPerformanceFrequency(c);
  QueryPerformanceCounter(t1);
  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 := FColorBack;
    Canvas.FillRect(LRect, 0, 0, [], 1);
    FDec2 := Canvas.TextWidth(',00') + 2;
    FDec4 := Canvas.TextWidth(',0000') + 2;
    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;
  QueryPerformanceCounter(t2);
  t := 1000 * (t2 - t1) div c;
// procedure TBafSimpleGrid.GridPaint
end;

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

  procedure lokEdit;
  var
    LLookupHelper: TBafComboHelper;
    p: integer;
    s: string;
  begin
    if ASave and (FEditReadOnly = false) then begin
      case FEditVisible of
        ecEdit: LText := FEdit.Text;
        ecCombo: if FEditCell.GetLookupHelper(LLookupHelper, false) then begin
          if FEditCell.CellType = ctLookupText then
            LText := LLookupHelper.GetName(FCombo)
          else
            LText := LLookupHelper.GetGuid(FCombo);

        end;
      end;
      FEditCell.Parents.Page.DoEditEvent(FEditCell.Parents, false, LText,
          LAbort, '', LText <> FEditCell.Text);
      if (FEditCell.CellType = ctDate) and (Length(LText) in [6, 8]) then begin
        p := Pos('.', LText);
        if p = 0 then
          LText := copy(LText, 1, 2) + '.' + copy(LText, 3, 2) + '.'
              + copy(LText, 5, MaxInt);
      end;
      LChanged := LText <> FEditCell.Text;
      FEditCell.SetTextChange(LText, false);
      FEditCell.Parents.Page.DoEditEvent(FEditCell.Parents, true, LText,
          LAbort, '', LChanged);
    end;
  end; // procedure lokEdit;

begin
  if FEditVisible <> ecNone then 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.Page.SetStatus;
    LKey := vkTab;
    if FEdit.HasTab2Return or LComboTab then
      NextCell(LKey);
  end;
end;

function TBafSimpleGrid.InsertRow(APos: integer): TBafSgRow;
var
  LPos: integer;
begin
  result := TBafSgRow.Create(true);
  result.RowType := rtData;
  result.FParents.CreateFrom(FParents);
  result.FParents.Row := result;
  result.FParents.Columns := FParents.SimpleGrid.Columns;
  if APos = MaxInt then
    result.RowIndex := FDataRowList.Add(result)
  else begin
    LPos := Min(APos, FDataRowList.Count);
    FDataRowList.Insert(LPos, result);
    result.RowIndex := LPos;
  end;
  result.RowInserted := true;

  result.FJoinList := TStringList.Create;
  result.FJoinList.Sorted := true;
  result.FJoinList.Duplicates := dupIgnore;
  result.FJoinList2 := TStringList.Create;
  result.FJoinList2.Sorted := true;
  result.FJoinList2.Duplicates := dupIgnore;
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 lokRadio;
  begin
    if (KeyChar in [' ']) or (Key = VK_RETURN) then
      LCell.ToggleRadio;
    LCell.Parents.SimpleGrid.Repaint;
  end; // procedure lokRadio

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

  procedure lokTriPlus;
  begin
    case KeyChar of
      '+', 'p', 'P': LCell.SetTodo('+');
      '-', 'm', 'M': LCell.SetTodo('-');
      '0', 'o', 'O': LCell.SetTodo('0');
    end;
    LCell.Parents.SimpleGrid.Repaint;
  end; // procedure lokTriPlus

  procedure lokTriEx;
  begin
    case KeyChar of
      '!', 'e', 'E', 'a', 'A': LCell.SetTodo('!');
      '?', 'q', 'Q', 'f', 'F': LCell.SetTodo('?');
      '0', 'o', 'O': LCell.SetTodo('0');
    end;
    LCell.Parents.SimpleGrid.Repaint;
  end; // procedure lokTriEx

  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, stXXGrid: 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);
  if LCell.ReadOnly then
    exit;
  case LCell.Parents.Column.CellType of
    ctBool, ctBool2: lokBool;
    ctRadio: lokRadio;
    ctTodo: lokTodo;
    ctTriPlus: lokTriPlus;
    ctTriEx: lokTriEx;
    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;

  function lokFarbe(AName: string): TAlphaColor;
  begin
    LObject2 := lokFindObject(LObject, AName);
    if Assigned(LObject2) and (LObject2 is TRectangle) then begin
      LRectangle := TRectangle(LObject2);
      result := LRectangle.Fill.Color;
    end;
  end;

begin
  LObject := FMX.Types.FindStyleResource('bafcolors');
  if Assigned(LObject) and (LObject is TLayout) then begin
    FColorBack := lokFarbe('backgroundcolor');
    FColorSelection := lokFarbe('selection');
    FColorHeader := lokFarbe('headerbackground');
    FColorReadonly := lokFarbe('readonly');
    FColorFont := lokFarbe('text');
    FColorEdit := lokFarbe('edit');
    FColorGreen := lokFarbe('green');
    FColorYellow := lokFarbe('yellow');
    FColorRed := lokFarbe('red');
  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 - Trunc(FVertScrollBar.Value))) then begin
    ARowType := rtDisplayData;
    ARow := Trunc(LValue) - FHeaderRowList.Count + Trunc(FVertScrollBar.Value);
  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, LInc: integer;
  LCell: TBafSgCell;
begin
  LInc := 1;
  LCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
  if LCell.Parents.Segment.SegmentType = stValueList then
    LInc := System.Math.Max(1, LCell.ColSpan);
  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, LInc);
      if not CanCellSelect(FSelectedCol, FSelectedRow) then
        NextCell(Key);
      if not CanCellSelect(FSelectedCol, FSelectedRow) then
        FSelectedCol := LCol;
    end
    else begin
      inc(FSelectedCol, LInc);
      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: TBafPageCellType;
  LLookupHelper: TBafComboHelper;

  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 ssShift in AShift then begin
    if (AKey = vkDown) then
      lokMultiRowDown(0)
    else if (AKey = vkUp) then
      lokMultiRowUp(0)
    else if (AKey = BAF_VK_PLUS) then
      lokMultiRowUp(1)
    else if (AKey = BAF_VK_MINUS) then
      lokMultiRowUp(-1)
    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;
      GetCell(rtDisplayData, FSelectedCol, FSelectedRow).SetTextChange(LValue);
    end;
  end
  else begin
    if AKey = vkDown then
      NextRow(LKey)
    else if AKey = vkUp then
      PriorRow(LKey);
    LCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
    if LCell.CellType = ctLookupLive then
      LCell.GetLookupHelper(LLookupHelper, true);
    LCell.SetTextChange(LValue);
  end;
  Repaint;
end;

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

  else if FIgnoreNextRow then
    FIgnoreNextRow := false
  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;
  LColumn: TBafSgColumn;
  LAlign: TBafAlignment;
  LCellType: TBafPageCellType;
  LSegmentType: TBafPageSegmentType;
  LVisible, LCellReadOnly: boolean;
  LArp: single;

  procedure lokSetColor;
  begin
    if (ACol = FSelectedCol) and (ARow = FSelectedRow)
        and (AType in [rtData, rtDisplayData]) and IsFocused then
      Canvas.Fill.Color := FColorSelection
    else begin
      case LCell.CellColor of
        TBafCellColor.ccGreen: Canvas.Fill.Color := FColorGreen;
        TBafCellColor.ccYellow: Canvas.Fill.Color := FColorYellow;
        TBafCellColor.ccRed: Canvas.Fill.Color := FColorRed;
        TBafCellColor.ccHeader: Canvas.Fill.Color := FColorHeader;
        TBafCellColor.ccIndex: Canvas.Fill.Color := LCell.CellAlphaColor;
        else begin
          if (AType in [rtFooter, rtHeader]) or (LCellType in [ctButton] ) then
            Canvas.Fill.Color := FColorHeader
          else if LCell.ReadOnly then
            Canvas.Fill.Color := FColorReadonly
          else
            Canvas.Fill.Color := FColorEdit;
        end;
      end;
    end;
  end; // procedure lokSetColor;

  function lokCheckBoolean: boolean;
  begin
    result := true;
    if LCellType in [ctBool, ctBool2] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      if BafIsYesChar(LText) then
        LText :=  #$f14a
      else
        LText := IfThen(LColumn.CellType = ctBool, #$f0c8, '');
    end
    else if LCellType in [ctRadio] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      if BafIsYesChar(LText) then
        LText :=  #$f192
      else
        LText := #$f111;
    end
    else if LCellType in [ctTodo] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      case AnsiUpperCase(LText + ' ')[1] of
        'A': LText := #$f192;
        'C': LText := #$f058;
        'R': LText := #$f057;
        else
          LText := '';
      end;
    end
    else if LCellType in [ctTriPlus] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      case AnsiUpperCase(LText + ' ')[1] of
        '+': LText := #$f055;
        '-': LText := #$f056;
        '0': LText := #$f111;
        else
          LText := '';
      end;
    end
    else if LCellType in [ctTriEx] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      case AnsiUpperCase(LText + ' ')[1] of
        '!': LText := #$f06a;
        '?': LText := #$f059;
        '0': LText := #$f111;
        else
          LText := '';
      end;
    end
    else if LCellType in [ctLink] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      if LText = '' then

      else if LCellReadOnly then
//        LText := #$f070
//        LText := #$f256
        LText := #$f28d
      else
        LText := #$f14d;
    end
    else if LCellType in [ctButton] then begin
      Canvas.Font.Family := 'Font Awesome 5 Free';
      LAlign := taCenter;
      LText := TBafPageUtils.GetButtonText((LText + ' ')[1],
          Parents.Segment.SegmentType);
    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;

  procedure lokPaint;
  var
    p: integer;
    LRect: TRectF;
    LWidth: single;
  begin
    case LAlign of
      taCenter: Canvas.FillText(ARect, LText,
          false, 1, [], TTextAlign.Center);
      taRightJustify: begin
        ARect.Right := ARect.Right - 2 - LArp;
        if LCellType in [ctInt] then begin
          if LText <> '' then
            LText := FormatFloat('# ### ### ###', round(StrToCurrDef(LText, -1)));
        end;
        Canvas.FillText(ARect, LText, false, 1, [], TTextAlign.Trailing);
      end;
      taDecimal2, taDecimal4: begin
        if LCellType in [ctInt, ctCurr, ctCurrInt] then begin
          if LText <> '' then
            LText := FormatFloat('### ### ### ### ### ##0.00', StrToCurrDef(LText, -1));
        end
        else if LCellType in [ctCurr4] then begin
          if LText <> '' then
            LText := FormatFloat('### ### ### ### ### ##0.0000', StrToCurrDef(LText, -1));
        end;
        p := Pos(',', LText);
        if p = 0 then
          p := Length(LText) + 1;
        LWidth := IfThen(LAlign = taDecimal2, FDec2, FDec4);
        LRect := ARect;
        LRect.Right := LRect.Right - LWidth;
        Canvas.FillText(LRect, copy(LText, 1, p - 1), false, 1, [], TTextAlign.Trailing);
        LRect := ARect;
        LRect.Left := LRect.Right - LWidth;
        Canvas.FillText(LRect, copy(LText, p, MaxInt), false, 1, [], TTextAlign.Leading);
      end;
    else
      Canvas.FillText(ARect, LText, false, 1, [], TTextAlign.Leading);
    end;
  end; // procedure lokPaint

  procedure lokHaken;
  var
    LColor: TAlphaColor;
    LThick: single;
  begin
    LThick :=  Canvas.Stroke.Thickness;
    LColor := Canvas.Stroke.Color;
    try
      Canvas.Stroke.Color := FColorHeader;
      Canvas.Stroke.Thickness := 3;
      Canvas.DrawLine(PointF(ARect.Right - 5, ARect.Top + 5),
          PointF(ARect.Right - 9, ARect.Top + 9), 0.8);
      Canvas.DrawLine(PointF(ARect.Right - 13, ARect.Top + 5),
          PointF(ARect.Right - 9, ARect.Top + 9), 0.8);
    finally
      Canvas.Stroke.Thickness := LThick;
      Canvas.Stroke.Color := LColor;
    end;
  end; // procedure lokHaken

  procedure lokGridType;
  begin
    if (LSegmentType in [stGrid, stXGrid, stXXGrid])
        and (LCell.FParents.Row.RowType in [rtData, rtDisplayData]) then
      LCellType := LCell.Parents.Column.CellType
    else
      LCellType := LCell.CellType;
    if (LSegmentType in [stGrid, stXGrid, stXXGrid]) then begin
      LCellReadOnly := LCell.Parents.Column.CellReadOnly;
      LArp :=  LCell.Parents.Column.CellArp;
    end
    else begin
      LCellReadOnly := LCell.ReadOnly;
      LArp := LCell.CellArp;
    end;
  end; // procedure lokGridType

begin
  Canvas.Font.Style := [];
  LCell := Cells[AType, ACol, ARow];
  if (FParents.Segment.SegmentType in [stValueList, stSGrid])
      or (AType in [rtHeader, rtFooter]) then
    LAlign := LCell.Alignment
  else
    LAlign := LCell.FParents.Column.CellAlignment;
  LColumn := LCell.Parents.Column;
  LText := LCell.GetDisplayText;
  LSegmentType := LCell.Parents.Segment.SegmentType;
  lokGridType;
  LVisible := LCell.Visible or (LSegmentType in [stGrid, stXGrid, stXXGrid, stSGrid]);
  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 := FColorFont;
  if AType = rtHeader then
    lokCheckSort;
  if AType = rtFooter then
    Canvas.Font.Family := '';
  if LVisible then
    lokPaint;
  if (LCellType in [ctLookup, ctLookupLive, ctLookupStatus, ctLookupText])
      and (AType in [rtData, rtDisplayData]) then
    lokHaken;
// 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 - 1 do 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 LCell.ColSpan > 1 then begin
        for i := 1 to LCell.ColSpan - 1 do begin
          LRight := 0;
          if LCol + i < Columns.Count then begin
            LSpanCell := Cells[AType, LCol + i, LRow];
            LSpanCell.ColSpan := 0;
            LRight := LSpanCell.Parents.Column.CalcedRect.Right;
          end;
        end;
        if LRight > 0 then
          LRectF.Right := LRight;
      end;
      if (LRectF.Width > 1) and (LRectF.Right > 0) and (LCell.ColSpan > 0)
          and (LRectF.Left < FPanelDesk.Width)
          and (LPosY < FPanelDesk.Height) and ((LPosY + LWidth) >= 0) then begin
        LRectF.Top := LPosY;
        LRectF.Bottom := LPosY + 22;
        PaintCell(AType, LCol, LRow, LRectF);
      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 Mouse2Cell(X, Y, LRowType, LCol, LRow) then begin
    if (ssDouble in Shift) and (LRowType = rtDisplayData) then begin
      FSelectedCol := LCol;
      FSelectedRow := LRow;
      ShowEdit(false);
    end
    else if (Button = TMouseButton.mbLeft) then begin
      FMouseDownCell := Cells[LRowType, LCol, LRow];
      FMouseDownPoint := PointF(X, Y);
      if (LRowType = rtHeader)
//          and (Abs(FMouseDownCell.Parents.Column.ScrollRect.Right - X) < 3) then
          and (Abs(FMouseDownCell.FDisplayRect.Right - X) < 3) then
        FColumnSizing := true
      else
        FColumnSizing := false;
      if FColumnSizing then
        Cursor := crHSplit;
    end;
  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 (LRowType = rtHeader) and (Abs(LCell.Parents.Column.ScrollRect.Right - X) < 3) then
    if (LRowType = rtHeader) and (Abs(LCell.FDisplayRect.Right - X) < 3) then
      Cursor := crHSplit
    else
      Cursor := crDefault;
    if LCell <> FHoverCell then begin
      Hint := LCell.Hint;
      FHoverCell := LCell;
      if Hint = '' then
        FParents.Page.HintMouseLeave(Self)
      else
        FParents.Page.HintMouseEnter(Self);
//      if LCell.Hint <> '' then
//        FHoverCell := LCell
//      else
//        FHoverCell := nil;
//      FPanelDesk.Repaint;
    end;
  end
  else begin
    FHoverCell := nil;
    Hint := '';
    FParents.Page.HintMouseLeave(Self);
//    FPanelDesk.Repaint;
  end;
end;

procedure TBafSimpleGrid.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LCol, LRow: integer;
  LCell: TBafSgCell;
  LRowType: TBafGridRowType;
  LValue: single;

  procedure lokClick;
  begin
    if LRowType = rtDisplayData then begin
      SelectedRow := LRow;
      SelectedCol := LCol;
      LCell := Cells[rtDisplayData, LCol, LRow];
      if LCell <> FMouseDownCell then begin
        if Assigned(LCell.Parents.Page.FOnPageDragDropCell) then
          LCell.Parents.Page.FOnPageDragDropCell(FMouseDownCell.Parents, LCell.Parents);
      end
      else begin
        case LCell.CellType of
          ctBool, ctBool2: LCell.ToggleBool;
          ctRadio: LCell.ToggleRadio;
          ctTodo: LCell.ToggleTodo;
          ctTriPlus: LCell.ToggleTriPlus;
          ctTriEx: LCell.ToggleTriEx;
          ctLink: begin
            if FParents.Segment.SegmentType in [stValueList, stSGrid] then begin
              if not LCell.ReadOnly then
                FParents.Page.LinkClick(LCell.FParents, LCell.Command, LCell.Text)
            end
            else begin
              if not LCell.Parents.Column.CellReadOnly then
                FParents.Page.LinkClick(LCell.FParents,
                    LCell.Parents.Column.CellCommand,  LCell.Text);
            end;
          end;
          ctButton: if Assigned(LCell.Parents.Page.FOnCellButton) then
            LCell.Parents.Page.FOnCellButton(LCell.Parents);
        end;
        if LCell.CellType in [ctBool, ctBool2, ctRadio, ctTodo, ctTriPlus, ctTriEx] then
          FPanelDesk.Repaint;
      end;
    end;
    if LRowType = rtHeader then begin
      LCell := Cells[rtHeader, LCol, LRow];
      if LCell = FMouseDownCell then begin
        Columns.Items[LCol].ToggleSort;
        FPanelDesk.Repaint;
      end;
    end;
  end; // procedure lokClick

begin
  if not IsFocused then
    SetFocus;
  FParents.Page.HideAllEdits;      // if we can click the panel, we're outside the edit control
//  if FEditVisible <> ecNone then
//    FParents.Page.HideAllEdits;

  if FColumnSizing then begin
    LValue := System.Math.Max(5, FMouseDownCell.Parents.Column.CalcedRect.Width
        + X - FMouseDownPoint.X);
    FMouseDownCell.Parents.Column.Width := LValue;
    FMouseDownCell.Parents.Column.Stretch := 0;
    Columns.GridHeaderLineBreak;
    LCol := FMouseDownCell.Parents.Column.Index;
    LRow := System.Math.Max(0, FSelectedRow);
    if (LRow >= 0) and (LCol >= 0) then begin
      if Assigned(Parents.Page.FOnResizeCell) then
        Parents.Page.FOnResizeCell(Cells[rtDisplayData, LCol, LRow].Parents);
    end;
  end
  else if Mouse2Cell(X, Y, LRowType, LCol, LRow) then
    lokClick;
  if Button = TMouseButton.mbLeft then begin
    FMouseDownCell := nil;
    FColumnSizing := false;
  end;
// procedure TBafSimpleGrid.PanelDeskMouseUp
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;
    CheckChangeCol;
    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.RecalcCells;
var
  c, t1, t2, t: int64;

  procedure lokRecalc(ARowType: TBafGridRowType);
  var
    LCol, LRow: integer;
    LCell: TBafSgCell;
    s: string;
    b: boolean;
  begin
    for LRow := 0 to RowCount(ARowType) - 1 do begin
      for LCol := 0 to Columns.Count - 1 do begin
        LCell := Cells[ARowType, LCol, LRow];
        if ((LCell.Command <> '') or (LCell.CellColorCommand <> ''))
            and not (LCell.CellType in [ctButton, ctLink])
            and Assigned(FParents.Page.FOnCellCalc) then begin
          s := LCell.Text;
          b := false;
          FParents.Page.FOnCellCalc(LCell.Parents, s, b, LCell.Command,
              LCell.HasChanged);
          if not b then
            LCell.Text := s;
        end;
      end;
    end;
  end; // procedure lokRecalc

begin
  QueryPerformanceFrequency(c);
  QueryPerformanceCounter(t1);
  lokRecalc(rtHeader);
  lokRecalc(rtData);
  lokRecalc(rtFooter);
  QueryPerformanceCounter(t2);
  t := 1000000 * (t2 - t1) div c;
// procedure TBafSimpleGrid.RecalcCells
end;

procedure TBafSimpleGrid.Resize;
begin
  inherited;
  if Abs(FWidthResizeOld - Width) > 1 then begin
    FWidthResizeOld := Width;
    Columns.GridHeaderLineBreak;
  end;
end;

function TBafSimpleGrid.RowCount(AType: TBafGridRowType): integer;
begin
  if Self = nil then
    result := 0
  else begin
    case AType of
      rtHeader: result := FHeaderRowList.Count;
      rtFooter: result := FFooterRowList.Count;
      rtDisplayData: result := FDataRowDisplayList.Count;
    else
      result := FDataRowList.Count;
    end;
  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 if LFilter = 'empty' then
      result := (LValue = '')
    else if LFilter = 'notempty' then
      result := (LValue <> '')
    else if LFilter = 'v1orv2' then
      result := (LValue = LWert1) or (LValue = LWert2)

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

  function lokCheckDate: boolean;
  var
    LDateValue, LDateWert1, LDateWert2: TDateTime;
  begin
    LDateValue := StrToDateTimeDef(LValue, -MaxInt);
    LDateWert1 := StrToDateTimeDef(LWert1, -MaxInt);
    LDateWert2 := StrToDateTimeDef(LWert2, -MaxInt);
    if LFilter = 'empty' then
      result := (LValue = '')
    else if LFilter = 'notempty' then
      result := (LValue <> '')
    else if (LDateValue = -MaxInt) or (LDateWert1 = -MaxInt) then
      result := false
    else if LFilter = 'Equal' then
      result := LDateValue = LDateWert1
    else if LFilter = 'EqualCi' then
      result := LDateValue = LDateWert1
    else if LFilter = '>' then
      result := LDateValue > LDateWert1
    else if LFilter = '>=' then
      result := LDateValue >= LDateWert1
    else if LFilter = '<' then
      result := LDateValue < LDateWert1
    else if LFilter = '<=' then
      result := LDateValue <= LDateWert1
    else if (LFilter = 'Between') and ((LDateWert2 <> -MaxInt)) then
      result := (LDateValue >= LDateWert1) and (LDateValue <= LDateWert2)
    else if LFilter = 'v1orv2' then
      result := (LDateValue = LDateWert1) or (LDateValue = LDateWert2)

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

  function lokCheckBool: boolean;
  var
    b1, b2: bool;
  begin
    if (LFilter = 'Equal') or (LFilter = 'EqualCi') then begin
      b1 := BafIsYesChar((LValue + ' ')[1]);
      b2 := BafIsYesChar((LWert1 + ' ')[1]);
      result := (b1 = b2);
    end
    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 if LFilter = 'empty' then
      result := (LValue = '')
    else if LFilter = 'notempty' then
      result := (LValue <> '')
    else if LFilter = 'v1orv2' then
      result := (LValue = LWert1) or (LValue = LWert2)

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

  function lokCheckCurr: boolean;
  var
    LCurValue, LCurWert1, LCurWert2: currency;
  begin
    LCurValue := StrToCurrDef(LValue, -MaxInt);
    LCurWert1 := StrToCurrDef(LWert1, -MaxInt);
    LCurWert2 := StrToCurrDef(LWert2, -MaxInt);
    if LFilter = 'empty' then
      result := (LValue = '')
    else if LFilter = 'notempty' then
      result := (LValue <> '')
    else 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 = '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 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 if LFilter = 'v1orv2' then
      result := (LCurValue = LCurWert1) or (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 := Columns.Items[i].Caption;
      if LCat = '' then
        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, ctLookupText] 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, ctLookupText, ctGuid, ctIBAN: result := lokCheckString;
          ctBool, ctBool2: result := lokCheckBool;
          ctInt, ctCurr, ctCurr4, ctCurrInt: result := lokCheckCurr;
          ctDate, ctDateMin, ctDateSek: result := lokCheckDate;
        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;
    end
    else begin
      FDataRowDisplayList := FDataRowList;
    end;

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

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

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

procedure TBafSimpleGrid.SetRowCount(AType: TBafGridRowType; ACount: integer);
var
  LRow: TBafSgRow;
  LList: TObjectList;
begin
  LList := nil;
  case AType of
    rtHeader: LList := FHeaderRowList;
    rtData: LList := FDataRowList;
    rtDisplayData: LList := FDataRowDisplayList;
    rtFooter: LList := FFooterRowList;
    else
      exit;
  end;
  while ACount > LList.Count do
    TBafSgRow.Create2List(AType, LList, FParents);
  while ACount < LList.Count do
    LList.Delete(LList.Count - 1);
end;

procedure TBafSimpleGrid.SetSelectedCol(const Value: integer);
begin
  if FSelectedCol <> Value then begin
    FSelectedCol := Value;
    if (FSelectedRow >= 0) and (FSelectedCol >= 0) then begin
      if Assigned(Parents.Page.FOnSelectCell) then
        Parents.Page.FOnSelectCell(Cells[rtDisplayData, FSelectedCol, FSelectedRow].Parents);
    end;
    FPanelDesk.Repaint;
  end;
end;

procedure TBafSimpleGrid.SetSelectedRow(const Value: integer);
var
  LCol: integer;
  LGridColumn: TBafSgColumn;
  LCell: TBafSgCell;

  procedure lokMemo;
  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; // procedure lokMemo

  procedure lokPic;
  begin
    if Value >= 0 then begin
      LCell := Cells[rtData, LCol, Value];
      LGridColumn.LinkedSegment.SetPic(LCell.Text);
      LGridColumn.LinkedSegment.LinkedCell := LCell;
      LGridColumn.LinkedSegment.HasChanged := false;
    end
    else begin
      LGridColumn.LinkedSegment.SetPic('');
      LGridColumn.LinkedSegment.LinkedCell := nil;
      LGridColumn.LinkedSegment.HasChanged := false;
    end;
  end; // procedure lokPic

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
          case LGridColumn.LinkedSegment.SegmentType of
            stMemo: lokMemo;
            stPic: lokPic;
          end;
        end;
      end;
      FParents.Page.SetStatus;
    end; // if Columns.HasLinkedColumns
    FSelectedRow := Value;
    if (FSelectedRow >= 0) and (FSelectedCol >= 0) then begin
      if Assigned(Parents.Page.FOnSelectCell) then
        Parents.Page.FOnSelectCell(Cells[rtDisplayData, FSelectedCol, FSelectedRow].Parents);
    end;
    FPanelDesk.Repaint;
  end;
end;

procedure TBafSimpleGrid.SetWithoutHorizontalScrollBar(const Value: boolean);
begin
  FWithoutHorizontalScrollBar := Value;
end;

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

  procedure lokEdit(AEdit: TEdit; AEditVisible: TBafClientEditComp);
  begin
    AEdit.ReadOnly := FEditReadOnly;
    AEdit.Visible := true;
    FEditVisible := AEditVisible;
    AEdit.SetBounds(LRect.Left, LRect.Top, LRect.Width, LRect.Height);
    case FParents.Segment.SegmentType of
      stValueList: begin
        AEdit.MaxLength := FEditCell.MaxLength;
        FEditCellType := FEditCell.CellType;
        AEdit.CharCase := FEditCell.CharCase;
        FEditCharsIgnore := FEditCell.CharsIgnore;
        FEditCharsAllowed := FEditCell.CharsAllowed;
        AEdit.Password := FEditCell.Password;
      end;
      stGrid, stXGrid, stXXGrid: begin
        AEdit.MaxLength := FEditCell.FParents.Column.CellMaxLength;
        FEditCellType := FEditCell.FParents.Column.CellType;
        AEdit.CharCase := FEditCell.FParents.Column.CellCharCase;
        FEditCharsIgnore := FEditCell.FParents.Column.CellCharsIgnore;
        FEditCharsAllowed := FEditCell.FParents.Column.CellCharsAllowed;
      end;
    end;
    if AKeyPress then
      AEdit.Text := FKeyPressText
    else
      AEdit.Text := FEditCell.Text;
    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 begin
      if FEditCell.CellType = ctLookupText then
        LLookupHelper.FillAndSetName(FCombo, FEditCell.Text)
      else
        LLookupHelper.FillAndSet(FCombo, FEditCell.Text);
    end;
    FCombo.Visible := true;
    FEditVisible := ecCombo;
    FCombo.SetBounds(LRect.Left, LRect.Top, LRect.Width, LRect.Height);
    FCombo.DropDown;
    if FCombo.CanFocus then
      FCombo.SetFocus;
  end; // procedure lokLookup

begin
  if (FEditVisible <> ecNone) then
    exit;

  FEditCell := GetCell(rtDisplayData, FSelectedCol, FSelectedRow);
  if FEditCell.CellType in [ctBool, ctBool2, ctRadio, ctTodo, ctTriPlus, ctTriEx] then
    exit;
  case FEditCell.Parents.Segment.SegmentType of
    stValueList: if not FEditCell.Visible then
      exit;
    stGrid, stXGrid, stXXGrid: 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, ctTriPlus, ctTriEx] then
    exit;   // we don't edit boolean
  FEditReadOnly := FEditCell.ReadOnly;
  case FEditCell.CellType of
    ctLookup, ctLookupStatus, ctLookupLive, ctLookupText: lokLookup;
    else
      lokEdit(FEdit, ecEdit);
  end;
  FParents.Page.SetStatus;
// procedure TBafSimpleGrid.ShowEdit
end;

{ TBafSgColumns }

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

function TBafSgColumns.AddColumn(AFieldName, ACaption, AHint: string;
  AReadOnly, AVisible: 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.CellVisible := AVisible and (result.GetWidthValue > 0);
  if not result.CellVisible then begin
    result.Width := 0;
    result.Stretch := 0;
  end;
  result.XGridIndex := -1;
  result.XXGridIndex := -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, ctCurrInt: 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);
    LColumn := mvColumns.Items[LCol];
    case LColumn.CellType of
      ctLookup, ctLookupLive, ctLookupStatus: begin
        if (LCol >= 0) and ((LDir = 'U') or (LDir = 'D')) then begin
          LText1 := LRow1.Cells[LCol].GetDisplayText(false);
          LText2 := LRow2.Cells[LCol].GetDisplayText(false);
        end;
      end
      else begin
        if (LCol >= 0) and ((LDir = 'U') or (LDir = 'D')) then begin
          LText1 := LRow1.Cells[LCol].Text;
          LText2 := LRow2.Cells[LCol].Text;
        end;
      end;  // else
    end; // case

    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).RowIndex := 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;
    Items[LCol].JoinReadOnly := false;
  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: TObject);
begin
  inherited Create(TBafSgColumn);
  if AGrid is TBafSimpleGrid then
    FParents := TBafSimpleGrid(AGrid).FParents
  else if AGrid is TBafMap then
    FParents := TBafMap(AGrid).FParents;
  FParents.Columns := Self;
  FRowCount := 1;
  FSortStack := TStringList.Create;
  FFieldNameList := TStringList.Create;
  FFieldNameList.Sorted := true;
  FFieldNameList.CaseSensitive := false;
  FFieldNameList.Duplicates := dupIgnore;
end;

destructor TBafSgColumns.Destroy;
begin
  FreeAndNil(FFieldNameList);
  FreeAndNil(FSortStack);

  inherited;
end;

function TBafSgColumns.FieldName2Col(AFieldName: string): TBafSgColumn;
var
  ix: integer;
begin
  result := nil;
  ix := FFieldNameList.IndexOf(AFieldName);
  if ix >= 0 then
    result := (FFieldNameList.Objects[ix] as TBafSgColumn);
end;

function TBafSgColumns.FieldName2Ix(AFieldName: string): integer;
var
  ix: integer;
begin
  result := -1;
  ix := FFieldNameList.IndexOf(AFieldName);
  if ix >= 0 then
    result := (FFieldNameList.Objects[ix] as TBafSgColumn).Index;
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;
  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 - LScrollBar.Height);
    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 + 50;
    LScrollBar.ViewportSize := LCompWidth;
    LScrollBar.Repaint;
  end
  else
    LScrollBar.Value := 0;
  FRowCount := LRow + 1;
  FParents.Segment.GridCalcHeight;
  FParents.Segment.FHeaderPanel.Width := System.Math.Max(LMaxWidth + 26, 150);
// 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;

function TBafSgColumn.GetCellYFieldName: string;
begin
  result := FCellYFieldName;
  if result = '' then
    result := FCellFieldName;
end;

function TBafSgColumn.GetStretch: single;
begin
  result := FStretch;
  if not CellVisible then
    result := 0;
end;

function TBafSgColumn.GetWidth: single;
begin
  Result := FWidth;
  if not CellVisible then
    result := 0;
end;

function TBafSgColumn.GetWidthValue: single;
begin
  result := FWidth;
end;

procedure TBafSgColumn.SetCellFieldName(const Value: string);
var
  ix: integer;
begin
  ix := FParents.Columns.FFieldNameList.IndexOfObject(Self);
  if ix >= 0 then
    FParents.Columns.FFieldNameList.Delete(ix);
  FCellFieldName := AnsiLowerCase(Value);
  FParents.Columns.FFieldNameList.AddObject(FCellFieldName, Self);
end;

procedure TBafSgColumn.SetCellType(const Value: TBafPageCellType);
var
  LRow: integer;
  LGrid: TBafSimpleGrid;
begin
  if FCellType <> Value then begin
    FCellType := Value;
    LGrid := Parents.SimpleGrid;
    for LRow := 0 to LGrid.RowCount(rtData) - 1 do
      LGrid.Cells[rtData, Index, LRow].CellType := Value;
  end;
end;

procedure TBafSgColumn.SetLinkCommand(const Value: string);
{ TODO : prüfen }
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);
begin
  mvSortCell := Index;
  mvSortUp := AUp;
  case CellType of
    ctCurr, ctCurr4, ctInt, ctCurrInt: mvSortType := soNumber;
    ctDate, ctDateMin, ctDateSek: mvSortType := soDate;
    else
      mvSortType := soText;
  end;
end;

procedure TBafSgColumn.ToggleSort;
var
  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, stXXGrid]) 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: TBafPageParents);
var
  LRow: TBafSgRow;
begin
  LRow := TBafSgRow.Create(true);
  LRow.RowType := ARowType;
  LRow.FParents.CreateFrom(AParents);
  LRow.FParents.Row := LRow;
  if AParents.Segment.SegmentType = stMap then
    LRow.FParents.Columns := AParents.Map.Columns
  else
    LRow.FParents.Columns := AParents.SimpleGrid.Columns;
  LRow.RowIndex := 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;
  if ACol >= 0 then
    result := (Items[ACol] as TBafSgCell);
end;

function TBafSgRow.GetCellByFieldName(AFieldName: string): TBafSgCell;
var
  LCol: integer;
begin
  LCol := -1;
  if Assigned(FParents.SimpleGrid) then
    LCol := FParents.SimpleGrid.Columns.FieldName2Ix(AFieldName)
  else if Assigned(FParents.Map) then
    LCol := FParents.Map.Columns.FieldName2Ix(AFieldName);
  result := GetCell(LCol);
end;

procedure TBafSgRow.SetHasCanged(const Value: boolean);
begin
  FHasCanged := Value;
  if Value then begin
    if FParents.Segment.SegmentType = stMap then
      FParents.Map.HasChanged := true
    else if FParents.Segment.SegmentType = stSGrid then begin
      FParents.SimpleGrid.HasChanged := true;
      if IsAdditionalRow then
        FParents.SimpleGrid.Row[rtData, RowIndex - 1].HasChanged := true;
    end
    else
      FParents.SimpleGrid.HasChanged := true;
  end;
end;

procedure TBafSgRow.SetMapCanCheck(const Value: boolean);
begin
  FCanCheck := Value;
  GetCellByFieldName(FParents.Map.Field_CanCheck).SetTextChange(
      IfThen(Value, 'Y', 'N'), false);
end;

procedure TBafSgRow.SetMapCaption(const Value: string);
begin
  FCaption := Value;
  GetCellByFieldName(FParents.Map.Field_Caption).SetTextChange(Value, false);
end;

procedure TBafSgRow.SetMapCaptionPos(const Value: TBafDiaLegendPos);
begin
  FCaptionPos := Value;
end;

procedure TBafSgRow.SetMapFontBold(const Value: boolean);
begin
  FFontBold := Value;
  GetCellByFieldName(FParents.Map.Field_FontBold).SetTextChange(
      IfThen(Value, 'Y', 'N'), false);
end;

procedure TBafSgRow.SetMapFontColor(const Value: TAlphaColor);
begin
  FFontColor := Value;
  GetCellByFieldName(FParents.Map.Field_FontColor).SetTextChange(
      IntToStr(Value), false);
end;

procedure TBafSgRow.SetMapFontSize(const Value: integer);
begin
  FFontSize := Value;
  GetCellByFieldName(FParents.Map.Field_FontSize).SetTextChange(
      IntToStr(Value), false);
end;

procedure TBafSgRow.SetMapHint(const Value: string);
begin
  FHint := Value;
  GetCellByFieldName(FParents.Map.Field_Hint).SetTextChange(Value, false);
end;

procedure TBafSgRow.SetMapIsChecked(const Value: boolean);
begin
  FIsChecked := Value;
  if FParents.Map.Field_IsChecked <> '' then
    GetCellByFieldName(FParents.Map.Field_IsChecked).SetTextChange(
        IfThen(Value, 'Y', 'N'), false);
end;

{ TBafSgCell }

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

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

destructor TBafSgCell.Destroy;
begin

  inherited;
end;

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

function TBafSgCell.GetDisplayText(ANumFormat: boolean = false): string;
var
  LLookupHelper: TBafComboHelper;
  i: integer;
  LAlign: TBafAlignment;
begin
  if CellType = ctGuid then
    result := '°°°'
  else if CellType = ctIBAN then begin
    for i := 1 to Length(FText) do begin
      result := result + FText[i];
      if i mod 4 = 0 then
        result := result + ' ';
    end;
  end
  else if Password then
    for i := 1 to Length(FText) do
      result := result + '*'
  else begin
    if CellType in [ctLookup, ctLookupLive, ctLookupStatus] then begin
      if GetLookupHelper(LLookupHelper, false) then
        result := LLookupHelper.GetCaptionGuid(FText);
    end
    else if ANumFormat and (CellType in [ctInt, ctCurr, ctCurr4, ctCurrInt]) then begin
      result := FText;
      if result <> '' then begin
        if (FParents.Segment.SegmentType in [stValueList]) then
          LAlign := Alignment
        else
          LAlign := FParents.Column.CellAlignment;
        case LAlign of
          taRightJustify: result := Trim(FormatFloat('# ### ### ###', StrToInt64Def(FText, -1)));
          taDecimal2: result := Trim(FormatFloat('### ### ### ### ### ##0.00', StrToCurrDef(FText, -1)));
          taDecimal4: result := Trim(FormatFloat('### ### ### ### ### ##0.0000', StrToCurrDef(FText, -1)));
         end;
      end; // if result <> ''
    end // else if CellType in [ctInt, ctCurr, ctCurr4, ctCurrInt]
    else
      result := 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 := '';
        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;
begin
  result := false;
  LFill := false;
  if (CellType in [ctLookup, ctLookupStatus, ctLookupText])
      and Assigned(LookupHelper) then begin
    ALookupHelper := LookupHelper;
    result := true;
  end
  else if (FParents.Column.CellType in [ctLookup, ctLookupStatus, ctLookupText])
      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;
  end
  else if (CellType in [ctLookupLive]) and Assigned(LookupHelper) then begin
    ALookupHelper := LookupHelper;
    result := true;
    LFill := true;
  end;
  if LFill and (ALive or not ALookupHelper.Filled)
      and Assigned(FParents.Page.FOnFillLookupLiveEvent) then
    FParents.Page.FOnFillLookupLiveEvent(FParents.Page, ALookupHelper, Self);

end;

function TBafSgCell.GetReadOnly: boolean;
begin
  Result := FReadOnly or FParents.Column.CellReadOnly or FParents.Segment.ReadOnly;
end;

function TBafSgCell.GetYFieldName: string;
begin
  Result := FYFieldName;
  if result = '' then
    result := FDataFieldName;
end;

function TBafSgCell.ReplaceParameter(AText: string): string;
begin
  result := StringReplace(AText, '$CELL()', Text, [rfIgnoreCase, rfReplaceAll]);
end;

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

procedure TBafSgCell.SetCellColorString(ACellColor: string);
var
  s: string;
  LIx: integer;
begin
  s := UpperCase(Trim(ACellColor));
  if copy(s,1,2) = 'IX' then begin
    LIx := StrToIntDef(copy(s, 3, MaxInt), 0);
    CellColor := TBafCellColor.ccIndex;
    CellAlphaColor := BafIndex2Color(LIx);
  end
  else if (s = 'GREEN') or (s = '1') then
    CellColor := TBafCellColor.ccGreen
  else if (s = 'YELLOW') or (s = '2') then
    CellColor := TBafCellColor.ccYellow
  else if (s = 'RED') or (s = '3') then
    CellColor := TBafCellColor.ccRed
  else if (s = 'HEADER') then
    CellColor := TBafCellColor.ccHeader
  else
    CellColor := TBafCellColor.ccNone;
end;

procedure TBafSgCell.SetCellType(const Value: TBafPageCellType);
begin
  if FCellType <> Value then begin
    FCellType := Value;
  end;
end;

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

procedure TBafSgCell.SetText(const Value: string);
var
  LRow: integer;
begin
  if (Length(Value) > 0) and (Value[1] = '_') then
    LRow := 1;
  if ((CellType = ctRadio) or (Parents.Column.CellType = ctRadio))
      and BafIsYesChar(Value) then begin
    if Parents.Segment.SegmentType in [stGrid, stXGrid, stXXGrid] then begin
      for LRow := 0 to Parents.Segment.Grid.RowCount(rtData) - 1 do
        Parents.Segment.Grid.Cells[rtData, Parents.Column.Index, LRow].Text := 'N';
    end;
  end;
  FText := Value;
end;

procedure TBafSgCell.SetTextChange(AText: string; AAlwaysChanged: boolean;
    AChangeWhileLoading: boolean; ALookupAsText: boolean);
// write text like an edit
var
  LAbort, LChanged: boolean;
  i: integer;
  s, LIgnore: string;
  LCharCase: TEditCharCase;

  procedure lokType;
  var
    i: integer;
    LLookupHelper: TBafComboHelper;
  begin
    case CellType of
      ctCurr, ctCurr4, ctCurrInt: 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]), BAFYESCHAR, BAFNOCHAR);
      ctLookup: if ALookupAsText then begin
        if Parents.Segment.SegmentType in [stGrid, stXGrid, stXXGrid] then
          AText := Parents.Column.CellLookupHelper.GetGuidFromCaption(AText)
        else
          AText := LookupHelper.GetGuidFromCaption(AText);
      end;
    end;
  end; // procedure lokType

  procedure lokChars;
  var
    i: integer;
  begin
    if FParents.Segment.SegmentType in [stGrid, stXGrid, stXXGrid] then
      LIgnore := FParents.Column.CellCharsIgnore
    else if FParents.Segment.SegmentType in [stValueList] then
      LIgnore := CharsIgnore;
    if LIgnore <> '' then begin
      s := '';
      for i := 1 to Length(AText) do begin
        if Pos(AText[i], LIgnore) = 0 then
          s := s + AText[i];
      end;
      AText := s;
    end;
  end; // procedure lokChars

begin
  LAbort := false;
  LChanged := Text <> AText;
  if not LAbort then begin
    if Text <> AText then begin
      lokType;
      lokChars;
      Text := AText;
    end;
    if AChangeWhileLoading or not FParents.Segment.IsDataLoading then
      HasChanged := AAlwaysChanged or LChanged;
  end;
  Parents.Page.SetStatus;
end;

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

procedure TBafSgCell.ToggleBool;
var
  s: string;
  LAbort: boolean;
begin
  if not ReadOnly and not Parents.Column.CellReadOnly then begin
    s := IfThen(BafIsYesChar(FText), BAFNOCHAR, BAFYESCHAR);
    LAbort := false;
    FParents.Page.DoEditEvent(FParents, false, s, LAbort, Command, HasChanged);
    if not LAbort then
      SetTextChange(s);
    FParents.Page.DoEditEvent(FParents, true, s, LAbort, Command, HasChanged);
  end; // DoEditEvent
end;

procedure TBafSgCell.ToggleRadio;
var
  s: string;
  LAbort: boolean;
begin
  if not ReadOnly and not Parents.Column.CellReadOnly then begin
    s := 'Y';
    LAbort := false;
    FParents.Page.DoEditEvent(FParents, false, s, LAbort, Command, HasChanged);
    if not LAbort then
      SetTextChange(s);
    FParents.Page.DoEditEvent(FParents, true, s, LAbort, Command, true);
  end; // DoEditEvent
end;

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

procedure TBafSgCell.ToggleTriEx;
begin
  case AnsiUpperCase(FText + ' ')[1] of
    '0': SetTextChange('?');
    '?': SetTextChange('!');
    else
      SetTextChange('0');
  end;
end;

procedure TBafSgCell.ToggleTriPlus;
begin
  case AnsiUpperCase(FText + ' ')[1] of
    '0': SetTextChange('-');
    '-': SetTextChange('+');
    else
      SetTextChange('0');
  end;
end;

{ TBafPageUtils }

class function TBafPageUtils.GetButtonText(AChar: Char;
    ASegmentType: TBafPageSegmentType): 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
//    'M', 'm': result := #$f5a0;   // Map
    'M', 'm': result := #$f279;   // Map
    'O', 'o': result := #$f06e;   // eye
    'P', 'p': result := #$f328;   // clipboard Paste
    'X', 'x': result := #$f1c3;   // Excel
    'Y', 'y': result := #$f1c1;   // PDF
    '+': if ASegmentType in [stMap] then
      result := #$f0fe   // plus-square
    else
      result := #$f055;   // plus-circle
    '-': result := #$f146;   // minus-square
    '?': result := #$f059;        // question-circle
  end;
end;

class procedure TBafPageUtils.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;

{ TBafDiagram }

function TBafDiagram.AddRow(ACol0: string): integer;
var
  LRow: TBafDiaRow;
begin
  LRow := TBafDiaRow.Create;
  SetLength(LRow.Cells, Columns.Count);
  result := FRows.AddObject(ACol0, LRow);
end;

function TBafDiagram.AddRowSl(ACol0: string): TBafDiaRow;
begin
  result :=TBafDiaRow.Create;
  SetLength(result.Cells, Columns.Count);
  FRows.AddObject(ACol0, result);
end;

function TBafDiagram.CalcXDate(AValue: TDateTime): single;
begin
  result := FDiaLeft + (AValue - FMinDateZoom) * FScaleHorz;
end;

function TBafDiagram.CalcXDateVal(APos: single): TDateTime;
begin
  result := (APos - FDiaLeft) / FScaleHorz + FMinDateZoom;
end;

function TBafDiagram.CalcY(AValue: double): single;
begin
  if LegendPos = lpTop then
    result := FLegendHeight + (FFontHeight / 2) + (FMaxValueZoom - AValue) * FScaleVert
  else
    result := (FFontHeight / 2) + (FMaxValueZoom - AValue) * FScaleVert;
end;

function TBafDiagram.CalcYVal(APos: single): double;
begin
  if LegendPos = lpTop then
    result := FMaxValueZoom - ((APos - FFontHeight / 2 - FLegendHeight) / FScaleVert)
  else
    result := FMaxValueZoom - ((APos - FFontHeight / 2) / FScaleVert);
end;

constructor TBafDiagram.Create(ASeg: TBafPageSegment);
begin
  inherited Create(ASeg.FParents.Page);
  FScrollHorzVisible := true;
  FScrollVertVisible := true;
  CreateComponents;
  FParents.CreateFrom(ASeg.FParents);
  FParents.Dia := Self;
  FColumns := TBafDiaColumns.Create(Self);
  FRows := TStringList.Create;
  FRows.OwnsObjects := true;
  FRows.Sorted := true;
  FRows.Duplicates := dupError;
  LoadStyle;
end;

procedure TBafDiagram.CreateComponents;
begin
  FVertScrollBar := TScrollBar.Create(Self);
  FVertScrollBar.Parent := Self;
  FVertScrollBar.Orientation := TOrientation.Vertical;
  FVertScrollBar.Align := TAlignLayout.Right;
  FVertScrollBar.OnChange := ScrollChangeVert;
  FPanelLeft := TPanel.Create(Self);
  FPanelLeft.Parent := Self;
  FPanelLeft.Align := TAlignLayout.Client;
  FPanelLeft.StyleLookup := 'pushpanel2';
  FHorzScrollBar := TScrollBar.Create(Self);
  FHorzScrollBar.Parent := FPanelLeft;
  FHorzScrollBar.Align := TAlignLayout.Bottom;
  FHorzScrollBar.OnChange := ScrollChangeHorz;
  FPanelDesk := TPanel.Create(Self);
  FPanelDesk.Parent := FPanelLeft;
  FPanelDesk.Align := TAlignLayout.Client;
  FPanelDesk.OnPaint := DiaPaint;
  FPanelDesk.ClipChildren := true;
  FPanelDesk.OnMouseUp := PanelDeskMouseUp;
  FPanelDesk.OnMouseDown := PanelDeskMouseDown;
  FPanelDesk.OnDblClick := PanelDeskDblClick;
  FPanelDesk.OnMouseEnter := PanelMouseEnter;
  FPanelDesk.OnMouseLeave := PanelMouseLeave;
  FPanelDesk.OnMouseMove := PanelMouseMove;
  FPanelDesk.StyleLookup := 'pushpanel2';
end;

destructor TBafDiagram.Destroy;
begin

  inherited;
end;

procedure TBafDiagram.DiaPaint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
begin
  Canvas.BeginScene;
  try
    PaintLegend(Canvas, ARect);
    PaintAxlesVert(Canvas);
    PaintAxlesHorz(Canvas);
    case DiaType of
      dtLine: PaintLine(Canvas);
      dtBar: PaintBar(Canvas);
      dtSbsBar: PaintSbs(Canvas);
      dtStack: PaintStack(Canvas);
    end;
  finally
    Canvas.EndScene;
  end;
  if Assigned(FLinkedDiagram) then
    FLinkedDiagram.LinkValues(Self);
end;

function TBafDiagram.GetCells(ACol, ARow: integer): double;
begin
  result := (FRows.Objects[ARow] as TBafDiaRow).Cells[ACol];
end;

function TBafDiagram.GetDiaTypeName: string;
begin
  case FDiaType of
    dtNone: result := 'none';
    dtLine: result := 'line';
    dtBar: result := 'bar';
    dtSbsBar: result := 'sbs';
    dtStack: result := 'stack';
    dtCake: result := 'cake';
  end;
end;

function TBafDiagram.GetLegendPosName: string;
begin
  case LegendPos of
    lpNone: result := 'none';
    lpLeft: result := 'left';
    lpBottom: result := 'bottom';
    lpRight: result := 'right';
    lpTop: result := 'top';
  end;
end;

procedure TBafDiagram.LinkValues(ASource: TBafDiagram);
var
  LPaint, LScroll: boolean;

  procedure lokDate;
  begin
    if FScaleHorz <> ASource.FScaleHorz then begin
      FScaleHorz := ASource.FScaleHorz;
      LScroll := true;
    end;
    if FMinDateZoom <> ASource.FMinDateZoom then begin
      FMinDateZoom := ASource.FMinDateZoom;
      LScroll := true;
    end;
    if FMaxDateZoom <> ASource.FMaxDateZoom then begin
      FMaxDateZoom := ASource.FMaxDateZoom;
      LScroll := true;
    end;
  end; // procedure lokDate

begin
  LPaint := false;
  LScroll := false;
  if ((FLegendPos = lpLeft) and (ASource.LegendPos = lpLeft))
      or ((FLegendPos = lpRight) and (ASource.LegendPos = lpRight)) then begin
    if FLinkedDiaColumnWidth <> ASource.FColumnWidth then begin
      FLinkedDiaColumnWidth := ASource.FColumnWidth;
      LPaint := true;
    end;
    if FLinkedDiaDiaLeft < ASource.FDiaLeft then begin
      FLinkedDiaDiaLeft := ASource.FDiaLeft;
      LPaint := true;
    end;
    lokDate;
  end
  else if FLinkedDiaColumnWidth > 0 then begin
    FLinkedDiaColumnWidth := 0;
    LPaint := true;
  end;
  if ((FLegendPos in [lpTop, lpBottom])
      and (ASource.LegendPos in [lpTop, lpBottom])) then begin
    lokDate;
    LPaint := true;
  end;
  if LPaint then
    Repaint;
  if LScroll then begin
    FScaleHorz := (FRectDia.Width - 4 - FDiaLeft) / (1 + FMaxDateZoom - FMinDateZoom);
    FHorzScrollBar.Enabled := (FMaxDateZoom - FMinDateZoom) < (0.98) * (FMaxDate - FMinDate);
    FHorzScrollBar.OnChange := nil;
    FHorzScrollBar.ViewportSize := FMaxDateZoom - FMinDateZoom;
    FHorzScrollBar.Value := FMinDateZoom;
    FScrollHorzOld := FHorzScrollBar.Value;
    FHorzScrollBar.OnChange := ScrollChangeHorz;
    FParents.Page.StartTimer('repaint');
  end;
// procedure TBafDiagram.LinkValues
end;

procedure TBafDiagram.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;

  function lokFarbe(AName: string): TAlphaColor;
  begin
    LObject2 := lokFindObject(LObject, AName);
    if Assigned(LObject2) and (LObject2 is TRectangle) then begin
      LRectangle := TRectangle(LObject2);
      result := LRectangle.Fill.Color;
    end;
  end;

begin
  LObject := FMX.Types.FindStyleResource('bafcolors');
  if Assigned(LObject) and (LObject is TLayout) then begin
//    FColorBack := lokFarbe('backgroundcolor');
//    FColorSelection := lokFarbe('selection');
//    FColorHeader := lokFarbe('headerbackgroud');
//    FColorReadonly := lokFarbe('readonly');
    FFontColor := lokFarbe('text');
  end;
end;

procedure TBafDiagram.PanelMouseEnter(Sender: TObject);
begin
  FHasMouse := true;
  Repaint;
end;

procedure TBafDiagram.PanelMouseLeave(Sender: TObject);
begin
  FHasMouse := false;
  Repaint;
end;

procedure TBafDiagram.PanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
  FMouseX := X;
  FMouseY := Y;
  Repaint;
end;

procedure TBafDiagram.PaintAxlesHorz(Canvas: TCanvas);
var
  LDateWidth, LDateWidthShort, LXMin, LXMax, LX: single;

  procedure lokLine(AX, AOpacity: single; ACaption: boolean; AText: string);
  begin
    if (AX > (FDiaLeft - 1)) and (AX < FRectDia.Right) then begin
      Canvas.DrawLine(PointF(AX, FRectDia.Top + 0.25 * FFontHeight),
          PointF(AX, FRectDia.Bottom - 1.25 * FFontHeight), AOpacity);
      if ACaption then
        Canvas.FillText(RectF(AX - LDateWidth,
            FRectDia.Bottom - FFontHeight * 1.4,
            AX + LDateWidth, FRectDia.Bottom - FFontHeight * 0.2),
          AText, false, AOpacity, [], TTextAlign.Center);
    end;
  end; // procedure lokLine

  procedure lokMainLinesDate;
  begin
    LXMin := CalcXDate(FMinDateZoom + 0.5);
    LXMax := CalcXDate(FMaxDateZoom + 0.5);
    lokLine(LXMin, 1, true, FormatDateTime('dd.mm.yyyy', FMinDateZoom));
    lokLine(LXMax, 1, true, FormatDateTime('dd.mm.yyyy', FMaxDateZoom));
  end; // procedure lokMainLines

  procedure lokCheckLine(AX, AOpacity: single; ACaption: boolean; AText: string);
  var
    LLength: single;
  begin
    if (Abs(AX - LXMin) > 5) and (Abs(AX - LXMax) > 5) then begin
      LLength := Canvas.TextWidth(AText) + (LDateWidth / 2);
      if ACaption then
        ACaption := (Abs(AX - LXMin) > LLength) and (Abs(AX - LXMax) > LLength);
      lokLine(AX, AOpacity, ACaption, AText);
    end;
  end; // procedure lokCheckLine

  procedure lokAddLines;
  var
    LDaysLong, LDaysShort: single;
    i, LDow: integer;
    LDay, LMonth, LYear: word;
  begin
    LDaysLong := (FMaxDateZoom - FMinDateZoom) * LDateWidth
        / (FRectDia.Width - FDiaLeft - 4);
    LDaysShort := (FMaxDateZoom - FMinDateZoom) * LDateWidthShort
        / (FRectDia.Width - FDiaLeft - 4);
    for i := trunc(FMinDateZoom) to trunc(FMaxDateZoom) do begin
      if LDaysShort < 0.5 then begin
        LX := CalcXDate(i + 0.5);
        lokCheckLine(LX, 0.66, true, FormatDateTime('dd.mm', i));
      end
      else if LDaysShort < 3 then begin
        LX := CalcXDate(i + 0.5);
        LDow := DayOfWeek(i);
        case LDow of
          1, 7: lokCheckLine(LX, 0.2, false, FormatDateTime('dd.mm', i));
          2: lokCheckLine(LX, 0.66, true, FormatDateTime('dd.mm', i));
        else
          lokCheckLine(LX, 0.66, false, FormatDateTime('dd.mm', i));
        end;
      end
      else if LDaysShort < 6 then begin
        LX := CalcXDate(i + 0.5);
        DecodeDate(i, LYear, LMonth, LDay);
        if LDay = 1 then
          lokCheckLine(LX, 1, true, FormatDateTime('dd.mm', i))
        else begin
          LDow := DayOfWeek(i);
          case LDow of
            1, 7: lokCheckLine(LX, 0.2, false, FormatDateTime('dd.mm', i));
          else
            lokCheckLine(LX, 0.5, false, FormatDateTime('dd.mm', i));
          end;
        end;
      end
      else if LDaysShort < 20 then begin
        LX := CalcXDate(i + 0.5);
        DecodeDate(i, LYear, LMonth, LDay);
        if (LDay = 1) and (LMonth = 1) then
          lokCheckLine(LX, 1, true, FormatDateTime('dd.mm', i))
        else if (LDay = 1) then
          lokCheckLine(LX, 0.66, true, FormatDateTime('dd.mm', i));
      end
      else if LDaysLong < 100 then begin
        LX := CalcXDate(i + 0.5);
        DecodeDate(i, LYear, LMonth, LDay);
        if (LDay = 1) and (LMonth = 1) then
          lokCheckLine(LX, 1, true, FormatDateTime('dd.mm.yyyy', i))
        else if (LDay = 1)  then
          lokCheckLine(LX, 0.66, false, '');
      end
      else if LDaysLong < 360 then begin
        LX := CalcXDate(i + 0.5);
        DecodeDate(i, LYear, LMonth, LDay);
        if (LDay = 1) and (LMonth = 1) then
          lokCheckLine(LX, 1, true, FormatDateTime('dd.mm.yyyy', i))
        else if (LDay = 1) and (LMonth in [1, 4, 7, 10]) then
          lokCheckLine(LX, 0.66, false, '');
      end
    end;
  end; // procedure lokAddLines

begin
  if DiaType in [dtLine, dtBar, dtSbsBar, dtStack] then begin
    LDateWidth := Canvas.TextWidth('31.12.2022');
    LDateWidthShort := Canvas.TextWidth('31.12');
    Canvas.Stroke.Color := FFontColor;
    Canvas.Fill.Color := FFontColor;
    if Columns.Items[0].ColumnType = dtDate then
      lokMainLinesDate;
    lokAddLines;
  end;
// procedure TBafDiagram.PaintAxlesHorz
end;

procedure TBafDiagram.PaintAxlesVert(Canvas: TCanvas);
var
  LY0, LYMax, LYMin, LYValue: single;
  LCaption: boolean;

  procedure lokLine(AY, AValue: single; AFull, ACaption: boolean);
  begin
    if (AY > FRectDia.Top) and (AY < (FRectDia.Bottom - FFontHeight * 1.4)) then begin
      Canvas.DrawLine(PointF(FDiaLeft, AY), PointF(FRectDia.Right - 4, AY),
          IfThen(AFull, 1, 0.66));
      if ACaption then
        Canvas.FillText(RectF(4, AY - FFontHeight * 1.2, FDiaLeft, AY + FFontHeight * 1.2),
          FormatFloat('0', AValue), false, IfThen(AFull, 1, 0.66), [],
          TTextAlign.Leading);
    end;
  end; // procedure lokLine

  procedure lokMainLines;
  begin
    LY0 := CalcY(0);
    LYMax := CalcY(FMaxValue);
    LYMin := CalcY(FMinValue);
    lokLine(LYMax, FMaxValue, true, true);
    lokLine(LYMin, FMinValue, true, true);
    LCaption := (Abs(LY0 - LYMax) > 2 * FFontHeight)
        and (Abs(LY0 - LYMin) > 2 * FFontHeight);
    if (Abs(LY0 - LYMax) > FFontHeight) and (Abs(LY0 - LYMin) > FFontHeight) then
      lokLine(LY0, 0, true, LCaption);
  end; // procedure lokMainLines

  procedure lokAddLines;
  var
    i, LMaxLines: integer;
    LRange: single;
    LSteps, LValue: currency;
  begin
    LMaxLines := round(FRectDia.Height / (5 * FFontHeight));
    LRange := FMaxValueZoom - FMinValueZoom;
    for i := Low(BAF_STEPS) + 1 to High(BAF_STEPS) do begin
      if (LRange / BAF_STEPS[i]) < LMaxLines then begin
        LSteps := BAF_STEPS[i - 1];
        Break;
      end;
    end;
    for i := round(10 * FMinValueZoom / LSteps) to round(10 * FMaxValueZoom / LSteps) do begin
      if i mod (10) = 0 then begin
        LValue := i * LSteps / 10;
        LYValue := CalcY(LValue);
        if (Abs(LYValue - LYMax) > 1.25 * FFontHeight) and (Abs(LYValue - LYMin) > 1.25 * FFontHeight)
            and (Abs(LYValue - LY0) > 1.25 * FFontHeight) then
          lokLine(LYValue, LValue, false, true)
        else if (Abs(LYValue - LYMax) > 0.4 * FFontHeight) and (Abs(LYValue - LYMin) > 0.4 * FFontHeight)
            and (Abs(LYValue - LY0) > 0.4 * FFontHeight) then
          lokLine(LYValue, LValue, false, false);
      end;
    end;
  end; // procedure lokAddLines

begin
  if DiaType in [dtLine, dtBar, dtSbsBar, dtStack] then begin
    Canvas.Stroke.Color := FFontColor;
    Canvas.Fill.Color := FFontColor;
    Canvas.Stroke.Thickness := 1;
    lokMainLines;
    lokAddLines;
  end;
// procedure TBafDiagram.PaintAxlesVert
end;

procedure TBafDiagram.PaintBar(Canvas: TCanvas);
var
  LRow, LCol, LMin, LMax: integer;
  LDiaRow: TBafDiaRow;
  LYUnten, LYOben, LX1, LX2: single;
  LDiaCol: TBafDiaColumn;
begin
  LMin := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMinDateZoom));
  LMax := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMaxDateZoom));
  for LRow := LMin to LMax do begin
    LX1 := CalcXDate(FMinDateZoom + LRow - LMin - 0.01) - 0.25;
    LX2 := CalcXDate(FMinDateZoom + LRow - LMin + 1.01) + 0.25;
    if (LRow >= 0) and (LRow < FRows.Count) then begin
      LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
      for LCol := 1 to Columns.Count - 1 do begin
        LDiaCol := Columns.Items[LCol];
        Canvas.Fill.Color := LDiaCol.FColor;
        if LDiaRow.Cells[LCol] > 0 then begin
          LYOben := CalcY(System.Math.Max(System.Math.Min(LDiaRow.Cells[LCol],
              FMaxValueZoom), FMinValueZoom));
          LYUnten := CalcY(System.Math.Max(System.Math.Min(0, FMaxValueZoom), FMinValueZoom));
        end
        else begin
          LYOben := CalcY(System.Math.Max(System.Math.Min(0, FMaxValueZoom), FMinValueZoom));
          LYUnten := CalcY(System.Math.Max(System.Math.Min(LDiaRow.Cells[LCol],
              FMaxValueZoom), FMinValueZoom));
        end;
        if LYOben - LYUnten < - 0.3 then
          Canvas.FillRect(RectF(LX1, LYOben, LX2, LYUnten), LDiaCol.Alpha);
      end;
    end;
  end;
end;

procedure TBafDiagram.PaintLegend(Canvas: TCanvas; const ARect: TRectF);

  procedure lokCalcColumnWidth(ALeftRight: boolean);
  var
    i: integer;
    s: string;
  begin
    FColumnWidth := 0;
    for i := 1 to FColumns.Count - 1 do begin
      s := FColumns.Items[i].Caption;
      FColumnWidth := System.Math.Max(FColumnWidth, Canvas.TextWidth(s));
    end;
    FColumnWidth := FColumnWidth + 30;
    if ALeftRight then
      FColumnWidth := System.Math.Max(FColumnWidth, FLinkedDiaColumnWidth);
  end; // procedure lokCalcColumnWidth

  procedure lokPaintVert(ARight: boolean);
  var
    i: integer;
    LColumn: TBafDiaColumn;
    LLeft, LTop: single;
    LDate: TDateTime;
    LValueX: double;

    procedure lokValuesMouseV;
    begin
      LDate := CalcXDateVal(FMouseX);
      LValueX := CalcYVal(FMouseY);
      if (LValueX >= FMinValueZoom) and (LValueX <= FMaxValueZoom) then begin
        LTop := ARect.Bottom - 3.75 * FFontHeight;
        Canvas.FillText(RectF(LLeft + 15, LTop, LLeft + FColumnWidth, LTop + FFontHeight),
            FormatFloat('0.00', LValueX), false, 1, [], TTextAlign.Leading);
      end;
      if (LDate >= FMinDateZoom) and (LDate <= FMaxDateZoom) then begin
        LTop := ARect.Bottom - 2.5 * FFontHeight;
        Canvas.FillText(RectF(LLeft + 15, LTop, LLeft + FColumnWidth, LTop + FFontHeight),
            FormatDateTime('dd.mm.yyyy', LDate), false, 1, [], TTextAlign.Leading);
      end;
    end; // procedure lokValuesMouseV

  begin
    lokCalcColumnWidth(true);
    if ARight then begin
      FRectDia := ARect;
      FRectDia.Right := FRectDia.Right - FColumnWidth;
      LLeft := FRectDia.Right + 2;
    end
    else begin
      FRectDia := ARect;
      FRectDia.Left := FRectDia.Left + FColumnWidth;
      LLeft := ARect.Left + 2;
    end;
    LTop := ARect.Top + 5;
    for i := 1 to FColumns.Count - 1 do begin
      LColumn := FColumns.Items[i];
      Canvas.Fill.Color := LColumn.Color;
      Canvas.Stroke.Color := LColumn.Color;
      Canvas.FillRect(RectF(LLeft, LTop + 4, LLeft + 10, LTop + 14), 1);
      Canvas.Fill.Color := FFontColor;
      Canvas.Stroke.Color := FFontColor;
      Canvas.FillText(RectF(LLeft + 15, LTop, LLeft + FColumnWidth, LTop + FFontHeight),
          LColumn.Caption, false, 1, [], TTextAlign.Leading);
      LTop := LTop + FFontHeight * 1.2;
    end;
    if (LTop < (ARect.Bottom - 4.75 * FFontHeight)) and FHasMouse then
      lokValuesMouseV;
  end; // procedure lokPaintVert

  procedure lokPaintHorz(ABottom: boolean);
  var
    i, LRows, LCols: integer;
    LTop, LLeft: single;
    LColumn: TBafDiaColumn;
  begin
    lokCalcColumnWidth(false);
    LCols := System.Math.Max(1, trunc(ARect.Width / FColumnWidth));
    LRows := (FColumns.Count - 1) div LCols + 1;
    if ABottom then begin
      FRectDia := ARect;
      FLegendHeight := LRows * 1.2 * FFontHeight;
      FRectDia.Bottom := FRectDia.Bottom - FLegendHeight - 0.6 * FFontHeight;
      for i := 1 to FColumns.Count - 1 do begin
        LColumn := FColumns.Items[i];
        Canvas.Fill.Color := LColumn.Color;
        Canvas.Stroke.Color := LColumn.Color;
        LLeft := 5 + ((i - 1) mod LCols) * FColumnWidth;
        LTop := ARect.Bottom - 5 - FLegendHeight
            + ((i - 1) div LCols) * 1.2 * FFontHeight;
        Canvas.FillRect(RectF(LLeft, LTop + 4, LLeft + 10, LTop + 14), 1);
        Canvas.Fill.Color := FFontColor;
        Canvas.Stroke.Color := FFontColor;
        Canvas.FillText(RectF(LLeft + 15, LTop, LLeft + FColumnWidth, LTop + FFontHeight),
            LColumn.Caption, false, 1, [], TTextAlign.Leading);
      end;
    end
    else begin
      FRectDia := ARect;
      FLegendHeight := LRows * 1.2 * FFontHeight;
      FRectDia.Top := FRectDia.Top + FLegendHeight;
      for i := 1 to FColumns.Count - 1 do begin
        LColumn := FColumns.Items[i];
        Canvas.Fill.Color := LColumn.Color;
        Canvas.Stroke.Color := LColumn.Color;
        LLeft := 5 + ((i - 1) mod LCols) * FColumnWidth;
        LTop := 5 + ((i - 1) div LCols) * 1.2 * FFontHeight;
        Canvas.FillRect(RectF(LLeft, LTop + 4, LLeft + 10, LTop + 14), 1);
        Canvas.Fill.Color := FFontColor;
        Canvas.Stroke.Color := FFontColor;
        Canvas.FillText(RectF(LLeft + 15, LTop, LLeft + FColumnWidth, LTop + FFontHeight),
            LColumn.Caption, false, 1, [], TTextAlign.Leading);
      end;
    end;
  end; // procedure lokPaintHorz

begin
  FFontHeight := Canvas.TextHeight('Wy');
  case LegendPos of
    lpLeft: lokPaintVert(false);
    lpRight: lokPaintVert(true);
    lpTop: lokPaintHorz(false);
    lpBottom: lokPaintHorz(true);
  else
    FRectDia := ARect;
  end;
  if FScaleVert = 0 then
    Zoom0;
// procedure TBafDiagram.PaintLegend
end;

procedure TBafDiagram.PaintLine(Canvas: TCanvas);
var
  LRow, LCol, LMin, LMax: integer;
  LDiaRow: TBafDiaRow;
  LY1, LY2, LX1, LX2: single;
  LDiaCol: TBafDiaColumn;
begin
  Canvas.Stroke.Thickness := System.Math.Max(Sqrt(FRectDia.Height / 100), 1);
  LMin := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMinDateZoom));
  LMax := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMaxDateZoom));
  for LCol := 1 to Columns.Count - 1 do begin
    LDiaCol := Columns.Items[LCol];
    Canvas.Stroke.Color := LDiaCol.FColor;
    for LRow := LMin to LMax do begin
      if (LRow >= 0) and (LRow < FRows.Count) then begin
        LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
        LX2 := CalcXDate(FMinDateZoom + LRow - LMin + 0.5);
        LY2 := CalcY(LDiaRow.Cells[LCol]);
        if LRow > LMin then
          Canvas.DrawLine(PointF(LX1, LY1), PointF(LX2, LY2), LDiaCol.Alpha);
        LX1 := LX2;
        LY1 := LY2;
      end;
    end;
  end;
  Canvas.Stroke.Thickness := 1
// procedure TBafDiagram.PaintLine
end;

procedure TBafDiagram.PaintSbs(Canvas: TCanvas);
var
  LRow, LCol, LMin, LMax, LColCount: integer;
  LDiaRow: TBafDiaRow;
  LYUnten, LYOben, LX1, LX2: single;
  LDiaCol: TBafDiaColumn;
begin
  LMin := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMinDateZoom));
  LMax := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMaxDateZoom));
  LColCount := Columns.Count - 1;
  if LColCount < 1 then
    exit;
  for LRow := LMin to LMax do begin
    if (LRow >= 0) and (LRow < FRows.Count) then begin
      LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
      for LCol := 1 to Columns.Count - 1 do begin
        LDiaCol := Columns.Items[LCol];
        Canvas.Fill.Color := LDiaCol.FColor;
        LX1 := CalcXDate(FMinDateZoom + LRow - LMin + ((LCol - 1) / LColCount));
        LX2 := CalcXDate(FMinDateZoom + LRow - LMin + (LCol / LColCount));
        if LDiaRow.Cells[LCol] > 0 then begin
          LYOben := CalcY(System.Math.Max(System.Math.Min(LDiaRow.Cells[LCol],
              FMaxValueZoom), FMinValueZoom));
          LYUnten := CalcY(System.Math.Max(System.Math.Min(0, FMaxValueZoom), FMinValueZoom));
        end
        else begin
          LYOben := CalcY(System.Math.Max(System.Math.Min(0, FMaxValueZoom), FMinValueZoom));
          LYUnten := CalcY(System.Math.Max(System.Math.Min(LDiaRow.Cells[LCol],
              FMaxValueZoom), FMinValueZoom));
        end;
        if LYOben - LYUnten < - 0.3 then
          Canvas.FillRect(RectF(LX1, LYOben, LX2, LYUnten), LDiaCol.Alpha);
      end;
    end;
  end;
end;

procedure TBafDiagram.PaintStack(Canvas: TCanvas);
var
  LRow, LCol, LMin, LMax: integer;
  LDiaRow: TBafDiaRow;
  LYUnten, LYOben, LX1, LX2: single;
  LSum: double;
  LDiaCol: TBafDiaColumn;
begin
  LMin := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMinDateZoom));
  LMax := FRows.IndexOf(FormatDateTime('yyyy-mm-dd', FMaxDateZoom));
  for LRow := LMin to LMax do begin
    LX1 := CalcXDate(FMinDateZoom + LRow - LMin - 0.01) - 0.25;
    LX2 := CalcXDate(FMinDateZoom + LRow - LMin + 1.01) + 0.25;
    if (LRow >= 0) and (LRow < FRows.Count) then begin
      LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
      LSum := 0;
      for LCol := 1 to Columns.Count - 1 do begin
        LDiaCol := Columns.Items[LCol];
        Canvas.Fill.Color := LDiaCol.FColor;
        LYUnten := CalcY(System.Math.Max(System.Math.Min(LSum, FMaxValueZoom), FMinValueZoom));
        LSum := LSum + LDiaRow.Cells[LCol];
        LYOben := CalcY(System.Math.Max(System.Math.Min(LSum, FMaxValueZoom), FMinValueZoom));
        if LYOben - LYUnten < - 0.3 then
          Canvas.FillRect(RectF(LX1, LYOben, LX2, LYUnten), LDiaCol.Alpha);
      end;
    end;
  end;
end;

procedure TBafDiagram.PanelDeskDblClick(Sender: TObject);
begin
  Zoom0;
  Repaint;
end;

procedure TBafDiagram.PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if Button = TMouseButton.mbLeft then begin
    FMouseDownX := X;
    FMouseDownY := Y;
  end;
end;

procedure TBafDiagram.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LYVal0, LYVal1: double;
  LXDate0, LXDate1: TDateTime;

  procedure lokDate;
  begin
    LXDate0 := CalcXDateVal(X);
    LXDate1 := CalcXDateVal(FMouseDownX);
    FMinDateZoom := trunc(System.Math.Max(IfThen(LXDate0 < LXDate1, LXDate0, LXDate1), FMinDate));
    FMaxDateZoom := trunc(System.Math.Min(IfThen(LXDate0 > LXDate1, LXDate0, LXDate1), FMaxDate));
    FScaleHorz := (FRectDia.Width - 4 - FDiaLeft) / (1 + FMaxDateZoom - FMinDateZoom);
    FHorzScrollBar.Enabled := (FMaxDateZoom - FMinDateZoom) < (0.98) * (FMaxDate - FMinDate);
    FHorzScrollBar.OnChange := nil;
    FHorzScrollBar.ViewportSize := FMaxDateZoom - FMinDateZoom;
    FHorzScrollBar.Value := FMinDateZoom;
    FScrollHorzOld := FHorzScrollBar.Value;
    FHorzScrollBar.OnChange := ScrollChangeHorz;
  end; // procedure lokDate

  procedure lokY;
  begin
    LYVal0 := CalcYVal(Y);
    LYVal1 := CalcYVal(FMouseDownY);
    FMinValueZoom := System.Math.Max(IfThen(LYVal0 < LYVal1, LYVal0, LYVal1), FMinValue);
    FMaxValueZoom := System.Math.Min(IfThen(LYVal0 > LYVal1, LYVal0, LYVal1), FMaxValue);
    FScaleVert := (FRectDia.Height - 2 * FFontHeight) / (FMaxValueZoom - FMinValueZoom);
    FVertScrollBar.Enabled := (FMaxValueZoom - FMinValueZoom) < (0.98) * (FMaxValue - FMinValue);
    FVertScrollBar.OnChange := nil;
    FVertScrollBar.ViewportSize := FMaxValueZoom - FMinValueZoom;
    FVertScrollBar.Value := FMaxValue - FMaxValueZoom;
    FScrollVertOld := FVertScrollBar.Value;
    FVertScrollBar.OnChange := ScrollChangeVert;
  end; // procedure lokY

begin
  if (Button = TMouseButton.mbLeft) then begin
    if (ssCtrl in Shift) then begin
      if Columns.Items[0].ColumnType = dtDate then
        FHorzScrollBar.Value := FHorzScrollBar.Value
            - (CalcXDateVal(X) - CalcXDateVal(FMouseDownX));
      FVertScrollBar.Value := FVertScrollBar.Value
          + (CalcYVal(Y) - CalcYVal(FMouseDownY));
    end
    else if (ssAlt in Shift) and ((Abs(X - FMouseDownX) > 20)   // scale x or y
        or (Abs(Y - FMouseDownY) > 20)) then begin
      if Abs(Y - FMouseDownY) > Abs(X - FMouseDownX) then
        lokY
      else begin
        if Columns.Items[0].ColumnType = dtDate then
          lokDate;
      end;
    end
    else if (Abs(X - FMouseDownX) > 20) and (Abs(Y - FMouseDownY) > 20) then begin // scale both
      lokY;
      if Columns.Items[0].ColumnType = dtDate then
        lokDate;
    end;
    Repaint;
  end;
end;

procedure TBafDiagram.Prepare;

  procedure lokStack;
  var
    LCol, LRow: integer;
    LDiaRow: TBafDiaRow;
    LSum: double;
  begin
    for LRow := 0 to FRows.Count - 1 do begin
      LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
      LSum := 0;
      for LCol := 1 to FColumns.Count - 1 do
        LSum := LSum + LDiaRow.Cells[LCol];
      FMaxValue := System.Math.Max(FMaxValue, LSum);
    end;
    FMinValue := 0;
  end; // procedure lokStack

  procedure lokLine;
  var
    LCol, LRow: integer;
    LDiaRow: TBafDiaRow;
  begin
    for LRow := 0 to FRows.Count - 1 do begin
      LDiaRow := (FRows.Objects[LRow] as TBafDiaRow);
      for LCol := 1 to FColumns.Count - 1 do begin
        FMaxValue := System.Math.Max(FMaxValue, LDiaRow.Cells[LCol]);
        FMinValue := System.Math.Min(FMinValue, LDiaRow.Cells[LCol]);
      end;
    end;
  end; // procedure lokLine

  procedure lokDate;
  var
    i: integer;
    s: string;
  begin
    FMinDate := (FRows.Objects[0] as TBafDiaRow).Cells[0];
    FMaxDate := (FRows.Objects[FRows.Count - 1] as TBafDiaRow).Cells[0];
    for i := trunc(FMinDate) to trunc(FMaxDate) do begin
      s := FormatDateTime('yyyy-mm-dd', i);
      if FRows.IndexOf(s) < 0 then
        AddRow(s);
    end;
  end; // procedure lokDate

begin
  FMinValue := MaxInt;
  FMaxValue := - MaxInt;
  if FRows.Count = 0 then
    exit;
  case FDiaType of
    dtLine, dtBar, dtSbsBar: lokLine;
    dtStack: lokStack;


  end;

  if Columns.Items[0].ColumnType = dtDate then
    lokDate;

// procedure TBafDiagram.Prepare
end;

procedure TBafDiagram.Resize;
begin
  inherited;
  FParents.Segment.GridCalcHeight;
  FParents.Page.FLastCatOpenClose := FParents.Category;
  FParents.Page.StartTimer('dia_zoom0');
end;

procedure TBafDiagram.ScrollChangeHorz(Sender: TObject);
begin
  FMaxDateZoom :=  FMaxDateZoom + (FHorzScrollBar.Value - FScrollHorzOld);
  FMinDateZoom :=  FMinDateZoom + (FHorzScrollBar.Value - FScrollHorzOld);
  FScrollHorzOld := FHorzScrollBar.Value;
  Repaint;
end;

procedure TBafDiagram.ScrollChangeVert(Sender: TObject);
begin
  FMaxValueZoom :=  FMaxValueZoom -  (FVertScrollBar.Value - FScrollVertOld);
  FMinValueZoom :=  FMinValueZoom -  (FVertScrollBar.Value - FScrollVertOld);
  FScrollVertOld := FVertScrollBar.Value;
  Repaint;
end;

procedure TBafDiagram.SetCells(ACol, ARow: integer; const Value: double);
var
  LCells: TStringList;
begin
  while ARow <= (FRows.Count) do
    AddRow('');
  (FRows.Objects[ARow] as TBafDiaRow).Cells[ACol] := Value;
end;

procedure TBafDiagram.SetDiaTypeName(const Value: string);
begin
  if Value = 'line' then
    DiaType := dtLine
  else if Value = 'bar' then
    DiaType := dtBar
  else if Value = 'sbs' then
    DiaType := dtSbsBar
  else if Value = 'stack' then
    DiaType := dtStack
  else if Value = 'cake' then
    DiaType := dtCake
  else
    DiaType := dtNone;
end;

procedure TBafDiagram.SetLegendPosName(const Value: string);
begin
  if Value = 'left'
    then LegendPos := lpLeft
  else if Value = 'bottom'
    then LegendPos := lpBottom
  else if Value = 'right'
    then LegendPos := lpRight
  else if Value = 'top'
    then LegendPos := lpTop
  else
    LegendPos := lpNone;
end;

procedure TBafDiagram.SetScrollHorzVisible(const Value: boolean);
begin
  FScrollHorzVisible := Value;
  FHorzScrollBar.Visible := Value;
end;

procedure TBafDiagram.SetScrollVertVisible(const Value: boolean);
begin
  FScrollVertVisible := Value;
  FVertScrollBar.Visible := Value;
end;

procedure TBafDiagram.Zoom0;
begin
  FMinValueZoom := FMinValue;
  FMaxValueZoom := FMaxValue;
  FMaxDateZoom := FMaxDate;
  FMinDateZoom := FMinDate;
  FDiaLeft := System.Math.Max(Canvas.TextWidth(FormatFloat('0', FMinValue)),
      Canvas.TextWidth(FormatFloat('0', FMaxValue))) + 10;
  FDiaLeft := System.Math.Max(FDiaLeft, FLinkedDiaDiaLeft);
  FScaleVert := (FRectDia.Height - 2 * FFontHeight) / (FMaxValueZoom - FMinValueZoom);
  FScaleHorz := (FRectDia.Width - 4 - FDiaLeft) / (FMaxDateZoom - FMinDateZoom);
  FHorzScrollBar.Enabled := false;
  FHorzScrollBar.OnChange := nil;
  FHorzScrollBar.Max := FMaxDate;
  FHorzScrollBar.Min := FMinDate;
  FHorzScrollBar.ViewportSize := FMaxDate - FMinDate;
  FHorzScrollBar.Value := 0;
  FHorzScrollBar.OnChange := ScrollChangeHorz;

  FVertScrollBar.Enabled := false;
  FVertScrollBar.OnChange := nil;
  FVertScrollBar.Max := FMaxValue;
  FVertScrollBar.Min := FMinValue;
  FVertScrollBar.ViewportSize := FMaxValue - FMinValue;
  FVertScrollBar.Value := 0;
  FVertScrollBar.OnChange := ScrollChangeVert;

end;

{ TBafDiaColumns }

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

function TBafDiaColumns.AddColumn(AFieldName, ACaption: string;
  AColor: TAlphaColor): TBafDiaColumn;
begin
  result := Add;
  result.FieldName := AFieldName;
  result.Caption := ACaption;
  result.Color := AColor;
end;

function TBafDiaColumns.AddItem(Item: TBafDiaColumn; Index: Integer): TBafDiaColumn;
begin
  if Item = nil then
    Result := TBafDiaColumn.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 TBafDiaColumns.Create(ADia: TBafDiagram);
begin
  inherited Create(TBafDiaColumn);
  FParents := ADia.FParents;
  FParents.DiaColumns := Self;
end;

destructor TBafDiaColumns.Destroy;
begin

  inherited;
end;

function TBafDiaColumns.GetItem(Index: Integer): TBafDiaColumn;
begin
  Result := TBafDiaColumn(inherited GetItem(Index));
end;

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

procedure TBafDiaColumns.SetItem(Index: Integer; const Value: TBafDiaColumn);
begin
  inherited SetItem(Index, Value);
end;

{ TBafDiaColumn }

constructor TBafDiaColumn.Create(Collection: TCollection);
begin
  inherited;
  FParents := (Collection as TBafDiaColumns).Parents;
  FParents.DiaColumn := Self;
  FColIx := -1;
end;

destructor TBafDiaColumn.Destroy;
begin

  inherited;
end;

{ TBafMap }

function TBafMap.CalcX(AValue: integer): single;
begin
  result := FDestRect.Left + (AValue / FStretch);
end;

function TBafMap.CalcXVal(APos: single): integer;
begin
  result := round((APos - FDestRect.Left) * FStretch);
end;

function TBafMap.CalcY(AValue: integer): single;
begin
  result := FDestRect.Top + (AValue / FStretch);
end;

function TBafMap.CalcYVal(APos: single): integer;
begin
  result := round((APos - FDestRect.Top) * FStretch);
end;

constructor TBafMap.Create(ASeg: TBafPageSegment);
begin
  inherited Create(ASeg.FParents.Page);
  FScrollHorzVisible := true;
  FScrollVertVisible := true;
  CreateComponents;
  FParents.CreateFrom(ASeg.FParents);
  FParents.Map := Self;
  FBmpBack := FMX.Graphics.TBitmap.Create;
  FBmpPaint := FMX.Graphics.TBitmap.Create;
  FDataRowList := TObjectList.Create(true);
  FDataRowDisplayList := FDataRowList;
  FDataRowFilteredList := TObjectList.Create(false);
  FHeaderRowList := TObjectList.Create(true);
  FColumns := TBafSgColumns.Create(Self);
  FFontSizeScale := 1;
  FHintRow := -1;
  InitMonthColors;
end;

procedure TBafMap.CreateComponents;
begin
  FVertScrollBar := TScrollBar.Create(Self);
  FVertScrollBar.Parent := Self;
  FVertScrollBar.Orientation := TOrientation.Vertical;
  FVertScrollBar.Align := TAlignLayout.Right;
  FVertScrollBar.OnChange := ScrollChangeVert;
  FPanelLeft := TPanel.Create(Self);
  FPanelLeft.Parent := Self;
  FPanelLeft.Align := TAlignLayout.Client;
  FPanelLeft.StyleLookup := 'pushpanel2';
  FHorzScrollBar := TScrollBar.Create(Self);
  FHorzScrollBar.Parent := FPanelLeft;
  FHorzScrollBar.Align := TAlignLayout.Bottom;
  FHorzScrollBar.OnChange := ScrollChangeHorz;
  FPanelDesk := TPanel.Create(Self);
  FPanelDesk.Parent := FPanelLeft;
  FPanelDesk.Align := TAlignLayout.Client;
  FPanelDesk.OnPaint := MapPaint;
  FPanelDesk.ClipChildren := true;
  FPanelDesk.OnMouseUp := PanelDeskMouseUp;
  FPanelDesk.OnMouseDown := PanelDeskMouseDown;
  FPanelDesk.OnDblClick := PanelDeskDblClick;
  FPanelDesk.OnMouseEnter := PanelMouseEnter;
  FPanelDesk.OnMouseLeave := PanelMouseLeave;
  FPanelDesk.OnMouseMove := PanelMouseMove;
  FPanelDesk.StyleLookup := 'pushpanel2';
end;

destructor TBafMap.Destroy;
begin
  if (FDefinition <> '') and Assigned(FOnSaveRect) then
    FOnSaveRect(FParents, FDestRect, FDefinition,
        FHorzScrollBar.Value, FHorzScrollBar.ViewportSize,
        FVertScrollBar.Value, FVertScrollBar.ViewportSize, FStretch);
  FreeAndNil(FBmpPaint);
  FreeAndNil(FBmpBack);
  inherited;
end;

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

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

function TBafMap.GetDataRowCount: integer;
begin
  result := FDataRowList.Count;
end;

function TBafMap.GetDisplayRow(ARow: integer): TBafSgRow;
begin
  result := (FDataRowDisplayList[ARow] as TBafSgRow);
end;

function TBafMap.GetMapType: string;
begin
  case FMapType of
    mtCheck: result := 'check';
    mtMonthCalendar: result := 'mc';
  end;
end;

function TBafMap.GetMonthColors(ACol, ARow: integer): TAlphaColor;
begin
  result := FMonthColors[ACol mod 7, ARow mod 2];
end;

procedure TBafMap.InitMonthColors;
begin
  FMonthColors[0, 0] := $FF00DD00;
  FMonthColors[1, 0] := $FF00DDDD;
  FMonthColors[2, 0] := $FFFF0000;
  FMonthColors[3, 0] := $FF0000FF;
  FMonthColors[4, 0] := $FFFF00FF;
  FMonthColors[5, 0] := $FF00DD00;
  FMonthColors[6, 0] := $FFFF0000;
  FMonthColors[0, 1] := $FF00DD00;
  FMonthColors[1, 1] := $FF00DDDD;
  FMonthColors[2, 1] := $FFFF0000;
  FMonthColors[3, 1] := $FF0000FF;
  FMonthColors[4, 1] := $FFFF00FF;
  FMonthColors[5, 1] := $FF00DD00;
  FMonthColors[6, 1] := $FFFF0000;
end;

procedure TBafMap.MapPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
var
  LSgRow: TBafSgRow;
  LRect: TRectF;
  LTick, LHeight, LZwo: Single;
begin
  Canvas.BeginScene;
  try
    Canvas.DrawBitmap(FBmpPaint, FSrcRect, FDestRect, 1);
    if FHintRow > -1 then begin
      LSgRow := (FDataRowDisplayList.Items[FHintRow] as TBafSgRow);
      if LSgRow.Hint <> '' then begin
        Canvas.Fill.Color := TAlphaColorRec.Yellow;
        LRect.Left := CalcX(round(LSgRow.CheckboxRect.Right));
        LRect.Top := CalcY(round(LSgRow.CheckboxRect.Bottom));
        LHeight := Canvas.TextHeight(LSgRow.Hint);
        LTick := 0.4 * LHeight;
        LRect.Width := Canvas.TextWidth(LSgRow.Hint) + LTick;
        LRect.Height := LHeight + LTick;
        if ((LRect.Right + LTick) > FPanelDesk.Width) then begin
          LZwo := CalcX(round(LSgRow.CheckboxRect.Left));
          if ((LRect.Right - FPanelDesk.Width) > (LRect.Width - LZwo)) then
            LRect.Offset(- LRect.Width - 1.2 * LHeight, 0);
        end;
        if ((LRect.Bottom + LTick) > FPanelDesk.Height) then begin
          LZwo := CalcY(round(LSgRow.CheckboxRect.Top));
          if ((LRect.Bottom - FPanelDesk.Height) > (LRect.Height - LZwo)) then
            LRect.Offset(0, - LRect.Height - 1.2 * LHeight);
        end;
        Canvas.FillRect(LRect, 1);
        Canvas.Fill.Color := TAlphaColorRec.Black;
        Canvas.FillText(LRect, LSgRow.Hint, false, 1, [], TTextAlign.Center);
      end;
    end;
  finally
    Canvas.EndScene;
  end;
end;

procedure TBafMap.PanelDeskDblClick(Sender: TObject);
begin
  Zoom0;
  Repaint;
end;

procedure TBafMap.PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if Button = TMouseButton.mbLeft then begin
    FMouseDownX := X;
    FMouseDownY := Y;
  end;
end;

procedure TBafMap.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LPosX1, LPosX2, LPosY1, LPosY2, LRow: integer;
  LStretchX, LStretchY, LValue, LMiddle: single;
  LCommand, s: string;
  LAbort: boolean;
  LCell: TBafSgCell;

  procedure lokStretch;
  begin
      if (FPanelDesk.Width = 0) or (FPanelDesk.Height = 0) then
        exit;
      if FMouseDownX > X then begin
        LValue := FMouseDownX;
        FMouseDownX := X;
        X := LValue;
      end;
      if FMouseDownY > Y then begin
        LValue := FMouseDownY;
        FMouseDownY := Y;
        Y := LValue;
      end;
      if ((X - FMouseDownX) / System.Math.Max(Y - FMouseDownY, 1))
          < (FPanelDesk.Width / FPanelDesk.Height) then begin
        LValue := (Y - FMouseDownY) * FPanelDesk.Width / FPanelDesk.Height / 2;
        LMiddle := (FMouseDownX + X) / 2;
        FMouseDownX := LMiddle - LValue;
        X := LMiddle + LValue;
      end
      else begin
        LValue := (X - FMouseDownX) * FPanelDesk.Height / FPanelDesk.Width / 2;
        LMiddle := (FMouseDownY + Y) / 2;
        FMouseDownY := LMiddle - LValue;
        Y := LMiddle + LValue;
      end;

      LPosX1 := CalcXVal(FMouseDownX);
      LPosX2 := CalcXVal(X);
      LPosY1 := CalcYVal(FMouseDownY);
      LPosY2 := CalcYVal(Y);

      LStretchX := Abs(LPosX1 - LPosX2) / FPanelDesk.Width;
      LStretchY := Abs(LPosY1 - LPosY2) / FPanelDesk.Height;
      FStretch := System.Math.Max(LStretchX, LStretchY);

      FDestRect.Left := (FPanelDesk.Width / 2) - ((LPosX2 + LPosX1) / 2) / FStretch;
      FDestRect.Right := (FPanelDesk.Width / 2)
          + (FBmpPaint.Width - (LPosX2 + LPosX1) / 2) / FStretch;

      FDestRect.Top := (FPanelDesk.Height / 2) - ((LPosY2 + LPosY1) / 2) / FStretch;
      FDestRect.Bottom := (FPanelDesk.Height / 2)
          + (FBmpPaint.Height - (LPosY2 + LPosY1) / 2) / FStretch;

      FHorzScrollBar.Enabled := Abs(LPosX1 - LPosX2) < FBmpPaint.Width;
      if FHorzScrollBar.Enabled then begin
        FHorzScrollBar.OnChange := nil;
        FHorzScrollBar.ViewportSize := Abs(LPosX1 - LPosX2);
        FHorzScrollBar.Value := LPosX1;
        FScrollHorzOld := LPosX1;
        FHorzScrollBar.OnChange := ScrollChangeHorz;
      end;
      FVertScrollBar.Enabled := Abs(LPosY1 - LPosY2) < FBmpPaint.Height;
      if FVertScrollBar.Enabled then begin
        FVertScrollBar.OnChange := nil;
        FVertScrollBar.ViewportSize := Abs(LPosY1 - LPosY2);
        FVertScrollBar.Value := LPosY1;
        FScrollVertOld := LPosY1;
        FVertScrollBar.OnChange := ScrollChangeVert;
      end;
  end; // procedure lokStretch

begin
  if (Button = TMouseButton.mbLeft) then begin
    if (ssCtrl in Shift) then begin    // move
      LPosX1 := CalcXVal(FMouseDownX);
      LPosX2 := CalcXVal(X);
      LPosY1 := CalcYVal(FMouseDownY);
      LPosY2 := CalcYVal(Y);
      FHorzScrollBar.Value := FHorzScrollBar.Value + LPosX1 - LPosX2;
      FVertScrollBar.Value := FVertScrollBar.Value + LPosY1 - LPosY2;
    end
    else if (Abs(X - FMouseDownX) > 20) and (Abs(Y - FMouseDownY) > 20) then begin
      lokStretch;
    end
    else if Mouse2Row(X, Y, LRow) then begin
      if (MapType in [mtCheck]) and (DisplayRow[LRow].CanCheck)
          and not FParents.Segment.ReadOnly then begin
        s := 'Y';
        LAbort := false;
        LCell := DisplayRow[LRow].GetCellByFieldName(Field_IsChecked);
        LCommand := LCell.Parents.Column.CellChangeCommand;
        FParents.Page.DoEditEvent(LCell.Parents, false, s, LAbort, LCommand, HasChanged);
        if not LAbort then begin
          DisplayRow[LRow].IsChecked := not DisplayRow[LRow].IsChecked;
          DisplayRow[LRow].HasChanged := true;
        end;
        FClickRow := LRow;
        FParents.Page.DoEditEvent(LCell.Parents, true, s, LAbort, LCommand, HasChanged);
        MapRefresh;
        FParents.Page.SetStatus;
        FClickRow := -1;
      end;
    end;
    Repaint;
  end;
// procedure TBafMap.PanelDeskMouseUp
end;

procedure TBafMap.PanelMouseEnter(Sender: TObject);
begin

end;

procedure TBafMap.PanelMouseLeave(Sender: TObject);
begin

end;

procedure TBafMap.PanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
var
  LRow: integer;
begin
  if Mouse2Row(X, Y, LRow) then begin
    if LRow <> FHintRow then begin
      FHintRow := LRow;
      FPanelDesk.Repaint;
    end;
  end
  else begin
    FHintRow := -1;
    FPanelDesk.Repaint;
  end;
end;

procedure TBafMap.MapRefresh;
var
  LCanvas: TCanvas;
  LRow: integer;
  LSgRow: TBafSgRow;
  s: string;
  h, w: single;

  procedure lokCheck;
  begin
    LCanvas.Font.Style := [];
    LCanvas.Font.Family := 'Font Awesome 5 Free Regular';
    LCanvas.Font.Size := LSgRow.FFontSize * FFontSizeScale;
//    LCanvas.Fill.Color := BafColor2AlphaColor(LSgRow.FontColor);
    LCanvas.Fill.Color := LSgRow.FontColor;
    s := IfThen(LSgRow.CanCheck, IfThen(LSgRow.IsChecked, #$f14a, #$f0c8), #$f192);
    h := LCanvas.TextHeight('Wy') / 3;
    LSgRow.CheckboxRect := RectF(LSgRow.Pos.X - 2 * h, LSgRow.Pos.Y - 2 * h,
        LSgRow.Pos.X + 2 * h, LSgRow.Pos.Y + 2 * h);

    LCanvas.FillText(LSgRow.CheckboxRect, s, false, 1, [], TTextAlign.Center);
    LCanvas.Font.Family := '';
    if LSgRow.FontBold then
      LCanvas.Font.Style := [TFontStyle.fsBold];
    w := LCanvas.TextWidth(LSgRow.Caption);
    case LSgRow.CaptionPos of
      lpLeft: LCanvas.FillText(RectF(LSgRow.Pos.X - 2.5 * h - w, LSgRow.Pos.Y - 2.4 * h,
          LSgRow.Pos.X - h, LSgRow.Pos.Y + 2 * h), LSgRow.Caption,
          false, 1, [], TTextAlign.Center);
      lpRight: LCanvas.FillText(RectF(LSgRow.Pos.X + h, LSgRow.Pos.Y - 2.4 * h,
          LSgRow.Pos.X + 2.5 * h + w, LSgRow.Pos.Y + 2 * h), LSgRow.Caption,
          false, 1, [], TTextAlign.Center);

      lpTop: LCanvas.FillText(RectF(LSgRow.Pos.X - w, LSgRow.Pos.Y - 5 * h,
          LSgRow.Pos.X + w, LSgRow.Pos.Y - 1.5 * h), LSgRow.Caption,
          false, 1, [], TTextAlign.Center);
      lpBottom: LCanvas.FillText(RectF(LSgRow.Pos.X - w, LSgRow.Pos.Y + 1 * h,
          LSgRow.Pos.X + w, LSgRow.Pos.Y + 4.5 * h), LSgRow.Caption,
          false, 1, [], TTextAlign.Center);
    end;
  end; // procedure lokCheck

  procedure lokMonthCalendar;
  var
    h2, px, py: single;
    LStart, LDate: TDate;
    year, month, day: word;
    dof, d, i: integer;

    procedure lokCaption;
    begin
      LCanvas.Font.Style := [];
      LCanvas.Font.Size := LSgRow.FFontSize * FFontSizeScale;
      h := LCanvas.TextHeight('Wy') / 3;
      LCanvas.Fill.Color := BafColor2AlphaColor(LSgRow.FontColor);
      if LSgRow.FontBold then
        LCanvas.Font.Style := [TFontStyle.fsBold];
      w := LCanvas.TextWidth(LSgRow.Caption);
      case LSgRow.CaptionPos of
        lpLeft: LCanvas.FillText(RectF(LSgRow.Pos.X - 2.5 * h - w, LSgRow.Pos.Y - 2.4 * h,
            LSgRow.Pos.X - h, LSgRow.Pos.Y + 2.4 * h), LSgRow.Caption,
            false, 1, [], TTextAlign.Center);
        lpRight: LCanvas.FillText(RectF(LSgRow.Pos.X + h, LSgRow.Pos.Y - 2.4 * h,
            LSgRow.Pos.X + 2.5 * h + w, LSgRow.Pos.Y + 2.4 * h), LSgRow.Caption,
            false, 1, [], TTextAlign.Center);

        lpTop: LCanvas.FillText(RectF(LSgRow.Pos.X - w, LSgRow.Pos.Y - 5 * h,
            LSgRow.Pos.X + w, LSgRow.Pos.Y - 1.5 * h), LSgRow.Caption,
            false, 1, [], TTextAlign.Center);
        lpBottom: LCanvas.FillText(RectF(LSgRow.Pos.X - w, LSgRow.Pos.Y + 1 * h,
            LSgRow.Pos.X + w, LSgRow.Pos.Y + 4.5 * h), LSgRow.Caption,
            false, 1, [], TTextAlign.Center);
      end;
    end; // procedure lokCaption

  begin
    h2 := MonthCalendarSize / 7;
    LCanvas.Stroke.Color := $FFE2E2E2;
    LStart := FParents.Segment.MapStartDate;

    // Gitter
    DecodeDate(IncMonth(LStart) - 1, year, month, day);
    dof := DayOfTheWeek(LStart) - 1;
    for d := dof to day + dof - 1 do begin
      py := LSgRow.Pos.Y + ((d div 7) - 2) * h2;
      px := LSgRow.Pos.X + ((d mod 7) - 4) * h2;
      LCanvas.DrawRect(RectF(px, py, px + h2, py + h2), 1);
    end;
    LSgRow.CheckboxRect := RectF(LSgRow.Pos.X - 4 * h2, LSgRow.Pos.Y - 2 * h2,
        LSgRow.Pos.X + 3 * h2, LSgRow.Pos.Y + 2 * h2);

    // Die gesetzten Tage
    for i := 0 to LSgRow.JoinList.Count - 1 do begin
      LDate := StrToDateDef(LSgRow.JoinList[i], 0);
      d := round(LDate - LStart) + dof;
      py := LSgRow.Pos.Y + ((d div 7) - 2) * h2;
      px := LSgRow.Pos.X + ((d mod 7) - 4) * h2;
      LCanvas.Fill.Color := MonthColors[d mod 7, (d div 7) mod 2];
      LCanvas.FillRect(RectF(px, py, px + h2, py + h2), 1);
    end;

    // Beschriftung
    if LSgRow.Caption <> '' then
      lokCaption;

  end; // procedure lokMonthCalendar

begin
  FBmpPaint.Assign(FBmpBack);
  LCanvas := FBmpPaint.Canvas;
  LCanvas.BeginScene;
  try
    for LRow := 0 to FDataRowDisplayList.Count - 1 do begin
      LSgRow := (FDataRowDisplayList[LRow] as TBafSgRow);
      case MapType of
        mtMonthCalendar: lokMonthCalendar;
        mtCheck: lokCheck;
      end;
    end;
  finally
    LCanvas.EndScene;
  end;
// procedure TBafMap.MapRefresh
end;

function TBafMap.Mouse2Row(X, Y: single; var ARow: integer): boolean;
var
  i, LX, LY: integer;
  LSgRow: TBafSgRow;
begin
  result := false;
  LX := CalcXVal(X);
  LY := CalcYVal(Y);
  for i := 0 to FDataRowDisplayList.Count - 1 do begin
    LSgRow := DisplayRow[i];
    result := LSgRow.CheckboxRect.Contains(PointF(LX, LY));
    if result then begin
      ARow := i;
      Break;
    end;
  end;
end;

procedure TBafMap.MouseWheel(Shift: TShiftState; WheelDelta: Integer;
  var Handled: Boolean);
begin
  inherited;
  Handled := false;
  if (WheelDelta < 0) then
    ZoomInOut(111)
  else if (WheelDelta > 0) then
    ZoomInOut(90);
end;

procedure TBafMap.Resize;
begin
  inherited;
  FParents.Segment.FHeaderPanel.Width := Width;
  FParents.Segment.GridCalcHeight;
  if not FRectLoaded then
    Zoom0;
end;

function TBafMap.RowCount(AType: TBafGridRowType): integer;
begin
  if Self = nil then
    result := 0
  else begin
    case AType of
      rtHeader: result := FHeaderRowList.Count;
      rtDisplayData: result := FDataRowDisplayList.Count;
    else
      result := FDataRowList.Count;
    end;
  end;
end;

procedure TBafMap.ScrollChangeHorz(Sender: TObject);
var
  LDiff: single;
begin
  LDiff := (FHorzScrollBar.Value - FScrollHorzOld) / FStretch;
  FDestRect.Left := FDestRect.Left - LDiff;
  FDestRect.Right := FDestRect.Right - LDiff;
  FScrollHorzOld := FHorzScrollBar.Value;
  Repaint;
end;

procedure TBafMap.ScrollChangeVert(Sender: TObject);
var
  LDiff: single;
begin
  LDiff := (FVertScrollBar.Value - FScrollVertOld) / FStretch;
  FDestRect.Top := FDestRect.Top - LDiff;
  FDestRect.Bottom := FDestRect.Bottom - LDiff;
  FScrollVertOld := FVertScrollBar.Value;
  Repaint;
end;

procedure TBafMap.SetDestRect(ALeft, ATop, ARight, ABottom, AHorzVal, AHorzVP,
    AVertVal, AVertVP, AStretch: single);
begin
  FHorzScrollBar.Value := AHorzVal;
  FScrollHorzOld := AHorzVal;
  FHorzScrollBar.ViewportSize := AHorzVp;
  FHorzScrollBar.Enabled := (FHorzScrollBar.ViewportSize < FHorzScrollBar.Max);
  FVertScrollBar.Value := AVertVal;
  FScrollVertOld := AVertVal;
  FVertScrollBar.ViewportSize := AVertVp;
  FVertScrollBar.Enabled := (FVertScrollBar.ViewportSize < FVertScrollBar.Max);
  FRectLoaded := true;
  FDestRect.Left := ALeft;
  FDestRect.Top := ATop;
  FDestRect.Right := ARight;
  FDestRect.Bottom := ABottom;
  FStretch := AStretch;
end;

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

procedure TBafMap.SetMapType(const Value: string);
begin
  if Value = 'mc' then
    MapType := mtMonthCalendar
  else
    MapType := mtCheck;
end;

procedure TBafMap.SetMonthColors(ACol, ARow: integer; const Value: TAlphaColor);
begin
  FMonthColors[ACol mod 7, ARow mod 2] := Value;
end;

procedure TBafMap.SetPictureFileName(const Value: string);
begin
  FPictureFileName := Value;
  gvBafDataCache.LoadBitmap(FBmpBack, Value);
  FSrcRect := RectF(0, 0, FBmpBack.Width, FBmpBack.Height);
  FHorzScrollBar.Min := 0;
  FHorzScrollBar.Max := FBmpBack.Width;
  FVertScrollBar.Min := 0;
  FVertScrollBar.Max := FBmpBack.Height;
  MapRefresh;
  Zoom0;
end;

procedure TBafMap.SetScrollHorzVisible(const Value: boolean);
begin
  FScrollHorzVisible := Value;
  FHorzScrollBar.Visible := Value;
end;

procedure TBafMap.SetScrollVertVisible(const Value: boolean);
begin
  FScrollVertVisible := Value;
  FVertScrollBar.Visible := Value;
end;

procedure TBafMap.Zoom0;
var
  LStretchX, LStretchY: single;
begin
  if Assigned(FBmpPaint) and (FPanelDesk.Width > 0)
      and (FPanelDesk.Height > 0) then begin
    LStretchX := FBmpPaint.Width / FPanelDesk.Width;
    LStretchY := FBmpPaint.Height / FPanelDesk.Height;
    FStretch := System.Math.Max(LStretchX, LStretchY);
    FDestRect.Left := (FPanelDesk.Width / 2) - (FBmpPaint.Width / 2) / FStretch;
    FDestRect.Right := (FPanelDesk.Width / 2) + (FBmpPaint.Width / 2) / FStretch;
    FDestRect.Top := (FPanelDesk.Height / 2) - (FBmpPaint.Height / 2) / FStretch;
    FDestRect.Bottom := (FPanelDesk.Height / 2) + (FBmpPaint.Height / 2) / FStretch;
    FHorzScrollBar.OnChange := nil;
    FVertScrollBar.OnChange := nil;
    FHorzScrollBar.Enabled := false;
    FHorzScrollBar.ViewportSize := FHorzScrollBar.Max;
    FVertScrollBar.Enabled := false;
    FVertScrollBar.ViewportSize := FVertScrollBar.Max;
    FHorzScrollBar.OnChange := ScrollChangeHorz;
    FVertScrollBar.OnChange := ScrollChangeVert;
  end;
end;

procedure TBafMap.ZoomInOut(APercent: single);
var
  LPosX1, LPosX2, LPosY1, LPosY2: integer;
  LStretchX, LStretchY, LValue, LMiddle: single;
begin
  if Assigned(FBmpPaint) and (FPanelDesk.Width > 0)
      and (FPanelDesk.Height > 0) then begin

    LPosX1 := CalcXVal(FPanelDesk.Width - FPanelDesk.Width * 100 / APercent);
    LPosX2 := CalcXVal(FPanelDesk.Width * 100 / APercent);
    LPosY1 := CalcYVal(FPanelDesk.Height - FPanelDesk.Height * 100 / APercent);
    LPosY2 := CalcYVal(FPanelDesk.Height * 100 / APercent);

    LStretchX := Abs(LPosX1 - LPosX2) / FPanelDesk.Width;
    LStretchY := Abs(LPosY1 - LPosY2) / FPanelDesk.Height;
    FStretch := System.Math.Max(LStretchX, LStretchY);

    FDestRect.Left := (FPanelDesk.Width / 2) - ((LPosX2 + LPosX1) / 2) / FStretch;
    FDestRect.Right := (FPanelDesk.Width / 2)
        + (FBmpPaint.Width - (LPosX2 + LPosX1) / 2) / FStretch;

    FDestRect.Top := (FPanelDesk.Height / 2) - ((LPosY2 + LPosY1) / 2) / FStretch;
    FDestRect.Bottom := (FPanelDesk.Height / 2)
        + (FBmpPaint.Height - (LPosY2 + LPosY1) / 2) / FStretch;

    FHorzScrollBar.Enabled := Abs(LPosX1 - LPosX2) < FBmpPaint.Width;
    if FHorzScrollBar.Enabled then begin
      FHorzScrollBar.OnChange := nil;
      FHorzScrollBar.ViewportSize := Abs(LPosX1 - LPosX2);
      FHorzScrollBar.Value := LPosX1;
      FScrollHorzOld := LPosX1;
      FHorzScrollBar.OnChange := ScrollChangeHorz;
    end;
    FVertScrollBar.Enabled := Abs(LPosY1 - LPosY2) < FBmpPaint.Height;
    if FVertScrollBar.Enabled then begin
      FVertScrollBar.OnChange := nil;
      FVertScrollBar.ViewportSize := Abs(LPosY1 - LPosY2);
      FVertScrollBar.Value := LPosY1;
      FScrollVertOld := LPosY1;
      FVertScrollBar.OnChange := ScrollChangeVert;
    end;
    Repaint;
  end;
end;

{ TBafPic }

function TBafPic.CalcX(AValue: integer): single;
begin
  result := FDestRect.Left + (AValue / FStretch);
end;

function TBafPic.CalcXVal(APos: single): integer;
begin
  result := round((APos - FDestRect.Left) * FStretch);
end;

function TBafPic.CalcY(AValue: integer): single;
begin
  result := FDestRect.Top + (AValue / FStretch);
end;

function TBafPic.CalcYVal(APos: single): integer;
begin
  result := round((APos - FDestRect.Top) * FStretch);
end;

constructor TBafPic.Create(ASeg: TBafPageSegment);
begin
  inherited Create(ASeg.FParents.Page);
  FScrollHorzVisible := true;
  FScrollVertVisible := true;
  CreateComponents;
  FParents.CreateFrom(ASeg.FParents);
  FParents.Pic := Self;
  FBmpPaint := FMX.Graphics.TBitmap.Create;
end;

procedure TBafPic.CreateComponents;
begin
  FVertScrollBar := TScrollBar.Create(Self);
  FVertScrollBar.Parent := Self;
  FVertScrollBar.Orientation := TOrientation.Vertical;
  FVertScrollBar.Align := TAlignLayout.Right;
  FVertScrollBar.OnChange := ScrollChangeVert;
  FPanelLeft := TPanel.Create(Self);
  FPanelLeft.Parent := Self;
  FPanelLeft.Align := TAlignLayout.Client;
  FPanelLeft.StyleLookup := 'pushpanel2';
  FHorzScrollBar := TScrollBar.Create(Self);
  FHorzScrollBar.Parent := FPanelLeft;
  FHorzScrollBar.Align := TAlignLayout.Bottom;
  FHorzScrollBar.OnChange := ScrollChangeHorz;
  FPanelDesk := TPanel.Create(Self);
  FPanelDesk.Parent := FPanelLeft;
  FPanelDesk.Align := TAlignLayout.Client;
  FPanelDesk.OnPaint := PicPaint;
  FPanelDesk.ClipChildren := true;
  FPanelDesk.OnMouseUp := PanelDeskMouseUp;
  FPanelDesk.OnMouseDown := PanelDeskMouseDown;
  FPanelDesk.OnDblClick := PanelDeskDblClick;
  FPanelDesk.StyleLookup := 'pushpanel2';
end;

destructor TBafPic.Destroy;
begin
  if (FDefinition <> '') and Assigned(FOnSaveRect) then
    FOnSaveRect(FParents, FDestRect, FDefinition,
        FHorzScrollBar.Value, FHorzScrollBar.ViewportSize,
        FVertScrollBar.Value, FVertScrollBar.ViewportSize, FStretch);
  FreeAndNil(FBmpPaint);
  inherited;
end;

procedure TBafPic.LoadFromHttp(AUrl: string; AIgnoreServerCertificate: boolean;
    AInter: TObject);
begin
  if TBafWebModule.GetBitmapFromUrl(AUrl, AIgnoreServerCertificate, FBmpPaint,
      AInter) then begin
    FSrcRect := RectF(0, 0, FBmpPaint.Width, FBmpPaint.Height);
    FHorzScrollBar.Min := 0;
    FHorzScrollBar.Max := FBmpPaint.Width;
    FVertScrollBar.Min := 0;
    FVertScrollBar.Max := FBmpPaint.Height;
    Zoom0;
  end;
end;

procedure TBafPic.PanelDeskDblClick(Sender: TObject);
begin
  Zoom0;
  Repaint;
end;

procedure TBafPic.PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if Button = TMouseButton.mbLeft then begin
    FMouseDownX := X;
    FMouseDownY := Y;
  end;
end;

procedure TBafPic.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LPosX1, LPosX2, LPosY1, LPosY2: integer;
  LStretchX, LStretchY, LValue, LMiddle: single;
  LCell: TBafSgCell;

  procedure lokStretch;
  begin
      if (FPanelDesk.Width = 0) or (FPanelDesk.Height = 0) then
        exit;
      if FMouseDownX > X then begin
        LValue := FMouseDownX;
        FMouseDownX := X;
        X := LValue;
      end;
      if FMouseDownY > Y then begin
        LValue := FMouseDownY;
        FMouseDownY := Y;
        Y := LValue;
      end;
      if ((X - FMouseDownX) / System.Math.Max(Y - FMouseDownY, 1))
          < (FPanelDesk.Width / FPanelDesk.Height) then begin
        LValue := (Y - FMouseDownY) * FPanelDesk.Width / FPanelDesk.Height / 2;
        LMiddle := (FMouseDownX + X) / 2;
        FMouseDownX := LMiddle - LValue;
        X := LMiddle + LValue;
      end
      else begin
        LValue := (X - FMouseDownX) * FPanelDesk.Height / FPanelDesk.Width / 2;
        LMiddle := (FMouseDownY + Y) / 2;
        FMouseDownY := LMiddle - LValue;
        Y := LMiddle + LValue;
      end;

      LPosX1 := CalcXVal(FMouseDownX);
      LPosX2 := CalcXVal(X);
      LPosY1 := CalcYVal(FMouseDownY);
      LPosY2 := CalcYVal(Y);

      LStretchX := Abs(LPosX1 - LPosX2) / FPanelDesk.Width;
      LStretchY := Abs(LPosY1 - LPosY2) / FPanelDesk.Height;
      FStretch := System.Math.Max(LStretchX, LStretchY);

      FDestRect.Left := (FPanelDesk.Width / 2) - ((LPosX2 + LPosX1) / 2) / FStretch;
      FDestRect.Right := (FPanelDesk.Width / 2)
          + (FBmpPaint.Width - (LPosX2 + LPosX1) / 2) / FStretch;

      FDestRect.Top := (FPanelDesk.Height / 2) - ((LPosY2 + LPosY1) / 2) / FStretch;
      FDestRect.Bottom := (FPanelDesk.Height / 2)
          + (FBmpPaint.Height - (LPosY2 + LPosY1) / 2) / FStretch;

      FHorzScrollBar.Enabled := Abs(LPosX1 - LPosX2) < FBmpPaint.Width;
      if FHorzScrollBar.Enabled then begin
        FHorzScrollBar.OnChange := nil;
        FHorzScrollBar.ViewportSize := Abs(LPosX1 - LPosX2);
        FHorzScrollBar.Value := LPosX1;
        FScrollHorzOld := LPosX1;
        FHorzScrollBar.OnChange := ScrollChangeHorz;
      end;
      FVertScrollBar.Enabled := Abs(LPosY1 - LPosY2) < FBmpPaint.Height;
      if FVertScrollBar.Enabled then begin
        FVertScrollBar.OnChange := nil;
        FVertScrollBar.ViewportSize := Abs(LPosY1 - LPosY2);
        FVertScrollBar.Value := LPosY1;
        FScrollVertOld := LPosY1;
        FVertScrollBar.OnChange := ScrollChangeVert;
      end;
  end; // procedure lokStretch

begin
  if (Button = TMouseButton.mbLeft) then begin
    if (ssCtrl in Shift) then begin    // move
      LPosX1 := CalcXVal(FMouseDownX);
      LPosX2 := CalcXVal(X);
      LPosY1 := CalcYVal(FMouseDownY);
      LPosY2 := CalcYVal(Y);
      FHorzScrollBar.Value := FHorzScrollBar.Value + LPosX1 - LPosX2;
      FVertScrollBar.Value := FVertScrollBar.Value + LPosY1 - LPosY2;
    end
    else if (Abs(X - FMouseDownX) > 20) and (Abs(Y - FMouseDownY) > 20) then begin
      lokStretch;
    end;
    Repaint;
  end;
end;

procedure TBafPic.PicPaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
begin
  Canvas.BeginScene;
  try
    Canvas.DrawBitmap(FBmpPaint, FSrcRect, FDestRect, 1);
  finally
    Canvas.EndScene;
  end;
end;

procedure TBafPic.Resize;
begin
  inherited;
  FParents.Segment.GridCalcHeight;
  if not FRectLoaded then
    Zoom0;
end;

procedure TBafPic.ScrollChangeHorz(Sender: TObject);
var
  LDiff: single;
begin
  LDiff := (FHorzScrollBar.Value - FScrollHorzOld) / FStretch;
  FDestRect.Left := FDestRect.Left - LDiff;
  FDestRect.Right := FDestRect.Right - LDiff;
  FScrollHorzOld := FHorzScrollBar.Value;
  Repaint;
end;

procedure TBafPic.ScrollChangeVert(Sender: TObject);
var
  LDiff: single;
begin
  LDiff := (FVertScrollBar.Value - FScrollVertOld) / FStretch;
  FDestRect.Top := FDestRect.Top - LDiff;
  FDestRect.Bottom := FDestRect.Bottom - LDiff;
  FScrollVertOld := FVertScrollBar.Value;
  Repaint;
end;

procedure TBafPic.SetDestRect(ALeft, ATop, ARight, ABottom, AHorzVal, AHorzVP,
  AVertVal, AVertVP, AStretch: single);
begin
  FHorzScrollBar.Value := AHorzVal;
  FScrollHorzOld := AHorzVal;
  FHorzScrollBar.ViewportSize := AHorzVp;
  FHorzScrollBar.Enabled := (FHorzScrollBar.ViewportSize < FHorzScrollBar.Max);
  FVertScrollBar.Value := AVertVal;
  FScrollVertOld := AVertVal;
  FVertScrollBar.ViewportSize := AVertVp;
  FVertScrollBar.Enabled := (FVertScrollBar.ViewportSize < FVertScrollBar.Max);
  FRectLoaded := true;
  FDestRect.Left := ALeft;
  FDestRect.Top := ATop;
  FDestRect.Right := ARight;
  FDestRect.Bottom := ABottom;
  FStretch := AStretch;
end;

procedure TBafPic.SetPictureFileName(const Value: string);
begin
  FPictureFileName := Value;
  if FileExists(Value) then begin
    FBmpPaint.LoadFromFile(Value);
    FSrcRect := RectF(0, 0, FBmpPaint.Width, FBmpPaint.Height);
  end;
  FHorzScrollBar.Min := 0;
  FHorzScrollBar.Max := FBmpPaint.Width;
  FVertScrollBar.Min := 0;
  FVertScrollBar.Max := FBmpPaint.Height;
//  Zoom0;
end;

procedure TBafPic.SetScrollHorzVisible(const Value: boolean);
begin
  FScrollHorzVisible := Value;
  FHorzScrollBar.Visible := Value;
end;

procedure TBafPic.SetScrollVertVisible(const Value: boolean);
begin
  FScrollVertVisible := Value;
  FVertScrollBar.Visible := Value;
end;

procedure TBafPic.Zoom0;
var
  LStretchX, LStretchY: single;
begin
  if Assigned(FBmpPaint) and (FPanelDesk.Width > 0)
      and (FPanelDesk.Height > 0) then begin
    LStretchX := FBmpPaint.Width / FPanelDesk.Width;
    LStretchY := FBmpPaint.Height / FPanelDesk.Height;
    FStretch := System.Math.Max(LStretchX, LStretchY);
    FDestRect.Left := (FPanelDesk.Width / 2) - (FBmpPaint.Width / 2) / FStretch;
    FDestRect.Right := (FPanelDesk.Width / 2) + (FBmpPaint.Width / 2) / FStretch;
    FDestRect.Top := (FPanelDesk.Height / 2) - (FBmpPaint.Height / 2) / FStretch;
    FDestRect.Bottom := (FPanelDesk.Height / 2) + (FBmpPaint.Height / 2) / FStretch;
    FHorzScrollBar.OnChange := nil;
    FVertScrollBar.OnChange := nil;
    FHorzScrollBar.Enabled := false;
    FHorzScrollBar.ViewportSize := FHorzScrollBar.Max;
    FVertScrollBar.Enabled := false;
    FVertScrollBar.ViewportSize := FVertScrollBar.Max;
    FHorzScrollBar.OnChange := ScrollChangeHorz;
    FVertScrollBar.OnChange := ScrollChangeVert;
  end;
end;

end.

