{********************************************************************}
{                                                                    }
{ written by TMS Software                                            }
{            copyright ©  2013 - 2014                                }
{            Email : info@tmssoftware.com                            }
{            Web : http://www.tmssoftware.com                        }
{                                                                    }
{ The source code is given as is. The author is not responsible      }
{ for any possible damage done due to the use of this code.          }
{ The complete source code remains property of the author and may    }
{ not be distributed, published, given or sold in any form as such.  }
{ No parts of the source code can be included in any other component }
{ or application without written authorization of the author.        }
{********************************************************************}

unit FMX.TMSGridData;

interface

{$I TMSDEFS.INC}

uses
  {$IFNDEF IOS}
  {$IFNDEF MACOS}
  {$IFNDEF ANDROID}
  Windows,
  {$ENDIF}
  {$ENDIF}
  {$ENDIF}
  Generics.Collections,
  SysUtils, Variants, Types, Classes, UITypes, FMX.Objects,
  FMX.Types, DateUtils, FMX.TMSBaseControl, FMX.TMSGridCell, FMX.TMSGridOptions,
  FMX.TMSBitmapContainer, FMX.Dialogs, RTLConsts, FMX.Colors, FMX.ListBox,
  FMX.Edit, FMX.TMSXUtil, FMX.TMSGridDataUtil, FMX.TMSBitmap, FMX.Controls
  , UIConsts, FMX.Styles, Data.DB
  {$IFDEF DELPHIXE5_LVL}
  , FMX.Graphics
  {$ENDIF}
  ;

const
  CSVSeparators: array[1..10] of char = (',',';','#',#9,'|','@','*','-','+','&');

type
  {$DEFINE VARIANTLIST}

  {$IFDEF VARIANTLIST}
  TCellData = string;
  {$ELSE}
  TCellData = string;
  {$ENDIF}

  TProtectedPopup = class(TPopup);

  TVariantList = class;

  PVariantItem = ^TVariantItem;
  TVariantItem = record
    FString: TCellData;
    FObject: TObject;
  end;

  PVariantItemList = ^TVariantItemList;
  TVariantItemList = array of TVariantItem;
  TVariantListSortCompare = function(List: TVariantList; Index1, Index2: Integer): Integer;

  TVariantList = class(TPersistent)
  private
    FList: TVariantItemList;
    FCount: Integer;
    FCapacity: Integer;
    FSorted: Boolean;
    FDuplicates: TDuplicates;
    FCaseSensitive: Boolean;
    FOnChange: TNotifyEvent;
    FOnChanging: TNotifyEvent;
    FOwnsObject: Boolean;
    FUpdateCount: Integer;
    procedure ExchangeItems(Index1, Index2: Integer);
    procedure Grow;
    procedure QuickSort(L, R: Integer; SCompare: TVariantListSortCompare);
    procedure SetSorted(Value: Boolean);
    procedure SetCaseSensitive(const Value: Boolean);
    function GetVariant(Index: Integer): TCellData;
    procedure PutVariant(Index: Integer; const Value: TCellData);
    function GetTextStr: string;
  protected
    procedure Changed; virtual;
    procedure Changing; virtual;
    function Get(Index: Integer): TCellData;
    function GetCapacity: Integer; //override;
    function GetCount: Integer; //override;
    function GetObject(Index: Integer): TObject; //override;
    procedure Put(Index: Integer; const S: TCellData);
    procedure PutObject(Index: Integer; AObject: TObject); //override;
    procedure SetCapacity(NewCapacity: Integer); //override;
    procedure SetUpdateState(Updating: Boolean); //override;
    function CompareVariants(const S1, S2: TCellData): Integer;
    procedure InsertItem(Index: Integer; const S: TCellData; AObject: TObject); virtual;

    procedure Error(const Msg: string; Data: Integer); overload;
    procedure Error(Msg: PResStringRec; Data: Integer); overload;
    function CompareStrings(const S1, S2: TCellData): Integer; virtual;
  public
    constructor Create; overload;
    constructor Create(OwnsObjects: Boolean); overload;
    destructor Destroy; override;
    procedure BeginUpdate;
    procedure EndUpdate;
    function Add(const S: TCellData): Integer;
    function AddObject(const S: TCellData; AObject: TObject): Integer;
    procedure Assign(Source: TPersistent); override;
    procedure Clear; //override;
    procedure Delete(Index: Integer); //override;
    procedure Move(CurIndex, NewIndex: Integer); virtual;
    procedure Exchange(Index1, Index2: Integer); //override;
    function Find(const S: TCellData; var Index: Integer): Boolean; virtual;
    function IndexOf(const S: TCellData): Integer;
    procedure Insert(Index: Integer; const S: TCellData);
    procedure InsertObject(Index: Integer; const S: TCellData;
      AObject: TObject);
    procedure Sort; virtual;
    procedure CustomSort(Compare: TVariantListSortCompare); virtual;
    property Count: Integer read GetCount;
    property Values[Index: Integer]: TCellData read GetVariant write PutVariant; default;
    property Objects[Index: Integer]: TObject read GetObject write PutObject;
    property Duplicates: TDuplicates read FDuplicates write FDuplicates;
    property Sorted: Boolean read FSorted write SetSorted;
    property CaseSensitive: Boolean read FCaseSensitive write SetCaseSensitive;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnChanging: TNotifyEvent read FOnChanging write FOnChanging;
    property OwnsObjects: Boolean read FOwnsObject write FOwnsObject;
    property Text: string read GetTextStr;
  end;

  TTMSFMXGridData = class;

  PCell = ^TCell;
  TCell = record
    Col, Row: Integer;
  end;

  TCellRange = record
    StartCol, StartRow, EndCol, EndRow: Integer;
  end;

  TCellState = (csNormal, csFocused, csFixed, csFixedSelected, csSelected);

  TSortDirection = (sdAscending, sdDescending);

  TSortFormat = (ssAutomatic, ssAlphabetic, ssAlphabeticNoCase, ssNumeric, ssDate, ssHTML, ssCheckBox, ssCustom, ssRaw);

  TTMSFMXGridSelectionMode = (smNone, smSingleCell, smSingleRow, smSingleColumn, smCellRange, smRowRange, smColumnRange, smDisjunctRow, smDisjunctColumn, smDisjunctCell);

  TTMSFMXGridSortFormatEvent = procedure(Sender: TObject; Col: Integer; var SortFormat: TSortFormat; var APrefix, ASuffix: string) of object;

  TTMSFMXGridCustomCompareEvent = procedure(Sender: TObject; Value1, Value2: TCellData; var res: integer) of object;

  TTMSFMXGridRawCompareEvent = procedure(Sender: TObject; Col, Row1, Row2: integer; var res: integer) of object;

  TTMSFMXGridClipboardBeforePasteEvent = procedure(Sender: TObject; Col, Row: integer; var Value: string; var Allow: boolean) of object;

  TTMSFMXGridClipboardAfterPasteEvent = procedure(Sender: TObject; Col, Row: integer; Value: string) of object;

  TTMSFMXGridNeedFilterDropDownEvent = procedure(Sender: TObject; Col, Row: integer; var Allow: boolean) of object;

  TTMSFMXGridNeedFilterDropDownDataEvent = procedure(Sender: TObject; Col, Row: integer; AValues: TStrings) of object;

  TTMSFMXGridFilterSelectEvent = procedure(Sender: TObject; Col: integer; var Condition: string) of object;

  TTMSFMXGridCellsChangedEvent = procedure(Sender: TObject; Cells: TCellRange) of object;

  TTMSFMXGridColumnCalcEvent = procedure(Sender: TObject; ACol, FromRow, ToRow: integer; var Res: double) of object;

  TColumnCalculation = (ccNone,ccSum,ccAvg,ccCount,ccMin,ccMax,ccCUSTOM,ccDistinct,ccStdDev);

  TCellProperty = class(TObject)
  private
    FColor: TAlphaColor;
    FBaseCol: integer;
    FBaseRow: integer;
    FColSpan: integer;
    FRowSpan: integer;
    FAlignHorz: TTextAlign;
    FAlignVert: TTextAlign;
    FControl: TObject;
    FObject: TObject;
    FReadOnly: boolean;
    FPrintPageNr: integer;
    FFontStyle: TFontStyles;
    FFontSize: single;
    FFontName: string;
    FFontColor: TAlphaColor;
    FAngle: single;
    FComment: string;
    FCommentColor: TAlphaColor;
    FBorderColor: TAlphaColor;
    FBorderWidth: single;
    FWordWrap: boolean;
    FFixed: boolean;
  protected
    property PrintPageNr: integer read FPrintPageNr write FPrintPageNr;
  public
    procedure Assign(Source: TCellProperty); virtual;
    constructor Create(DefaultFont: TFont);
    destructor Destroy; override;
    function IsBaseCell(Col,Row: integer): boolean;
    procedure FreeControl;
    property Color: TAlphaColor read FColor write FColor;
    property CellObject: TObject read FObject write FObject;
    property ReadOnly: boolean read FReadOnly write FReadOnly;
    property AlignHorz: TTextAlign read FAlignHorz write FAlignHorz;
    property AlignVert: TTextAlign read FAlignVert write FAlignVert;
    property BorderColor: TAlphaColor read FBorderColor write FBorderColor;
    property BorderWidth: single read FBorderWidth write FBorderWidth;
    property Fixed: boolean read FFixed write FFixed;
    property FontStyle: TFontStyles read FFontStyle write FFontStyle;
    property FontSize: single read FFontSize write FFontSize;
    property FontName: string read FFontName write FFontName;
    property FontColor: TAlphaColor read FFontColor write FFontColor;
    property BaseCol: integer read FBaseCol write FBaseCol;
    property ColSpan: integer read FColSpan write FColSpan;
    property BaseRow: integer read FBaseRow write FBaseRow;
    property RowSpan: integer read FRowSpan write FRowSpan;
    property Control: TObject read FControl write FControl;
    property Angle: single read FAngle write FAngle;
    property Comment: string read FComment write FComment;
    property CommentColor: TAlphaColor read FCommentColor write FCommentColor;
    property WordWrap: boolean read FWordWrap write FWordWrap;
  end;

  TCellCheckBox = class(TObject)
  private
    FChecked: boolean;
    FHeader: boolean;
  public
    property Checked: boolean read FChecked write FChecked;
    property Header: boolean read FHeader write FHeader;
  end;

  TCellDataCheckBox = class(TObject)
  private
  public
  end;

  TCellRadioButton = class(TObject)
  private
    FChecked: boolean;
    FInteger: Integer;
  public
    property Checked: boolean read FChecked write FChecked;
    property Index: integer read FInteger write FInteger;
  end;

  TCellButton = class(TObject)
  private
    FText: string;
    FWidth: integer;
  public
    property Text: string read FText write FText;
    property Width: integer read FWidth write FWidth;
  end;

  TCellBitmapName = class(TObject)
  private
    FBitmapName: string;
    FData: boolean;
  public
    constructor Create;
    property Data: boolean read FData write FData;
    property BitmapName: string read FBitmapName write FBitmapName;
  end;

  TCellBitmap = class;

  TBitmapEx = class(TBitmap)
  private
    FOwner: TTMSFMXGridData;
  public
    procedure Assign(Source: TPersistent); override;
  end;

  TCellBitmap = class(TObject)
  private
    FBitmap: TBitmapEx;
    procedure SetBitmap(const Value: TBitmapEx);
  public
    constructor Create(AOwner: TTMSFMXGridData);
    destructor Destroy; override;
    property Bitmap: TBitmapEx read FBitmap write SetBitmap;
  end;

  TCellNode = class(TObject)
  private
    FState: TNodeState;
    FSpan: integer;
  public
    property State: TNodeState read FState write FState;
    property Span: integer read FSpan write FSpan;
  end;

  TCellSummary = class(TObject)
  private
    FMerged: boolean;
  public
    property Merged: boolean read FMerged write FMerged;
  end;

  TCellProgressBar = class(TObject)
  private
    FValue: single;
    FData: boolean;
  public
    property Value: single read FValue write FValue;
    property Data: boolean read FData write FData;
  end;

  TCellControl = class(TObject)
  private
    FControl: TFMXObject;
  public
    property Control: TFMXObject read FControl write FControl;
  end;

  TCellCombo = class(TObject)
  private
    FItems: TStringList;
    FEditable: boolean;
    procedure SetItems(const Value: TStringList);
  public
    constructor Create;
    destructor Destroy; override;
    property Items: TStringList read FItems write SetItems;
    property Editable: boolean read FEditable write FEditable;
  end;

  TSortIndexList = class(TIntList)
  private
    function GetSortColumns(i: Integer): Integer;
    function GetSortDirections(i: Integer): Boolean;
    procedure SetSortColumns(i: Integer; const Value: Integer);
    procedure SetSortDirections(i: Integer; const Value: Boolean);
  public
    function SaveToString: string;
    procedure LoadFromString(s: string);
    procedure AddIndex(ColumnIndex: integer; ADirection: TSortDirection);
    function FindIndex(ColumnIndex: integer):integer;
    procedure ToggleIndex(ColumnIndex: integer);
    property SortColumns[i: Integer]: Integer read GetSortColumns write SetSortColumns;
    property SortDirections[i: Integer]: Boolean read GetSortDirections write SetSortDirections;
  end;

  TFilterCells = (fcVirtual, fcNormal, fcStripHTML, fcRow);

  TFilterOperation = (foSHORT, foNONE, foAND, foXOR, foOR);

  TRowState = (rsDefault, rsSelected);

  {$IFDEF VARIANTLIST}
  TDataList = class(TVariantList);
//  TCellData = variant;
  {$ELSE}
  TDataList = class(TStringList);
//  TCellData = string;
  {$ENDIF}

  {TRowInfo}

  TRowInfo = class(TObject)
  private
    FData: TDataList;
    FState: TRowState;
    procedure SetState(const Value: TRowState);
  public
    constructor Create;
    destructor Destroy; override;
    property Data: TDataList read FData;
    property State: TRowState read FState write SetState;
  end;

  {TFilterData}

  TFilterData = class(TCollectionItem)
  private
    FColumn: SmallInt;
    FCondition: string;
    FCaseSensitive: Boolean;
    FSuffix: string;
    FPrefix: string;
    FOperation: TFilterOperation;
    FData: TFilterCells;
  public
    constructor Create(ACollection: TCollection); override;
    procedure Assign(Source: TPersistent); override;
  published
    property Column: smallint read FColumn write FColumn;
    property Condition:string read FCondition write FCondition;
    property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive default True;
    property Data: TFilterCells read FData write FData default fcVirtual;
    property Prefix: string read FPrefix write FPrefix;
    property Suffix: string read FSuffix write FSuffix;
    property Operation: TFilterOperation read FOperation write FOperation;
  end;

  {TFilter}

  TFilter = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXGridData;
    function GetItem(Index: Integer): TFilterData;
    procedure SetItem(Index: Integer; Value: TFilterData);
    function GetColFilter(Col: Integer): TFilterData;
  public
    constructor Create(AOwner: TTMSFMXGridData);
    function Add: TFilterData;
    function Insert(index: Integer): TFilterData;
    property Items[Index: Integer]: TFilterData read GetItem write SetItem; default;
    property ColumnFilter[Col: Integer]: TFilterData read GetColFilter;
    function HasFilter(Col: integer): Boolean;
    procedure RemoveColumnFilter(Col: integer);
  protected
    function GetOwner: TPersistent; override;
  end;

  TCellCoordinate = record
    Col,
    Row: integer;
  end;

  TFileStringList = class(TStringList)
  private
    fp: integer;
    cache: string;
    function GetEOF: boolean;
  public
    procedure Reset;
    procedure ReadLn(var s: string);
    procedure Write(s: string);
    procedure WriteLn(s: string);
    property Eof: boolean read GetEOF;
  end;

  TFindParameters = (fnMatchCase,fnMatchFull,fnMatchRegular,fnDirectionLeftRight,
    fnMatchStart,fnFindInCurrentRow,fnFindInCurrentCol,fnIncludeFixed,fnAutoGoto,
    fnIgnoreHTMLTags,fnBackward,fnIncludeHiddenColumns,fnFindInPresetCol,fnFindInPresetRow,fnSelectedCells,fnIncludeHiddenRows);

  TFindParams = set of TFindParameters;

  TSingleListItem = class(TCollectionItem)
  private
    FOwner: TTMSFMXGridData;
    FValue: Single;
    FCellVal: Integer;
    procedure SeTCellData(const Value: Single);
  public
    constructor Create(Collection: TCollection); override;
  published
    property Value: Single read FValue write SeTCellData;
    property CellVal: Integer read FCellVal write FCellVal;
  end;

  TSingleList = class(TTMSFMXBaseCollection)
  private
    FOwner: TTMSFMXGridData;
    FOnChange: TNotifyEvent;
    function GetItem(Index: Integer): TSingleListItem;
    procedure SetItem(Index: Integer; const Value: TSingleListItem);
  protected
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    function GetOwner: TPersistent; override;
  public
    function IndexOf(ACellVal: Integer): Integer;
    constructor Create(AOwner: TTMSFMXGridData);
    property Items[Index: Integer]: TSingleListItem read GetItem write SetItem; default;
    function Add: TSingleListItem;
    function Insert(Index: Integer): TSingleListItem;
    procedure Clear;
  end;

  TTMSFMXMarkType = (mtHighlight, mtError);

  TCellMarkItem = class(TCollectionItem)
  private
    FCellRange: TCellRange;
    FValue: string;
    FCaseSensitive: boolean;
    FMarkType: TTMSFMXMarkType;
  public
    property CellRange: TCellRange read FCellRange write FCellRange;
  published
    property CaseSensitive: boolean read FCaseSensitive write FCaseSensitive;
    property Value: string read FValue write FValue;
    property MarkType: TTMSFMXMarkType read FMarkType write FMarkType;
  end;

  TCellMarkList = class(TTMSFMXBaseCollection)
  private
    function GetItem(Index: Integer): TCellMarkItem;
    procedure SetItem(Index: Integer; const Value: TCellMarkItem);
  protected
  public
    constructor Create(AOwner: TTMSFMXGridData);
    property Items[Index: Integer]: TCellMarkItem read GetItem write SetItem; default;
    function Add: TCellMarkItem;
    function Insert(Index: Integer): TCellMarkItem;
    procedure RemoveMarker(CellRange: TCellRange; MarkerType: TTMSFMXMarkType);
  end;

  TCellList = class(TList<TCell>)
  private
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddCell(Cell: TCell);
    procedure DeleteCell(Cell: TCell); overload;
    procedure DeleteCell(index: Integer); overload;
    procedure Clear;
    function Find(Cell: TCell): integer;
    function FindFixed(Cell: TCell): integer;
  end;

  TGridExportState = (esExportStart, esExportNewRow, esExportDone, esExportSelRow, esExportFail, esExportNextRow);
  TGridImportState = (isImportStart, isImportNewRow, isImportDone, isImportSelRow);

  TTMSFMXGridEditorType = (etEdit, etNumericEdit, etSignedNumericEdit, etFloatEdit, etSignedFloatEdit, etUppercaseEdit, etMixedCaseEdit, etLowerCaseEdit,
    etMoneyEdit, etHexEdit, etAlphaNumericEdit, etValidCharsEdit, etEditBtn, etNumericEditBtn, etSignedNumericEditBtn, etFloatEditBtn, etSignedFloatEditBtn, etUppercaseEditBtn,
    etMixedCaseEditBtn, etLowerCaseEditBtn, etMoneyEditBtn, etHexEditBtn, etAlphaNumericEditBtn, etValidCharsEditBtn, etComboBox, etComboEdit, etSpinBox, etDatePicker, etDateEdit, etColorPicker,
    etColorComboBox, etTrackBar, etArcDial, etCustom);

  TTMSFMXGridIOProgressEvent = procedure(Sender: TObject; CurrentRow, TotalRows: integer; Progres: smallint) of object;

  TTMSFMXGridIOEvent = procedure(Sender: TObject; Col,Row: integer; var Value: string) of object;


  TTMSFMXGridColumnType = (ctDefault, ctButton, ctCheckBox, ctProgressBar, ctRadioButton);

  TTMSFMXGridColumn = class(TCollectionItem)
  private
    FColor: TAlphaColor;
    FVertAlignment: TTextAlign;
    FFontColor: TAlphaColor;
    FHorzAlignment: TTextAlign;
    FSortFormat: TSortFormat;
    FFont: TFont;
    FEditor: TTMSFMXGridEditorType;
    FReadOnly: boolean;
    FBorderColor: TAlphaColor;
    FID: string;
    FBorderWidth: single;
    FWordWrap: boolean;
    FFixed: boolean;
    FSortPrefix: string;
    FSortSuffix: string;
    FBrushKind: TBrushKind;
    FTag: integer;
    FComboItems: TStringList;
    FColumnType: TTMSFMXGridColumnType;
    FName: string;
    procedure SetColor(const Value: TAlphaColor);
    procedure SetFontColor(const Value: TAlphaColor);
    procedure SetHorzAlignment(const Value: TTextAlign);
    procedure SetVertAlignment(const Value: TTextAlign);
    procedure SetFont(const Value: TFont);
    procedure SetBorderColor(const Value: TAlphaColor);
    procedure SetBorderWidth(const Value: single);
    procedure SetWordWrap(const Value: boolean);
    procedure SetFixed(const Value: boolean);
    procedure SetBrushKind(const Value: TBrushKind);
    procedure SetComboItems(const Value: TStringList);
    procedure SetColumnType(const Value: TTMSFMXGridColumnType);
  protected
    procedure Changed;
    property BrushKind: TBrushKind read FBrushKind write SetBrushKind;
    function GetDisplayName: string; override;
  public
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
  published
    property BorderColor: TAlphaColor read FBorderColor write SetBorderColor default claSilver;
    property BorderWidth: single read FBorderWidth write SetBorderWidth;
    property Color: TAlphaColor read FColor write SetColor;
    property ColumnType: TTMSFMXGridColumnType read FColumnType write SetColumnType default ctDefault;
    property ComboItems: TStringList read FComboItems write SetComboItems;
    property Editor: TTMSFMXGridEditorType read FEditor write FEditor;
    property Fixed: boolean read FFixed write SetFixed;
    property FontColor: TAlphaColor read FFontColor write SetFontColor;
    property Font: TFont read FFont write SetFont;
    {$IFDEF DELPHIXE6_LVL}
    property HorzAlignment: TTextAlign read FHorzAlignment write SetHorzAlignment default TTextAlign.Leading;
    {$ELSE}
    property HorzAlignment: TTextAlign read FHorzAlignment write SetHorzAlignment default TTextAlign.taLeading;
    {$ENDIF}
    property ID: string read FID write FID;
    property Name: string read FName write FName;
    property ReadOnly: boolean read FReadOnly write FReadOnly default false;
    property SortFormat: TSortFormat read FSortFormat write FSortFormat default ssAutomatic;
    property SortSuffix: string read FSortSuffix write FSortSuffix;
    property SortPrefix: string read FSortPrefix write FSortPrefix;
    property Tag: integer read FTag write FTag default 0;
    {$IFDEF DELPHIXE6_LVL}
    property VertAlignment: TTextAlign read FVertAlignment write SetVertAlignment default TTextAlign.Center;
    {$ELSE}
    property VertAlignment: TTextAlign read FVertAlignment write SetVertAlignment default TTextAlign.taCenter;
    {$ENDIF}
    property WordWrap: boolean read FWordWrap write SetWordWrap default false;
  end;

  TTMSFMXGridColumns = class(TOwnedCollection)
  private
    function GetItem(Index: integer): TTMSFMXGridColumn;
    procedure SetItem(Index: integer; const Value: TTMSFMXGridColumn);
  public
    constructor Create(AOwner: TPersistent);
    function ColumnByName(AName: string): TTMSFMXGridColumn;
    function ColumnByID(AID: string): TTMSFMXGridColumn;
    property Items[Index: integer]: TTMSFMXGridColumn read GetItem write SetItem; default;
    function Add: TTMSFMXGridColumn;
    function Insert(Index: integer): TTMSFMXGridColumn;
    function ByID(Id: string): TTMSFMXGridColumn;
  end;

  TTMSFMXGridData = class(TTMSFMXCustomGridData)
  private
    FInternalColumnUpdate, FInternalRowUpdate: Boolean;
    FColumnProp: TCellProperty;
    FColumns: TTMSFMXGridColumns;
    FUseColumns: boolean;
    FUpdateCount: Integer;
    FFirstCellApply: Boolean;
    FBlockUpdate: Boolean;
    FFocusedCell, FStartCell, FStopCell: TCell;
    FRowList: TList<TRowInfo>;
    FRowDisplayList: TList<Integer>;
    FColumnDisplayList: TList<Integer>;
    FHiddenColumnList: TList<Integer>;
    FHiddenRowList: TList<Integer>;
    FColSelect: TIntList;
    FSelectedCells: TCellList;
    FRowCount: integer;
    FSortDirection: TSortDirection;
    FSortColumn: integer;
    FSortDir: integer;
    FIOOffset: TPoint;
    FFilter: TFilter;
    FSortIndexes: TSortIndexList;
    FCellCalcList: TIntList;
    FSaveHiddenCells: boolean;
    FFindCol, FFindRow: integer;
    FSearchCell: TPoint;
    FSearchCache: string;
    FFindParams: TFindParams;
    FFindBusy: boolean;
    FColumnCount: Integer;
    FFixedRows: Integer;
    FFixedColumns: Integer;
    FRowH: TSingleList;
    FFixedRightColumns: Integer;
    FColumnW: TSingleList;
    FFixedFooterRows: Integer;
    FDefaultRowHeight: Single;
    FDefaultColumnWidth: Single;
    FCheckFalse: string;
    FCheckTrue: string;
    FPrevProgress: smallint;
    FGroupColumn: integer;
    FGroupWidth: single;
    FGroupCaption: string;
    FGroupFooter: string;
    FIsGrouping: boolean;
    FFloatFormat: string;
    FColumnOrder: TIntList;
    FSelectionMode: TTMSFMXGridSelectionMode;
    FBitmapContainer: TTMSFMXBitmapContainer;
    FOnIOProgress: TTMSFMXGridIOProgressEvent;
    FOnSortFormat: TTMSFMXGridSortFormatEvent;
    FOnCustomCompare: TTMSFMXGridCustomCompareEvent;
    FOnRawCompare: TTMSFMXGridRawCompareEvent;
    FOptions: TTMSFMXGridOptions;
    FMarkList: TCellMarkList;
    FDefaultFont: TFont;
    FOnClipboardAfterPasteCell: TTMSFMXGridClipboardAfterPasteEvent;
    FOnClipboardBeforePasteCell: TTMSFMXGridClipboardBeforePasteEvent;
    FOnCellsChanged: TTMSFMXGridCellsChangedEvent;
    FOnClipboardPaste: TTMSFMXGridCellsChangedEvent;
    FOnLoadCell: TTMSFMXGridIOEvent;
    FOnSaveCell: TTMSFMXGridIOEvent;
    FFilterListBox: TListbox;
    FFilterTimer: TTimer;
    FFilterSenderListBox: TObject;
    FOnNeedFilterDropDown: TTMSFMXGridNeedFilterDropDownEvent;
    FOnNeedFilterDropDownData: TTMSFMXGridNeedFilterDropDownDataEvent;
    FOnFilterSelect: TTMSFMXGridFilterSelectEvent;
    FOnGroupCalc: TTMSFMXGridColumnCalcEvent;
    FOnColumnCalc: TTMSFMXGridColumnCalcEvent;
    procedure SetDefaultColumnWidth(const Value: Single);
    procedure SetDefaultRowHeight(const Value: Single);
    procedure EnsureRow(Row: integer);
    procedure EnsureCol(Col: integer);
    function GetCells(Col, Row: integer): TCellData;
    procedure SetCells(Col, Row: integer; const Value: TCellData);
    function GetIntObjects(Col, Row: integer): TObject;
    procedure SetIntObjects(Col, Row: integer; const Value: TObject);
    function GetRowCount: integer;
    function GetHiddenColCount: integer;
    function GetHiddenRowCount: integer;
    function GetTotalColCount: integer;
    function GetTotalRowCount: integer;
    function GetAllCells(Col, Row: integer): string;
    procedure SetAllCells(Col, Row: integer; const Value: string);
    function GetColors(Col, Row: integer): TAlphaColor;
    procedure SetColors(Col, Row: integer; const Value: TAlphaColor);
    procedure SetColumnCount(const Value: Integer);
    procedure SetRowCount(const Value: Integer);
    procedure SetColumnW(const Value: TSingleList);
    procedure SetFixedColumns(const Value: Integer);
    procedure SetFixedFooterRows(const Value: Integer);
    procedure SetFixedRightColumns(const Value: Integer);
    procedure SetFixedRows(const Value: Integer);
    procedure SetRowH(const Value: TSingleList);
    function GetColumnCount: Integer;
    procedure SetColWidths(Col: Integer; const Value: Single);
    procedure SetRowHeights(Row: Integer; const Value: Single);
    procedure SetSelectionMode(const Value: TTMSFMXGridSelectionMode);
    function GetSelection: TCellRange;
    procedure SetSelection(const Value: TCellRange);
    function GetFocusedCell: TCell;
    procedure SetFocusedCell(const Value: TCell);
    function GetCellProps(Col, Row: integer): TCellProperty;
    function GetRowSelect(Row: integer): boolean;
    procedure SetRowSelect(Row: integer; const Value: boolean);
    procedure SetSortColumn(const Value: integer);
    procedure SetSortDirection(const Value: TSortDirection);
    procedure SetOptions(const Value: TTMSFMXGridOptions);
    function GetObjects(Col, Row: integer): TObject;
    procedure SetObjects(Col, Row: integer; const Value: TObject);
    function GetAllFloats(Col, Row: integer): double;
    procedure SetAllFloats(Col, Row: integer; const Value: double);
    function GetMergeCellPrintPageNr(Col, Row: integer): integer;
    procedure SetMergeCellPrintPageNr(Col, Row: integer; const Value: integer);
    function GetHorzAlignments(Col, Row: integer): TTextAlign;
    procedure SetHorzAlignments(Col, Row: integer; const Value: TTextAlign);
    function GetVertAlignments(Col, Row: integer): TTextAlign;
    procedure SetVertAlignments(Col, Row: integer; const Value: TTextAlign);
    function GetFontStyles(Col, Row: integer): TFontStyles;
    procedure SetFontStyles(Col, Row: integer; const Value: TFontStyles);
    function GetFontSizes(Col, Row: integer): single;
    procedure SetFontSizes(Col, Row: integer; const Value: single);
    function GetFontNames(Col, Row: integer): string;
    procedure SetFontNames(Col, Row: integer; const Value: string);
    function GetFontColors(Col, Row: integer): TAlphaColor;
    procedure SetFontColors(Col, Row: integer; const Value: TAlphaColor);
    function GetRowSelectionCount: integer;
    function GetAngles(Col, Row: integer): single;
    procedure SetAngles(Col, Row: integer; const Value: single);
    function GetCellControls(Col, Row: integer): TFMXObject;
    procedure SetCellControls(Col, Row: integer; const Value: TFMXObject);
    function GetReadOnlys(Col, Row: integer): boolean;
    procedure SetReadOnlys(Col, Row: integer; const Value: boolean);
    function SaveToHTMLString(dir: string): string;
    function GetColumnSelect(Col: integer): boolean;
    function GetColumnSelectionCount: integer;
    procedure SetColumnSelect(Col: integer; const Value: boolean);
    function GetCellSelect(Col, Row: integer): boolean;
    function GetCellSelectionCount: integer;
    procedure SetCellSelect(Col, Row: integer; const Value: boolean);
    function GetSelectedColumn(index: integer): integer;
    function GetSelectedRow(index: integer): integer;
    function GetSelectedCell(index: integer): TCell;
    function GetCheckBoxState(Col, Row: integer): boolean;
    procedure SetCheckBoxState(Col, Row: integer; const Value: boolean);
    function GetFixedCellSelect(Col, Row: integer): boolean;
    procedure HandleFilterListClick(Sender: TObject);
    procedure ApplyFilterListBoxStyleLookUp(Sender: TObject);
    procedure HandleFilterTimer(Sender: TObject);
    function GetComments(Col, Row: Integer): string;
    procedure SetComments(Col, Row: Integer; const Value: string);
    function GetCommentColors(Col, Row: Integer): TAlphaColor;
    procedure SetCommentColors(Col, Row: Integer; const Value: TAlphaColor);
    function GetColumnCalculation(Col: Integer): TColumnCalculation;
    procedure SetColumnCalculation(Col: Integer;
      const Value: TColumnCalculation);
    procedure UpdateColumnCalc(Col: Integer; Value: TColumnCalculation);
    function GetFloats(Col, Row: Integer): double;
    procedure SetFloats(Col, Row: Integer; const Value: double);
    function GetStrippedCells(Col, Row: Integer): string;
    function GetRowText(Row: integer): string;
    function GetBitmaps(Col, Row: Integer): TBitmap;
    function GetBooleans(Col, Row: Integer): Boolean;
    procedure SetBitmaps(Col, Row: Integer; const Value: TBitmap);
    procedure SetBooleans(Col, Row: Integer; const Value: Boolean);
    procedure SetColumns(const Value: TTMSFMXGridColumns);
  protected
    function GetCellState(ACol, ARow: Integer): TCellState; virtual;
    procedure AssignBitmapEx(Source, Dest: TPersistent); virtual;
    property UpdateCount: Integer read FUpdateCount write FUpdateCount;
    property FirstCellApply: Boolean read FFirstCellApply write FFirstCellApply;
    property GroupColumn: Integer read FGroupColumn;
    property BlockUpdate: Boolean read FBlockUpdate write FBlockUpdate;
    procedure SelectCell(Cell: TCell; Shift: TShiftState = []; MouseDragging: Boolean = False); virtual;
    property StartCell: TCell read FStartCell write FStartCell;
    property StopCell: TCell read FStopCell write FStopCell;
    property FocCell: TCell read FFocusedCell write FFocusedCell;
    procedure SetStartCell(ACol, ARow: Integer);
    procedure SetStopCell(ACol, ARow: Integer);
    procedure SetFocusCell(ACol, ARow: Integer);
    function GetColWidths(AIndex: integer): Single;
    function GetRowHeights(AIndex: integer): Single;
    function CompareLine(Col, Row1, Row2: integer): integer;
    function CompareRows(Col, Row1, Row2: integer): integer;
    function CompareRowsIndexed(Col, Row1, Row2: integer): integer;
    procedure QuickSortRows(Col,Left,Right: Integer);
    procedure QuickSortRowsIndexed(Col,Left,Right: Integer);
    procedure SortGroupedInt(Indexed: boolean);
    function MatchFilter(ARow: Integer): Boolean; virtual;
    function MatchCell(Col,Row: Integer): Boolean;
    function MaxCharsInCol(ACol: Integer): Integer;
    procedure InputFromCSV(FileName: string;insertmode: Boolean; MaxRows: integer = -1);
    procedure OutputToCSV(FileName: string;appendmode: Boolean; unicode: Boolean);
    procedure OutputToHTML(FileName: string;appendmode: Boolean; Show: boolean = false; Unicode: boolean = true);
    procedure OutputToAscii(FileName: string;appendmode: Boolean; unicode: Boolean);
    function FindInternal(StartCell:TPoint; s:string; FindParams: TFindParams): TPoint;
    procedure ResetUpdate; virtual;
    procedure UpdateGridCells; virtual;
    procedure UpdateGrid(Update: Boolean = False); virtual;
    property ColumnW: TSingleList read FColumnW write SetColumnW;
    property RowH: TSingleList read FRowH write SetRowH;
    property CellProps[Col,Row: integer]: TCellProperty read GetCellProps;
    procedure ExportNotification(AState: TGridExportState; ARow: Integer); virtual;
    procedure ImportNotification(AState: TGridImportState; ARow: Integer); virtual;
    procedure DoIOProgress(CurrentRow,TotalRows: integer); virtual;
    procedure DoSortFormat(Col: integer; var SortFormat: TSortFormat; var Prefix, Suffix: string); virtual;
    procedure DoCustomCompare(Value1,Value2: TCellData; var res: integer); virtual;
    procedure DoRawCompare(Col, Row1, Row2: integer; var res: integer); virtual;
    procedure DoClipboardBeforePasteCell(Col,Row: integer; var AValue: string; var Allow: boolean); virtual;
    procedure DoClipboardAfterPasteCell(Col,Row: integer; AValue: string); virtual;
    procedure DoNeedFilterDropDown(Col,Row: integer; var Allow: boolean); virtual;
    procedure DoNeedFilterDropDownData(Col,Row: integer; Values: TStrings); virtual;
    procedure DoCellsChanged(Cells: TCellRange); virtual;
    procedure DoClipboardPaste(Cells: TCellRange); virtual;
    function DoSaveCell(Col,Row: integer): string; virtual;
    procedure DoLoadCell(Col,Row: integer; var Value: string); virtual;
    procedure DoFilterSelect(Col: integer; var Condition: string); virtual;

    procedure RemoveCellControl(Col,Row: Integer; AClass: TClass);
    function IsCellControl(Col,Row: Integer; AClass: TClass): boolean;

    function IsIgnoredColumn(Col: integer): boolean;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure Loaded; override;
    property IntObjects[Col,Row: integer]: TObject read GetIntObjects write SetIntObjects;
    property MergeCellPrintPageNr[Col,Row: integer]: integer read GetMergeCellPrintPageNr write SetMergeCellPrintPageNr;
    function GetDefaultFont(Col,Row: integer): TFont;

    procedure DoOptionsChange(Sender: TObject);
    property FixedCellSelect[Col,Row: integer]: boolean read GetFixedCellSelect;

    procedure DoFixedCellCheckBoxClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoFixedCellSpinBoxChange(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoFixedCellButtonClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoFixedCellDropDownButtonClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoFixedCellBitmapClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;

    procedure DoGetCellReadOnly(ACol, ARow: Integer; var AReadOnly: Boolean); virtual;
    function DoGetCellIsDataCheckBox(ACol, ARow: Integer): Boolean; virtual;

    procedure DoCellClick(ACol, ARow: Integer); virtual;
    procedure DoFixedCellClick(Shift: TShiftState; ACol, ARow: Integer); virtual;

    procedure DoCellCheckBoxClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellRadioButtonClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellShowPopup(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellHidePopup(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellButtonClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellBitmapClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellCommentClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoCellSortClick(Shift: TShiftState; ACol, ARow: Integer; Cell: TFMXObject; Direction: TSortDirection); virtual;
    procedure DoCellNodeClick(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoGetCellIsFixed(ACol, ARow: Integer; var ACellFixed: Boolean); virtual;
    procedure DoGetRowIsBand(ARow: Integer; var ARowBand: Boolean); virtual;
    procedure DoCanInsertRow(ARow: Integer; var Allow: Boolean); virtual;
    procedure DoCanDeleteRow(ARow: Integer; var Allow: Boolean); virtual;
    procedure DoCanAppendRow(ARow: Integer; var Allow: Boolean); virtual;
    procedure DoCanAppendColumn(ACol: Integer; var Allow: Boolean); virtual;

    procedure DoInsertRow(ARow: Integer); virtual;
    procedure DoDeleteRow(ARow: Integer); virtual;
    procedure DoAppendRow(ARow: Integer); virtual;
    procedure DoAppendColumn(ACol: Integer); virtual;

    procedure DoGetCellControl(ACol, ARow: Integer; var AControl: TFMXObject); virtual;
    procedure DoGetCellClass(ACol, ARow: Integer; var CellClassType: TFMXObjectClass); virtual;
    procedure DoGetCellData(ACol, ARow: Integer; var CellString: String); virtual;
    procedure DoGetCellProperties(ACol, ARow: Integer; Cell: TFMXObject); virtual;
    procedure DoGetCellAppearance(ACol, ARow: Integer; Cell: TFMXObject; ACellState: TCellState); virtual;
    procedure DoGetCellLayout(ACol, ARow: Integer; ALayout: TTMSFMXGridCellLayout; ACellState: TCellState); virtual;
    procedure DoGetCellMergeInfo(ACol, ARow: Integer; var ABaseCol: Integer; var ABaseRow: Integer; var AColSpan: Integer; var ARowSpan: Integer); virtual;

    procedure DoGetCellEditorType(ACol, ARow: Integer; var CellEditorType: TTMSFMXGridEditorType); virtual;
    procedure DoGetCellEditorCustomClassType(ACol, ARow: Integer; var CellEditorCustomClassType: TFMXObjectClass); virtual;
    procedure DoCellEditGetData(ACol, ARow: Integer; CellEditor: TFMXObject; var CellString: String); virtual;
    procedure DoCellEditValidateData(ACol, ARow: Integer; CellEditor: TFMXObject; var CellString: String; var Allow: Boolean); virtual;
    procedure DoCellEditSetData(ACol, ARow: Integer; CellEditor: TFMXObject; var CellString: String); virtual;

    procedure DoCellEditGetColor(ACol, ARow: Integer; CellEditor: TFMXObject; var CellColor: TAlphaColor); virtual;
    procedure DoCellEditValidateColor(ACol, ARow: Integer; CellEditor: TFMXObject; var CellColor: TAlphaColor; var Allow: Boolean); virtual;
    procedure DoCellEditSetColor(ACol, ARow: Integer; CellEditor: TFMXObject; var CellColor: TAlphaColor); virtual;

    procedure DoCellEditDone(ACol, ARow: Integer; CellEditor: TFMXObject); virtual;
    procedure DoGetCellEditorProperties(ACol, ARow: Integer; CellEditor: TFMXObject); virtual;

    function DoColumnCalc(Acol,FromRow, ToRow: integer): double; virtual;
    function DoColumnCalcGroup(Acol,FromRow, ToRow: integer): double; virtual;

    procedure DoCellAnchorClick(ACol, ARow: Integer; AAnchor: String); virtual;
    procedure DoCanEditCell(ACol, ARow: Integer; var Allow: Boolean); virtual;
    procedure DoCanSizeColumn(ACol: Integer; var Allow: Boolean); virtual;
    procedure DoCanSizeRow(ARow: Integer; var Allow: Boolean); virtual;
    procedure DoColumnSize(ACol: Integer; var NewWidth: Single); virtual;
    procedure DoRowSize(ARow: Integer; var NewHeight: Single); virtual;
    procedure DoColumnSized(ACol: Integer; NewWidth: Single); virtual;
    procedure DoRowSized(ARow: Integer; NewHeight: Single); virtual;
    procedure DoCanSortColumn(ACol: Integer; var Allow: Boolean); virtual;
    procedure DoColumnSorted(ACol: Integer; Direction: TSortDirection); virtual;
    function DoIsCellSelected(ACol, ARow: Integer): boolean; virtual;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Clear;
    procedure ClearSortColumn;
    procedure ClearCells(CellRange: TCellRange);

    procedure Debug;

    function DisplToRealRow(Row: integer): integer;
    function RealToDisplRow(Row: integer): integer;

    function DisplToRealColumn(Col: integer): integer;
    function RealToDisplColumn(Col: integer): integer;

    function GetColumnRange(Col: integer; DoFixed: boolean = false): TCellRange;
    function GetRowRange(Row: integer; DoFixed: boolean = false): TCellRange;
    function GetGridRange(DoFixed: boolean = false): TCellRange;
    function GetCellRange(Col,Row: Integer): TCellRange;

    procedure RandomFill(DoFixed: Boolean = false;rnd: Integer = 100);
    procedure LinearFill(DoFixed: Boolean = false);

    property Columns: TTMSFMXGridColumns read FColumns write SetColumns;

    property FloatFormat: string read FFloatFormat write FFloatFormat;
    property ColumnSelectionCount: integer read GetColumnSelectionCount;
    property ColumnSelect[Col: integer]: boolean read GetColumnSelect write SetColumnSelect;

    property CellSelectionCount: integer read GetCellSelectionCount;
    property CellSelect[Col,Row: integer]: boolean read GetCellSelect write SetCellSelect;

    property SelectedRow[index: integer]: integer read GetSelectedRow;
    property SelectedColumn[index: integer]: integer read GetSelectedColumn;
    property SelectedCell[index: integer]: TCell read GetSelectedCell;

    property RowSelectionCount: integer read GetRowSelectionCount;
    property RowSelect[Row: integer]: boolean read GetRowSelect write SetRowSelect;
    property Selection: TCellRange read GetSelection write SetSelection;
    property FocusedCell: TCell read GetFocusedCell write SetFocusedCell;
    function CellRange(AStartCol, AStartRow, AEndCol, AEndRow: Integer): TCellRange;
    function SingleCell(ACol, ARow: Integer): TCell;

    function IsReadOnly(Col,Row: integer): boolean;

    procedure ClearRowSelect;
    function GetSelectedRowCount: Integer;
    procedure SelectRows(FromRow, ToRow: integer);
    procedure UnSelectRows(FromRow, ToRow: integer);

    procedure ClearColumnSelect;
    function GetSelectedColumnCount: Integer;
    procedure SelectColumns(FromColumn, ToColumn: integer);
    procedure UnSelectColumns(FromColumn, ToColumn: integer);

    procedure ClearCellSelect;
    function GetSelectedCellCount: Integer;
    procedure SelectCells(ARange: TCellRange);
    procedure UnSelectCells(ARange: TCellRange);

    procedure SetColumnOrder;
    procedure ResetColumnOrder;

    function ColumnStatesToString: string;
    procedure StringToColumnStates(Value: string);

    function ColumnPosition(ACol: integer): integer;
    function ColumnAtPosition(ACol: integer): integer;

    function GetColumnDragLayoutFill: TBrush;
    function GetColumnDragLayoutStroke: TBrush;
    function GetColumnDragLayoutStrokeThickness: Single;
    function GetColumnDragLayoutStrokeJoin: TStrokeJoin;
    function GetColumnDragLayoutStrokeDash: TStrokeDash;
    function GetColumnDragLayoutStrokeCap: TStrokeCap;

    function GetRowDragLayoutFill: TBrush;
    function GetRowDragLayoutStroke: TBrush;
    function GetRowDragLayoutStrokeThickness: Single;
    function GetRowDragLayoutStrokeJoin: TStrokeJoin;
    function GetRowDragLayoutStrokeDash: TStrokeDash;
    function GetRowDragLayoutStrokeCap: TStrokeCap;

    function GetDefaultSortFill: TBrush;
    function GetDefaultIndexedSortFill: TBrush;
    function GetDefaultSortStroke: TBrush;
    function GetDefaultIndexedSortStroke: TBrush;
    function GetDefaultSortFont: TFont;
    function GetDefaultIndexedSortFont: TFont;
    function GetDefaultSortFontFill: TText;
    function GetDefaultIndexedSortFontFill: TText;

    function GetDefaultGroupLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultSummaryLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultNormalLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultSelectedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultFixedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultFocusedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultFixedSelectedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultBandLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
    function GetDefaultDropDownBitmap: TTMSFMXBitmap;

    function Find(StartCell:TPoint; s:string; FindParams: TFindParams): TPoint;
    function FindFirst(s:string; FindParams: TFindParams): TPoint;
    function FindNext: TPoint;
    function Replace(OrigStr,NewStr: string; FindParams: TFindParams): integer;
    function IsFixed(Col, Row: Integer): Boolean; virtual;
    function IsBand(Row: Integer): Boolean; virtual;
    function IsNormalFixed(Col, Row: Integer): Boolean; virtual;
    function NextSelectableRow(Col, Row: Integer): TCell; virtual;
    function NextSelectableColumn(Col, Row: Integer): TCell; virtual;
    function PreviousSelectableColumn(Col, Row: Integer): TCell; virtual;
    function PreviousSelectableRow(Col, Row: Integer): TCell; virtual;
    procedure ApplyFilter;
    procedure RemoveFilter;
    property FindCol: integer read FFindCol write FFindCol;
    property FindRow: integer read FFindRow write FFindRow;

    function LookupInColumn(Col: integer; AValue: string; AllRows: boolean = false; AutoGoto: boolean = false): integer;
    function LookupInColumnFromRow(Col,Row: integer; AValue: string; AllRows: boolean = false; AutoGoto: boolean = false): integer;

    property SortColumn: integer read FSortColumn write SetSortColumn;
    property SortDirection: TSortDirection read FSortDirection write SetSortDirection;
    property IOOffset: TPoint read FIOOffset write FIOOffset;
    property Filter: TFilter read FFilter;
    procedure AutoNumberCol(Col: integer);
    procedure AutoNumberRow(Row: integer);

    procedure AddHeaderCheckBox(Col,Row: integer; State: boolean = false);
    procedure AddCheckBox(Col,Row: integer; State: boolean = false);
    procedure AddCheckBoxColumn(Col: integer);
    procedure RemoveCheckBox(Col,Row: integer);
    function IsCheckBox(Col,Row: integer): boolean;
    property CheckBoxState[Col,Row: integer]: boolean read GetCheckBoxState write SetCheckBoxState;

    procedure AddDataCheckBox(Col,Row: integer; State: boolean = false);
    procedure AddDataCheckBoxColumn(Col: integer);

    procedure AddRadioButton(Col,Row,Index: integer; State: boolean = false);
    procedure AddRadioButtonColumn(Col,Index: integer);
    procedure RemoveRadioButton(Col,Row: integer);
    function IsRadioButton(Col,Row: integer): boolean;
    function RadioButtonState(Col,Row: integer): boolean;

    procedure AddButton(Col,Row: integer; AText: string; AWidth: integer = 24);
    function IsButton(Col,Row: integer): boolean;
    procedure RemoveButton(Col,Row: integer);

    procedure AddBitmap(Col,Row: Integer; AName: string); overload;
    procedure AddBitmap(Col,Row: Integer; ABitmap: TBitmap); overload;
    procedure AddBitmapFile(Col,Row: Integer; AFileName: string);
    function CreateBitmap(Col,Row: Integer): TBitmap;
    function CreateCheck(Col,Row: Integer): Boolean;

    procedure AddDataBitmap(Col,Row: Integer);

    procedure RemoveBitmap(Col,Row: Integer);
    function IsBitmap(Col,Row: integer): boolean;
    function GetBitmap(Col,Row: integer): TBitmap;
    procedure SetBitmapName(Col,Row: integer; AName: string);
    function GetBitmapName(Col,Row: integer): string;

    procedure AddProgressBar(Col,Row: Integer; Value: Single);
    procedure AddDataProgressBar(Col,Row: Integer);
    procedure SetProgressBarValue(Col,Row: Integer; Value: single);
    function GetProgressBarValue(Col,Row: integer): single;
    function IsProgressBar(Col,Row: Integer): boolean;
    procedure RemoveProgressBar(Col,Row: Integer);

    procedure AddSummary(Row: integer);
    procedure RemoveSummary(Row: integer);
    function IsSummary(Row: integer): boolean;
    procedure AddNode(Row, Span: Integer);
    procedure RemoveNode(Row: Integer);
    function IsNode(Row: Integer): boolean;
    procedure SetNodeState(Row: Integer; State: TNodeState);
    function GetNodeState(Row: integer): TNodeState;
    procedure SetNodeSpan(Row: Integer; Span: Integer);
    function GetNodeSpan(Row: Integer): Integer;
    function GetNode(Row: Integer): TCellNode;
    procedure OpenNode(Row: Integer);
    procedure CloseNode(Row: Integer);
    procedure OpenAllNodes;
    procedure CloseAllNodes;

    procedure AddComboBox(Col,Row: Integer; Items: TStrings; Editable: boolean = false);
    procedure RemoveComboBox(Col,Row: Integer);
    function IsComboBox(Col,Row: integer): boolean;
    function GetComboIndex(Col,Row: integer): integer;
    procedure SetComboIndex(Col,Row,Value: integer);

    procedure MergeSelection(ASelection: TCellRange);
    procedure MergeCells(Col,Row,ColCount,RowCount: integer);
    function IsMerged(Col,Row: integer): boolean;
    function IsYMergedCell(Col,Row: integer): boolean;
    function IsXMergedCell(Col,Row: integer): boolean;
    function IsBaseCell(Col,Row: integer): boolean;
    function ColSpan(Col,Row: integer): integer;
    function RowSpan(Col,Row: integer): integer;
    function CellSpan(Col,Row: integer): TPoint;
    function GetCellSize(Col, Row: Integer): TSizeF;
    function BaseCell(Col,Row: integer): TCell;
    function IsBaseCellEx(Col,Row: integer; var BaseCol,BaseRow: integer): boolean;
    procedure SplitCell(Col,Row: integer);

    procedure DeleteRow(Row: integer);
    procedure DeleteRows(Row,Count: integer);
    procedure InsertRow(Row: integer);
    procedure InsertRows(Row,Count: integer);
    procedure SwapCells(Col, Row1, Row2: integer);
    procedure SwapNormalCells(Row1, Row2: integer);
    procedure SwapRows(Row1, Row2: integer);
    procedure MoveRow(FromRow, ToRow: integer);

    procedure HighlightInCell(DoCase: Boolean; Col,Row: Integer; HiText: string);
    procedure HighlightInCol(DoFixed,DoCase: Boolean; Col: Integer; HiText: string);
    procedure HighlightInRow(DoFixed,DoCase: Boolean; Row: Integer; HiText: string);
    procedure HighlightInGrid(DoFixed,DoCase: Boolean; HiText: string);
    procedure UnHighlightInCell(Col,Row: Integer);
    procedure UnHighlightInCol(DoFixed: Boolean; Col: Integer);
    procedure UnHighlightInRow(DoFixed: Boolean; Row: Integer);
    procedure UnHighlightInGrid(DoFixed: Boolean);
    procedure UnHighlightAll;
    procedure MarkInCell(DoCase: Boolean; Col,Row: Integer; HiText: string);
    procedure MarkInCol(DoFixed,DoCase: Boolean; Col: Integer; HiText: string);
    procedure MarkInRow(DoFixed,DoCase: Boolean; Row: Integer; HiText: string);
    procedure MarkInGrid(DoFixed,DoCase: Boolean; HiText: string);
    procedure UnMarkInCell(Col,Row: Integer);
    procedure UnMarkInCol(DoFixed: Boolean; Col: Integer);
    procedure UnMarkInRow(DoFixed: Boolean; Row: Integer);
    procedure UnMarkInGrid(DoFixed: Boolean);
    procedure UnMarkAll;

    procedure UnGroup;
    procedure Group(Column: integer);

    procedure GroupCalc(Col,method: Integer);
    procedure GroupSum(Col: Integer);
    procedure GroupAvg(Col: Integer);
    procedure GroupMin(Col: Integer);
    procedure GroupMax(Col: Integer);

    function ColumnSum(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnAvg(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnMin(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnMax(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnDistinct(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnStdDev(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnCustomCalc(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;
    function ColumnCustomCalcGroup(ACol: integer; FromRow: integer = -1; ToRow: Integer = -1): Double;

    procedure UpdateCalculations;
    procedure UpdateCalculation(ACol: integer);

    procedure GroupCustomCalc(Col: Integer);
    procedure GroupCount(Col: Integer);
    procedure GroupDistinct(Col: Integer);
    procedure GroupStdDev(Col: Integer);

    procedure HideRow(Row: integer);
    procedure HideRows(Row: integer; Count: integer);
    procedure UnHideRow(Row: integer);
    procedure UnHideRows(Row: integer; Count: integer);
    procedure UnHideRowsAll;
    function IsHiddenRow(Row: integer): boolean;

    procedure HideColumn(Col: integer);
    procedure HideColumns(Col: integer; Count: integer);
    procedure UnHideColumn(Col: integer);
    procedure UnHideColumns(Col: integer; Count: integer);
    procedure UnHideColumnsAll;
    function IsHiddenColumn(Col: integer): boolean;

    procedure DeleteColumn(Col: integer);
    procedure InsertColumn(Col: integer);
    procedure MoveColumn(FromCol, ToCol: integer);
    procedure SwapColumns(Col1,Col2: integer);

    procedure SortData(Column: integer; Direction: TSortDirection);
    procedure SortIndexed;
    procedure SortGrouped(Column: integer; Direction: TSortDirection);
    procedure SortGroupedIndexed;
    property SortIndexes: TSortIndexList read FSortIndexes;

    property Cells[Col,Row: Integer]: TCellData read GetCells write SetCells;
    property StrippedCells[Col,Row: Integer]: string read GetStrippedCells;
    property RowText[Row: Integer]: string read GetRowText;
    property ColumnWidths[Col: Integer]: Single read GetColWidths write SetColWidths;
    property RowHeights[Row: Integer]: Single read GetRowHeights write SetRowHeights;
    property AllCells[Col,Row: Integer]: string read GetAllCells write SetAllCells;
    property AllFloats[Col,Row: Integer]: double read GetAllFloats write SetAllFloats;
    property Bitmaps[Col,Row: Integer]: TBitmap read GetBitmaps write SetBitmaps;
    property Booleans[Col,Row: Integer]: Boolean read GetBooleans write SetBooleans;
    property Floats[Col,Row: Integer]: double read GetFloats write SetFloats;
    property Colors[Col,Row: Integer]: TAlphaColor read GetColors write SetColors;
    property Angles[Col,Row: Integer]: single read GetAngles write SetAngles;
    property CellControls[Col,Row: Integer]: TFMXObject read GetCellControls write SetCellControls;
    property HorzAlignments[Col,Row: Integer]: TTextAlign read GetHorzAlignments write SetHorzAlignments;
    property VertAlignments[Col,Row: Integer]: TTextAlign read GetVertAlignments write SetVertAlignments;
    property FontSizes[Col,Row: Integer]: single read GetFontSizes write SetFontSizes;
    property FontStyles[Col,Row: Integer]: TFontStyles read GetFontStyles write SetFontStyles;
    property FontNames[Col,Row: Integer]: string read GetFontNames write SetFontNames;
    property FontColors[Col,Row: Integer]: TAlphaColor read GetFontColors write SetFontColors;
    property ReadOnlys[Col,Row: Integer]: boolean read GetReadOnlys write SetReadOnlys;
    property Objects[Col,Row: Integer]: TObject read GetObjects write SetObjects;
    property Comments[Col,Row: Integer]: string read GetComments write SetComments;
    property CommentColors[Col,Row: Integer]: TAlphaColor read GetCommentColors write SetCommentColors;

    property ColumnCalculation[Col: Integer]: TColumnCalculation read GetColumnCalculation write SetColumnCalculation;

    property HiddenRowCount: Integer read GetHiddenRowCount;
    property HiddenColumnCount: Integer read GetHiddenColCount;
    property SaveHiddenCells: Boolean read FSaveHiddenCells write FSaveHiddenCells;
    property TotalRowCount: Integer read GetTotalRowCount;
    property TotalColCount: Integer read GetTotalColCount;

    procedure CopyToClipboard(SelectedCells: boolean = True);
    procedure CutToClipboard(SelectedCells: boolean = True);
    procedure PasteFromClipboard;

    procedure LoadFromCSV(FileName: string;  MaxRows: Integer = -1);
    procedure InsertFromCSV(FileName: String; MaxRows: Integer = -1);
    procedure LoadFromFile(FileName: string);
    procedure LoadFromFixed(FileName:string;positions: TIntList; DoTrim: boolean = true; MaxRows: integer = -1);
    procedure LoadFromStream(Stream: TStream);
    //procedure LoadFromXML(FileName: string; LevelToRow: boolean = false;LoadFieldDescr:boolean = true);

    procedure SaveToFile(FileName: string; Unicode: boolean = true);
    procedure SaveToCSV(FileName: string; Unicode: boolean = true);
    procedure AppendToCSV(FileName: string; Unicode: boolean = true);

    procedure AppendToHTML(FileName: string; Show: boolean = false; Unicode: boolean = true);
    procedure SaveToHTML(FileName: string; Show: boolean = false; Unicode: boolean = true);

    procedure SaveToASCII(FileName: string; Unicode: boolean = true);
    procedure AppendToASCII(FileName: string; Unicode: boolean = true);

    procedure SaveToFixed(FileName: string; Positions: TIntList);
    procedure SaveToStream(Stream: TStream);

    procedure SaveToXML(FileName: string; ListDescr, RecordDescr:string;FieldDescr: TStrings; ExportEmptyCells: boolean = false);

    property CheckTrue: string read FCheckTrue write FCheckTrue;
    property CheckFalse: string read FCheckFalse write FCheckFalse;
    property FilterListbox: TListBox read FFilterListBox;
    property UseColumns: boolean read FUseColumns write FUseColumns;

  published
    property BitmapContainer: TTMSFMXBitmapContainer read FBitmapContainer write FBitmapContainer;

    property DefaultRowHeight: Single read FDefaultRowHeight write SetDefaultRowHeight;
    property DefaultColumnWidth: Single read FDefaultColumnWidth write SetDefaultColumnWidth;

    property FixedColumns: Integer read FFixedColumns write SetFixedColumns default 1;
    property FixedRows: Integer read FFixedRows write SetFixedRows default 1;
    property FixedRightColumns: Integer read FFixedRightColumns write SetFixedRightColumns default 0;
    property FixedFooterRows: Integer read FFixedFooterRows write SetFixedFooterRows default 0;

    property ColumnCount: Integer read GetColumnCount write SetColumnCount default 5;
    property RowCount: Integer read GetRowCount write SetRowCount default 10;

    property SelectionMode: TTMSFMXGridSelectionMode read FSelectionMode write SetSelectionMode default smSingleCell;

    property OnColumnCalc: TTMSFMXGridColumnCalcEvent read FOnColumnCalc write FOnColumnCalc;
    property OnGroupCalc: TTMSFMXGridColumnCalcEvent read FOnGroupCalc write FOnGroupCalc;
    property OnCustomCompare: TTMSFMXGridCustomCompareEvent read FOnCustomCompare write FOnCustomCompare;
    property OnIOProgress: TTMSFMXGridIOProgressEvent read FOnIOProgress write FOnIOProgress;
    property OnRawCompare: TTMSFMXGridRawCompareEvent read FOnRawCompare write FOnRawCompare;
    property OnSortFormat: TTMSFMXGridSortFormatEvent read FOnSortFormat write FOnSortFormat;
    property OnCellsChanged: TTMSFMXGridCellsChangedEvent read FOnCellsChanged write FOnCellsChanged;
    property OnClipboardBeforePasteCell: TTMSFMXGridClipboardBeforePasteEvent read FOnClipboardBeforePasteCell write FOnClipboardBeforePasteCell;
    property OnClipboardAfterPasteCell: TTMSFMXGridClipboardAfterPasteEvent read FOnClipboardAfterPasteCell write FOnClipboardAfterPasteCell;
    property OnClipboardPaste: TTMSFMXGridCellsChangedEvent read FOnClipboardPaste write FOnClipboardPaste;
    property OnNeedFilterDropDown: TTMSFMXGridNeedFilterDropDownEvent read FOnNeedFilterDropDown write FOnNeedFilterDropDown;
    property OnNeedFilterDropDownData: TTMSFMXGridNeedFilterDropDownDataEvent read FOnNeedFilterDropDownData write FOnNeedFilterDropDownData;
    property OnFilterSelect: TTMSFMXGridFilterSelectEvent read FOnFilterSelect write FOnFilterSelect;
    property OnLoadCell: TTMSFMXGridIOEvent read FOnLoadCell write FOnLoadCell;
    property OnSaveCell: TTMSFMXGridIOEvent read FOnSaveCell write FOnSaveCell;
    property Options: TTMSFMXGridOptions read FOptions write SetOptions;
  end;


  function CellRange(AStartCol, AStartRow, AEndCol, AEndRow: Integer): TCellRange;
  function EqualsCellRange(Range1,Range2: TCellRange): boolean;
  function InCellRange(Col,Row: integer; Range: TCellRange): boolean;
  function Cell(ACol,ARow: integer): TCell;

implementation

uses
  Math, FMX.Platform, FMX.TMSHTMLEngine;

function Cell(ACol,ARow: integer): TCell;
begin
  Result.Col := ACol;
  Result.Row := ARow;
end;

function CellRange(AStartCol, AStartRow, AEndCol, AEndRow: Integer): TCellRange;
begin
  Result.StartCol := AStartCol;
  Result.StartRow := AStartRow;
  Result.EndCol := AEndCol;
  Result.EndRow := AEndRow;
end;

function EqualsCellRange(Range1,Range2: TCellRange): boolean;
begin
  Result := (Range1.StartCol = Range2.StartCol) and
            (Range1.StartRow = Range2.StartRow) and
            (Range1.EndCol = Range2.EndCol) and
            (Range1.EndRow = Range2.EndRow);
end;

function InCellRange(Col,Row: integer; Range: TCellRange): boolean;
begin
  Result := (Col >= Range.StartCol) and (Col <= Range.EndCol) and
            (Row >= Range.StartRow) and (Row <= Range.EndRow);
end;

{ TVariantList }

destructor TVariantList.Destroy;
//var
//  I: Integer;
//  Temp: TArray<TObject>;
begin
  FOnChange := nil;
  FOnChanging := nil;

  // If the list owns the Objects gather them and free after the list is disposed
//  if OwnsObjects then
//  begin
//    SetLength(Temp, FCount);
//    for I := 0 to FCount - 1 do
//      Temp[I] := FList[I].FObject;
//  end;

  inherited Destroy;
  FCount := 0;
  SetCapacity(0);

  // Free the objects that were owned by the list
//  if Length(Temp) > 0 then
//    for I := 0 to Length(Temp) - 1 do
//      Temp[I].Free;
end;

function TVariantList.Add(const S: TCellData): Integer;
begin
  Result := AddObject(S, nil);
end;

function TVariantList.AddObject(const S: TCellData; AObject: TObject): Integer;
begin
  if not Sorted then
    Result := FCount
  else
    if Find(S, Result) then
      case Duplicates of
        dupIgnore: Exit;
        dupError: Error(@SDuplicateString, 0);
      end;
  InsertItem(Result, S, AObject);
end;

procedure TVariantList.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  if Source is TVariantList then
  begin
    FCaseSensitive := TVariantList(Source).FCaseSensitive;
    FDuplicates := TVariantList(Source).FDuplicates;
    FSorted := TVariantList(Source).FSorted;
  end;
end;

procedure TVariantList.BeginUpdate;
begin
  if FUpdateCount = 0 then SetUpdateState(True);
  Inc(FUpdateCount);
end;

procedure TVariantList.Changed;
begin
  if (FUpdateCount = 0) and Assigned(FOnChange) then
    FOnChange(Self);
end;

procedure TVariantList.Changing;
begin
  if (FUpdateCount = 0) and Assigned(FOnChanging) then
    FOnChanging(Self);
end;

procedure TVariantList.Clear;
//var
//  I: Integer;
//  Temp: TArray<TObject>;
begin
  if FCount <> 0 then
  begin
    Changing;

    // If the list owns the Objects gather them and free after the list is disposed
//    if OwnsObjects then
//    begin
//      SetLength(Temp, FCount);
//      for I := 0 to FCount - 1 do
//        Temp[I] := FList[I].FObject;
//    end;

    FCount := 0;
    SetCapacity(0);

    // Free the objects that were owned by the list
//    if Length(Temp) > 0 then
//      for I := 0 to Length(Temp) - 1 do
//        Temp[I].Free;

    Changed;
  end;
end;

procedure TVariantList.Delete(Index: Integer);
var
  Obj: TObject;
begin
  if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index);
  Changing;
  // If this list owns its objects then free the associated TObject with this index
  if OwnsObjects then
    Obj := FList[Index].FObject
  else
    Obj := nil;

  // Direct memory writing to managed array follows
  //  see http://dn.embarcadero.com/article/33423
  // Explicitly finalize the element we about to stomp on with move
  Finalize(FList[Index]);
  Dec(FCount);
  if Index < FCount then
  begin
    System.Move(FList[Index + 1], FList[Index],
      (FCount - Index) * SizeOf(TVariantItem));
    // Make sure there is no danglng pointer in the last (now unused) element
    PPointer(@FList[FCount])^ := nil;
  end;
  if Obj <> nil then
    Obj.Free;
  Changed;
end;

procedure TVariantList.Error(const Msg: string; Data: Integer);
begin
  raise EStringListError.CreateFmt(Msg, [Data]) at
    PPointer(PByte(@Msg) + SizeOf(Msg) + SizeOf(Self) + SizeOf(Pointer))^;
end;

procedure TVariantList.EndUpdate;
begin
  Dec(FUpdateCount);
  if FUpdateCount = 0 then SetUpdateState(False);
end;

procedure TVariantList.Error(Msg: PResStringRec; Data: Integer);
begin
  raise EStringListError.CreateFmt(LoadResString(Msg), [Data]) at
    PPointer(PByte(@Msg) + SizeOf(Msg) + SizeOf(Self) + SizeOf(Pointer))^;

end;

procedure TVariantList.Exchange(Index1, Index2: Integer);
begin
  if (Index1 < 0) or (Index1 >= FCount) then Error(@SListIndexError, Index1);
  if (Index2 < 0) or (Index2 >= FCount) then Error(@SListIndexError, Index2);
  Changing;
  ExchangeItems(Index1, Index2);
  Changed;
end;

procedure TVariantList.ExchangeItems(Index1, Index2: Integer);
var
  Temp: Pointer;
  Item1, Item2: PStringItem;
begin
  Item1 := @FList[Index1];
  Item2 := @FList[Index2];
  Temp := Pointer(Item1^.FString);
  Pointer(Item1^.FString) := Pointer(Item2^.FString);
  Pointer(Item2^.FString) := Temp;
  Temp := Item1^.FObject;
  Item1^.FObject := Item2^.FObject;
  Item2^.FObject := Temp;
end;

function TVariantList.Find(const S: TCellData; var Index: Integer): Boolean;
var
  L, H, I, C: Integer;
begin
  Result := False;
  L := 0;
  H := FCount - 1;
  while L <= H do
  begin
    I := (L + H) shr 1;
    C := CompareStrings(FList[I].FString, S);
    if C < 0 then L := I + 1 else
    begin
      H := I - 1;
      if C = 0 then
      begin
        Result := True;
        if Duplicates <> dupAccept then L := I;
      end;
    end;
  end;
  Index := L;
end;

function TVariantList.Get(Index: Integer): TCellData;
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Result := FList[Index].FString;
end;

function TVariantList.GetCapacity: Integer;
begin
  Result := FCapacity;
end;

function TVariantList.GetCount: Integer;
begin
  Result := FCount;
end;

function TVariantList.GetObject(Index: Integer): TObject;
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Result := FList[Index].FObject;
end;

function TVariantList.GetTextStr: string;
var
  i: integer;
begin
  Result := '';
  for i := 0 to Count - 1 do
  begin
    Result := Result + FList[i].FString;
    if (i > 0) and (i < Count - 1) then
      Result := Result + ',';
  end;
end;

function TVariantList.GetVariant(Index: Integer): TCellData;
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Result := FList[Index].FString;
end;

procedure TVariantList.Grow;
var
  Delta: Integer;
begin
  if FCapacity > 64 then Delta := FCapacity div 4 else
    if FCapacity > 8 then Delta := 16 else
      Delta := 4;
  SetCapacity(FCapacity + Delta);
end;

function TVariantList.IndexOf(const S: TCellData): Integer;
begin
  if not Sorted then Result := IndexOf(S) else
    if not Find(S, Result) then Result := -1;
end;

procedure TVariantList.Insert(Index: Integer; const S: TCellData);
begin
  InsertObject(Index, S, nil);
end;

procedure TVariantList.InsertObject(Index: Integer; const S: TCellData;
  AObject: TObject);
begin
  if Sorted then Error(@SSortedListError, 0);
  if (Index < 0) or (Index > FCount) then Error(@SListIndexError, Index);
  InsertItem(Index, S, AObject);
end;

procedure TVariantList.Move(CurIndex, NewIndex: Integer);
var
  TempObject: TObject;
  TempString: TCellData;
begin
  if CurIndex <> NewIndex then
  begin
    BeginUpdate;
    try
      TempString := Get(CurIndex);
      TempObject := GetObject(CurIndex);
      PutObject(CurIndex, nil);
      Delete(CurIndex);
      InsertObject(NewIndex, TempString, TempObject);
    finally
      EndUpdate;
    end;
  end;
end;

procedure TVariantList.InsertItem(Index: Integer; const S: TCellData; AObject: TObject);
begin
  Changing;
  if FCount = FCapacity then Grow;
  if Index < FCount then
    System.Move(FList[Index], FList[Index + 1],
      (FCount - Index) * SizeOf(TVariantItem));

  with FList[Index] do
  begin
    Pointer(FString) := nil;
    //FString := varEmpty;
    FObject := AObject;
    FString := S;
  end;
  Inc(FCount);
  Changed;
end;

procedure TVariantList.Put(Index: Integer; const S: TCellData);
begin
  if Sorted then Error(@SSortedListError, 0);
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Changing;
  FList[Index].FString := S;
  Changed;
end;

procedure TVariantList.PutObject(Index: Integer; AObject: TObject);
begin
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Changing;
  FList[Index].FObject := AObject;
  Changed;
end;

procedure TVariantList.PutVariant(Index: Integer; const Value: TCellData);
begin
  if Sorted then Error(@SSortedListError, 0);
  if Cardinal(Index) >= Cardinal(FCount) then
    Error(@SListIndexError, Index);
  Changing;
  FList[Index].FString := Value;
  Changed;
end;

procedure TVariantList.QuickSort(L, R: Integer; SCompare: TVariantListSortCompare);
var
  I, J, P: Integer;
begin
  repeat
    I := L;
    J := R;
    P := (L + R) shr 1;
    repeat
      while SCompare(Self, I, P) < 0 do Inc(I);
      while SCompare(Self, J, P) > 0 do Dec(J);
      if I <= J then
      begin
        if I <> J then
          ExchangeItems(I, J);
        if P = I then
          P := J
        else if P = J then
          P := I;
        Inc(I);
        Dec(J);
      end;
    until I > J;
    if L < J then QuickSort(L, J, SCompare);
    L := I;
  until I >= R;
end;

procedure TVariantList.SetCapacity(NewCapacity: Integer);
begin
  if NewCapacity < FCount then
    Error(@SListCapacityError, NewCapacity);
  if NewCapacity <> FCapacity then
  begin
    SetLength(FList, NewCapacity);
    FCapacity := NewCapacity;
  end;
end;

procedure TVariantList.SetSorted(Value: Boolean);
begin
  if FSorted <> Value then
  begin
    if Value then Sort;
    FSorted := Value;
  end;
end;

procedure TVariantList.SetUpdateState(Updating: Boolean);
begin
  if Updating then Changing else Changed;
end;

function StringListCompareStrings(List: TVariantList; Index1, Index2: Integer): Integer;
begin
  Result := List.CompareStrings(List.FList[Index1].FString,
                                List.FList[Index2].FString);
end;

procedure TVariantList.Sort;
begin
  CustomSort(StringListCompareStrings);
end;

procedure TVariantList.CustomSort(Compare: TVariantListSortCompare);
begin
  if not Sorted and (FCount > 1) then
  begin
    Changing;
    QuickSort(0, FCount - 1, Compare);
    Changed;
  end;
end;

function TVariantList.CompareStrings(const S1, S2: TCellData): Integer;
begin
  Result := AnsiCompareText(S1, S2);
end;

function TVariantList.CompareVariants(const S1, S2: TCellData): Integer;
begin
  if S1 > S2 then
    Result := 1
  else
    if S1 = S2 then
      Result := 0
    else
      Result := -1;

//  if CaseSensitive then
//    Result := AnsiCompareStr(S1, S2)
//  else
//    Result := AnsiCompareText(S1, S2);
end;

constructor TVariantList.Create;
begin
  inherited Create;
end;

constructor TVariantList.Create(OwnsObjects: Boolean);
begin
  inherited Create;
  FOwnsObject := OwnsObjects;
end;

procedure TVariantList.SetCaseSensitive(const Value: Boolean);
begin
  if Value <> FCaseSensitive then
  begin
    FCaseSensitive := Value;
    if Sorted then
    begin
      // Calling Sort won't sort the list because CustomSort will
      // only sort the list if it's not already sorted
      Sorted := False;
      Sorted := True;
    end;
  end;
end;

{ TSortIndexList }

procedure TSortIndexList.AddIndex(ColumnIndex: Integer; ADirection: TSortDirection);
begin
  if ADirection = sdAscending then
    Add(ColumnIndex)
  else
    Add(integer($80000000) or ColumnIndex);
end;

function TSortIndexList.FindIndex(ColumnIndex: integer): integer;
var
  i: Integer;
begin
  Result := -1;
  i := 0;
  while i < Count do
  begin
    if Items[i] and $7FFFFFFF = ColumnIndex then
    begin
      Result := i;
      Break;
    end;
    Inc(i);
  end;
end;

function TSortIndexList.GetSortColumns(i: Integer): Integer;
begin
  Result := Items[i] and $7FFFFFFF;
end;

function TSortIndexList.GetSortDirections(i: Integer): Boolean;
begin
  Result := not ((Items[i] and $80000000) = $80000000);
end;

procedure TSortIndexList.LoadFromString(s: string);
var
  a: string;
  i,e: integer;
begin
  Clear;

  while length(s) > 0 do
  begin
    if pos(';',s) > 0 then
    begin
      a := copy(s,1,pos(';',s) - 1);
      System.Delete(s,1,pos(';',s));
    end
    else
    begin
      a := s;
      s := '';
    end;

    if length(a) > 1 then
    begin
      val(copy(a,2,length(a)),i,e);
      if a[1] = 'u' then
        AddIndex(i,sdAscending)
      else
        AddIndex(i,sdDescending);
    end;
  end;
end;

function TSortIndexList.SaveToString: string;
var
  i: integer;
  s,a: string;
begin
  s := '';
  a := '';
  for i := 0 to Count - 1 do
  begin
    if Items[i] and $80000000 = $80000000 then
    begin
      a := 'd' + inttostr($FFFF and Items[i]);
    end
    else
    begin
      a := 'u' + inttostr($FFFF and Items[i]);
    end;
    if s = '' then
      s := a
    else
      s := s + ';'+ a;
  end;
  Result := s;
end;

procedure TSortIndexList.SetSortColumns(i: Integer; const Value: Integer);
begin
  Items[i] := (DWord(Value) and $7FFFFFFF) + (Items[i] and $80000000);
end;

procedure TSortIndexList.SetSortDirections(i: Integer;
  const Value: Boolean);
begin
  if Value then
    Items[i] := (Items[i] and $7FFFFFFF)
  else
    Items[i] := (DWord(Items[i]) or $80000000);
end;

procedure TSortIndexList.ToggleIndex(ColumnIndex: integer);
var
  i: Integer;
begin
  i := 0;
  while i < Count do
  begin
    if Items[i] and $7FFFFFFF = ColumnIndex then
    begin
      if Items[i] and $80000000 = $80000000 then
        Items[i] := Items[i] and $7FFFFFFF
      else
        Items[i] := Items[i] or Integer($80000000);
      Break;
    end;
    Inc(i);
  end;
end;


{ TTMSFMXGridData }

procedure TTMSFMXGridData.AppendToASCII(FileName: string; Unicode: boolean);
begin
  OutputToASCII(FileName, true, Unicode);
end;

procedure TTMSFMXGridData.AppendToCSV(FileName: string; Unicode: boolean);
begin
  OutputToCSV(FileName,True,Unicode);
end;

procedure TTMSFMXGridData.AppendToHTML(FileName: string; Show,
  Unicode: boolean);
begin
  OutputToHTML(Filename, true, Show, Unicode);
end;

procedure TTMSFMXGridData.ApplyFilter;
var
  i,h: Integer;
  HideList: TList<Integer>;
  cnd: boolean;

begin
  HideList := TList<Integer>.Create;


  try
    for i := 1 to RowCount - 1 do
    begin
      cnd := (Options.Filtering.Rows = frAll) or
             (Options.Filtering.Rows = frNormal) and not IsNode(i) and not IsSummary(i) and not IsFixed(FixedColumns,i);

      if not MatchFilter(i) and cnd then
      begin
        HideList.Add(i);
      end;
    end;

    h := 0;

    for i := 0 to HideList.Count - 1 do
    begin
      while TRowInfo(FRowDisplayList.Items[h]) <> TRowInfo(HideList.Items[i]) do
        inc(h);

      if TRowInfo(FRowDisplayList.Items[h]) = TRowInfo(HideList.Items[i]) then
        FRowDisplayList.Delete(h);
    end;

    FRowCount := FRowCount - HideList.Count;

  finally
    HideList.Free;
    UpdateGrid;
  end;
end;

procedure TTMSFMXGridData.ApplyFilterListBoxStyleLookUp(Sender: TObject);
begin
  (Sender as TListBox).ViewportPosition := PointF(0, 0);
end;

procedure TTMSFMXGridData.AssignBitmapEx(Source, Dest: TPersistent);
begin
//
end;

procedure TTMSFMXGridData.AutoNumberCol(Col: integer);
var
  i: integer;
begin
  for i := 0 to FRowList.Count - 1 do
  begin
    Cells[Col,i] := inttostr(i);
  end;
end;

procedure TTMSFMXGridData.AutoNumberRow(Row: integer);
var
  i: integer;
begin
  for i := 0 to ColumnCount - 1 do
  begin
    Cells[i, Row] := inttostr(i);
  end;
end;

function TTMSFMXGridData.IsBaseCellEx(Col,Row: integer; var BaseCol,BaseRow: integer): boolean;
var
  c: TCell;
begin
  c := BaseCell(Col,Row);
  BaseCol := c.Col;
  BaseRow := c.Row;
  Result := (c.Col = Col) and (c.Row = Row);
end;

function TTMSFMXGridData.IsBand(Row: Integer): Boolean;
begin
  Result := Odd(Row);
  DoGetRowIsBand(Row, Result);
end;

function TTMSFMXGridData.IsBaseCell(Col, Row: integer): boolean;
var
  c: TCell;
begin
  c := BaseCell(Col,Row);
  Result := (c.Col = Col) and (c.Row = Row);
end;

function TTMSFMXGridData.BaseCell(Col, Row: integer): TCell;
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  Result.Col := Col;
  Result.Row := Row;
  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
  begin
    Result.Col := cp.BaseCol;
    Result.Row := DisplToRealRow(cp.BaseRow);
  end
end;

function TTMSFMXGridData.CellRange(AStartCol, AStartRow, AEndCol,
  AEndRow: Integer): TCellRange;
begin
  Result.StartCol := AStartCol;
  Result.StartRow := AStartRow;
  Result.EndCol := AEndCol;
  Result.EndRow := AEndRow;
end;

function TTMSFMXGridData.SingleCell(ACol, ARow: Integer): TCell;
begin
  Result.Col := ACol;
  Result.Row := ARow;
end;

procedure TTMSFMXGridData.Clear;
var
  i,j: integer;
  cp: TCellProperty;
  ri: TRowInfo;
begin
  FirstCellApply := False;
  BeginUpdate;

  UnHideRowsAll;
  UnHideColumnsAll;

  for i := FRowList.Count - 1 downto 0 do
  begin
    if Assigned(FRowList.Items[i]) then
    begin
      for j := 0 to TRowInfo(FRowList.Items[i]).Data.Count - 1 do
      begin
        cp := TCellProperty(TRowInfo(FRowList.Items[i]).Data.Objects[j]);

        if Assigned(cp) and cp.IsBaseCell(j,i) then
        begin
          cp.FreeControl;
          cp.Free;
        end;
      end;
      ri := TRowInfo(FRowList.Items[i]);
      ri.Free;
    end;
    FRowList.Delete(i);
  end;

  FRowDisplayList.Clear;
  FColumnDisplayList.Clear;
  FHiddenColumnList.Clear;
  FHiddenRowList.Clear;

  EndUpdate;
end;

procedure TTMSFMXGridData.ClearCells(CellRange: TCellRange);
var
  i,j: integer;
  cp: TCellProperty;
  sl: TDataList;
begin
  BeginUpdate;


  for i := CellRange.StartRow to CellRange.EndRow do
  begin
    if (i < FRowList.Count) and Assigned(FRowList.Items[i]) then
    begin
      for j := CellRange.StartCol to Min(CellRange.EndCol, TRowInfo(FRowList.Items[i]).Data.Count - 1) do
      begin

        SplitCell(j,i);

        sl := TRowInfo(FRowList.Items[i]).Data;

        sl.Values[j] := '';

        cp := TCellProperty(sl.Objects[j]);
        if Assigned(cp) then
        begin
          cp.Free;
          sl.Objects[j] := nil;
        end;
        {$IFDEF IOS}
        Sleep(10);
        {$ENDIF}
      end;
    end;
  end;

  EndUpdate;
end;

procedure TTMSFMXGridData.ClearCellSelect;
begin
  FSelectedCells.Clear;
end;

procedure TTMSFMXGridData.ClearColumnSelect;
begin
  FColSelect.Clear;
end;

constructor TTMSFMXGridData.Create(AOwner: TComponent);
var
  i: integer;
begin
  inherited;

  FUseColumns := True;
  FColumns := TTMSFMXGridColumns.Create(Self);
  for i := 0 to 4 do FColumns.Add;

  FSelectionMode := smSingleCell;
  FGroupColumn := -1;
  FSortColumn := -1;
  FRowCount := 10;
  FColumnCount := 5;
  FDefaultRowHeight := 24;
  FDefaultColumnWidth := 68;
  FFixedRows := 1;
  FFixedColumns := 1;
  FFixedRightColumns := 0;
  FFixedFooterRows := 0;
  FCheckTrue := 'TRUE';
  FCheckFalse := 'FALSE';
  FFloatFormat := '%g';

  FColumnOrder := TIntList.Create;

  FDefaultFont := TFont.Create;

  FOptions := TTMSFMXGridOptions.Create;
  FOptions.OnChange := DoOptionsChange;

  FColumnW := TSingleList.Create(Self);
  FRowH := TSingleList.Create(Self);

  FRowList := TList<TRowInfo>.Create;
  FRowDisplayList := TList<Integer>.Create;
  FColumnDisplayList := TList<Integer>.Create;
  FHiddenColumnList := TList<Integer>.Create;
  FHiddenRowList := TList<Integer>.Create;
  FColSelect := TIntList.Create;
  FSelectedCells := TCellList.Create;

  FMarkList := TCellMarkList.Create(Self);
  FCellCalcList := TIntList.Create;

  FIOOffset := Point(0,0);
  FFilter := TFilter.Create(Self);
  FSortIndexes := TSortIndexList.Create;

  FFilterListBox := TListBox.Create(Self);
  FFilterListBox.Name := Name + 'fltr';
  FFilterListBox.Tag := -1;
  FFilterListBox.OnClick := HandleFilterListClick;
  FFilterListBox.OnApplyStyleLookup := ApplyFilterListBoxStyleLookUp;

  FFilterTimer := TTimer.Create(Self);
  FFilterTimer.OnTimer := HandleFilterTimer;
  FFilterTimer.Interval := 100;
  FFilterTimer.Enabled := False;

  FColumnProp := TCellProperty.Create(FDefaultFont);

  SetColumnCount(FColumnCount);
end;

procedure TTMSFMXGridData.CutToClipboard(SelectedCells: boolean = true);
begin
  CopyToClipboard(SelectedCells);
  if SelectedCells then
  begin
    ClearCells(Selection);
    DoCellsChanged(Selection);
  end
  else
  begin
    Clear;
    DoCellsChanged(CellRange(0,0,ColumnCount - 1, RowCount - 1));
  end;
end;

destructor TTMSFMXGridData.Destroy;
begin
  Clear;
  FColumns.Free;
  FColumnProp.Free;
  FFilterTimer.Free;
  FDefaultFont.Free;
  FOptions.Free;
  FColumnW.Free;
  FRowH.Free;
  FRowDisplayList.Free;
  FColumnDisplayList.Free;
  FHiddenColumnList.Free;
  FHiddenRowList.Free;
  FRowList.Free;
  FFilter.Free;
  FSortIndexes.Free;
  FMarkList.Free;
  FColSelect.Free;
  FSelectedCells.Free;
  FFilterListBox.Free;
  FCellCalcList.Free;
  FColumnOrder.Clear;
  FColumnOrder.Free;
  inherited;
end;

procedure TTMSFMXGridData.DoAppendColumn(ACol: Integer);
begin

end;

procedure TTMSFMXGridData.DoAppendRow(ARow: Integer);
begin

end;

procedure TTMSFMXGridData.DoCanAppendColumn(ACol: Integer; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanAppendRow(ARow: Integer; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanDeleteRow(ARow: Integer; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanEditCell(ACol, ARow: Integer;
  var Allow: Boolean);
var
  cp: TCellProperty;
begin
  Allow := Options.Editing.Enabled;
  cp := CellProps[ACol,ARow];

//  cp := TCellProperty(IntObjects[ACol,ARow]);
  if Assigned(cp) then
    Allow := Allow and not cp.ReadOnly
  else
  begin
    if UseColumns and (ACol >= 0) and (ACol <= Columns.Count - 1) then
    begin
      ACol := DisplToRealColumn(ACol);
      Allow := Allow and not Columns[ACol].ReadOnly;
    end;
  end;
end;

procedure TTMSFMXGridData.DoCanInsertRow(ARow: Integer; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanSizeColumn(ACol: Integer;
  var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanSizeRow(ARow: Integer;
  var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCanSortColumn(ACol: Integer;
  var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCellAnchorClick(ACol,
  ARow: Integer; AAnchor: String);
begin

end;

procedure TTMSFMXGridData.DoCellBitmapClick(ACol,
  ARow: Integer; Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoCellButtonClick(ACol,
  ARow: Integer; Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoCellCheckBoxClick(ACol, ARow: Integer; Cell: TFMXObject);
var
  cp: TCellProperty;
  i,rr,ns, c,r: integer;
begin
  c := DisplToRealColumn(ACol);
  r := DisplToRealRow(ARow);
  cp := TCellProperty(IntObjects[c, r]);

  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellCheckBox) then
  begin
    (cp.Control as TCellCheckBox).Checked := (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg;

    if (cp.Control as TCellCheckBox).Header then
    begin
      for i := ARow + 1 to RowCount - 1 - FixedFooterRows do
      begin
        if IsCheckBox(ACol,i) then
          CheckBoxState[ACol,i] := (cp.Control as TCellCheckBox).Checked
        else
          break;
      end;
      UpdateGridCells;
    end;
  end;

  if Options.Grouping.AutoCheckGroup then
  begin
    rr := DisplToRealRow(ARow);
    if IsNode(rr) then
    begin
      ns := GetNodeSpan(rr);
      for I := rr to rr + ns do
      begin
        CheckBoxState[ACol,i] := (cp.Control as TCellCheckBox).Checked
      end;
      UpdateGridCells;
    end;
  end;

  if UseColumns and not Assigned(cp) then
  begin
    c := DisplToRealColumn(ACol);
    if c < Columns.Count then
    begin
      if (cell is TTMSFMXCheckGridCell) and (Columns[c].ColumnType = ctCheckBox) then
      begin
        if (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg then
          Cells[ACol,ARow] := CheckTrue
        else
          Cells[ACol,ARow] := CheckFalse;
      end;
    end;
  end
  else
  begin
    if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellDataCheckBox) then
    begin
      if (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg then
        Cells[ACol,ARow] := CheckTrue
      else
        Cells[ACol,ARow] := CheckFalse;
    end;
  end;
end;

procedure TTMSFMXGridData.DoCellClick(ACol, ARow: Integer);
var
  rr,i,ns: integer;
begin
  //
  if (GroupColumn <> -1) and (Options.Grouping.AutoSelectGroup) then
  begin
    rr := DisplToRealRow(ARow);
    if IsNode(rr) then
    begin
      ns := GetNodeSpan(rr);

      if SelectionMode = smDisjunctRow then
      begin
        for i := rr to rr + ns  do
          RowSelect[i] := true;
      end
      else
        if SelectionMode = smCellRange then
          Selection := CellRange(FixedColumns, ARow ,ColumnCount - 1 -  FixedRightColumns, ARow + ns);
    end;
  end;
end;

procedure TTMSFMXGridData.DoCellCommentClick(ACol,
  ARow: Integer; Cell: TFMXObject);
begin
  //
end;

procedure TTMSFMXGridData.DoCellEditDone(ACol, ARow: Integer; CellEditor: TFMXObject);
var
  cc: TColumnCalculation;
begin
  if (FixedFooterRows > 0) then
  begin
    cc := ColumnCalculation[ACol];
    if cc <> ccNone then
      UpdateColumnCalc(ACol,cc);
  end;
end;

procedure TTMSFMXGridData.DoCellEditSetColor(ACol, ARow: Integer;
  CellEditor: TFMXObject; var CellColor: TAlphaColor);
begin
  Colors[ACol, ARow] := CellColor;
end;

procedure TTMSFMXGridData.DoCellEditSetData(ACol,
  ARow: Integer; CellEditor: TFMXObject; var CellString: String);
begin
  Cells[ACol, ARow] := CellString;
end;

procedure TTMSFMXGridData.DoCellEditValidateColor(ACol, ARow: Integer;
  CellEditor: TFMXObject; var CellColor: TAlphaColor; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCellEditValidateData(ACol, ARow: Integer;
  CellEditor: TFMXObject; var CellString: String; var Allow: Boolean);
begin

end;

procedure TTMSFMXGridData.DoCellHidePopup(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

function TTMSFMXGridData.DoGetCellIsDataCheckBox(ACol, ARow: Integer): Boolean;
var
  cp: TCellProperty;
  c, r: Integer;
begin
  c := DisplToRealColumn(ACol);
  r := DisplToRealRow(ARow);
  Result := False;
  cp := CellProps[c,r];
  if UseColumns and not Assigned(IntObjects[c, r]) then
  begin
    if (c < Columns.Count) then
      Result := (Columns[c].ColumnType = ctCheckBox) or (Columns[c].ColumnType = ctRadioButton);
  end
  else
  begin
    if Assigned(cp) then
      Result := cp.Control is TCellDataCheckBox;
  end;
end;

procedure TTMSFMXGridData.DoGetCellIsFixed(ACol, ARow: Integer;
  var ACellFixed: Boolean);
var
  cp: TCellProperty;
begin
  inherited;

  cp := CellProps[ACol,ARow];

  if Assigned(cp) then
  begin
    ACellFixed := cp.Fixed;
  end;
end;

procedure TTMSFMXGridData.DoCellNodeClick(ACol, ARow: Integer;
  Cell: TFMXObject);
var
  cn: TCellNode;
  rr: integer;
begin
  rr := DisplToRealRow(ARow);

  cn := GetNode(rr);

  if not Assigned(cn) then
    Exit;

  if cn.Span <> -1 then
  begin
    if cn.State = nsOpen then
      CloseNode(rr)
    else
      OpenNode(rr);
  end;
end;

procedure TTMSFMXGridData.DoCellRadioButtonClick(ACol, ARow: Integer; Cell: TFMXObject);
var
  cp, cpt: TCellProperty;
  r: Integer;
  c, rr: Integer;
begin
  c := DisplToRealColumn(ACol);
  r := DisplToRealRow(ARow);
  cp := TCellProperty(IntObjects[c, r]);
  if UseColumns and not Assigned(cp) then
  begin
    for rr := FixedRows to RowCount - 1 - FixedFooterRows do
      Cells[ACol, rr] := CheckFalse;
    Cells[ACol, ARow] := CheckTrue;
  end
  else
  begin
    if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellRadioButton) then
    begin
      for rr := 0 to RowCount - 1 do
      begin
        cpt := TCellProperty(IntObjects[ACol,rr]);
        if Assigned(cpt) and Assigned(cpt.Control) and (cpt.Control is TCellRadioButton) and (cpt <> cp) then
        begin
          if (cpt.Control as TCellRadioButton).Index = (cp.Control as TCellRadioButton).Index then
            (cpt.Control as TCellRadioButton).Checked := false;
        end;
      end;
      (cp.Control as TCellRadioButton).Checked := (Cell as TTMSFMXRadioGridCell).RadioButton.IsCheckedProg;
    end;
  end;
end;

procedure TTMSFMXGridData.DoCellsChanged(Cells: TCellRange);
begin
  if Assigned(OnCellsChanged) then
    OnCellsChanged(Self, Cells);
end;

procedure TTMSFMXGridData.DoCellShowPopup(ACol, ARow: Integer;
  Cell: TFMXObject);
var
  sl: TStringList;
  i: integer;
begin
  if (Cell is TTMSFMXFixedGridCell) and (Options.Filtering.DropDown) and (Options.Filtering.DropDownFixedRow = ARow) and
     ((Cell as TTMSFMXFixedGridCell).PopupControl = FFilterListBox) then
  begin
    sl := TStringList.Create;

    try
      sl.Duplicates := dupIgnore;
      sl.Sorted := true;

      for i := FixedRows to RowCount - FixedFooterRows - 1 + HiddenRowCount do
      begin
        sl.Add(AllCells[ACol,i]);
      end;

      DoNeedFilterDropDownData(ACol,ARow,sl);

      ((Cell as TTMSFMXFixedGridCell).PopupControl as TListBox).BeginUpdate;
      ((Cell as TTMSFMXFixedGridCell).PopupControl as TListBox).Items.Assign(sl);
      ((Cell as TTMSFMXFixedGridCell).PopupControl as TListBox).ItemIndex := -1;
      ((Cell as TTMSFMXFixedGridCell).PopupControl as TListBox).EndUpdate;

      ((Cell as TTMSFMXFixedGridCell).PopupControl as TListBox).Tag := ACol;
    finally
      sl.Free;
    end;
  end;
end;

procedure TTMSFMXGridData.DoCellSortClick(Shift: TShiftState; ACol, ARow: Integer;
  Cell: TFMXObject; Direction: TSortDirection);
begin
  if FGroupColumn <> -1 then
  begin
    case Options.Sorting.Mode of
      gsmNormal: SortGrouped(ACol, Direction);
      gsmIndexed: SortGroupedIndexed;
    end;
  end
  else
  begin
    case Options.Sorting.Mode of
      gsmNormal: SortData(ACol, Direction);
      gsmIndexed: SortIndexed;
    end;
  end;
end;

procedure TTMSFMXGridData.DoClipboardAfterPasteCell(Col, Row: integer;
  AValue: string);
begin
  if Assigned(OnClipboardAfterPasteCell) then
    OnClipboardAfterPasteCell(Self,Col,Row,AValue);
end;

procedure TTMSFMXGridData.DoClipboardBeforePasteCell(Col, Row: integer;
  var AValue: string; var Allow: boolean);
begin
  if Assigned(OnClipboardBeforePasteCell) then
    OnClipboardBeforePasteCell(Self,Col,Row,AValue, Allow);
end;

procedure TTMSFMXGridData.DoClipboardPaste(Cells: TCellRange);
begin
  if Assigned(OnClipboardPaste) then
    FOnClipboardPaste(Self, Cells);
end;

function TTMSFMXGridData.DoColumnCalc(Acol, FromRow, ToRow: integer): double;
var
  res: double;
begin
  res := 0;
  if Assigned(OnColumnCalc) then
    OnColumnCalc(Self, ACol, FromRow, ToRow, res);

  Result := res;
end;

function TTMSFMXGridData.DoColumnCalcGroup(Acol, FromRow, ToRow: integer): double;
var
  res: double;
begin
  res := 0;
  if Assigned(OnGroupCalc) then
    OnGroupCalc(Self, ACol, FromRow, ToRow, res);

  Result := res;
end;


procedure TTMSFMXGridData.DoColumnSize(ACol: Integer;
  var NewWidth: Single);
begin
  //
end;

procedure TTMSFMXGridData.DoColumnSized(ACol: Integer;
  NewWidth: Single);
begin
  //
end;

procedure TTMSFMXGridData.DoColumnSorted(ACol: Integer;
  Direction: TSortDirection);
begin
  //
end;

procedure TTMSFMXGridData.DoFilterSelect(Col: integer; var Condition: string);
begin
  if Assigned(OnFilterSelect) then
    OnFilterSelect(Self, Col, Condition);
end;

procedure TTMSFMXGridData.DoFixedCellBitmapClick(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoFixedCellButtonClick(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoFixedCellCheckBoxClick(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoFixedCellClick(Shift: TShiftState; ACol,
  ARow: Integer);
begin

end;

procedure TTMSFMXGridData.DoFixedCellDropDownButtonClick(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoFixedCellSpinBoxChange(ACol, ARow: Integer;
  Cell: TFMXObject);
begin

end;

procedure TTMSFMXGridData.DoInsertRow(ARow: Integer);
begin

end;

procedure TTMSFMXGridData.DoIOProgress(CurrentRow, TotalRows: integer);
var
  np: smallint;
begin

  if Assigned(FOnIOProgress) and (TotalRows > 0) then
  begin
    np := Round(CurrentRow/TotalRows*100);

    if np <> FPrevProgress then
    begin
      FOnIOProgress(Self, CurrentRow, TotalRows, np);
      FPrevProgress := np;
    end;
  end;
end;

procedure TTMSFMXGridData.DoGetCellAppearance(ACol,
  ARow: Integer; Cell: TFMXObject; ACellState: TCellState);
begin
 //
end;

procedure TTMSFMXGridData.DoGetCellClass(ACol, ARow: Integer;
  var CellClassType: TFMXObjectClass);
var
  cp: TCellProperty;
  c, r: Integer;
begin
  if (Options.Sorting.FixedColumns or ((ACol >= FixedColumns) and (ACol <= ColumnCount - 1 - FixedRightColumns))) and (ARow = 0) then
    CellClassType := TTMSFMXFixedGridCell
  else
    CellClassType := TTMSFMXGridCell;

  c := DisplToRealColumn(ACol);
  r := DisplToRealRow(ARow);

  cp := CellProps[c,r];

  if not Assigned(IntObjects[c, r]) and UseColumns and (c < Columns.Count) and not IsFixed(c,r) then
  begin
    case Columns[c].ColumnType of
    ctButton: CellClassType := TTMSFMXButtonGridCell;
    ctCheckBox: CellClassType := TTMSFMXCheckGridCell;
    ctProgressBar: CellClassType := TTMSFMXProgressGridCell;
    ctRadioButton: CellClassType := TTMSFMXRadioGridCell;
    end;
  end;

  if Assigned(cp) and (cp.Angle <> 0) then
    CellClassType := TTMSFMXRotatedTextGridCell;

  if Assigned(cp) and Assigned(cp.Control) then
  begin
    if cp.Control is TCellCheckBox then
      CellClassType := TTMSFMXCheckGridCell;

    if cp.Control is TCellDataCheckBox then
      CellClassType := TTMSFMXCheckGridCell;

    if cp.Control is TCellRadioButton then
      CellClassType := TTMSFMXRadioGridCell;

    if cp.Control is TCellButton then
      CellClassType := TTMSFMXButtonGridCell;

    if cp.Control is TCellBitmapName then
      CellClassType := TTMSFMXBitmapGridCell;

    if cp.Control is TCellProgressBar then
      CellClassType := TTMSFMXProgressGridCell;

    if cp.Control is TCellBitmap then
      CellClassType := TTMSFMXBitmapGridCell;

    if cp.Control is TCellNode then
      CellClassType := TTMSFMXNodeGridCell;

    if cp.Control is TCellControl then
      CellClassType := TTMSFMXContainerGridCell;
  end;

  if Assigned(cp) then
  begin
    if (CellClassType = TTMSFMXGridCell) and (cp.Comment <> '') then
      CellClassType := TTMSFMXCommentGridCell;
  end;
end;

procedure TTMSFMXGridData.DoGetCellControl(ACol, ARow: Integer;
  var AControl: TFMXObject);
begin
  //
  if IsCellControl(ACol,ARow, TCellControl) then
  begin
    AControl := CellControls[ACol,ARow];
    (AControl as TControl).Visible := true;
  end;
end;

procedure TTMSFMXGridData.DoGetCellData(ACol, ARow: Integer;
  var CellString: String);
var
  cp: TCellProperty;
  emptydata: boolean;
  i,vp,rowc: integer;
  rr: integer;
  c: Integer;
begin
  rr := DisplToRealRow(ARow);
  c := DisplToRealColumn(ACol);
  cp := CellProps[c,rr];

  emptydata := false;

  if Assigned(cp) and Assigned(cp.Control) then
  begin
    if (cp.Control is TCellBitmapName) then
    begin
      emptydata := (cp.Control as TCellBitmapName).Data;
    end;

    if (cp.Control is TCellDataCheckBox) then
      emptydata := true;
  end;

  if UseColumns and not Assigned(IntObjects[c, rr]) then
  begin
    if (c >= 0) and (c < Columns.Count - 1) and not IsFixed(c,rr) then
      emptydata := (Columns[c].ColumnType = ctCheckBox) or (Columns[c].ColumnType = ctRadioButton);
  end;

  if not emptydata then
  begin
    CellString := Cells[ACol,ARow];

    for i := 0 to FMarkList.Count - 1 do
    begin
      if InCellRange(ACol,ARow,FMarkList.Items[i].CellRange) then
      begin
        case FMarkList.Items[i].MarkType of
        mtHighlight: CellString := Hilight(CellString,FMarkList.Items[i].Value,'hi',FMarkList.Items[i].CaseSensitive);
        mtError: CellString := Hilight(CellString,FMarkList.Items[i].Value,'e',FMarkList.Items[i].CaseSensitive);
        end;
      end;
    end;

    if Options.URL.Show and not Options.URL.Full then
    begin
      if VarPos('://', CellString,vp) > 0 then
         CellString := Copy(CellString,vp + 3,255)
      else
        if VarPos('mailto:', CellString,vp) = 1 then
           CellString := Copy(CellString,vp + 7,255);
    end;

    if Options.Grouping.ShowGroupCount and (FGroupColumn <> -1) and (ACol = 1) and IsNode(rr) then
    begin
      rowc := GetNodeSpan(rr);

      if Options.Grouping.GroupCountFormat <> '' then
        CellString := CellString + ' ' + Format(Options.Grouping.GroupCountFormat,[rowc])
      else
        CellString := CellString + ' ('+inttostr(rowc)+')';
    end;
  end
  else
    CellString := '';
end;

procedure TTMSFMXGridData.DoCellEditGetColor(ACol, ARow: Integer;
  CellEditor: TFMXObject; var CellColor: TAlphaColor);
begin

end;

procedure TTMSFMXGridData.DoCellEditGetData(ACol,
  ARow: Integer; CellEditor: TFMXObject; var CellString: String);
begin

end;

procedure TTMSFMXGridData.DoGetCellEditorProperties(ACol,
  ARow: Integer; CellEditor: TFMXObject);
var
  cp: TCellProperty;
  c: Integer;
begin
  inherited;

  if (CellEditor is TComboBox) or (CellEditor is TComboEdit) then
  begin
    c := DisplToRealColumn(ACol);
    if UseColumns and (c < Columns.Count) then
    begin
      if (Columns[c].Editor in [etComboBox, etComboEdit]) then
      begin
        if (CellEditor is TComboEdit) then
          (CellEditor as TComboEdit).Items.Assign(Columns[c].ComboItems)
        else if (CellEditor is TComboBox) then
          (CellEditor as TComboBox).Items.Assign(Columns[c].ComboItems);
      end;
    end;

    if IsCellControl(ACol,ARow, TCellCombo) then
    begin
      cp := TCellProperty(IntObjects[ACol,ARow]);

      if Assigned(cp) then
      begin
        if (CellEditor is TComboEdit) then
           (CellEditor as TComboEdit).Items.Assign((cp.Control as TCellCombo).Items);

        if (CellEditor is TComboBox) then
          (CellEditor as TComboBox).Items.Assign((cp.Control as TCellCombo).Items);
      end;
    end;
  end;
end;

procedure TTMSFMXGridData.DoGetCellEditorType(ACol, ARow: Integer;
  var CellEditorType: TTMSFMXGridEditorType);
var
  cp: TCellProperty;
  c: Integer;
begin
  c := DisplToRealColumn(ACol);

  if c < Columns.Count then
    CellEditorType := Columns[c].Editor;

  if IsCellControl(ACol,ARow, TCellCombo) then
  begin
    cp := TCellProperty(IntObjects[ACol,ARow]);

    if (cp.Control as TCellCombo).Editable then
      CellEditorType := etComboEdit
    else
      CellEditorType := etComboBox;
  end;
end;

procedure TTMSFMXGridData.DoGetCellLayout(ACol, ARow: Integer;
  ALayout: TTMSFMXGridCellLayout; ACellState: TCellState);
var
  cp: TCellProperty;
  c, r: Integer;
begin
  c := DisplToRealColumn(ACol);
  r := DisplToRealRow(ARow);
  cp := CellProps[c,r];

  if FUseColumns and not Assigned(cp) then
  begin
    if (ACellState = TCellState.csNormal) then
    begin
      FColumnProp.Color := Columns.Items[c].Color;
      FColumnProp.FontColor := Columns.Items[C].FontColor;
      FColumnProp.FontName := Columns.Items[c].Font.Family;
      FColumnProp.FontStyle := Columns.Items[C].Font.Style;
      FColumnProp.FontSize := Columns.Items[c].Font.Size;
      FColumnProp.BorderColor := Columns.Items[c].BorderColor;
      FColumnProp.BorderWidth := Columns.Items[c].BorderWidth;
    end;
    FColumnProp.AlignHorz := Columns.Items[c].HorzAlignment;
    FColumnProp.AlignVert := Columns.Items[c].VertAlignment;
    FColumnProp.ReadOnly := Columns.Items[c].ReadOnly;
    FColumnProp.Fixed := Columns.Items[c].Fixed;
    FColumnProp.WordWrap := Columns.Items[c].WordWrap;
    cp := FColumnProp;
  end;

  if FUseColumns then
    ALayout.WordWrapping := Columns.Items[c].WordWrap;

  if Assigned(cp) then
  begin
    if (cp.Color <> claNull) then
      ALayout.Fill.Color := cp.Color;

    if (cp.FontColor <> claNull) and (ACellState = TCellState.csNormal) then
      ALayout.FontFill.Color := cp.FontColor;

    if (cp.BorderColor <> claNull) and (ACellState = TCellState.csNormal) then
      ALayout.Stroke.Color := cp.BorderColor;

    ALayout.TextAlign := cp.AlignHorz;
    ALayout.VerticalTextAlign := cp.AlignVert;
    ALayout.Font.Style := cp.FontStyle;
    ALayout.Font.Size := cp.FontSize;
    ALayout.Font.Family := cp.FontName;
    ALayout.StrokeThickness := cp.BorderWidth;
    if not FUseColumns then
      ALayout.WordWrapping := cp.WordWrap;
  end;

  if Options.URL.Show then
  begin
    if IsURL(Cells[c,r]) then
    begin
      ALayout.FontFill.Color := Options.URL.Color;
      if Options.URL.Underline then
        ALayout.Font.Style := ALayout.Font.Style + [TFontStyle.fsUnderline];
    end;
  end;
end;

procedure TTMSFMXGridData.DoGetCellEditorCustomClassType(ACol,
  ARow: Integer; var CellEditorCustomClassType: TFMXObjectClass);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[ACol,ARow]);
  if Assigned(cp) and cp.ReadOnly then
    CellEditorCustomClassType := nil;
  inherited;
end;

procedure TTMSFMXGridData.DoGetCellMergeInfo(ACol,
  ARow: Integer; var ABaseCol, ABaseRow, AColSpan, ARowSpan: Integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[DisplToRealColumn(ACol),DisplToRealRow(ARow)]);

  if Assigned(cp) and (cp.BaseCol <> -1) then
  begin
    ABaseCol := RealToDisplColumn(cp.BaseCol);
    ABaseRow := RealToDisplRow(cp.BaseRow);
    AColSpan := cp.ColSpan;
    ARowSpan := cp.RowSpan;
  end;
end;

procedure TTMSFMXGridData.DoGetCellProperties(ACol,
  ARow: Integer; Cell: TFMXObject);
var
  cp: TCellProperty;
  ch: TNotifyEvent;
  realc,realr: integer;
  allow: boolean;
  gn: String;
begin
  ch := nil;
  realc := DisplToRealColumn(ACol);
  realr := DisplToRealRow(ARow);

  if (Cell is TTMSFMXRotatedTextGridCell) then
  begin
    cp := CellProps[realc,realr];
    if Assigned(cp) and (Cell is TTMSFMXRotatedTextGridCell) then
      (Cell as TTMSFMXRotatedTextGridCell).RotatedText.RotationAngle := cp.Angle;
  end;

  if (Cell is TTMSFMXButtonGridCell) then
  begin
    cp := CellProps[realc,realr];
    if Assigned(cp) and (cp.Control is TCellButton) then
    begin
      (Cell as TTMSFMXButtonGridCell).Button.Enabled := not ReadOnlys[realc, realr];
      (Cell as TTMSFMXButtonGridCell).Button.Text := (cp.Control as TCellButton).Text;
      (Cell as TTMSFMXButtonGridCell).Button.Width := (cp.Control as TCellButton).Width;
    end;
  end;

  if (Cell is TTMSFMXRadioGridCell) then
  begin
    cp := CellProps[realc,realr];
    if UseColumns and not Assigned(IntObjects[realc, realr]) then
    begin
      if realc < Columns.Count then
      begin
        if Columns[realc].ColumnType = ctRadioButton then
        begin
          ch := (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange;
          (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange := nil;
          gn := (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName;
          (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := '-1';
          (Cell as TTMSFMXRadioGridCell).RadioButton.Enabled := not ReadOnlys[realc, realr];
//          (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := IntToStr((cp.Control as TCellRadioButton).Index);
          (Cell as TTMSFMXRadioGridCell).RadioButton.IsCheckedProg := Cells[ACol, ARow] = CheckTrue;
          (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange := ch;
          (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := gn;
        end;
      end;
    end
    else
    begin
      if Assigned(cp) and (cp.Control is TCellRadioButton) then
      begin
        ch := (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange;
        (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange := nil;
         gn := (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName;
        (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := '-1';
        (Cell as TTMSFMXRadioGridCell).RadioButton.Enabled := not ReadOnlys[realc, realr];
        (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := IntToStr((cp.Control as TCellRadioButton).Index);
        (Cell as TTMSFMXRadioGridCell).RadioButton.IsCheckedProg := (cp.Control as TCellRadioButton).Checked;
        (Cell as TTMSFMXRadioGridCell).RadioButton.OnChange := ch;
        (Cell as TTMSFMXRadioGridCell).RadioButton.GroupName := gn;
      end;
    end;
  end;

  if (Cell is TTMSFMXCommentGridCell) then
  begin
    cp := CellProps[realc,realr];
    if Assigned(cp) then
    begin
      (Cell as TTMSFMXCommentGridCell).CommentText.Text := cp.Comment;
      if cp.CommentColor <> claNull then
      begin
        (Cell as TTMSFMXCommentGridCell).Comment.Fill.Color := cp.CommentColor;
        (Cell as TTMSFMXCommentGridCell).Comment.Stroke.Color:= cp.CommentColor;
      end;
    end;
  end;

  if (Cell is TTMSFMXCheckGridCell) then
  begin
    cp := CellProps[realc,realr];
    if UseColumns and not Assigned(IntObjects[realc, realr]) then
    begin
      if realc < Columns.Count then
      begin
        if Columns[realc].ColumnType = ctCheckBox then
        begin
          ch := (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange;
          (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := nil;
          (Cell as TTMSFMXCheckGridCell).CheckBox.Enabled := not ReadOnlys[realc, realr];
          (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg := Cells[ACol, ARow] = CheckTrue;
          (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := ch;
        end;
      end;
    end
    else
    begin
      if Assigned(cp) and (cp.Control is TCellCheckBox) then
      begin
        ch := (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange;
        (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := nil;
        (Cell as TTMSFMXCheckGridCell).CheckBox.Enabled := not ReadOnlys[realc, realr];
        (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg := (cp.Control as TCellCheckBox).Checked;
        (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := ch;
      end;

      if Assigned(cp) and (cp.Control is TCellDataCheckBox) then
      begin
        ch := (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange;
        (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := nil;
        (Cell as TTMSFMXCheckGridCell).CheckBox.Enabled := not ReadOnlys[realc, realr];
        (Cell as TTMSFMXCheckGridCell).CheckBox.IsCheckedProg := Cells[ACol,ARow] = CheckTrue;
        (Cell as TTMSFMXCheckGridCell).CheckBox.OnChange := ch;
      end;
    end;
  end;

  if (Cell is TTMSFMXBitmapGridCell) then
  begin
    cp := CellProps[realc,realr];

    (Cell as TTMSFMXBitmapGridCell).BitmapContainer := BitmapContainer;

    if Assigned(cp) and (cp.Control is TCellBitmapName) then
    begin
      if (cp.Control as TCellBitmapName).Data then
        (Cell as TTMSFMXBitmapGridCell).Bitmap.BitmapName := Cells[ACol,ARow]
      else
        (Cell as TTMSFMXBitmapGridCell).Bitmap.BitmapName := (cp.Control as TCellBitmapName).BitmapName;
    end;

    if Assigned(cp) and (cp.Control is TCellBitmap) then
      (Cell as TTMSFMXBitmapGridCell).Bitmap.Bitmap.Assign((cp.Control as TCellBitmap).Bitmap);
  end;

  if (Cell is TTMSFMXNodeGridCell) then
  begin
    cp := CellProps[realc,realr];
    if Assigned(cp) and (cp.Control is TCellNode) then
    begin
      (Cell as TTMSFMXNodeGridCell).State := (cp.Control as TCellNode).State;
    end;
  end;

  if (Cell is TTMSFMXProgressGridCell) then
  begin
    cp := CellProps[realc,realr];
    if UseColumns and not Assigned(IntObjects[realc, realr]) then
    begin
      if realc < Columns.Count then
      begin
        if Columns[realc].ColumnType = ctProgressBar then
          (Cell as TTMSFMXProgressGridCell).ProgressBar.Value := StrToInt(Cells[ACol,ARow])
      end;
    end
    else
    begin
      if Assigned(cp) and (cp.Control is TCellProgressBar) then
      begin
        if (cp.Control as TCellProgressBar).Data then
          (Cell as TTMSFMXProgressGridCell).ProgressBar.Value := StrToInt(Cells[ACol,ARow])
        else
          (Cell as TTMSFMXProgressGridCell).ProgressBar.Value := (cp.Control as TCellProgressBar).Value;
      end;
    end;
  end;

  if (Cell is TTMSFMXFixedGridCell) then
    (Cell as TTMSFMXFixedGridCell).PopupControl := nil;

  if Options.Filtering.DropDown and (Cell is TTMSFMXFixedGridCell) then
  begin
    Allow := false;

    if (Options.Filtering.DropDownFixedRow = realr) and (realc >= FixedColumns) and (realc < ColumnCount - FixedRightColumns) then
    begin
      Allow := true;
      DoNeedFilterDropDown(realc, realr, Allow);
    end;

    (Cell as TTMSFMXFixedGridCell).ShowDropDownButton := Allow;
    if Allow then
    begin
      FFilterListbox.Width := Options.Filtering.DropDownWidth;
      FFilterListbox.Height := Options.Filtering.DropDownHeight;
      (Cell as TTMSFMXFixedGridCell).PopupControl := FFilterListbox;
    end
    else
      (Cell as TTMSFMXFixedGridCell).PopupControl := nil;
  end;
end;

procedure TTMSFMXGridData.DoGetCellReadOnly(ACol, ARow: Integer;
  var AReadOnly: Boolean);
begin
  AReadOnly := ReadOnlys[ACol,ARow];
end;

procedure TTMSFMXGridData.DoGetRowIsBand(ARow: Integer; var ARowBand: Boolean);
begin
  if Options.Bands.TotalRowCount > 0 then
  begin
    ARowBand := (((ARow - FixedRows) mod Options.Bands.TotalRowCount) < Options.Bands.BandRowCount);
  end
  else
    ARowBand := Odd(ARow);
end;

function TTMSFMXGridData.DoIsCellSelected(ACol, ARow: Integer): boolean;
begin
  Result := False;
  case SelectionMode of
  smDisjunctColumn: Result := ColumnSelect[ACol];
  smDisjunctRow: Result := RowSelect[ARow];
  smDisjunctCell: Result := CellSelect[ACol,ARow];
  end;
end;

procedure TTMSFMXGridData.DoLoadCell(Col, Row: integer; var Value: string);
begin
  if Assigned(FOnLoadCell) then
    OnLoadCell(Self,Col,Row,Value);
  Cells[Col,Row] := Value;
end;

procedure TTMSFMXGridData.DoNeedFilterDropDOwn(Col, Row: integer;
  var Allow: boolean);
begin
  if Assigned(OnNeedFilterDropDown) then
    OnNeedFilterDropDown(Self, Col, Row, Allow);
end;

procedure TTMSFMXGridData.DoNeedFilterDropDownData(Col, Row: integer;
  Values: TStrings);
begin
  if Assigned(OnNeedFilterDropDownData) then
    OnNeedFilterDropDownData(Self, Col, Row, Values);
end;

procedure TTMSFMXGridData.DoOptionsChange(Sender: TObject);
begin
  if (UpdateCount > 0) or BlockUpdate or (csDestroying in ComponentState) then
    Exit;

  BeginUpdate;
  NeedStyleLookUp;
  Repaint;
  EndUpdate;
end;

procedure TTMSFMXGridData.DoRowSize(ARow: Integer;
  var NewHeight: Single);
begin

end;

procedure TTMSFMXGridData.DoRowSized(ARow: Integer;
  NewHeight: Single);
begin

end;

function TTMSFMXGridData.DoSaveCell(Col, Row: integer): string;
begin
  Result := Cells[Col,Row];
  if Options.IO.SaveVirtualCellData then
    DoGetCellData(Col, Row, Result);
  if Assigned(FOnSaveCell) then
    OnSaveCell(Self,Col,Row,Result);
end;

procedure TTMSFMXGridData.DoSortFormat(Col: integer;
  var SortFormat: TSortFormat; var Prefix, Suffix: string);
var
  c: Integer;
begin
  SortFormat := ssAutomatic;
  Prefix := '';
  Suffix := '';

  c := DisplToRealColumn(Col);
  if UseColumns and (c < Columns.Count) then
  begin
    SortFormat := Columns[c].SortFormat;
    Suffix := Columns[c].SortSuffix;
    Prefix := Columns[c].SortPrefix;
  end;

  if Assigned(OnSortFormat) then
    OnSortFormat(Self,Col,SortFormat,Prefix, Suffix);
end;

procedure TTMSFMXGridData.DoCustomCompare(Value1,Value2: TCellData; var res: integer);
begin
  if Assigned(OnCustomCompare) then
    OnCustomCompare(Self, Value1, Value2, res);
end;

procedure TTMSFMXGridData.DoDeleteRow(ARow: Integer);
begin

end;

procedure TTMSFMXGridData.DoRawCompare(Col, Row1, Row2: integer; var res: integer);
begin
  if Assigned(OnRawCompare) then
    OnRawCompare(Self, Col, Row1, Row2, res);
end;

function TTMSFMXGridData.Find(StartCell: TPoint; s: string;
  FindParams: TFindParams): TPoint;
begin
  Result := FindInternal(StartCell,s,FindParams);
  if (fnAutoGoto in FindParams) and (Result.X <> -1) then
  begin
    Selection := CellRange(Result.X, Result.Y, Result.X, Result.Y);
  end;
end;

procedure TTMSFMXGridData.MarkInCell(DoCase: Boolean; Col, Row: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetCellRange(Col,Row);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtError;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.MarkInCol(DoFixed, DoCase: Boolean; Col: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetColumnRange(Col,DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtError;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.MarkInGrid(DoFixed, DoCase: Boolean; HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetGridRange(DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtError;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.MarkInRow(DoFixed, DoCase: Boolean; Row: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetRowRange(Row, DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtError;
  UpdateGridCells;
end;


function TTMSFMXGridData.MatchCell(Col,Row: Integer): Boolean;
var
  res1,res2: Boolean;
  ct,cs: string;
  ic: Integer;
begin
  res2 := True;
//  if not (fnIncludeHiddenColumns in FFindParams) then
//    Col := RemapCol(Col);

  if fnIncludeHiddenRows in FFindParams then
    cs := AllCells[Col,Row]
  else
    cs := Cells[Col,Row];

  if not (fnMatchCase in FFindParams) then
    ct := AnsiUpperCase(cs)
  else
    ct:= cs;

  if fnIgnoreHTMLTags in FFindParams then
    ct := HTMLStrip(ct);

  if FSearchCache = '""' then
  begin
    MatchCell := ct = '';
    Exit;
  end;

  ic := Pos(FSearchCache,ct);

  if fnMatchStart in FFindParams then
    res1 := ic = 1
  else
    res1 := ic > 0;

  if fnMatchFull in FFindParams then
    res2 := FSearchCache = ct;

  if fnMatchRegular in FFindParams then
  begin
    MatchCell := MatchStrEx(FSearchCache,ct,(fnMatchCase in FFindParams));
  end
  else
    MatchCell := res1 and res2;
end;


function TTMSFMXGridData.FindInternal(StartCell:TPoint; s:string; FindParams: TFindParams):TPoint;
var
  MaxCol,MinCol: Integer;
  MaxRow,MinRow: Integer;
  i,j: Integer;

begin
  Result.x := -1;
  Result.y := -1;

  FFindParams := FindParams;
  FFindBusy := True;

  if not (fnMatchCase in FindParams) then
    FSearchCache := AnsiUpperCase(s)
  else
    FSearchCache := s;

  //if (ColCount = FixedCols) or (ColCount = 0) then Exit;
  //if (RowCount + NumHiddenRows = FixedRows) or (RowCount = 0) then Exit;

  if (fnIncludeFixed in FindParams) then
  begin
    MaxCol := ColumnCount - 1;
    MaxRow := RowCount - 1;
    MinCol := 0;
    MinRow := 0;
  end
  else
  begin
    MaxCol := ColumnCount - 1; //- FixedRightCols;
    MaxRow := RowCount - 1; //- FixedFooters;
    MinCol := FIOOffset.X;
    MinRow := FIOOffset.Y;
  end;

  if fnSelectedCells in FindParams then
  begin
    MaxCol := Selection.EndCol;
    MaxRow := Selection.EndRow;
    MinCol := Selection.StartCol;
    MinRow := Selection.StartRow;
  end;

  if fnIncludeHiddenColumns in FindParams then
    MaxCol := MaxCol + HiddenColumnCount;

  if (fnIncludeHiddenRows in FindParams) then
    MaxRow := MaxRow + HiddenRowCount;

  if (StartCell.x = -1) and (StartCell.y = -1) then
  begin
    if fnBackward in FindParams then
    begin
      StartCell.x := MaxCol;
      StartCell.y := MaxRow;
    end
    else
    begin
      StartCell.x := MinCol;
      StartCell.y := MinRow;
    end;
  end
  else
  begin
    if fnDirectionLeftRight in Findparams then
    begin
      if fnBackward in FindParams then
      begin
        if StartCell.x >= MinCol then
          Dec(StartCell.x)
        else
          if StartCell.y >= MinRow then
            Dec(StartCell.y)
          else
          begin
            StartCell.x := MaxCol;
            StartCell.y := MaxRow;
          end;
      end
      else
      begin
        if StartCell.x <= MaxCol then
          Inc(StartCell.x)
        else
          if StartCell.y <= MaxRow then
            Inc(StartCell.y)
          else
          begin
            StartCell.x := MinCol;
            StartCell.y := MinRow;
          end;
      end;
    end
    else
    begin
      if fnBackward in FindParams then
      begin
        if StartCell.y >= MinRow then
          Dec(StartCell.y)
        else
          if StartCell.x >= MinCol then
            Dec(StartCell.x)
          else
          begin
            StartCell.x := MaxCol;
            StartCell.y := MaxRow;
          end;
      end
      else
      begin
        if StartCell.y <= MaxRow then
          Inc(StartCell.y)
        else
          if StartCell.x <= MaxCol then
            Inc(StartCell.x)
          else
          begin
            StartCell.x := MinCol;
            StartCell.y := MinRow;
          end;
      end;
    end;
  end;

  i := StartCell.x;
  j := StartCell.y;

  if fnFindInCurrentRow in Findparams then
  begin
    j := Selection.StartRow;
    MaxRow := Selection.StartRow;
    MinRow := Selection.StartRow;
  end;

  if fnFindInPresetRow in Findparams then
  begin
    j := FFindRow;
    MaxRow := FFindRow;
    MinRow := FFindRow;
  end;

  if fnFindInCurrentCol in Findparams then
  begin
    i := Selection.StartCol;
    MaxCol := Selection.StartCol;
    MinCol := Selection.StartCol;
  end;

  if fnFindInPresetCol in Findparams then
  begin
    i := FFindCol;
    MaxCol := FFindCol;
    MinCol := FFindCol;
  end;

  StartCell.x := i;
  StartCell.y := j;

  if fnDirectionLeftRight in Findparams then
  begin
    while (j <= MaxRow) and (j >= MinRow) do
    begin
      while (i <= MaxCol) and (i >= MinCol) do
      begin

          if MatchCell(i,j) then
          begin
            FSearchCell.x := i;
            FSearchCell.y := j;
            Result := FSearchCell;
            Exit;
          end;

        if fnBackward in FindParams then
          Dec(i)
        else
          Inc(i);
      end;

//    if fnFindInCurrentCol in FindParams then
//      i := Col
//    else
      begin
        if fnBackward in FindParams then
          i := MaxCol
        else
          i := MinCol;
      end;

      if fnBackward in FindParams then
        Dec(j)
      else
        Inc(j);
    end;

  end
  else
  begin
    while (i <= MaxCol) and (i >= MinCol) do
    begin
      while (j <= MaxRow) and (j >= MinRow) do
      begin

        if MatchCell(i,j) then
        begin
          FSearchCell.x := i;
          FSearchCell.y := j;
          Result := FSearchcell;
          Exit;
          {
          if fnAutoGoto in Findparams then
          begin
            nr := j;

            if (fnIncludeHiddenRows in FindParams) and (NumHiddenRows > 0) then
            begin
              if IsHiddenRow(j) and (FNumNodes > 0) then
              begin
                pr := GetNodeFromRow(j);

                if pr <> -1 then
                begin
                  pr := RealRowIndex(pr);
                  ExpandNode(pr);
                end;
              end;
              nr := DisplRowIndex(nr);
            end;

            if MouseActions.DisjunctRowSelect then
            begin
              ClearRowSelect;
              RowSelect[nr] := true;
              Row := nr;
            end
            else
            begin
              Row := nr;
              Col := i;
            end;
          end;
          Exit;
        end;
        }
      end;
        if fnBackward in Findparams then
          Dec(j)
        else
          Inc(j);
      end;

//    if fnFindInCurrentRow in Findparams then
//      j := Row
//    else
    begin
      if fnBackward in FindParams then
        j := MaxRow
      else
        j := MinRow;
    end;

    if fnBackward in Findparams then
      Dec(i)
    else
      Inc(i);
    end;
  end;

  FFindBusy := False;
end;

function TTMSFMXGridData.FindFirst(s: string; FindParams: TFindParams): TPoint;
begin
  FSearchCell := Find(Point(-1,-1),s,FindParams);
  Result := FSearchCell;
end;

function TTMSFMXGridData.FindNext: TPoint;
begin
  FSearchCell := Find(FSearchCell,FSearchCache,FFindParams);
  Result := FSearchCell;
end;

procedure TTMSFMXGridData.Debug;
begin
  {$IFNDEF MACOS}
//  outputdebugstring(pchar(inttostr(FColumnDisplayList.Count)+':'+inttostr(TotalColCount)));
  {$ENDIF}
end;

procedure TTMSFMXGridData.DeleteColumn(Col: integer);
var
  i: integer;
begin
  BeginUpdate;

  ClearCells(CellRange(Col, 0, Col, RowCount - 1));

  for i := 0 to FRowList.Count - 1 do
  begin
    if Assigned(FRowList.Items[i]) and (TRowInfo(FRowList.Items[i]).Data.Count > Col) then
      TRowInfo(FRowList.Items[i]).Data.Delete(Col);
  end;

  ColumnCount := ColumnCount - 1;
  EndUpdate;
end;

procedure TTMSFMXGridData.DeleteRow(Row: integer);
begin
  DeleteRows(Row,1);
end;

procedure TTMSFMXGridData.DeleteRows(Row, Count: integer); var
  i: integer;
  ri: TRowInfo;
begin
  BeginUpdate;

  ClearCells(CellRange(0, Row, ColumnCount - 1, Row + Count - 1));

  for i := 1 to Count do
  begin
    if Assigned(FRowList.Items[Row]) then
    begin
      ri := TRowInfo(FRowList.Items[Row]);
      ri.Free;
    end;

    if Row < FRowList.Count then
     FRowList.Delete(Row);
    if Row < FRowDisplayList.Count then
     FRowDisplayList.Delete(Row);
    FRowCount := FRowCount - 1;
  end;

  for i := Row to FRowDisplayList.Count - 1 do
  begin
    FRowDisplayList.Items[i] := FRowDisplayList.Items[i] - Count;
  end;

  EndUpdate;
end;

function TTMSFMXGridData.GetHorzAlignments(Col, Row: integer): TTextAlign;
var
  CellProp: TCellProperty;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TTextAlign.Leading;
  {$ELSE}
  Result := TTextAlign.taLeading;
  {$ENDIF}

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.AlignHorz;
end;

function TTMSFMXGridData.GetAllCells(Col, Row: integer): string;
var
  sl: TDataList;

begin
  Result := '';

  if Row < FRowList.Count then
  begin
    if Assigned(FRowList.Items[Row]) then
    begin
      sl := TRowInfo(FRowList.Items[Row]).Data;
      if Col < sl.Count then
        Result := sl.Values[Col];
    end;
  end;
end;

function CleanupSep(value: string): string;
var
  i: integer;
  s: string;
begin
  s := '';

  {$IFDEF DELPHI_LLVM}
  for i := 0 to Length(value) - 1 do
  {$ENDIF}
  {$IFNDEF DELPHI_LLVM}
  for i := 1 to Length(value) do
  {$ENDIF}
  begin
    if (Value[i] = FormatSettings.ThousandSeparator) then
      Continue;

    if (Value[i] = FormatSettings.DecimalSeparator) then
      s := s + '.'
    else
      s := s + Value[i];
  end;
  Result := s;
end;

function TTMSFMXGridData.GetAllFloats(Col, Row: integer): double;
var
  s: string;
  Res: Double;
  Err: Integer;
begin
  s := AllCells[Col,Row];

  if (s = '') or (s = '-') then
    s := '0';

  s := CleanupSep(s);

  Val(s,Res,Err);

  if Err <> 0 then
  begin
    Result := 0;
    //raise Exception.Create('Cell does not contain a float value');
  end
  else
    Result := Res;
end;

function TTMSFMXGridData.GetAngles(Col, Row: integer): single;
var
  CellProp: TCellProperty;
begin
  Result := 0;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
   Result := CellProp.Angle;
end;

function TTMSFMXGridData.GetCellProps(Col, Row: integer): TCellProperty;
begin
  Result := TCellProperty(IntObjects[Col,Row]);
end;

function TTMSFMXGridData.GetCellRange(Col, Row: Integer): TCellRange;
begin
  Result := CellRange(Col,Row,Col,Row);
end;

function TTMSFMXGridData.GetCells(Col, Row: integer): TCellData;
var
  sl: TDataList;
  RDisp,CDisp: integer;
begin
  Result := '';

  if Row >= FRowDisplayList.Count then
    Exit;

  if Col >= FColumnDisplayList.Count then
    Exit;

  RDisp := -1;
  if (Row >= 0) and (Row <= FRowDisplayList.Count - 1) then
    RDisp := integer(FRowDisplayList.Items[Row]);
  CDisp := -1;
  if (Col >= 0) and (Col <= FColumnDisplayList.Count - 1) then
    CDisp := integer(FColumnDisplayList.Items[Col]);

  if (RDisp >= 0) and (RDisp < FRowList.Count) then
  begin
    if Assigned(FRowList.Items[RDisp]) then
    begin
      sl := TRowInfo(FRowList.Items[RDisp]).Data;
      if (CDisp >= 0) and (CDisp < sl.Count) then
      begin
        {$IFDEF VARIANTLIST}
        Result := sl.Values[CDisp];
        {$ELSE}
        Result := sl.Strings[CDisp];
        {$ENDIF}
      end;
    end;
  end;
end;

function TTMSFMXGridData.GetCellSelect(Col, Row: integer): boolean;
begin
  Result := FSelectedCells.Find(Cell(Col,Row)) <> -1;
end;

function TTMSFMXGridData.GetCellSelectionCount: integer;
begin
  Result := FSelectedCells.Count;
end;

function TTMSFMXGridData.GetCellSize(Col, Row: Integer): TSizeF;
begin
  Result.Width := ColumnWidths[Col];
  Result.Height := RowHeights[Row];
end;

function TTMSFMXGridData.GetCellState(ACol, ARow: Integer): TCellState;
begin
  Result := TCellState.csNormal;
end;

function TTMSFMXGridData.GetCheckBoxState(Col, Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellCheckBox) then
  begin
    Result := (cp.Control as TCellCheckBox).Checked;
  end;
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellDataCheckBox) then
  begin
    Result := Cells[Col,Row] = CheckTrue;
  end;
end;

function TTMSFMXGridData.GetColumnCalculation(Col: Integer): TColumnCalculation;
begin
  if Col >= FCellCalcList.Count then
  begin
    Result := ccNone;
  end
  else
  begin
    Result := TColumnCalculation(FCellCalcList.Items[Col]);
  end;

end;

function TTMSFMXGridData.GetColumnCount: integer;
begin
  Result := FColumnCount;
  //NO GRID DATA
//  Result := FColumnDisplayList.Count;
  //NO GRID DATA
end;

function TTMSFMXGridData.GetColumnDragLayoutFill: TBrush;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Fill;
end;

function TTMSFMXGridData.GetColumnDragLayoutStroke: TBrush;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke;
end;

function TTMSFMXGridData.GetColumnDragLayoutStrokeCap: TStrokeCap;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeCap.Flat;
  {$ELSE}
  Result := TStrokeCap.scFlat;
  {$ENDIf}
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Cap;
end;

function TTMSFMXGridData.GetColumnDragLayoutStrokeDash: TStrokeDash;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeDash.Solid;
  {$ELSE}
  Result := TStrokeDash.sdSolid;
  {$ENDIF}
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Dash;
end;

function TTMSFMXGridData.GetColumnDragLayoutStrokeJoin: TStrokeJoin;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeJoin.Miter;
  {$ELSE}
  Result := TStrokeJoin.sjMiter;
  {$ENDIF}
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Join;
end;

function TTMSFMXGridData.GetColumnDragLayoutStrokeThickness: Single;
var
  obj: TFmxObject;
begin
  Result := 1;
  obj := FindStyleResource('columndraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Thickness;
end;

function TTMSFMXGridData.GetColumnRange(Col: integer;
  DoFixed: boolean): TCellRange;
begin
  if DoFixed then
    Result := CellRange(Col,0,Col,RowCount - 1)
  else
    Result := CellRange(Col,FixedRows,Col,RowCount - 1 - FixedFooterRows);
end;

function TTMSFMXGridData.GetColumnSelect(Col: integer): boolean;
begin
  Result := FColSelect.HasValue(Col);
end;

function TTMSFMXGridData.GetColumnSelectionCount: integer;
begin
  Result := FColSelect.Count;
end;

function TTMSFMXGridData.GetDefaultBandLayout(
  AControl: TFmxObject): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;

  obj := ctl.FindStyleResource('bandcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultDropDownBitmap: TTMSFMXBitmap;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := FindStyleResource('fixedcelldropdownbitmap');
  if Assigned(obj) and (obj is TTMSFMXBitmap) then
    Result := (obj as TTMSFMXBitmap);
end;

function TTMSFMXGridData.GetDefaultFixedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;

  obj := ctl.FindStyleResource('fixedcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultFixedSelectedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('fixedselectedcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultFocusedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('focusedcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultFont(Col, Row: integer): TFont;
var
  gc: TTMSFMXGridCell;
begin
  if IsFixed(Col,Row) then
    gc := GetDefaultFixedLayout
  else
    gc := GetDefaultNormalLayout;

  if Assigned(gc) then
    Result := gc.Layout.Font
  else
    Result := FDefaultFont;
end;

function TTMSFMXGridData.GetDefaultGroupLayout(AControl: TFmxObject): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('groupedcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultIndexedSortFill: TBrush;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('indexedsortlayout');
  if obj is TRectangle then
    Result := (obj as TRectangle).Fill;
end;

function TTMSFMXGridData.GetDefaultIndexedSortFont: TFont;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('indexedsorttext');
  if obj is TText then
    Result := (obj as TText).Font;
end;

function TTMSFMXGridData.GetDefaultIndexedSortFontFill: TText;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('indexedsorttext');
  if obj is TText then
    Result := (obj as TText);
end;

function TTMSFMXGridData.GetDefaultIndexedSortStroke: TBrush;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('indexedsortlayout');
  if obj is TRectangle then
    Result := (obj as TRectangle).Stroke;
end;

function TTMSFMXGridData.GetDefaultNormalLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('normalcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultSelectedLayout(AControl: TFmxObject = nil): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('selectedcelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetDefaultSortFill: TBrush;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('sortlayout');
  if obj is TRectangle then
    Result := (obj as TRectangle).Fill;
end;

function TTMSFMXGridData.GetDefaultSortFont: TFont;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('sorttext');
  if obj is TText then
    Result := (obj as TText).Font;
end;

function TTMSFMXGridData.GetDefaultSortFontFill: TText;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('sorttext');
  if obj is TText then
    Result := (obj as TText);
end;

function TTMSFMXGridData.GetDefaultSortStroke: TBrush;
var
  obj: TFMXObject;
begin
  Result := nil;
  obj := FindStyleResource('sortlayout');
  if obj is TRectangle then
    Result := (obj as TRectangle).Stroke;
end;


function TTMSFMXGridData.GetDefaultSummaryLayout(
  AControl: TFmxObject): TTMSFMXGridCell;
var
  obj: TFmxObject;
  ctl: TFmxObject;
begin
  Result := nil;
  if Assigned(Acontrol) then
    ctl := AControl
  else
    ctl := Self;
  obj := ctl.FindStyleResource('summarycelllayout');
  if obj is TTMSFMXGridCell then
    Result := obj as TTMSFMXGridCell;
end;

function TTMSFMXGridData.GetRowDragLayoutFill: TBrush;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Fill;
end;

function TTMSFMXGridData.GetRowDragLayoutStroke: TBrush;
var
  obj: TFmxObject;
begin
  Result := nil;
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke;
end;

function TTMSFMXGridData.GetRowDragLayoutStrokeCap: TStrokeCap;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeCap.Flat;
  {$ELSE}
  Result := TStrokeCap.scFlat;
  {$ENDIF}
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Cap;
end;

function TTMSFMXGridData.GetRowDragLayoutStrokeDash: TStrokeDash;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeDash.Solid;
  {$ELSE}
  Result := TStrokeDash.sdSolid;
  {$ENDIF}
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Dash;
end;

function TTMSFMXGridData.GetRowDragLayoutStrokeJoin: TStrokeJoin;
var
  obj: TFmxObject;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TStrokeJoin.Miter;
  {$ELSE}
  Result := TStrokeJoin.sjMiter;
  {$ENDIF}
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Join;
end;

function TTMSFMXGridData.GetRowDragLayoutStrokeThickness: Single;
var
  obj: TFmxObject;
begin
  Result := 1;
  obj := FindStyleResource('rowdraglayout');
  if Assigned(obj) and (obj is TRectangle) then
    Result := (obj as TRectangle).Stroke.Thickness;
end;


function TTMSFMXGridData.GetColWidths(AIndex: integer): Single;
var
  idx: Integer;
begin
  idx := ColumnW.IndexOf(AIndex);
  if idx <> -1 then
    Result := ColumnW[idx].Value
  else
    Result := DefaultColumnWidth;
end;

function TTMSFMXGridData.GetCellControls(Col, Row: integer): TFMXObject;
var
  cp: TCellProperty;
begin
  Result := nil;
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellControl) then
  begin
    Result := (cp.Control as TCellControl).Control;
  end;
end;

function TTMSFMXGridData.GetFixedCellSelect(Col, Row: integer): boolean;
begin
  Result := FSelectedCells.FindFixed(Cell(Col,Row)) <> -1;
end;

function TTMSFMXGridData.GetFloats(Col, Row: Integer): double;
var
  s: string;
  Res: Double;
  Err: Integer;
begin
  s := Cells[Col,Row];

  if (s = '') or (s = '-') then
    s := '0';

  Val(s,Res,Err);

  if Err <> 0 then
  begin
    Result := 0;
    //raise Exception.Create('Cell does not contain a float value');
  end
  else
    Result := Res;
end;

function TTMSFMXGridData.GetFocusedCell: TCell;
begin
  Result := FFocusedCell;
end;

function TTMSFMXGridData.GetFontColors(Col, Row: integer): TAlphaColor;
var
  CellProp: TCellProperty;
begin
  Result := claNull;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.FontColor;
end;

function TTMSFMXGridData.GetFontNames(Col, Row: integer): string;
var
  CellProp: TCellProperty;
begin
  Result := FDefaultFont.Family;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.FontName;
end;

function TTMSFMXGridData.GetFontSizes(Col, Row: integer): single;
var
  CellProp: TCellProperty;
begin
  Result := 12;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.FontSize;
end;

function TTMSFMXGridData.GetFontStyles(Col, Row: integer): TFontStyles;
var
  CellProp: TCellProperty;
begin
  Result := [];

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.FontStyle;
end;

function TTMSFMXGridData.GetGridRange(DoFixed: boolean): TCellRange;
begin
  if DoFixed then
    Result := CellRange(0,0,ColumnCount - 1, RowCount - 1)
  else
    Result := CellRange(FixedColumns, FixedRows,ColumnCount - 1 - FixedRightColumns, RowCount - 1 - FixedFooterRows);
end;

function TTMSFMXGridData.GetColors(Col, Row: integer): TAlphaColor;
var
  CellProp: TCellProperty;
begin
  Result := claNull;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.Color;
end;

function TTMSFMXGridData.GetHiddenColCount: integer;
begin
  Result := FHiddenColumnList.Count;
  //Result := TotalColCount - FColumnDisplayList.Count;
end;

function TTMSFMXGridData.GetHiddenRowCount: integer;
begin
  Result := FRowList.Count - FRowDisplayList.Count;
end;

function TTMSFMXGridData.GetIntObjects(Col, Row: integer): TObject;
var
  sl: TDataList;
begin
  //
  Result := nil;

  if (Row >= 0) and (Row <= FRowList.Count - 1) then
  begin
    if Assigned(FRowList.Items[Row]) then
    begin
      sl := TRowInfo(FRowList.Items[Row]).Data;
      if (Col >= 0) and (Col <= sl.Count -1) then
        Result := sl.Objects[col];
    end;
  end;
end;

function TTMSFMXGridData.GetMergeCellPrintPageNr(Col, Row: integer): integer;
var
  cp: TCellProperty;
begin
  Result := -1;

  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) and (cp.BaseCol <> -1) then
  begin
    Result := cp.FPrintPageNr;
  end;
end;

function TTMSFMXGridData.GetReadOnlys(Col, Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;

  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) then
    Result := cp.ReadOnly;
end;

function TTMSFMXGridData.GetRowCount: integer;
begin
  Result := FRowCount;
  //NO GRID DATA
//  Result := FRowDisplayList.Count;
  //NO GRID DATA
end;

function TTMSFMXGridData.GetRowHeights(AIndex: integer): Single;
var
  idx: Integer;
begin
  idx := RowH.IndexOf(DisplToRealRow(AIndex));
  if idx <> -1 then
    Result := RowH[idx].Value
  else
    Result := DefaultRowHeight;
end;

function TTMSFMXGridData.GetRowRange(Row: integer; DoFixed: boolean): TCellRange;
begin
  if DoFixed then
    Result := CellRange(0,Row,ColumnCount - 1,Row)
  else
    Result := CellRange(FixedColumns,Row,ColumnCount - 1 - FixedRightColumns,Row)
end;

function TTMSFMXGridData.GetRowSelect(Row: integer): boolean;
begin
  Result := false;

  if Row < FRowList.Count then
  begin
    if Assigned(FRowList[Row]) then
      Result := TRowInfo(FRowList[Row]).State = rsSelected;
  end;
end;

function TTMSFMXGridData.GetRowSelectionCount: integer;
var
  i,res: integer;
begin
  res := 0;

  for I := 0 to FRowList.Count - 1 do
  begin
    if RowSelect[i] then
      inc(res);
  end;

  Result := res;
end;

function TTMSFMXGridData.GetRowText(Row: integer): string;
var
  sl: TDataList;
  RDisp: integer;
begin
  Result := '';

  if Row >= FRowDisplayList.Count then
    Exit;

  RDisp := integer(FRowDisplayList.Items[Row]);

  if RDisp < FRowList.Count then
  begin
    if Assigned(FRowList.Items[RDisp]) then
    begin
      sl := TRowInfo(FRowList.Items[RDisp]).Data;
      Result := sl.Text;
    end;
  end;
end;

function TTMSFMXGridData.GetSelectedCell(index: integer): TCell;
begin
  Result := FSelectedCells.Items[Index];
end;

function TTMSFMXGridData.GetSelectedCellCount: Integer;
begin
  Result := FSelectedCells.Count;
end;

function TTMSFMXGridData.GetSelectedColumn(index: integer): integer;
begin
  Result := integer(FColSelect.Items[index]);
end;

function TTMSFMXGridData.GetSelectedColumnCount: Integer;
begin
  Result := FColSelect.Count;
end;

function TTMSFMXGridData.GetSelectedRow(index: integer): integer;
var
  i,cnt: integer;
begin
  Result := -1;
  cnt := 0;
  for i := 0 to RowCount - 1 do
  begin
    if TRowInfo(FRowList[i]).State = rsSelected then
    begin
      if cnt = index then
      begin
        Result := i;
        break;
      end;
      inc(cnt);
    end;
  end;
end;

function TTMSFMXGridData.GetSelectedRowCount: Integer;
var
  i: integer;
begin
  Result := 0;
  for i := FixedRows to RowCount - FixedFooterRows - 1 do
  begin
    if i >= FRowList.Count then
      Break;

    if Assigned(FRowList[i]) and (TRowInfo(FRowList[I]).State = rsSelected) then
      Inc(Result);
  end;
end;

function TTMSFMXGridData.GetSelection: TCellRange;
begin
  Result.StartCol := FStartCell.Col;
  Result.StartRow := FStartCell.Row;
  Result.EndCol := FStopCell.Col;
  Result.EndRow := FStopCell.Row;
end;

function TTMSFMXGridData.GetStrippedCells(Col, Row: Integer): string;
begin
  Result := HTMLStrip(Cells[Col,Row]);
end;

function TTMSFMXGridData.GetTotalColCount: integer;
begin
  Result := ColumnCount + FHiddenColumnList.Count;
end;

function TTMSFMXGridData.GetTotalRowCount: integer;
begin
  Result := FRowList.Count;
end;


function TTMSFMXGridData.GetVertAlignments(Col, Row: integer): TTextAlign;
var
  CellProp: TCellProperty;
begin
  {$IFDEF DELPHIXE6_LVL}
  Result := TTextAlign.Center;
  {$ELSE}
  Result := TTextAlign.taCenter;
  {$ENDIF}

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
    Result := CellProp.AlignVert;
end;

procedure TTMSFMXGridData.UnGroup;
var
  i,j: Integer;
  nc: string;
  grpc: Integer;
//  eo: boolean;
begin
  if FGroupColumn <= 0 then
    Exit;

  BeginUpdate;

//  eo := CellNode.ExpandOne;
//  CellNode.ExpandOne := false;

  OpenAllNodes;

  if FGroupColumn = 1 then
    grpc := 2
  else
    grpc := 1;

  i := FixedRows;

  FIsGrouping := true;

  while i <= RowCount - 1 - FFixedFooterRows do
  begin
    if IsNode(i) then
    begin
      if Options.Grouping.MergeHeader then
        SplitCell(FixedColumns,i);

      if Options.Grouping.MergeSummary then
      begin
        j := GetNodeSpan(i);
        SplitCell(FixedColumns,i + j + 1);
      end;
    end;
    if IsSummary(i) then
      RemoveSummary(i);
    inc(i);
  end;


  FInternalColumnUpdate := True;
  InsertColumn(FGroupColumn);
  FInternalColumnUpdate := False;
  ColumnWidths[FGroupColumn] := FGroupWidth;

  // restore the column group caption
  if FixedRows > 0 then
    Cells[FGroupColumn,0] := FGroupCaption;

  if FixedFooterRows > 0 then
    Cells[FGroupColumn, RowCount - 1] := FGroupFooter;

  i := FixedRows;

  FIsGrouping := true;

  while i <= RowCount - 1 - FFixedFooterRows do
  begin
    if IsNode(i) then
    begin
      if Options.Grouping.Summary {Grouping.MergeSummary} then
      begin
        j := GetNodeSpan(i);

        //if Options.Grouping.MergeSummary then
        //  SplitCell(FixedColumns,i + j + 1)
        //else
        begin
          //if Grouping.SummaryColor <> clNone then
          //  RowColor[i + j] := clNone;
          //if Grouping.SummaryColorTo <> clNone then
          //  RowColorTo[i + j] := clNone;
        end;

        ClearCells(CellRange(0,i + j + 1, ColumnCount - 1, i + j + 1));
        DeleteRows(i + j + 1,1);
      end;

      nc := Cells[grpc,i];
      RemoveNode(i);

      //if Grouping.MergeHeader then
      //  SplitCell(1,i)
      //else
      begin
        //if Grouping.HeaderColor <> clNone then
        //  RowColor[i] := clNone;
        //if Grouping.HeaderColorTo <> clNone then
        //  RowColorTo[i] := clNone;
      end;

      ClearCells(CellRange(0,i,ColumnCount - 1,i));

      DeleteRows(i,1);
    end
    else
    begin
      Cells[FGroupColumn,i] := nc;
      inc(i);
    end;
  end;

  FIsGrouping := false;
  FGroupColumn := -1;
//  CellNode.ExpandOne := eo;

  ResetUpdate;
  UpdateGrid(true);

end;

procedure TTMSFMXGridData.Group(Column: integer);
var
  i,np: Integer;
  lc,nc: string;
  grp,grpc: Integer;
begin
  if (Column < FixedColumns) then
    Exit;


  if FGroupColumn <> -1 then
    UnGroup;

  if (RowCount - FixedRows < 1) then
    Exit;

  BeginUpdate;

  FIsGrouping := true;

  try
    FGroupColumn := Column;
    FGroupWidth := ColumnWidths[Column];

    if FixedRows > 0 then
      FGroupCaption := Cells[Column,0];

    if FixedFooterRows > 0 then
      FGroupFooter := Cells[Column, RowCount - 1];

    grp := Column;
    grpc := 1 {+ FixedCols};

    // sort the Column to group first

    //if FSortSettings.AutoSortForGrouping then
    //begin
    //  FSortSettings.Column := Colindex;
    //  QSort;
    //end;

    if grpc = Column then
      Inc(grpc);

    np := -1;
    lc := string(#255#254);

    i := FixedRows;

    while i <= RowCount - 1 - FFixedFooterRows do
    begin
      nc := Cells[Column,i];

      if lc <> nc then
      begin
        if np <> -1 then
        begin

          if (np > FixedRows) and Options.Grouping.Summary then // this is a summary row insert for the last group
          begin
            FInternalRowUpdate := True;
            InsertRows(np,1);
            FInternalRowUpdate := False;
            AddSummary(np);

            // set style of group summary span
            if Options.Grouping.MergeSummary and Options.Grouping.Summary then
            begin
              {
              MergeCells(grpc,np,ColCount - grpc,1);
              if Grouping.SummaryColor <> clNone then
                Colors[grpc,np] := ColorToRGB(Grouping.SummaryColor);
              if Grouping.SummaryColorTo <> clNone then
                ColorsTo[grpc,np] := ColorToRGB(Grouping.SummaryColorTo);
              if Grouping.SummaryTextColor <> clNone then
                FontColors[grpc,np] := ColorToRGB(Grouping.SummaryTextColor);
              }
            end
            else
            begin
            //  if Grouping.SummaryColor <> clNone then
            //    RowColor[np] := ColorToRGB(Grouping.SummaryColor);
            //  if Grouping.SummaryColorTo <> clNone then
            //    RowColorTo[np] := ColorToRGB(Grouping.SummaryColorTo);
            //  if Grouping.SummaryTextColor <> clNone then
            //    RowFontColor[np] := ColorToRGB(Grouping.SummaryTextColor);
            end;

            inc(np);
            AddNode(np,i - np );
            inc(i);
          end
          else
          begin
            AddNode(np,i - np - 1);
            //outputdebugstring(pchar(inttostr(np)+';'+inttostr(i-np- 1)));
          end;
        end;

        // insert group header row

        FInternalRowUpdate := True;
        InsertRows(i,1);
        FInternalRowUpdate := False;
        Cells[grpc,i] := nc;

        // set style of group header span

        if Options.Grouping.MergeHeader then
        begin
          (*
          MergeCells(grpc,i,ColumnCount - grpc,1);
          if Grouping.HeaderColor <> clNone then
            Colors[grpc,i] := ColorToRGB(Grouping.HeaderColor);
          if Grouping.HeaderColorTo <> clNone then
            ColorsTo[grpc,i] := ColorToRGB(Grouping.HeaderColorTo);
          if Grouping.HeaderTextColor <> clNone then
            FontColors[grpc,i] := ColorToRGB(Grouping.HeaderTextColor);
          *)
        end
        else
        begin
          (*
          if Grouping.HeaderColor <> clNone then
            RowColor[i] := ColorToRGB(Grouping.HeaderColor);
          if Grouping.HeaderColorTo <> clNone then
            RowColorTo[i] := ColorToRGB(Grouping.HeaderColorTo);
          if Grouping.HeaderTextColor <> clNone then
            RowFontColor[i] := ColorToRGB(Grouping.HeaderTextColor);
          *)
        end;

        np := i;
        Inc(i);
      end;

      if i < RowCount - 1 then
        lc := Cells[grp,i];
      Inc(i);
    end;

    if np <> -1 then
    begin

      if (np > FixedRows) and Options.Grouping.Summary then
      begin
        FInternalRowUpdate := True;
        InsertRows(np,1);
        FInternalRowUpdate := False;
        AddSummary(np);

        if Options.Grouping.MergeSummary and Options.Grouping.Summary then
        begin
          (*
          MergeCells(grpc,np,ColumnCount - grpc,1);
          if Grouping.SummaryColor <> clNone then
            Colors[grpc,np] := ColorToRGB(Grouping.SummaryColor);
          if Grouping.SummaryColorTo <> clNone then
            ColorsTo[grpc,np] := ColorToRGB(Grouping.SummaryColorTo);
          if Grouping.SummaryTextColor <> clNone then
            FontColors[grpc,np] := ColorToRGB(Grouping.SummaryTextColor);
          *)
        end
        else
        begin
          (*
          if Grouping.SummaryColor <> clNone then
            RowColor[np] := ColorToRGB(Grouping.SummaryColor);
          if Grouping.SummaryColorTo <> clNone then
            RowColorTo[np] := ColorToRGB(Grouping.SummaryColorTo);
          if Grouping.SummaryTextColor <> clNone then
            RowFontColor[np] := ColorToRGB(Grouping.SummaryTextColor);
          *)
        end;

        inc(np);
        inc(i);
      end;

      AddNode(np,i - np - 1);
      //outputdebugstring(pchar(inttostr(np)+';'+inttostr(i-np - 1)));


      if Options.Grouping.MergeHeader then
      begin
        (*
        MergeCells(grpc,np,ColCount - grpc,1);
        if Grouping.HeaderColor <> clNone then
          Colors[grpc,np] := Grouping.HeaderColor;
        if Grouping.HeaderColorTo <> clNone then
          ColorsTo[grpc,np] := Grouping.HeaderColorTo;
        if Grouping.HeaderTextColor <> clNone then
          FontColors[grpc,np] := Grouping.HeaderTextColor;
        *)
      end
      else
      begin
        (*
        if Grouping.HeaderColor <> clNone then
          RowColor[np] := Grouping.HeaderColor;
        if Grouping.HeaderColorTo <> clNone then
          RowColorTo[np] := Grouping.HeaderColorTo;
        if Grouping.HeaderTextColor <> clNone then
          RowFontColor[np] := Grouping.HeaderTextColor;
        *)
      end;
    end
    else
      AddNode(FixedRows,i - 1  - 1);


    if Options.Grouping.Summary then
    begin
      FInternalRowUpdate := True;
      InsertRows(i,1);
      FInternalRowUpdate := False;
      AddSummary(i);

      if Options.Grouping.MergeSummary and Options.Grouping.Summary then
      begin
        (*
        MergeCells(grpc,i,ColumnCount - grpc,1);
        if Grouping.SummaryColor <> clNone then
          Colors[grpc,i] := Grouping.SummaryColor;
        if Grouping.SummaryColorTo <> clNone then
          ColorsTo[grpc,i] := Grouping.SummaryColorTo;
        if Grouping.SummaryTextColor <> clNone then
          FontColors[grpc,i] := Grouping.SummaryTextColor;
        *)
      end
      else
      begin
        (*
        if Grouping.SummaryColor <> clNone then
          RowColor[i] := Grouping.SummaryColor;
        if Grouping.SummaryColorTo <> clNone then
          RowColorTo[i] := Grouping.SummaryColorTo;
        if Grouping.SummaryTextColor <> clNone then
          RowFontColor[i] := Grouping.SummaryTextColor;
        *)
      end;
    end;

    DeleteColumn(grp);

    if Options.Grouping.MergeHeader or (Options.Grouping.MergeSummary and Options.Grouping.Summary) then
    begin
      for i := FixedRows to RowCount - 1 do
      begin
        grpc := 1;
        if IsNode(i) and Options.Grouping.MergeHeader then
        begin
          MergeCells(grpc,i,ColumnCount - grpc,1);
          (*
          if Grouping.HeaderColor <> clNone then
            Colors[grpc,i] := ColorToRGB(Grouping.HeaderColor);
          if Grouping.HeaderColorTo <> clNone then
            ColorsTo[grpc,i] := ColorToRGB(Grouping.HeaderColorTo);
          if Grouping.HeaderTextColor <> clNone then
            FontColors[grpc,i] := ColorToRGB(Grouping.HeaderTextColor);
          *)
        end;

        if (IsNode(i + 1) or (i = RowCount - 1)) and Options.Grouping.MergeSummary and Options.Grouping.Summary then
        begin
          MergeCells(grpc,i,ColumnCount - grpc,1);
          (*
          if Grouping.SummaryColor <> clNone then
            Colors[grpc,i] := Grouping.SummaryColor;
          if Grouping.SummaryColorTo <> clNone then
            ColorsTo[grpc,i] := Grouping.SummaryColorTo;
          if Grouping.SummaryTextColor <> clNone then
            FontColors[grpc,i] := Grouping.SummaryTextColor;
          *)
        end;
      end;
    end;

   (*
   if CellNode.ExpandOne then
    begin
      for i := FixedRows to AllRowCount - 1 do
        if IsNode(i) then
        begin
          ExpandNode(i);
          Break;
        end;
    end;
   *)
   (*
    if (Col >= ColCount) or (Row >= RowCount) or (Col < 0) or (Row < 0) then
    begin
      GotoCellInt(FixedCols,FixedRows);
    end;

    Row := FixedRows;
   *)
  finally
    FIsGrouping := false;
    EndUpdate;
    (*
    if SearchFooter.Visible then
    begin // force correction of painting issue!
      SearchPanel.Width := SearchPanel.Width + 1;
      SearchPanel.Width := SearchPanel.Width - 1;
    end;
    *)
  end;
end;

procedure TTMSFMXGridData.GroupAvg(Col: Integer);
begin
  GroupCalc(Col,2);
end;

procedure TTMSFMXGridData.GroupCalc(Col, method: Integer);
var
  i,j,k: Integer;
  res: double;
begin
  i := FixedRows;
  while (i < RowCount) do
  begin
    if IsNode(i) then
    begin
      //if not GetNodeState(i)  then
      begin
        j := i + GetNodeSpan(i);

        if Options.Grouping.Summary then
          k := i + GetNodeSpan(i) + 1
        else
          k := i + 1;

        res := 0;

        case Method of
        1:res := ColumnSum(Col,i + 1,j );
        2:res := ColumnAvg(Col,i + 1,j );
        3:res := ColumnMin(Col,i + 1,j );
        4:res := ColumnMax(Col,i + 1,j );
        5:res := j - 1 - i;
        6:res := ColumnCustomCalcGroup(Col,i + 1,j - 1);
        7:res := ColumnDistinct(Col, i + 1, j );
        8:res := ColumnStdDev(Col, i + 1, j );
        end;

        Cells[Col,k] := Format(Options.Grouping.CalcFormat,[res]);

        i := j;
      end
      //else
      //  inc(i);
    end
    else
      Inc(i);
  end;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.GroupCount(Col: Integer);
begin
  GroupCalc(Col,5);
end;

procedure TTMSFMXGridData.GroupCustomCalc(Col: Integer);
begin
  GroupCalc(Col,6);
end;

procedure TTMSFMXGridData.GroupDistinct(Col: Integer);
begin
  GroupCalc(Col,7);
end;

procedure TTMSFMXGridData.GroupMax(Col: Integer);
begin
  GroupCalc(Col,4);
end;

procedure TTMSFMXGridData.GroupMin(Col: Integer);
begin
  GroupCalc(Col,3);
end;

procedure TTMSFMXGridData.GroupStdDev(Col: Integer);
begin
  GroupCalc(Col,8);
end;

procedure TTMSFMXGridData.GroupSum(Col: Integer);
begin
  GroupCalc(Col,1);
end;

procedure TTMSFMXGridData.HandleFilterListClick(Sender: TObject);
begin
  FFilterSenderListBox := Sender;
  FFilterTimer.Enabled := True;
end;

procedure TTMSFMXGridData.HandleFilterTimer(Sender: TObject);
var
  fd: TFilterData;
  cnd: string;
  i, filtercol: integer;
begin
  if Assigned(FFilterSenderListBox) then
  begin
    filtercol := (FFilterSenderListBox as TListbox).Tag;

    if Options.Filtering.MultiColumn then
    begin
      for i := Filter.Count - 1 downto 0 do
      begin
        if Filter.Items[i].Column = filtercol then
          Filter.Delete(i);
      end;
    end
    else
      Filter.Clear;

    fd := Filter.Add;
    fd.Column := filtercol; // need to set filter column here!!

    cnd := '';
    if (FFilterListBox.ItemIndex >= 0) and (FFilterListBox.ItemIndex <= FFilterListBox.Items.Count - 1) then
      cnd := FFilterListbox.Items[FFilterListbox.ItemIndex];

    if pos(' ',cnd) > 0 then
      cnd := '"' + cnd + '"';

    TProtectedPopup(((FFilterSenderListBox as TListBox).Parent as TPopup)).ClosePopup;

    DoFilterSelect(fd.Column, cnd);
    fd.Condition := cnd;

    UnhideRowsAll;
    ApplyFilter;
  end;

  FFilterSenderListBox := nil;
  FFilterTimer.Enabled := False;
end;

function TTMSFMXGridData.IsHiddenColumn(Col: integer): boolean;
var
  i: integer;
begin
  Result := true;

  for i := 0 to FColumnDisplayList.Count - 1 do
  begin
    if integer(FColumnDisplayList.Items[i]) = Col then
    begin
      Result := false;
      break;
    end;
  end;
end;

function TTMSFMXGridData.IsHiddenRow(Row: integer): boolean;
var
  i: integer;
begin
  Result := false;

  for i := 0 to FHiddenRowList.Count - 1 do
  begin
    if integer(FHiddenRowList.Items[i]) = Row then
    begin
      Result := true;
      break;
    end;
  end;
end;

procedure TTMSFMXGridData.HideColumn(Col: integer);
var
  i: integer;
  fnd: boolean;
begin
  if IsHiddenColumn(Col) then
    Exit;

  fnd := false;

  BeginUpdate;

  for i := 0 to FColumnDisplayList.Count - 1 do
  begin
    if integer(FColumnDisplayList.Items[i]) = Col then
    begin
      FHiddenColumnList.Add(Col);
      FColumnDisplayList.Delete(i);
      fnd := true;
      break;
    end;
  end;

  if fnd then
    FColumnCount := FColumnCount - 1;

  EndUpdate;
end;

procedure TTMSFMXGridData.HideColumns(Col, Count: integer);
var
  i,c: integer;
begin
  BeginUpdate;

  c := 0;
  i := 0;

  while (i  < FColumnDisplayList.Count) do
  begin
    if (integer(FColumnDisplayList.Items[i]) = Col + c) and (c < Count) then
    begin
      FHiddenColumnList.Add(Col + c);
      FColumnDisplayList.Delete(i);
      inc(c)
    end
    else
      inc(i);
  end;

  FColumnCount := FColumnCount - c;

  EndUpdate;
end;

function TTMSFMXGridData.DisplToRealRow(Row: integer): integer;
begin
  if (Row >= 0) and (Row < FRowDisplayList.Count) then
    Result := integer(FRowDisplayList.Items[Row])
  else
    Result := Row;
end;

function TTMSFMXGridData.RealToDisplRow(Row: integer): integer;
var
  i: integer;
begin
  Result := Row;
  i := 0;
  while i < FRowDisplayList.Count do
  begin
    if integer(FRowDisplayList.Items[i]) = Row then
    begin
      Result := i;
      break;
    end;
    inc(i);
  end;
end;

function TTMSFMXGridData.DisplToRealColumn(Col: integer): integer;
begin
  if (Col >= 0) and (Col < FColumnDisplayList.Count) then
    Result := integer(FColumnDisplayList.Items[Col])
  else
    Result := Col;
end;

function TTMSFMXGridData.RealToDisplColumn(Col: integer): integer;
var
  i: integer;
begin
  Result := -1;
  i := 0;
  while i < FColumnDisplayList.Count do
  begin
    if integer(FColumnDisplayList.Items[i]) = Col then
    begin
      Result := i;
      break;
    end;
    inc(i);
  end;
end;

procedure TTMSFMXGridData.HideRow(Row: integer);
var
  i: integer;
begin
  BeginUpdate;
  for i := 0 to FRowDisplayList.Count - 1 do
  begin
    if integer(FRowDisplayList.Items[i]) = Row then
    begin
      FHiddenRowList.Add(Row);
      FRowDisplayList.Delete(i);
      FRowCount := FRowCount - 1;
      break;
    end;
  end;
  EndUpdate;
end;

procedure TTMSFMXGridData.HideRows(Row, Count: integer);
var
  i,c: integer;
  flg: boolean;
begin
  c := 0;
  i := 0;
  flg := false;

  while i < FRowDisplayList.Count do
  begin
    if integer(FRowDisplayList.Items[i]) = Row + c then
    begin
      FHiddenRowList.Add(Row + c);
      FRowDisplayList.Delete(i);
      FRowCount := FRowCount - 1;
      flg := true;
      if c < Count - 1 then
        inc(c)
      else
        break;
    end
    else
      inc(i);
  end;
  if flg then
    UpdateGrid(true);
end;

procedure TTMSFMXGridData.HighlightInCell(DoCase: Boolean; Col, Row: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetCellRange(Col,Row);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtHighlight;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.HighlightInCol(DoFixed, DoCase: Boolean; Col: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetColumnRange(Col,DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtHighlight;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.HighlightInGrid(DoFixed, DoCase: Boolean;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetGridRange(DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtHighlight;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.HighlightInRow(DoFixed, DoCase: Boolean; Row: Integer;
  HiText: string);
var
  mi: TCellMarkItem;
begin
  mi := FMarkList.Add;
  mi.CellRange := GetRowRange(Row, DoFixed);
  mi.Value := HiText;
  mi.CaseSensitive := DoCase;
  mi.MarkType := mtHighlight;
  UpdateGridCells;
end;


procedure TTMSFMXGridData.ImportNotification(AState: TGridImportState;
  ARow: Integer);
begin
  if AState = isImportStart then
    FPrevProgress := -1;
end;

procedure TTMSFMXGridData.InsertColumn(Col: integer);
var
  i: integer;
  sl: TRowInfo;
  cidx: Integer;
begin
  BeginUpdate;

  for i := 0 to FRowList.Count - 1 do
  begin
    sl := FRowList.Items[i];
    if Assigned(sl) then
    begin
      if Col < sl.Data.Count then
        sl.Data.InsertObject(Col,'',nil);
    end;
  end;


  if not FInternalColumnUpdate then
  begin
    cidx := ColumnW.Insert(Col).Index;
    if (cidx >= 0) and (cidx <= ColumnW.Count - 1) then
    begin
      ColumnW.Items[cidx].Value := DefaultColumnWidth;
      ColumnW.Items[cidx].CellVal := Col;
      for I := cidx + 1 to ColumnW.Count - 1 do
        ColumnW.Items[I].CellVal := ColumnW.Items[I].CellVal + 1;
    end;
  end;

  EndUpdate;

  ColumnCount := ColumnCount + 1;

//  outputdebugstring(pchar(inttostr(fcolumndisplaylist.Count)));
// UpdateGridCells;
end;

procedure TTMSFMXGridData.InsertFromCSV(FileName: String; MaxRows: integer = -1);
begin
  InputFromCSV(FileName, True, MaxRows);
end;

procedure TTMSFMXGridData.InsertRow(Row: integer);
begin
  InsertRows(Row,1);
end;

procedure TTMSFMXGridData.InsertRows(Row, Count: integer);
var
  i: integer;
  ridx: Integer;
begin
  if Row >= RowCount then
    Exit;

  BeginUpdate;

  EnsureRow(Row);

  for i := 1 to Count do
    FRowList.Insert(Row, Pointer(TRowInfo.Create));

  for i := 1 to Count do
    FRowDisplayList.Insert(Row,Row + i - 1);

  for i := Row + Count to FRowDisplayList.Count - 1 do
  begin
    FRowDisplayList.Items[i] := FRowDisplayList.Items[i] + Count;
  end;

  if not FInternalRowUpdate then
  begin
    ridx := RowH.Insert(Row).Index;
    if (ridx >= 0) and (ridx <= RowH.Count -1) then
    begin
      RowH.Items[ridx].Value := DefaultRowHeight;
      RowH.Items[ridx].CellVal := Row;
      for I := ridx + 1 to RowH.Count - 1 do
        RowH.Items[I].CellVal := RowH.Items[I].CellVal + 1;
    end;
  end;

  FRowCount := FRowCount + Count;

  EndUpdate;
end;

function TTMSFMXGridData.IsFixed(Col, Row: Integer): Boolean;
begin
  Result := (Row < FixedRows) or (Row > RowCount - 1 - FixedFooterRows) or (Col < FixedColumns) or (Col > ColumnCount - 1 - FixedRightColumns);
  Result := Result or IsNormalFixed(Col, Row);
end;

function TTMSFMXGridData.IsIgnoredColumn(Col: integer): boolean;
begin
  Result := false;
end;

function TTMSFMXGridData.IsYMergedCell(Col,Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;

  cp := TCellProperty(IntObjects[Col,DisplToRealRow(Row)]);

  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
  begin
    Result := cp.RowSpan > 1;
  end;
end;

function TTMSFMXGridData.IsXMergedCell(Col,Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;

  cp := TCellProperty(IntObjects[Col,DisplToRealRow(Row)]);

  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
  begin
    Result := cp.ColSpan > 1;
  end;
end;


function TTMSFMXGridData.IsMerged(Col, Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;

  cp := TCellProperty(IntObjects[Col,DisplToRealRow(Row)]);

  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
  begin
    Result := (cp.ColSpan > 1) or (cp.RowSpan > 1);
  end;
end;

procedure TTMSFMXGridData.LinearFill(DoFixed: Boolean);
var
  i,j: Integer;
  ro,co,re,ce: Integer;
begin
  if DoFixed then
  begin
    ro := 0;
    co := 0;
    re := RowCount - 1;
    ce := ColumnCount - 1;
  end
  else
  begin
    ro := FixedRows;
    co := FixedColumns;
    re := RowCount - 1 - FixedFooterRows;
    ce := ColumnCount - 1 - FixedRightColumns;
  end;

  for i := ro to re do
    for j := co to ce do
      Cells[j,i] := IntToStr(j)+':'+IntToStr(i);
end;

procedure TTMSFMXGridData.Loaded;
begin
  inherited;
  IOOffset := Point(FixedColumns, FixedRows);
  SetColumnOrder;
end;

procedure TTMSFMXGridData.LoadFromCSV(FileName: string; MaxRows: integer = -1);
begin
  InputFromCSV(FileName, False, MaxRows);
end;

procedure TTMSFMXGridData.InputFromCSV(FileName: string; InsertMode: boolean; MaxRows: integer = -1);
var
  buffer,celltext: string;
  s,z: Integer;
  strtCol,strtRow: Integer;
  c1,c2,cm: Integer;
  OldDelimiter: Char;
  delimiterpos,quotepos: Integer;
  lr: TStringList;
  NewRows: integer;
  sl: TFileStringList;
  sh: integer;
  hasquote: boolean;

begin
  StrtCol := FIOOffset.X; //SaveStartCol;
  StrtRow := FIOOffset.Y; //SaveStartRow;

  sl := TFileStringList.Create;
  sl.LoadFromFile(FileName);

  if sl.Eof then
  begin
    raise Exception.Create('File is empty');
    sl.Free;
    Exit;
  end;

  z := StrtRow;

  lr := TStringList.Create;

  if InsertMode then
    z := RowCount;

  OldDelimiter := Options.IO.Delimiter;

  BeginUpdate;

  // do intelligent estimate of the separator
  if (Options.IO.Delimiter = #0) then
  begin
    CellText := '';

    sl.ReadLn(buffer);

    if not sl.Eof then sl.ReadLn(CellText);
    sl.Reset;

    cm := 0;
    for s := 1 to 10 do
    begin
      c1 := NumSingleChar(CSVSeparators[s],Buffer);
      c2 := NumSingleChar(CSVSeparators[s],Celltext);
      if (c1 = c2) and (c1 > cm) then
      begin
        Options.IO.Delimiter := CSVSeparators[s];
        cm := c1;
      end;
    end;

    if cm = 0 then
      for s := 1 to 10 do
      begin
        c1 := NumChar(CSVSeparators[s],Buffer);
        c2 := NumChar(CSVSeparators[s],Celltext);
        if (c1 = c2) and (c1 > cm) then
        begin
          Options.IO.Delimiter := CSVSeparators[s];
          cm := c1;
        end;
      end;

    // if no matching delimiter count found on line1 & line2, take maximum
    if cm = 0 then
      for s := 1 to 10 do
      begin
        c1 := NumChar(CSVSeparators[s],Buffer);
        c2 := NumChar(CSVSeparators[s],Celltext);
        if (c1 > cm) or (c2 > cm) then
        begin
          Options.IO.Delimiter := CSVSeparators[s];
          cm := Max(c1,c2);
        end;
      end;
  end;

  sl.Reset;

  NewRows := 0;

  sh := 0;

  while (not sl.Eof) and ((MaxRows <= 0) or (NewRows < MaxRows)) do
  begin
    sl.ReadLn(buffer);

    inc(NewRows);

    s := StrtCol;

    if z >= RowCount - FixedFooterRows then
    begin
      RowCount := z + 1000;
    end;

    while VarCharPos(Options.IO.Delimiter,Buffer,DelimiterPos) > 0 do
    begin
      {$IFDEF DELPHI_LLVM}
      DelimiterPos := DelimiterPos + 1;
      hasquote := Buffer[0] = '"';
      {$ELSE}
      hasquote := Buffer[1] = '"';
      {$ENDIF}
      if hasquote then
      begin
        Delete(buffer,1,1);   //delete first quote from buffer

        if SinglePos('"',Buffer,QuotePos) > 0 then  //search for next single quote
        begin
          CellText := Copy(buffer,1,QuotePos - 1);
          CellText := DoubleToSingleChar('"',CellText);
          Delete(buffer,1,QuotePos);
        end
        else
          CellText := '';

        VarCharPos(Options.IO.Delimiter,buffer,DelimiterPos);
        {$IFDEF DELPHI_LLVM}
        DelimiterPos := DelimiterPos + 1;
        {$ENDIF}
      end
      else
      begin
        CellText := Copy(buffer,1,DelimiterPos - 1);
      end;

      CSVToLineFeeds(CellText);

      Cells[s,z] := CellText;

      Delete(buffer,1,DelimiterPos);

      Inc(s);
      if (s >= ColumnCount + sh) then
      begin
        ColumnCount := s - sh;
      end;
    end;

    // remaining part
    if Length(Buffer) > 0 then
    begin
      {$IFDEF DELPHI_LLVM}
      hasquote := Buffer[0] = '"';
      {$ELSE}
      hasquote := Buffer[1] = '"';
      {$ENDIF}
      if hasquote then
        Delete(buffer,1,1);

      if Length(Buffer) > 0 then
      begin
        {$IFDEF DELPHI_LLVM}
        if hasquote and (Buffer[Length(Buffer) - 1] = '"') then
        {$ELSE}
        if hasquote and (Buffer[Length(Buffer)] = '"') then
        {$ENDIF}
          Delete(Buffer,Length(Buffer),1);
      end;
      if hasquote then
        CellText := DoubleToSingleChar('"',Buffer)
      else
        CellText := Buffer;

      CSVToLineFeeds(CellText);

      Cells[s,z] := CellText;

      Inc(s);

      if (s > ColumnCount + sh) then
      begin
        ColumnCount := s - sh;
      end;
    end;

    Inc(z);

    DoIOProgress(z, sl.Count);
  end;

  sl.Free;

  RowCount := z + FixedFooterRows;

  lr.Free;

  Options.IO.Delimiter := OldDelimiter;

  DoCellsChanged(CellRange(0,0,ColumnCount - 1, RowCount - 1));

  EndUpdate;
end;


procedure TTMSFMXGridData.LoadFromFile(FileName: String);
var
  X,Y,CW: Integer;
  ss,ss1:string;
  strtCol,strtRow: Integer;
  oprogr: Integer;
  seppos: Integer;
  sl: TFileStringList;

  function MStrToInt(s:string): Integer;
  var
    code,i: Integer;
  begin
    val(s,i,code);
    Result := i;
  end;

begin
  sl := TFileStringList.Create;

  sl.LoadFromFile(FileName);

  StrtCol := 0; //FIOOffset.X;
  StrtRow := 0; //FIOOffset.Y;

  sl.ReadLn(ss);

  if ss <> '' then
  begin
    {$IFDEF DELPHI_LLVM}
    ss1 := Copy(ss,0,CharPos(',',ss));
    {$ELSE}
    ss1 := Copy(ss,1,CharPos(',',ss) - 1);
    {$ENDIF}
    ColumnCount := MStrToInt(ss1) + StrtCol;
    {$IFDEF DELPHI_LLVM}
    ss1 := Copy(ss,CharPos(',',ss) + 2,Length(ss));
    {$ELSE}
    ss1 := Copy(ss,CharPos(',',ss) + 1,Length(ss));
    {$ENDIF}
    FRowCount := MStrToInt(ss1) + StrtRow;
  end;

  if (ColumnCount = 0) or (FRowCount = 0) then
  begin
    sl.Free;
    raise Exception.Create('File contains no data or corrupt file '+FileName);
  end;

  BeginUpdate;

  try
    Cells[ColumnCount - 1, FRowCount - 1] := '';

    oprogr := 1;

    while not sl.Eof do
    begin
      sl.Readln(ss);
      if Pos('cw',ss) = 1 then {parse cw i,Width }
      begin
        seppos := CharPos(',',ss);
        ss1 := Copy(ss,4,seppos - 4);
        ss := Copy(ss,seppos + 1,255);
        CW := MStrToInt(ss1);
        if (cw >= 0) and (cw < ColumnCount) then
          ColumnWidths[cw] := mstrtoint(ss);
      end
      else
      begin
        ss1 := GetToken(ss,',');
        X := mStrToInt(ss1);
        ss1 := GetToken(ss,',');
        Y := mStrToInt(ss1);

        if (X < ColumnCount) and (Y < FRowCount) then
        begin
          ss := FileToLF(ss);
          DoLoadCell(X,Y,ss);
        end;
      end;

      inc(oprogr);
      DoIOProgress(oprogr, sl.Count);
    end;
  finally
    sl.Free;

    DoCellsChanged(CellRange(0,0,ColumnCount - 1, RowCount - 1));

    EndUpdate;
  end;
end;


procedure TTMSFMXGridData.LoadFromFixed(FileName: string; positions: TIntList;
  DoTrim: boolean; MaxRows: integer);
var
  f: TextFile;
  s,sub: string;
  c,r,i: Integer;
  rc: integer;
begin
  AssignFile(f, FileName);
  {$i-}
  Reset(f);
  {$i+}
  if IOResult <> 0 then
    raise Exception.Create('File ' + FileName + ' not found');

  ColumnCount := FIOOffset.X + Positions.Count - 1;

  //Positions.Sort(@compareInts);

  r := FIOOffset.Y;

  rc := 1;

  while not Eof(f) do
  begin
    ReadLn(f,s);
    c := FIOOffset.X;

    for i := 2 to Positions.Count do
    begin
      sub := Copy(s,Positions.Items[i-2],Positions.Items[i-1] - Positions.Items[i-2]);
      if DoTrim then
        sub := Trim(sub);

      DoLoadCell(c,r,sub);
      Inc(c);
    end;

    Inc(r);

    Inc(rc);

    if (MaxRows <> -1) then
      if rc > MaxRows then
        break;


    if (r >= RowCount) and not Eof(f) then
      RowCount := r + 1;
  end;

  CloseFile(f);

  DoCellsChanged(CellRange(0,0,ColumnCount - 1, RowCount - 1));
end;


procedure TTMSFMXGridData.LoadFromStream(Stream: TStream);
var
  X,Y,LP: Integer;
  ss,ss1: string;

  function ReadString(var s:string): Integer;
  var
    c: char;
  begin
    c := '0';
    s := '';
    while (Stream.Position < Stream.Size) and (c <> #13) do
    begin
      Stream.Read(c,2);
      if (c <> #13) then s := s + c;
    end;
    Stream.Read(c,2);
    Result := Length(s);
  end;

begin
  {Allow to put other data before Grid's data in the stream}
  {stream.position:=0;}

  BeginUpdate;

  try
    if (Stream.Position < Stream.Size) then
    begin
      if (Readstring(ss) > 0) then
      begin
        ss1 := Copy(ss,1,Pos(',',ss) - 1);
        ColumnCount := StrToInt(ss1) + FIOOffset.X;
        ss1 := Copy(ss,Pos(',',ss) + 1,Length(ss));
        FRowCount := StrToInt(ss1) + FIOOffset.Y;
      end;

      Cells[ColumnCount - 1, FRowCount - 1] := '';
    end;

    while (Stream.Position < Stream.Size) do
    begin
      LP := Stream.Position;

      ReadString(ss);
      if Pos('cw',ss) = 1 then
      begin
        Delete(ss,1,3);
        ss1 := GetToken(ss,',');
        X := StrToInt(ss1);
        Y := StrToInt(ss);
        ColumnWidths[X] := Y;
      end
      else
      begin
        ss1 := GetToken(ss,',');
        if (ss1 = '') then // irregular data was found
        begin
          Stream.Position := LP;
          Break;
        end;
        X := StrToInt(ss1);
        ss1 := GetToken(ss,',');
        if (ss1 = '') then // irregular data was found
        begin
          Stream.Position := LP;
          Break;
        end;
        Y := StrToInt(ss1);
        ss := FileToLF(ss);
        DoLoadCell(X  + FIOOffset.X, Y + FIOOffset.Y,  ss);
      end;
    end;
  finally
    DoCellsChanged(CellRange(0,0,ColumnCount - 1,RowCount - 1));
    EndUpdate;
  end;
end;

function TTMSFMXGridData.LookupInColumn(Col: integer; AValue: string;
  AllRows: boolean = false; AutoGoto: boolean = false): integer;
var
  sr: integer;
begin
  if AllRows then
    sr := 0
  else
    sr := FixedRows;

  Result := LookupInColumnFromRow(Col, sr, AValue, AllRows, AutoGoto);
end;


function TTMSFMXGridData.LookupInColumnFromRow(Col,Row: integer; AValue: string;
  AllRows: boolean = false; AutoGoto: boolean = false): integer;
var
  i: integer;
  sr,er,m: integer;
  s,v: string;

begin
  Result := -1;

  v := AValue;
  if not Options.Lookup.CaseSensitive then
    v := Uppercase(v);

  if AllRows then
  begin
    sr := FixedRows;
    er := RowCount - 1;
  end
  else
  begin
    sr := Row;
    er := RowCount - 1 - FixedFooterRows;
  end;

  if Col = SortColumn then
  begin
    repeat
      m := sr + round((er - sr) / 2);

      s := Cells[Col,m];

      if not Options.Lookup.CaseSensitive then
        s := UpperCase(s);

      if Pos(v, s) = 1  then
      begin
        Result := m;
        break;
      end;

      if v > s then
        sr := m
      else
        er := m;
    until (er - sr <= 1);

    if (m > sr) and (Result <> -1) then
    begin
      repeat
        s := Cells[Col,m - 1];
        if not Options.Lookup.CaseSensitive then
          s := UpperCase(s);

        if Pos(v, s) = 1  then
          dec(m)
        else
          break;
      until (m <= sr);
      Result := m;
    end;
  end
  else
  begin
    for i := sr to er do
    begin
      s := Cells[Col,i];

      if not Options.Lookup.CaseSensitive then
        s := UpperCase(s);

      if Pos(v, s) = 1 then
      begin
        Result := i;
        break;
      end;
    end;
  end;

  if AutoGoto and (Result <> -1) then
    FocusedCell := Cell(Col,Result);
end;

function TTMSFMXGridData.MatchFilter(ARow: Integer): Boolean;
var
  i: Integer;
  s:string;
  temp: Boolean;

begin
  Result := True;

  for i := 1 to FFilter.Count do
  begin
    with FFilter.Items[i - 1] do
    begin
      case FFilter.Items[i - 1].Data of
      fcVirtual: s := Cells[Column,ARow];
      fcNormal:
        begin
          s := Cells[Column, ARow];
          DoGetCellData(Column,ARow,s);
        end;
      fcStripHTML: s := StrippedCells[Column, ARow];
      fcRow: s := RowText[ARow];
      //fcCalculated: s := CalcCell(Column,ARow);
      end;

      s := Trim(s);

      if FFilter.Items[i - 1].Data = fcRow then
      begin
        temp := Pos(Uppercase(Condition), Uppercase(s)) > 0;
      end
      else
      begin
        if Prefix <> '' then
          if Pos(Prefix,s) = 1 then
            Delete(s,1,Length(FFilter.Items[i - 1].Prefix));

        if Suffix <> '' then
          if Pos(Suffix,s) = 1 + Length(s) - Length(Suffix) then
            Delete(s,1 + Length(s) - Length(Suffix),Length(s));

        temp := true;
        try
          if FFilter.Items[i - 1].Condition <> '' then
            temp := MatchStrEx(Condition,s,CaseSensitive);
          //  temp := Condition = '=""'
          //else
        except
          temp := false;
        end;
      end;

      case FFilter.Items[i - 1].Operation of
      foSHORT:
        begin
          Result := temp;
          if not Result then
            Break;
        end;
      foNONE: Result := temp;
      foAND: Result := Result AND temp;
      foOR:  Result := Result OR temp;
      foXOR: Result := Result XOR temp;
      end;
    end;
  end;

//  if Assigned(FOnCustomFilter) then
//  begin
//    temp := Result;
//    FOnCustomFilter(Self, ARow, temp);
//    Result := temp;
//  end;

end;

function TTMSFMXGridData.MaxCharsInCol(ACol: Integer): Integer;
var
  i,k,rc: Integer;
  s,substr: string;

begin
  Result := 0;

  rc := ACol;

  for i := 0 to RowCount - 1 do
  begin
    s := DoSaveCell(rc,i);
    repeat
      substr := GetNextLine(s,true);
      k := Length(substr);
      if k > Result then
        Result := k;
    until s = '';
  end;
end;

procedure TTMSFMXGridData.MergeCells(Col, Row, ColCount, RowCount: integer);
var
  cp: TCellProperty;
  i,j: integer;
  cl: TCell;
  usecp: boolean;
begin
  BlockUpdate := True;

  usecp := false;
  cp := nil;

  for i := Col to Col + ColCount - 1 do
  begin
    for j := Row to Row + RowCount - 1 do
    begin
      if not usecp then
      begin
        cp := TCellProperty(IntObjects[i,j]);

        if not Assigned(cp) then
          cp := TCellProperty.Create(GetDefaultFont(Col,Row));

        cp.BaseCol := Col;
        cp.BaseRow := Row;
        cp.ColSpan := ColCount;
        cp.RowSpan := RowCount;
        usecp := true;
      end;

      IntObjects[i,j] := cp;
    end;
  end;

  cl := BaseCell(Col, Row);
  cl.Col := Col;
  cl.Row := Row;
  SetFocusedCell(cl);

  BlockUpdate := False;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.MergeSelection(ASelection: TCellRange);
begin
  MergeCells(ASelection.StartCol, ASelection.StartRow, ASelection.EndCol - ASelection.StartCol + 1,
    ASelection.EndRow - ASelection.StartRow + 1);
end;

procedure TTMSFMXGridData.MoveColumn(FromCol, ToCol: integer);
var
  rfi,rti,ii: integer;
  si: TSingleListItem;
  cw: TList<single>;
begin
  if FColumnOrder.Count = 0 then
    SetColumnOrder;

  FColumnDisplayList.Move(FromCol, ToCol);

  cw := TList<single>.Create;

  for ii := 0 to ColumnCount - 1 do
    cw.Add(ColumnWidths[ii]);

  cw.Move(FromCol,ToCol);

  ColumnW.Clear;

  for ii := 0 to ColumnCount - 1 do
  begin
    si := ColumnW.Add;
    si.CellVal := ii;
    si.Value := cw.Items[ii];
  end;

  cw.Free;

  rfi := DisplToRealColumn(FromCol);
  rti := DisplToRealColumn(ToCol);


  {
  for i := 0 to FRowList.Count - 1 do
  begin
    ri := TRowInfo(FRowList.Items[i]);

    if Assigned(ri) then
    begin
      sl := ri.Data;
      if Assigned(sl) and
        (sl.Count > rfi) and
        (sl.Count > rti) then
      begin
        sl.Move(rfi, rti);
      end;
    end;
  end;
  }

  if (FColumnOrder.Count > Rfi) and
     (FColumnOrder.Count > Rti) then
  begin
    ii := FColumnOrder.Items[Rfi];
    FColumnOrder.Delete(Rfi);
    FColumnOrder.Insert(Rti,ii);
  end;

  UpdateGrid;
end;

procedure TTMSFMXGridData.SwapColumns(Col1, Col2: integer);
var
  i: integer;
  v: TCellData;
  o: TObject;
  val1, val2: Single;
begin
  for i := 0 to FRowList.Count - 1 do
  begin
    if Assigned(FRowList.Items[i]) and
      (TRowInfo(FRowList.Items[i]).Data.Count > Col1) and
      (TRowInfo(FRowList.Items[i]).Data.Count > Col2) then
    begin
      {$IFDEF VARIANTLIST}
      v := TRowInfo(FRowList.Items[i]).Data.Values[Col1];
      o := TRowInfo(FRowList.Items[i]).Data.Objects[Col1];

      TRowInfo(FRowList.Items[i]).Data.Values[Col1] := TRowInfo(FRowList.Items[i]).Data.Values[Col2];
      TRowInfo(FRowList.Items[i]).Data.Objects[Col1] := TRowInfo(FRowList.Items[i]).Data.Objects[Col2];
      TRowInfo(FRowList.Items[i]).Data.Values[Col2] := v;
      TRowInfo(FRowList.Items[i]).Data.Objects[Col2] := o;
      {$ELSE}
      s := TRowInfo(FRowList.Items[i]).Data.Strings[Col1];
      TRowInfo(FRowList.Items[i]).Data.Strings[Col1] := TRowInfo(FRowList.Items[i]).Data.Strings[Col2];
      TRowInfo(FRowList.Items[i]).Data.Strings[Col2] := s;
      {$ENDIF}
    end;
  end;

  val1 := ColumnWidths[Col1];
  val2 := ColumnWidths[Col2];

  ColumnWidths[Col1] := val2;
  ColumnWidths[Col2] := val1;

  UpdateGridCells;
end;


procedure TTMSFMXGridData.MoveRow(FromRow, ToRow: integer);
begin
  FRowList.Move(FromRow, ToRow);
end;

function TTMSFMXGridData.NextSelectableColumn(Col, Row: Integer): TCell;
var
  c, corig, r, rorig: Integer;
  cs: Integer;
  chk: Boolean;
begin
  c := Col;
  r := Row;

  corig := c;
  rorig := r;

  Result.Col := Col;
  Result.Row := Row;

  cs := ColSpan(c, r);
  if cs > 0 then
  begin
    chk := (Result.Col + cs <= ColumnCount - 1 - FixedRightColumns);
    if chk then
      Result.Col := Result.Col + cs
  end
  else
  begin
    chk := (Result.Col + 1 <= ColumnCount - 1 - FixedRightColumns);
    if chk then
      Result.Col := Result.Col + 1;
  end;

  if chk then
  begin
    while IsNormalFixed(Result.Col, Result.Row) and (Result.Col < ColumnCount - 1 - FixedRightColumns) do
    begin
      cs := ColSpan(c, r);
      if cs > 0 then
        Result.Col := Result.Col + cs
      else
        Result.Col := Result.Col + 1;
    end;
  end;

  if IsNormalFixed(Result.Col, Result.Row) then
  begin
    Result.Col := corig;
    Result.Row := rorig;
  end;
end;

function TTMSFMXGridData.NextSelectableRow(Col, Row: Integer): TCell;
var
  c, r, corig, rorig: Integer;
  rs: Integer;
  chk: Boolean;
begin
  c := Col;
  r := Row;
  corig := c;
  rorig := r;

  Result.Col := Col;
  Result.Row := Row;

  rs := RowSpan(c, r);
  if rs > 0 then
  begin
    chk := (Result.Row + rs <= RowCount - 1 - FixedFooterRows);
    if chk then
      Result.Row := Result.Row + rs
  end
  else
  begin
    chk := (Result.Row + 1 <= RowCount - 1 - FixedFooterRows);
    if chk then
      Result.Row := Result.Row + 1;
  end;

  if chk then
  begin
    while IsNormalFixed(Result.Col, Result.Row) and (Result.Row < RowCount - 1 - FixedFooterRows) do
    begin
      rs := RowSpan(c, r);
      if rs > 0 then
        Result.Row := Result.Row + rs
      else
        Result.Row := Result.Row + 1;
    end;
  end;

  if IsNormalFixed(Result.Col, Result.Row) then
  begin
    Result.Col := corig;
    Result.Row := rorig;
  end;
end;

procedure TTMSFMXGridData.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FBitmapContainer) then
    FBitmapContainer := nil;
end;

procedure TTMSFMXGridData.OutputToAscii(FileName: string; AppendMode,
  unicode: Boolean);
var
  sc,z,n: Integer;
  CellText,CellStr,str,alistr,remainingstr:string;
  i,rc: Integer;
  MultiLineList: TStringlist;
  anotherlinepos: Integer;
  blanksfiller: string;
  blankscount,NeededLines: Integer;
  AlignValue: TTextAlign;
  Colchars:array of byte;
  StrtCol, StrtRow: integer;
  sl: TFileStringList;

begin
  sl := TFileStringList.Create;
  sl.LineBreak := #13#10;

  if AppendMode then
    if FileExists(FileName) then
      sl.LoadFromFile(FileName);

  if FSaveHiddenCells then
    n := HiddenColumnCount
  else
    n := 0;

  SetLength(ColChars,ColumnCount);

  StrtCol := IOOffset.X;
  StrtRow := IOOffset.Y;

  ColumnCount := ColumnCount + n;

  for i := StrtCol to ColumnCount - 1 do
  begin
    if SaveHiddenCells then
      rc := i
    else
      rc := DisplToRealColumn(i);

    Colchars[rc] := MaxCharsInCol(rc);
  end;

  try
    ExportNotification(esExportStart, StrtRow);

    MultiLineList := TStringlist.Create;
    for z := StrtRow to RowCount - 1 do
    begin
      ExportNotification(esExportNextRow, z);
      str := '';
      for sc := StrtCol to ColumnCount - 1 do
      begin
        if SaveHiddenCells then
          rc := sc
        else
          rc := DisplToRealColumn(sc);

        CellText := DoSaveCell(rc,z);

        if (Pos(#13#10, CellText) > 0) {and MultiLineCells} then
        begin
          CellStr := Copy(CellText,0, Pos(#13#10, CellText) - 1);
          remainingstr := copy(CellText, Pos(#13#10, CellText)+2, Length(CellText));
          NeededLines := 0;
          repeat
            inc(NeededLines);
            blanksfiller := '';
            blankscount := 0;

            if (MultiLineList.Count < NeededLines) then  {we haven't already added a new line for an earlier Colunn}
              MultiLineList.Add('');

            {nr of spaces before cell text}
            for i := 0 to rc - 1 do
              BlanksCount := BlanksCount + ColChars[i] + 1;

            {add to line sufficient blanks}
            for i := 0 to (blankscount - Length(MultiLineList[NeededLines-1])-1) do
              BlanksFiller := BlanksFiller + ' ';

            MultiLineList[NeededLines - 1] := MultiLineList[NeededLines - 1] + BlanksFiller;

            AnotherLinePos := Pos(#13#10, remainingstr);

            if AnotherLinePos > 0 then
            begin
              alistr := Copy(remainingstr, 0, AnotherLinePos - 1);
              remainingstr := Copy(remainingstr,pos(#13#10,remainingstr)+2,Length(remainingstr));
            end
            else
            begin
              alistr := remainingstr;
            end;

           {$IFDEF DELPHIXE6_LVL}
            AlignValue := TTextAlign.Leading; //GetCellAlignment(rc,z).Alignment;
            case AlignValue of
            TTextAlign.Trailing:while (Length(alistr)<Colchars[rc]) do alistr := ' '+alistr;
            TTextAlign.Center:while (Length(alistr) < Colchars[rc]) do alistr := ' '+alistr+' ';
            end;
            {$ELSE}
            AlignValue := TTextAlign.taLeading; //GetCellAlignment(rc,z).Alignment;
            case AlignValue of
            TTextAlign.taTrailing:while (Length(alistr)<Colchars[rc]) do alistr := ' '+alistr;
            TTextAlign.taCenter:while (Length(alistr) < Colchars[rc]) do alistr := ' '+alistr+' ';
            end;
            {$ENDIF}
            MultiLineList[NeededLines-1] := MultiLineList[NeededLines-1]+alistr;

          until anotherlinepos = 0;
        end
        else
          cellstr := CellText;

        if Pos(#13#10,CellStr) > 0 then
          CellStr := Copy(CellStr,0,pos(#13#10,CellStr)-1);

        {$IFDEF DELPHIXE6_LVL}
        AlignValue := TTextAlign.Leading; //GetCellAlignment(rc,z).Alignment;
        case AlignValue of
        TTextAlign.Trailing: while (Length(cellstr) < Colchars[rc]) do cellstr := ' ' + cellstr;
        TTextAlign.Center: while (Length(cellstr) < Colchars[rc]) do cellstr := ' ' + cellstr+' ';
        end;
        {$ELSE}
        AlignValue := TTextAlign.taLeading; //GetCellAlignment(rc,z).Alignment;
        case AlignValue of
        TTextAlign.taTrailing: while (Length(cellstr) < Colchars[rc]) do cellstr := ' ' + cellstr;
        TTextAlign.taCenter: while (Length(cellstr) < Colchars[rc]) do cellstr := ' ' + cellstr+' ';
        end;
        {$ENDIF}

        blanksfiller := '';
        blankscount := Colchars[rc];
        for i := 0 to (blankscount - Length(cellstr)) do
          blanksfiller := blanksfiller + ' ';

        str := str + cellstr + blanksfiller;
      end;  {Column}

      sl.Writeln(Str);
      for i := 0 to MultiLineList.Count-1 do
        sl.Writeln(MultiLineList[i]);     {finally, add the extra lines for this Row}
      MultiLineList.Clear;

    end;    {Row}
    MultiLineList.Free;
  finally
    ColumnCount := ColumnCount - n;
    ExportNotification(esExportDone, -1);

    if Unicode then
      sl.SaveToFile(FileName, TEncoding.Unicode)
    else
      sl.SaveToFile(FileName);

    sl.Free;
  end;
end;

procedure TTMSFMXGridData.OutputToCSV(FileName: string; appendmode, unicode: Boolean);
var
  z,s,n,rs: Integer;
  CellText: String;
  Delim: Char;
  dblquotes: Boolean;
  sl: TFileStringList;

begin
  if FSaveHiddenCells then
    n := HiddenColumnCount
  else
    n := 0;

  if Options.IO.Delimiter = #0 then
    Delim := ','
  else
    Delim := Options.IO.Delimiter;

  sl := TFileStringList.Create;
  sl.LineBreak := #13#10;

  if AppendMode then
    sl.LoadFromFile(FileName);

  ExportNotification(esExportStart, FIOOffset.Y);

  for z := FIOOffset.Y to RowCount - 1 do
  begin
    ExportNotification(esExportNextRow, z);

    for s := FIOOffset.X to ColumnCount - 1 + n do
    begin
      if s > FIOOffset.X then
        sl.write(Delim);

      //if FSaveHiddenCells then
        rs := s;
      //else
      //  rs := RemapCol(s);

      CellText := DoSaveCell(rs,z);

      CellText := CSVQuotes(CellText);

      dblquotes := false;

      if Options.IO.AlwaysQuotes or ((Pos(Delim,CellText) = 0) and (Pos('"',CellText) > 0)) then
      begin
        CellText := '"' + CellText + '"';
        dblquotes := true;
      end;

      if CellText = '' then
      begin
        if Options.IO.QuoteEmptyCells then
          CellText := '""';
      end;

      if (Pos(Delim,CellText) > 0) or (LinesInText(CellText {,FMultiLineCells}) > 1) then
      begin
        if not dblquotes then
          LinefeedsToCSV(CellText)
        else
          LinefeedsToCSVNQ(CellText);
      end;
      sl.Write(CellText);
    end; // xloop

    sl.Writeln('');

    DoIOProgress(z, RowCount - 1);
  end;

  ExportNotification(esExportDone, -1);

  if Unicode  then
    sl.SaveToFile(FileName, TEncoding.UTF8)
  else
    sl.SaveToFile(FileName);

  sl.Free;
end;

procedure TTMSFMXGridData.OutputToHTML(FileName: string; appendmode, Show,
  Unicode: boolean);
var
  dir:string;
  i: integer;
  hdrsl, sl: TStringList;

begin
  sl := TStringList.Create;

  if AppendMode then
  try
    sl.LoadFromFile(FileName);
  except
  end;

  if Options.HTMLExport.HeaderFile <> '' then
  begin
    hdrsl := TStringList.Create;
    try
      hdrsl.LoadFromFile(Options.HTMLExport.HeaderFile);
      sl.AddStrings(hdrsl);
    finally
      hdrsl.Free;
    end;
  end;

  dir := ExtractFilePath(filename);

  for i := 1 to length(dir) do
  begin
    if dir[i] = '\' then
      dir[i] := '/';
  end;

  sl.Add(SaveToHTMLString(dir));

  if Options.HTMLExport.FooterFile <> '' then
  begin
    hdrsl := TStringList.Create;
    try
      hdrsl.LoadFromFile(Options.HTMLExport.FooterFile);
      sl.AddStrings(hdrsl);
    finally
      hdrsl.Free;
    end;
  end;

  try
    if Unicode then
      sl.SaveToFile(FileName, TEncoding.Unicode)
    else
      sl.SaveToFile(FileName, TEncoding.ASCII);
  finally
    sl.Free;
  end;

  if Options.HTMLExport.Show or Show then
  begin
    {$IFDEF DELPHI_LLVM}
    XOpenFile(FileName);
    {$ELSE}
    XOpenFile('open',FileName,'','');
    {$ENDIF}
  end;
end;

procedure TTMSFMXGridData.PasteFromClipboard;
var
  cliptext: string;
  Content,endofRow: PChar;
  cr: PChar;
  ct,line:string;
  s,z,tabpos,mr,mc: Integer;
  Allow: Boolean;
  nc: boolean;
  ClipService: IFMXClipboardService;
begin
  BlockUpdate := True;
  if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(ClipService)) then
    cliptext := ClipService.GetClipboard.AsString;

  s := Selection.StartCol;
  z := Selection.StartRow;

  if (s = -1) or (z = -1) then
    Exit;

  mr := z;
  mc := s;

  content := PChar(cliptext);

  EndOfRow := StrScan(content,#0);

  repeat
    cr := StrScan(Content,#13);
    if cr = nil then
      cr := EndOfRow;
    Line := Copy(StrPas(Content),1,cr - Content);

    if (Length(Line) > 0) and (Options.Clipboard.PasteAction = paInsert) then
    begin
      FInternalRowUpdate := True;
      InsertRows(z,1);
      FInternalRowUpdate := False;
    end;

    while (VarPos(#9,Line,TabPos) > 0) do
    begin
      ct := Copy(line,1,TabPos - 1);

      if (Pos(#10,ct) > 0) then
      begin
        if {FExcelClipboardFormat or} (1 > 0) then
        begin
          if Pos('"',ct)=1 then Delete(ct,1,1);
          if Pos('"',ct) = Length(ct) then
            Delete(ct,Length(ct),1);

          CSVToLineFeeds(ct);
        end;
      end;

      if (s <= ColumnCount) and (z <= RowCount) then
      begin
        if Options.Clipboard.IgnoreReadOnly or not IsReadOnly(s,z) then
        begin
          Allow := True;

          DoClipboardBeforePasteCell(s,z,ct,Allow);

          if Allow then
          begin
            Cells[s,z] := ct;

            if s > mc then
              mc := s;
            if z > mr then
              mr := z;

            DoClipboardAfterPasteCell(s,z,ct);
          end;
        end;
      end;

      Delete(line,1,tabpos);
      inc(s);
      if (s > ColumnCount) and Options.Clipboard.AllowColGrow then
        ColumnCount := s;
    end;

    nc := false;

    if (s <= ColumnCount) and (z <= RowCount) then
    begin
      if Options.Clipboard.IgnoreReadOnly or not IsReadOnly(s,z) then
      begin
        if (cr <> EndOfRow) or (Line <> '') then
        begin
          Allow := True;
          nc := true;

          if (Pos(#10,Line) > 0) then
          begin
            if Pos('"',Line)=1 then Delete(Line,1,1);
            if Pos('"',Line) = Length(Line) then
              Delete(Line,Length(Line),1);
            CSVToLineFeeds(Line);
          end;

          DoClipboardBeforePasteCell(s,z,Line,Allow);

          if Allow then
          begin
            if (s <= ColumnCount) and (z <= RowCount) then
            begin
              Cells[s,z] := Line;

              if s > mc then
                mc := s;
              if z > mr then
                mr := z;

              if (s < ColumnCount) and (z < RowCount) then
                DoClipboardAfterPasteCell(s,z,Line);
            end;
          end;
        end;
      end;
    end;

    if nc then
      Inc(s);

    if (s > ColumnCount) and Options.Clipboard.AllowColGrow then
      ColumnCount := s;

    Content := cr + 1;

    if Content^ = #10 then
    begin
      Content := cr + 2;
    end;

    s := Selection.StartCol;

    if (Content^ <> #0) then
      Inc(z);

    if (z = RowCount) and (cr <> EndOfRow) and (Content^ <> #0) and Options.Clipboard.AllowRowGrow then
      RowCount := RowCount + 1;

  until cr = EndOfRow;

//  if Assigned(UndoRedo) then
//    UndoRedo.StopSequence;

  DoClipboardPaste(CellRange(Selection.StartCol, Selection.StartRow, mc, mr));

  DoCellsChanged(CellRange(Selection.StartCol, Selection.StartRow, mc, mr));

  BlockUpdate := False;
  UpdateGridCells;
  if Options.Clipboard.AllowColGrow or Options.Clipboard.AllowRowGrow then
    UpdateGrid(true);
end;

function TTMSFMXGridData.PreviousSelectableColumn(Col, Row: Integer): TCell;
var
  c, r, rorig, corig: Integer;
begin
  c := Col;
  r := Row;
  corig := c;
  rorig := r;

  Result.Col := Col;
  Result.Row := Row;

  if (Result.Col - 1 >= FixedColumns) then
  begin
    Result.Col := Result.Col - 1;

    while IsNormalFixed(Result.Col, Result.Row) and (Result.Col > FixedColumns) do
      Result.Col := Result.Col - 1;
  end;

  if IsNormalFixed(Result.Col, Result.Row) then
  begin
    Result.Col := corig;
    Result.Row := rorig;
  end;
end;

function TTMSFMXGridData.PreviousSelectableRow(Col, Row: Integer): TCell;
var
  c, r, corig, rorig: Integer;
begin
  c := Col;
  r := Row;
  corig := c;
  rorig := r;

  Result.Col := Col;
  Result.Row := Row;

  if (Result.Row - 1 >= FixedRows) then
  begin
    Result.Row := Result.Row - 1;

    while IsNormalFixed(Result.Col, Result.Row) and (Result.Row > FixedRows) do
      Result.Row := Result.Row - 1;
  end;

  if IsNormalFixed(Result.Col, Result.Row) then
  begin
    Result.Col := corig;
    Result.Row := rorig;
  end;
end;

function TTMSFMXGridData.ColSpan(Col, Row: integer): integer;
var
  cp: TCellProperty;
begin
  Result := 0;

  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
    Result := cp.ColSpan;
end;

function TTMSFMXGridData.ColumnAtPosition(ACol: integer): integer;
begin
  Result := ACol;
  if FColumnOrder.Count = 0 then
  begin
    SetColumnOrder;
    if FColumnOrder.Count = 0 then
      Exit;
  end;

  Result := FColumnOrder.Items[ACol];

  if GroupColumn <> -1 then
  begin
    if Result >= GroupColumn then
      Result := Result + 1;
  end;
end;

function TTMSFMXGridData.ColumnAvg(ACol, FromRow, ToRow: Integer): Double;
begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  if (ToRow - FromRow + 1) > 0 then
    Result := ColumnSum(ACol,FromRow,ToRow)/(ToRow - FromRow + 1)
  else
    Result := 0;
end;

function TTMSFMXGridData.ColumnCustomCalc(ACol, FromRow,
  ToRow: Integer): Double;
begin
  Result := DoColumnCalc(ACol,FromRow, ToRow);
end;

function TTMSFMXGridData.ColumnCustomCalcGroup(ACol, FromRow,
  ToRow: Integer): Double;
begin
  Result := DoColumnCalcGroup(ACol,FromRow, ToRow);
end;


function TTMSFMXGridData.ColumnDistinct(ACol, FromRow, ToRow: Integer): Double;
var
  sl: TStringList;
  i: integer;
begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  ExportNotification(esExportStart, FromRow);

  sl := TStringList.Create;

  try
    sl.Duplicates := dupIgnore;
    sl.Sorted := true;
    sl.CaseSensitive := false;

    for i := FromRow to ToRow do
    begin
      ExportNotification(esExportNextRow, i);
      sl.Add(Cells[ACol,i]);
    end;
    Result := sl.Count;

  finally
    ExportNotification(esExportDone, -1);
    sl.Free;
  end;
end;

function TTMSFMXGridData.ColumnMax(ACol, FromRow, ToRow: Integer): Double;
var
  m: Double;
  i: Integer;

begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  m := AllFloats[ACol,fromRow];

  ExportNotification(esExportStart, FromRow);

  for i := FromRow to ToRow do
  begin
    if IsBaseCell(ACol, i) then
    begin
      ExportNotification(esExportNextRow, i);
      try
        if m < AllFloats[ACol,i] then
          m := AllFloats[ACol,i];
      except
      end;
    end;
  end;

  ExportNotification(esExportDone, -1);
  Result := m;
end;

function TTMSFMXGridData.ColumnMin(ACol, FromRow, ToRow: Integer): Double;
var
  m: Double;
  i: Integer;

begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  m := AllFloats[ACol,fromRow];

  ExportNotification(esExportStart, FromRow);

  for i := FromRow to ToRow do
  begin
    if IsBaseCell(ACol, i) then
    begin
      ExportNotification(esExportNextRow, i);
      try
        if m > AllFloats[ACol,i] then
          m := AllFloats[ACol,i];
      except
      end;
    end;
  end;

  ExportNotification(esExportDone, -1);
  Result := m;
end;

function TTMSFMXGridData.ColumnPosition(ACol: integer): integer;
var
  i: integer;
begin
  Result := ACol;

  if FColumnOrder.Count = 0 then
    Exit;

  for i := 1 to ColumnCount + HiddenColumnCount do
  begin
    if ACol = FColumnOrder.Items[i - 1] then
    begin
      Result := i - 1;
      break;
    end;
  end;

  if GroupColumn <> -1 then
  begin
    if ACol >= GroupColumn then
    begin
      Result := Result - 1;
    end;
  end;
end;

function TTMSFMXGridData.ColumnStatesToString: string;
var
  i,ci: integer;
  res: string;
begin
  res := inttostr(ColumnCount + HiddenColumnCount) + '#';

  for i := 0 to ColumnCount - 1 + HiddenColumnCount do
  begin
    ci := trunc(ColumnWidths[i]);
    if i = 0 then
      res := res + inttostr(ci)
    else
      res := res + ',' + inttostr(ci);
  end;

  res := res + '#';

  if FColumnOrder.Count = 0 then
  begin
    for i := 0 to ColumnCount - 1 + HiddenColumnCount do
    begin
      if i = 0 then
        res := res + IntToStr(i)
      else
        res := res + ',' + IntToStr(i);
    end;
  end
  else
    for i := 0 to FColumnOrder.Count - 1 do
    begin
      if i = 0 then
        res := res + IntToStr(FColumnOrder.Items[i])
      else
        res := res + ',' + IntToStr(FColumnOrder.Items[i]);
    end;

  res := res + '#';


  for i := 0 to ColumnCount - 1 + HiddenColumnCount do
  begin
    if i = 0 then
    begin
      if IsHiddenColumn(i) then
        res := res + '0'
      else
        res := res + '1';
    end
    else
    begin
      if IsHiddenColumn(i) then
        res := res + ',0'
      else
        res := res + ',1';
    end;
  end;

  Result := res;
end;

function TTMSFMXGridData.ColumnStdDev(ACol, FromRow, ToRow: Integer): Double;
var
  m,d: double;
  i: integer;
begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  m := ColumnAvg(ACol, FromRow, ToRow);

  ExportNotification(esExportStart, FromRow);

  d := 0;

  for i := fromRow to toRow do
  begin
    d := d + sqr(AllFloats[ACol,i] - m);
  end;

  Result := sqrt(d / (ToRow - FromRow + 1));

  ExportNotification(esExportDone, -1);
end;

function TTMSFMXGridData.ColumnSum(ACol, FromRow, ToRow: Integer): Double;
var
  i: Integer;
  sum: Double;
begin
  if (FromRow = -1) or (ToRow = -1) then
  begin
    FromRow := FixedRows;
    ToRow := RowCount - 1 - FixedFooterRows;
  end;

  sum := 0;

  ExportNotification(esExportStart, FromRow);

  for i := FromRow to ToRow do
  begin
    if IsBaseCell(ACol, i) then
    begin
      ExportNotification(esExportNextRow, i);
      try
        Sum := Sum + AllFloats[ACol,i]
      except
      end;
    end;
  end;

  ExportNotification(esExportDone, -1);
  Result := sum;
end;

function TTMSFMXGridData.CompareRows(Col, Row1, Row2: integer): integer;
var
  s1,s2: variant;
  SortFormat: TSortFormat;
  Prefix, Suffix: string;
  dt1,dt2: TDateTime;
  db1,db2: double;
  st1,st2: string;
  b1,b2: boolean;
  res,err: integer;
  t1,t2: TAutoType;
begin
  s1 := Cells[Col,Row1];
  s2 := Cells[Col,Row2];

  {$IFDEF DELPHIXE6_LVL}
  {$IFDEF DELPHI_LLVM}
  if (Options.Sorting.IgnoreCase) and (VarType(s1) = vtWideString) and (VarType(s2) = vtWideString) then
  {$ELSE}
  if (Options.Sorting.IgnoreCase) and (VarType(s1) = vtString) and (VarType(s2) = vtString) then
  {$ENDIF}
  {$ELSE}
  if (Options.Sorting.IgnoreCase) and (VarType(s1) = vtString) and (VarType(s2) = vtString) then
  {$ENDIF}
  begin
    s1 := Uppercase(s1);
    s2 := Uppercase(s2);
  end;

  DoSortFormat(Col, SortFormat, Prefix, Suffix);

  case SortFormat of
    ssAutomatic:
      begin
        t1 := isType(s1);
        t2 := isType(s2);

        if (t1 in [atNumeric, atFloat]) and (t2 in [atNumeric,atFloat]) then
        begin
          val(s1,db1,err);
          val(s2,db2,err);
          if db1 = db2 then
            res := 0
          else
            if db1 > db2 then
              res := 1
            else
              res := -1;

        end
        else
        begin
          if s1 = s2 then
            res := 0
          else
            if s1 > s2 then
              res := 1
            else
              res := -1;
        end;
      end;
    ssAlphabetic, ssHTML:
      begin
        st1 := s1;
        st2 := s2;

        if SortFormat = ssHTML then
        begin
          st1 := HTMLStrip(st1);
          st2 := HTMLStrip(st2);
        end;

        if st1 = st2 then
          res := 0
        else
          if st1 > st2 then
            res := 1
          else
            res := -1;
      end;
    ssAlphabeticNoCase:
      begin
        st1 := Uppercase(s1);
        st2 := Uppercase(s2);
        if st1 = st2 then
          res := 0
        else
          if st1 > st2 then
            res := 1
          else
            res := -1;
      end;
    ssNumeric:
      begin
        db1 := s1;
        db2 := s2;
        if db1 = db2 then
          res := 0
        else
          if db1 > db2 then
            res := 1
          else
            res := -1;
      end;
    ssDate:
      begin
        dt1 := VarToDateTime(s1);
        dt2 := VarToDateTime(s2);
        if dt1 = dt2 then
          res := 0
        else
          if dt1 > dt2 then
            res := 1
          else
            res := -1;
      end;
    ssCheckBox:
      begin
        b1 := CheckBoxState[Col,Row1];
        b2 := CheckBoxState[Col,Row2];
        if b1 and not b2 then
          res := 1
        else
          if b1 = b2 then
            res := 0
          else
            res := -1;
      end;
    ssCustom:
      begin
        DoCustomCompare(s1,s2,res);
      end;
    ssRaw:
      begin
        DoRawCompare(Col,Row1,Row2,res);
      end;
  end;

  if Options.Sorting.IgnoreBlanks then
  begin
    case FSortDirection of
    sdAscending:
      begin
        if (s1 = '') and (s2 <> '') then
          if Options.Sorting.BlankPosition = TTMSFMXGridSortBlankPosition.blFirst then
            res := -1
          else
            res := +1;

        if (s2 = '') and (s1 <> '') then
          if Options.Sorting.BlankPosition = TTMSFMXGridSortBlankPosition.blFirst then
            res := +1
          else
            res := -1;

      end;
    sdDescending:
      begin
        if (s1 = '') and (s2 <> '') then
          if Options.Sorting.BlankPosition = TTMSFMXGridSortBlankPosition.blFirst then
            res := +1
          else
            res := -1;

        if (s2 = '') and (s1 <> '') then
          if Options.Sorting.BlankPosition = TTMSFMXGridSortBlankPosition.blFirst then
            res := -1
          else
            res := +1;

      end;
    end;
  end;

  Result := res;
end;

function TTMSFMXGridData.CompareLine(Col, Row1, Row2: Integer): Integer;
var
  res: Integer;
begin
  if IsIgnoredColumn(Col) then
    res := 0
  else
    res := CompareRows(Col, Row1, Row2);

  if (res = 0) and Options.Sorting.MultiColumn then
  begin
    if Col <= ColumnCount - 2 then
    begin
      Inc(Col);
      res := CompareLine(Col, Row1, Row2);
    end;
  end;

  CompareLine := res;
end;


function TTMSFMXGridData.CompareRowsIndexed(Col, Row1, Row2: integer): integer;
var
  res: Integer;
  idx: Integer;
  sd: TSortDirection;
begin
  idx := FSortIndexes.Items[Col] and $7FFFFFFF;

  if (FSortIndexes.Items[Col] and $80000000 = $80000000) then
    sd := sdDescending
  else
    sd := sdAscending;

  if IsIgnoredColumn(idx) then
    res := 0
  else
    res := CompareRows(idx,Row1,Row2);

  if (res = 0) and Options.Sorting.MultiColumn then
  begin
    if (Col < FSortIndexes.Count - 1) then
    begin
      Inc(Col);
      res := CompareRowsIndexed(Col, Row1, Row2);
    end;
  end
  else
  begin
    if sd = sdDescending then
    begin
      res := -res;
    end;
  end;

  CompareRowsIndexed := res;
end;

procedure TTMSFMXGridData.CopyToClipboard(SelectedCells: boolean);
var
  s,z: Integer;
  ct, cliptext: string;
  ACellRange: TCellRange;
  ClipService: IFMXClipboardService;
begin
  if SelectedCells then
    ACellRange := Selection
  else
    ACellRange := CellRange(0,0,ColumnCount - 1, RowCount - 1);

  if (ACellRange.StartRow = -1) or (ACellRange.StartCol = -1) or (ACellRange.EndRow = -1) or (ACellRange.EndCol = -1) then
    Exit;

  cliptext := '';

  for z := ACellRange.StartRow to ACellRange.EndRow do
  begin
    for s := ACellRange.StartCol to ACellRange.EndCol do
    begin
      ct := Cells[s,z];

      if (Pos(#13,ct) > 0) then
      begin
        ct := '"' + CRToLF(ct) + '"';
      end;

      if {not FNavigation.CopyHTMLTagsToClipboard and} (Pos('</',ct) > 0) then
        ct := HTMLStrip(ct);

      if (LinesInText(ct,true)>1) {and FExcelClipboardFormat} then
        LineFeedsToCSV(ct);

      if s < ACellRange.EndCol then
        cliptext := cliptext + ct + #9
      else
        cliptext := cliptext + ct;
    end;

    if z < ACellRange.EndRow then
      cliptext := cliptext + #13#10;
  end;

  if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(ClipService)) then
    ClipService.SetClipboard(cliptext);
end;

procedure TTMSFMXGridData.QuickSortRows(Col, Left, Right: Integer);
var
  i,j,k,m: Integer;

begin
  if FSortDirection = sdAscending then
    FSortDir := 1
  else
    FSortDir := -1;

  i := Left;
  j := Right;
  m := (Left + Right) shr 1;

  for k := 0 to ColumnCount - 1 do
  begin
    Cells[k, RowCount - 1] := Cells[k, m];
    Objects[k, RowCount - 1] := Objects[k, m];
  end;

  repeat
    while (CompareRows(Col,RowCount - 1,i) = FSortDir) and (i < Right) do Inc(i);
    while (CompareRows(Col,j,RowCount - 1) = FSortDir) and (j > Left) do Dec(j);

    if i <= j then
    begin
      if i <> j then
      begin
        if CompareRows(Col,i,j) <> 0 then
        begin
          case Options.Sorting.Columns of
          scAll: SwapRows(i,j);
          scSingle: SwapCells(Col,i,j);
          scNormal: SwapNormalCells(i,j);
          end;
        end;
      end;
      Inc(i);
      Dec(j);
    end;
  until i > j;

  if Left < j then
    QuickSortRows(Col,Left,j);
  if i < Right then
    QuickSortRows(Col,i,Right);
end;


procedure TTMSFMXGridData.QuickSortRowsIndexed(Col, Left, Right: Integer);
var
  i,j,k,m: Integer;

begin
  if FSortDirection = sdAscending then
    FSortDir := 1
  else
    FSortDir := -1;

  i := Left;
  j := Right;
  m := (Left + Right) shr 1;

  for k := 0 to ColumnCount - 1 do
    Cells[k, RowCount - 1] := Cells[k, m];

  repeat
    while (CompareRowsIndexed(Col,RowCount - 1,i) = FSortDir) and (i < Right) do Inc(i);
    while (CompareRowsIndexed(Col,j,RowCount - 1) = FSortDir) and (j > Left) do Dec(j);

    if i <= j then
    begin
      if i <> j then
      begin
        if CompareRowsIndexed(Col,i,j) <> 0 then
        begin
          case Options.Sorting.Columns of
            scAll: SwapRows(i,j);
            scNormal: SwapNormalCells(i,j);
            scSingle: SwapCells(Col,i,j);
          end;
        end;
      end;
      Inc(i);
      Dec(j);
    end;
  until i > j;

  if Left < j then
    QuickSortRowsIndexed(Col,Left,j);
  if i < Right then
    QuickSortRowsIndexed(Col,i,Right);
end;

function TTMSFMXGridData.Replace(OrigStr, NewStr: string;
  FindParams: TFindParams): integer;
var
  cp: TPoint;
  i: integer;
begin
  i := 0;

  cp := FindFirst(OrigStr, FindParams);

  while (cp.X <> -1) do
  begin
    Cells[cp.X, cp.Y] := NewStr;
    inc(i);
    cp := FindNext;
  end;

  Result := i;
end;

procedure TTMSFMXGridData.ResetColumnOrder;
var
  i: Integer;
  rst: Boolean;
begin
  UnHideColumnsAll;
  if FColumnOrder.Count = 0 then
  begin
    SetColumnOrder;
    Exit;
  end;

  rst := False;
  while not rst do
  begin
    rst := True;
    for i := 1 to ColumnCount do
    begin
      if i - 1 > FColumnOrder.Items[i - 1] then
      begin
        rst := False;
        MoveColumn(i - 1,FColumnOrder.Items[i - 1]);
      end;
    end;
  end;
end;

procedure TTMSFMXGridData.ResetUpdate;
begin
  //
end;

function TTMSFMXGridData.RowSpan(Col, Row: integer): integer;
var
  cp: TCellProperty;
begin
  Result := 0;
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
    Result := cp.RowSpan;
end;

function TTMSFMXGridData.CellSpan(Col, Row: integer): TPoint;
var
  cp: TCellProperty;
begin
  Result := Point(0,0);
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and (cp.BaseCol <> -1) and (cp.BaseRow <> -1) then
  begin
    Result := Point(cp.ColSpan, cp.RowSpan);
  end;
end;


procedure TTMSFMXGridData.SaveToASCII(FileName: string; Unicode: boolean);
begin
  OutputToASCII(FileName, false, Unicode);
end;

procedure TTMSFMXGridData.SaveToCSV(FileName: string; Unicode: boolean);
begin
  OutputToCSV(FileName,False,Unicode);
end;

procedure TTMSFMXGridData.SaveToFile(FileName: string; Unicode: boolean);
var
  i,j,n: Integer;
  ss,CellText: string;
  sl: TStringList;
begin
  sl := TStringList.Create;

  if FSaveHiddenCells then
    n := HiddenColumnCount
  else
    n := 0;

  ss := IntToStr(ColumnCount+n) + ',' + IntToStr(RowCount);

  sl.Add(ss);

  for i := FIOOffset.X to ColumnCount - 1 + n do
  begin
    ss := 'cw '+IntToStr(i) + ',' + floattostr(ColumnWidths[i]);
    sl.Add(ss);
  end;

  ExportNotification(esExportStart, FIOOffset.Y);

  for i := FIOOffset.Y to RowCount - 1 do
  begin
    ExportNotification(esExportNextRow, i);
    for j := FIOOffset.X to ColumnCount + n - 1 do
    begin
      CellText := DoSaveCell(j,i);

      if CellText <> '' then
      begin
        ss := IntToStr(j) + ',' + IntToStr(i) + ',' + LFToFile(CellText);
        sl.Add(ss);
      end;
    end;

    DoIOProgress(i, RowCount - 1);
  end;

  ExportNotification(esExportDone, -1);

  if Unicode then
    sl.SaveToFile(FileName, TEncoding.Unicode)
  else
    sl.SaveToFile(FileName);

  sl.Free;
end;

procedure TTMSFMXGridData.SaveToFixed(FileName: string; positions: TIntList);
var
  f: TextFile;
  c,r,m,n,nh,rc,CC: Integer;
  s,su: string;

begin
  Assignfile(f,FileName);
  {$i-}
  ReWrite(f);
  {$i+}
  if IOResult <> 0 then
    raise Exception.Create('Cannot Create file '+FileName);

  cc := 0;

  if SaveHiddenCells then
    nh := HiddenColumnCount
  else
    nh := 0;

  CC := CC + nh;

//  for c := FIOOffset.X to CC - 1 do
//    if Positions.Count - 1 < c - FIOOffset.X then Positions.Add(MaxCharsInCol(c) + 1);

  ExportNotification(esExportStart, FIOOffset.Y);

  for r := FIOOffset.Y to RowCount - 1 do
  begin
    ExportNotification(esExportNextRow, r);

    s := '';
    for c := FIOOffset.X to CC - 1 do
    begin
      //if SaveHiddenCells then
        rc := c;
      //else
      //  rc := RemapCol(c);

      su := DoSaveCell(rc,r);
      n := Length(su);

      if n > Positions.Items[c - FIOOffset.X] then
        su := Copy(su,1,Positions.Items[c - FIOOffset.X])
      else
        for m := 1 to Positions.Items[c - FIOOffset.X] - n do
          su := su + ' ';

      s := s + su;
    end;
    WriteLn(f,s);

    DoIOProgress(r, RowCount - 1);
  end;

  ExportNotification(esExportDone, -1);

  CloseFile(f);
  //ColCount := ColCount - nh;
end;

procedure TTMSFMXGridData.SaveToHTML(FileName: string; Show, Unicode: boolean);
begin
  OutputToHTML(Filename,false, Show, Unicode);
end;

function TTMSFMXGridData.SaveToHTMLString(dir: string): string;
var
  i,j,mc,mr: Integer;
  s,al,ac,av,afs,afe,ass,ase,tablestyle: string;
  VAlignValue: TTextAlign;
  slist: TStringlist;
  AlignValue: TTextAlign;
  FirstRow: Integer;
  strtCol,strtRow: Integer;
  wraptxt,colwtxt,CellText,SpanTxt: string;
  DoneColW: Boolean;
  Span:TPoint;
  VAlign: TTextAlign;
  cssval: string;
  cssidx: integer;
  CSSList: TStringList;
  res: string;
  Pic: TBitmap;
  images_counter:integer;
  htmlhdr: string;
  summ: string;
  l: TTMSFMXGridCellLayout;
  cellclasstype,prevclasstype: TFmxObjectClass;
  cellstr: string;
  cell: TFMXObject;

  function HTMLAddImage(Pic: TBitmap;counter:integer;dir:string):string;
  var
    fname:string;
  begin
    if not DirectoryExists(dir + 'images') then
      if not CreateDir(dir + 'images') then
        raise Exception.Create('Cannot create ' + dir + 'images');

    fname := dir + 'images/img' + inttostr(counter) + '.png';
    Pic.SaveToFile(fname);
    Result := fname;
  end;

  function MakeHREF(s:string):string;
  begin
   Result := s;

   if not Options.URL.Show then
     Exit;

   if (pos('://',s) > 0) and (pos('</',s) = 0) then
   begin
     if not Options.URL.Full then
       Result := '<a href=' + s + '>' + copy(s,pos('://',s)+3,255) + '</a>'
     else
       Result := '<a href=' + s + '>' + s + '</a>';
    end;

   if (pos('mailto:',s) > 0) and (pos('</',s) = 0) then
    begin
     if not Options.URL.Full then
        Result := '<a href=' + s + '>' + copy(s,pos('mailto:',s) + 7,255)+'</a>'
      else
        Result := '<a href=' + s + '>' + s + '</a>';
    end;
  end;

  procedure AppendStr(var s: string; newstr: string);
  begin
    if s = '' then
      s := newstr
    else
      s := s + ';' + newstr;
  end;

  function CSS(AFont: TFont; AColor, AFontColor: TAlphaColor; AHAlign, AVAlign: TTextAlign): string;
  var
    res: string;
  begin
    res := '';

    if (AColor <> $7fffffff) and Options.HTMLExport.SaveColors then
      AppendStr(res,'background-color: #'+ HTMLColor(AColor)+'');

    if (AFontColor <> $7fffffff) and Options.HTMLExport.SaveColors then
      AppendStr(res,'color: #'+ HTMLColor(AFontColor)+'');

    AppendStr(res,'font-size:'+IntTostr(Round(AFont.Size))+'pt');

    if (TFontStyle.fsItalic in AFont.Style) then
      AppendStr(res,'font-style: italic');

    if (TFontStyle.fsBold in AFont.Style) then
      AppendStr(res,'font-weight: bold');

    AppendStr(res,'font-family: '+AFont.Family+'');

    {$IFDEF DELPHIXE6_LVL}
    case AHAlign of
    TTextAlign.Center: AppendStr(res,'text-align: center');
    TTextAlign.Trailing: AppendStr(res,'text-align: right');
    end;

    case VAlign of
    TTextAlign.Leading: AppendStr(res,'vertical-align: top');
    TTextAlign.Center: AppendStr(res,'vertical-align: middle');
    TTextAlign.Trailing: AppendStr(res,'vertical-align: bottom');
    end;
    {$ELSE}
    case AHAlign of
    TTextAlign.taCenter: AppendStr(res,'text-align: center');
    TTextAlign.taTrailing: AppendStr(res,'text-align: right');
    end;

    case VAlign of
    TTextAlign.taLeading: AppendStr(res,'vertical-align: top');
    TTextAlign.taCenter: AppendStr(res,'vertical-align: middle');
    TTextAlign.taTrailing: AppendStr(res,'vertical-align: bottom');
    end;
    {$ENDIF}

    Result := res;
  end;

  function ExportImg(ACol,ARow: integer; lst: TStringList; celltext: string; Pic: TBitmap): string;
  var
    image: string;
  begin
    if Assigned(Pic) and not Pic.IsEmpty then
    begin
      image := '<img src= "file://' + HTMLAddImage(Pic,images_counter,dir)+'">';
      lst.Add('<TD class="c'+inttostr(cssidx)+'"'+spantxt + wraptxt + colwtxt+'>' + image + s +'</td>');
      inc(images_counter);
    end
    else
      lst.Add('<TD class="c'+inttostr(cssidx)+'"'+spantxt + wraptxt + colwtxt+'>' +s+'</td>');
  end;

  function GetCellContainer: TControl;
  var
    obj: TFmxObject;
  begin
    Result := nil;
    obj := FindStyleResource('gridcells');
    if Assigned(obj) and (obj is TControl) then
      Result := obj as TControl;
  end;

begin
  StrtCol := IOOffset.X;
  StrtRow := IOOffset.Y;

  SList := TStringlist.Create;
  SList.Sorted := False;

  CSSList := TStringList.Create;

  DoneColW := False;

  htmlhdr := '';

  cell := nil;
  cellclasstype := nil;
  prevclasstype := nil;

  l := TTMSFMXGridCellLayout.Create(nil);

  try

    if Options.HTMLExport.XHTML then
    begin
      htmlhdr := htmlhdr + '<?xml version="1.0" encoding="utf-8"?>'#13#10
      +'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'#13#10
      +'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'#13#10
      +'<HTML xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">'#13#10;
    end;

    if Options.HTMLExport.PrefixTag <> '' then
      htmlhdr := htmlhdr + Options.HTMLExport.PrefixTag;

    if Options.HTMLExport.TableStyle <> '' then
      TableStyle := ' ' + Options.HTMLExport.TableStyle + ' '
    else
      TableStyle := '';

    if Options.HTMLExport.Summary <> '' then
      summ := 'SUMMARY="' + Options.HTMLExport.Summary + '" '
    else
      summ := '';

    SList.Add('<TABLE BORDER="'+IntToStr(Options.HTMLExport.BorderSize)+'" '+
        'CELLSPACING="'+IntToStr(Options.HTMLExport.CellSpacing)+'" ' +
        'CELLPADDING="'+IntToStr(Options.HTMLExport.CellPadding)+'" ' +
        summ +
        TableStyle +
        ' WIDTH="' + IntToStr(Options.HTMLExport.Width) + '%">');  // begin the table

    FirstRow := StrtRow;

    if (FixedRows > 0) and (IOOffset.Y < FixedRows) then
    begin
      SList.Add('<TR>');
      DoneColW := True;

      for i := StrtCol to ColumnCount - 1 do
      begin
        if IsBaseCellEx(i,0,mc,mr) then
        begin
          Span := CellSpan(i,0);

          SpanTxt := '';

          if Span.X > 0 then
            SpanTxt := ' COLSPAN="' + IntToStr(Span.X) + '"';

          if Span.Y > 0 then
            SpanTxt := SpanTxt + ' ROWSPAN="' + IntToStr(Span.Y) + '"';

          CellText := DoSaveCell(i,0);

          s := HTMLLineBreaks(CellText);
          s := MakeHREF(s);

          //if s = '' then
          //  s := '<BR>';
          if s = '' then
            s := '&nbsp;';

          if Options.HTMLExport.ColumnWidths.Count > i then
            colwtxt := ' WIDTH=' + IntToStr(Options.HTMLExport.ColumnWidths.Items[i])
          else
            colwtxt := '';

          DoGetCellClass(i, 0, cellclasstype);
          DoGetCellData(i, 0, cellstr);

          if (prevclasstype <> cellclasstype) and (prevclasstype <> nil) and Assigned(cell) then
          begin
            GetCellContainer.RemoveObject(cell);
            cell.Free;
          end;

          if (prevclasstype <> cellclasstype) then
          begin
            cell := cellclasstype.Create(Self);
            GetCellContainer.AddObject(cell);

            if (cell is TTMSFMXGridCell) then
            begin
              (cell as TTMSFMXGridCell).Layout.Font.OnChanged := nil;
              (cell as TTMSFMXGridCell).HitTest := False;
              (cell as TTMSFMXGridCell).BitmapContainer := BitmapContainer;
            end;
            prevclasstype := cellclasstype;
          end;

          if (Cell is TTMSFMXGridCell) then
          begin
            l.Assign((Cell as TTMSFMXGridCell).Layout);
            l.Assign(GetDefaultNormalLayout.Layout);

            DoGetCellLayout(i, 0, l, TCellState.csNormal);
            (Cell as TTMSFMXGridCell).AssignLayout(l);
          end;

          DoGetCellAppearance(i, 0, cell, TCellState.csNormal);
          DoGetCellProperties(i, 0, cell);

          //if WW then
            WrapTxt := '';
          //else
          //  WrapTxt := ' nowrap';

          cssval := css(l.Font, l.Fill.Color, l.FontFill.Color, l.TextAlign, l.VerticalTextAlign);

          cssidx := CSSList.IndexOf(cssval);
          if cssidx = -1 then
            cssidx := CSSList.Add(cssval);

          ac := ' bgcolor="#' + HTMLColor(l.Fill.Color) + '"';

          //Add('<td' + wraptxt + al + ac + av + colwtxt + spantxt + '>' + ass + afs + s + afe + ase + '</td>');

        if Options.HTMLExport.ExportImages then
          Pic := GetBitmap(i,0)
        else
          Pic := nil;

        ExportImg(i, 0, SList, celltext, pic);

        end;
      end;
      SList.Add('</TR>');
      FirstRow := 1;
    end;

    ExportNotification(esExportStart, FirstRow);

    images_counter := 1;

    for i := FirstRow to RowCount - 1 do
    begin
      ExportNotification(esExportNextRow, i);
      SList.Add('<TR>');

      for j := StrtCol to ColumnCount - 1 do
      begin
        if IsBaseCellEx(j,i,mc,mr) then
        begin
          Span := CellSpan(j,i);

          SpanTxt := '';

          if Span.X > 0 then
            SpanTxt := ' COLSPAN="' + IntToStr(Span.X) + '"';

          if Span.Y > 0 then
            SpanTxt := SpanTxt + ' ROWSPAN="' + IntToStr(Span.Y) + '"';

          CellText := DoSaveCell(j,i);
          s := CellText;

          if Options.HTMLExport.ConvertSpecialChars then
            s := FixMarkup(s);

          if Options.HTMLExport.NonBreakingText then
            s := FixNonBreaking(s);

          s := HTMLLineBreaks(s);
          s := MakeHREF(s);

          al := '';
          ac := '';
          afs := '';
          afe := '';
          ass := '';
          ase := '';


          DoGetCellClass(j, i, cellclasstype);
          DoGetCellData(j, i, cellstr);

          if (prevclasstype <> cellclasstype) and (prevclasstype <> nil) and Assigned(cell) then
          begin
            GetCellContainer.RemoveObject(cell);
            cell.Free;
          end;

          if (prevclasstype <> cellclasstype) then
          begin
            cell := cellclasstype.Create(Self);
            GetCellContainer.AddObject(cell);

            if (cell is TTMSFMXGridCell) then
            begin
              (cell as TTMSFMXGridCell).Layout.Font.OnChanged := nil;
              (cell as TTMSFMXGridCell).HitTest := False;
              (cell as TTMSFMXGridCell).BitmapContainer := BitmapContainer;
            end;
            prevclasstype := cellclasstype;
          end;

          if (Cell is TTMSFMXGridCell) then
          begin
            l.Assign((Cell as TTMSFMXGridCell).Layout);
            l.Assign(GetDefaultNormalLayout.Layout);

            DoGetCellLayout(j, i, l, TCellState.csNormal);
            (Cell as TTMSFMXGridCell).AssignLayout(l);
          end;

          DoGetCellAppearance(j, i, cell, TCellState.csNormal);
          DoGetCellProperties(j, i, cell);

          AlignValue := l.TextAlign;

          {$IFDEF DELPHIXE6_LVL}
          if (AlignValue <> TTextAlign.Leading) and (s <> '') then
          begin
            if AlignValue = TTextAlign.Trailing then
              al := ' align="right"';

            if AlignValue = TTextAlign.Center then
              al := ' align="center"';
          end;

          VAlignValue := l.VerticalTextAlign;
          if (s <> '') and (VAlignValue <> TTextAlign.Center) then
          begin
            if VAlignValue = TTextAlign.Leading then
              av := ' valign="top"';
            if VAlignValue = TTextAlign.Trailing then
              av := ' valign="bottom"';
          end;
          {$ELSE}
          if (AlignValue <> TTextAlign.taLeading) and (s <> '') then
          begin
            if AlignValue = TTextAlign.taTrailing then
              al := ' align="right"';

            if AlignValue = TTextAlign.taCenter then
              al := ' align="center"';
          end;

          VAlignValue := l.VerticalTextAlign;
          if (s <> '') and (VAlignValue <> TTextAlign.taCenter) then
          begin
            if VAlignValue = TTextAlign.taLeading then
              av := ' valign="top"';
            if VAlignValue = TTextAlign.taTrailing then
              av := ' valign="bottom"';
          end;
          {$ENDIF}


          if (i < FixedRows) or (j < FixedColumns) then
          begin
            ac := ' bgcolor="#' + HTMLColor(l.Fill.Color) + '"';
          end;

          cssval := css(l.Font, l.Fill.Color, l.FontFill.Color, l.TextAlign, l.VerticalTextAlign);

          cssidx := CSSList.IndexOf(cssval);
          if cssidx = -1 then
            cssidx := CSSList.Add(cssval);

          if (Options.HTMLExport.ColumnWidths.Count > j - strtcol) and not DoneColW then
            colwtxt := ' width=' + IntToStr(Options.HTMLExport.ColumnWidths.Items[j - strtcol ])
          else
            colwtxt := '';

          //if Trim(s) = '' then
          //  if HTMLSettings.XHTML then
          //    s := '<BR/>'
          //  else
          //    s := '<BR>';

          if Trim(s) = '' then
            s := '&nbsp;';

          //if WW then
            WrapTxt := '';
          //else
          //  WrapTxt := ' nowrap';

          //Export Images
          if Options.HTMLExport.ExportImages then
            Pic := GetBitmap(j,i)
          else
            Pic := nil;
          ExportImg(j,i,SList, celltext, Pic);
        end;
      end;

      SList.Add('</TR>');
    end;

    ExportNotification(esExportDone, -1);

    SList.Add('</TABLE>');

    // render suffix
    if Options.HTMLExport.SuffixTag <> '' then
      SList.Add(Options.HTMLExport.SuffixTag);

    if Options.HTMLExport.XHTML then
      SList.Add('</HTML>');

    // render CSS
    res := '<style>'#13#10;
    for cssidx := 0 to CSSList.Count - 1 do
      res := res + '.c' + IntToStr(cssidx)+' {'+csslist[cssidx]+'}'#13#10;
    res := res + '</style>'#13#10;


    Result := htmlhdr + res + SList.Text;

  finally
    if Assigned(cell) then
      cell.Free;
    l.Free;
    SList.Free;
    CSSList.Free;
  end;
end;

procedure TTMSFMXGridData.SaveToStream(Stream: TStream);
var
  ss,CellText: string;
  i,j: Integer;

  procedure Writestring(s:string);
  var
    buf:PChar;
    c: array[0..1] of char;
    l,len: integer;
  begin
    l := length(s) * 2;
    len := l + 2;
    GetMem(buf,len);
    System.Move(s[1],buf^,l);
    Stream.Writebuffer(buf^,l);
    c[0] := #13;
    c[1] := #10;
    Stream.Writebuffer(c,4);
    FreeMem(buf);
  end;

begin
  ss := IntToStr(ColumnCount - FIOOffset.X) + ',' + IntToStr(RowCount - FIOOffset.Y);
  WriteString(ss);

  //save column Widths
  for i := 1 to ColumnCount do
    WriteString('cw '+IntToStr(i-1) + ',' + IntToStr(Round(ColumnWidths[i - 1])));

  ExportNotification(esExportStart, FIOOffset.Y);

  //save cell contents
  for i := FIOOffset.Y to RowCount - 1 do
  begin
    ExportNotification(esExportNextRow, i);
    for j := FIOOffset.X to ColumnCount - 1 do
    begin
      CellText := DoSaveCell(j,i);

      if CellText <> '' then
      begin
        ss := IntToStr(j - FIOOffset.X) + ',' + IntToStr(i - FIOOffset.Y) + ',' + LFToFile(CellText);
        Writestring(ss);
      end;
    end;

    DoIOProgress(i, RowCount - 1);
  end;

  ExportNotification(esExportDone, -1);
end;

procedure TTMSFMXGridData.SaveToXML(FileName, ListDescr, RecordDescr: string;
  FieldDescr: TStrings; ExportEmptyCells: boolean);
var
  i,j: Integer;
  f: TextFile;
  s: string;
  cr, n: Integer;
  StrtCol,StrtRow: integer;

begin
  Assignfile(f,filename);
  {$i-}
  Rewrite(f);
  {$i+}
  if IOResult <> 0 then
    raise Exception.Create('Cannot Create file '+FileName);

  writeln(f,'<?xml version="1.0" encoding="'+ Options.IO.XMLEncoding +'" ?>');
  writeln(f,'<' + ListDescr + '>');

  StrtCol := IOOffset.X;
  StrtRow := IOOffset.Y;

  ExportNotification(esExportStart, StrtRow);

  if FSaveHiddenCells then
    n := HiddenColumnCount
  else
    n := 0;

  for i := StrtRow to RowCount - 1 do
  begin
    ExportNotification(esExportNextRow, i);

    writeln(f,'<'+RecordDescr+'>');
    for j := StrtCol to ColumnCount - 1 + n do
    begin
      cr := DisplToRealColumn(j);

      if IsBaseCell(cr,i) then
      begin
        s := DoSaveCell(j,i);

        if (s <> '') or ExportEmptyCells then
        begin
          if Options.IO.QuoteEmptyCells and (s = '') then
          begin
            s := '""';
          end;

          if Assigned(FieldDescr) and (j - StrtCol < FieldDescr.Count) then
            write(f,'<' + FieldDescr.Strings[j - StrtCol]+'>')
          else
            write(f,'<FIELD' + IntToStr(j - StrtCol)+'>');

          s := StringReplace(s,'&','$amp;',[rfReplaceAll]);
          s := StringReplace(s,'>','&gt;',[rfReplaceAll]);
          s := StringReplace(s,'<','&lt;',[rfReplaceAll]);
          s := StringReplace(s,'"','&quot;',[rfReplaceAll]);

          write(f,s);

          if Assigned(FieldDescr) and (j - StrtCol< FieldDescr.Count) then
            writeln(f,'</' + FieldDescr.Strings[j - StrtCol]+'>')
          else
            writeln(f,'</FIELD' + IntToStr(j - StrtCol)+'>');
        end;
      end;
    end;
    writeln(f,'</' + RecordDescr + '>');

    DoIOProgress(i, RowCount - 1 - StrtRow);
  end;

  writeln(f,'</' + ListDescr + '>');

  ExportNotification(esExportDone, -1);
  CloseFile(f);
end;

procedure TTMSFMXGridData.SelectCell(Cell: TCell; Shift: TShiftState;
  MouseDragging: Boolean);
begin
//
end;

procedure TTMSFMXGridData.SelectCells(ARange: TCellRange);
begin
  SelectCell(Cell(Arange.StartCol, ARange.StartRow));
  SelectCell(Cell(ARange.EndCol, ARange.EndRow), [ssShift, ssCtrl]);
end;

procedure TTMSFMXGridData.SelectColumns(FromColumn, ToColumn: integer);
begin
  SelectCell(Cell(FromColumn, 0));
  SelectCell(Cell(ToColumn, 0), [ssCtrl, ssShift]);
end;

procedure TTMSFMXGridData.SetHorzAlignments(Col, Row: integer;
  const Value: TTextAlign);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.AlignHorz := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.AlignHorz := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SetAllCells(Col, Row: integer; const Value: string);
var
  sl: TRowInfo;
begin
  while (FRowList.Count <= Row) do
  begin
    FRowList.Add(nil);
  end;

  while (FRowDisplayList.Count + FHiddenRowList.Count <= Row) do
  begin
    FRowDisplayList.Add(FRowDisplayList.Count);
  end;

  while (FColumnDisplayList.Count + FHiddenColumnList.Count <= Col) do
  begin
    FColumnDisplayList.Add(FColumnDisplayList.Count);
    ColumnCount := Col + 1;
  end;

  if Assigned(FRowList.Items[Row]) then
  begin
    sl := TRowInfo(FRowList.Items[Row]);
  end
  else
  begin
    sl := TRowInfo.Create;
    FRowList.Items[Row] := sl;
  end;

  while sl.Data.Count <= Col do
    sl.Data.AddObject('',nil);

  sl.Data.Values[col] := Value;
end;

procedure TTMSFMXGridData.SetAllFloats(Col, Row: integer; const Value: double);
begin
  AllCells[Col,Row] := Format(FloatFormat,[Value]);
end;

procedure TTMSFMXGridData.SetAngles(Col, Row: integer; const Value: single);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.Angle := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.Angle := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.EnsureRow(Row: integer);
begin
  while (FRowList.Count <= Row) do
  begin
    FRowList.Add(nil);
  end;

  while (FRowDisplayList.Count <= Row) do
  begin
    FRowDisplayList.Add(FRowDisplayList.Count);
  end;
end;

procedure TTMSFMXGridData.ExportNotification(AState: TGridExportState;
  ARow: Integer);
begin
  if AState = esExportStart then
    FPrevProgress := -1;
end;

procedure TTMSFMXGridData.EnsureCol(Col: integer);
begin
  while (FColumnDisplayList.Count <= Col) do
  begin
    FColumnDisplayList.Add(FColumnDisplayList.Count);
    ColumnCount := Col + 1;
  end;
end;

procedure TTMSFMXGridData.SetCells(Col, Row: integer; const Value: TCellData);
var
  sl: TRowInfo;
  RDisp,CDisp: integer;
begin
  EnsureRow(Row);
  EnsureCol(Col);

  RDisp := integer(FRowDisplayList.Items[Row]);
  CDisp := integer(FColumnDisplayList.Items[Col]);

  if Assigned(FRowList.Items[RDisp]) then
  begin
    sl := TRowInfo(FRowList.Items[RDisp]);
  end
  else
  begin
    sl := TRowInfo.Create;
    FRowList.Items[RDisp] := sl;
  end;

  while sl.Data.Count <= CDisp do
    sl.Data.AddObject('',nil);

  {$IFDEF VARIANTLIST}
  sl.Data.Values[CDisp] := Value;
  {$ELSE}
  sl.Data.Strings[CDisp] := Value;
  {$ENDIF}
  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetCellSelect(Col, Row: integer;
  const Value: boolean);
var
  idx: integer;
  c: TCell;
begin
  c := Cell(Col,Row);
  idx := FSelectedCells.Find(c);

  if (idx = -1) and Value then
    FSelectedCells.AddCell(c);

  if (idx <> -1) and not Value then
    FSelectedCells.DeleteCell(idx);

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetCheckBoxState(Col, Row: integer;
  const Value: boolean);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellCheckBox) then
  begin
   (cp.Control as TCellCheckBox).Checked := Value;
  end;
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellDataCheckBox) then
  begin
    if Value then
      Cells[Col,Row] := CheckTrue
    else
      Cells[Col,Row] := CheckFalse;
  end;
end;

procedure TTMSFMXGridData.AddBitmap(Col,Row: integer; AName: string);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmapName.Create;
  (cp.Control as TCellBitmapName).BitmapName := AName;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddDataBitmap(Col,Row: integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmapName.Create;
  (cp.Control as TCellBitmapName).Data := true;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;


function TTMSFMXGridData.CreateBitmap(Col,Row: integer): TBitmap;
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmap.Create(Self);
  Result := (cp.Control as TCellBitmap).Bitmap;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

function TTMSFMXGridData.CreateCheck(Col, Row: Integer): Boolean;
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellCheckBox.Create;
  Result := (cp.Control as TCellCheckBox).Checked;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddBitmap(Col, Row: Integer; ABitmap: TBitmap);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmap.Create(Self);
  (cp.Control as TCellBitmap).Bitmap.Assign(ABitmap);
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddBitmapFile(Col, Row: Integer; AFileName: string);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmap.Create(Self);
  (cp.Control as TCellBitmap).Bitmap.LoadFromFile(AFileName);
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;


procedure TTMSFMXGridData.RemoveBitmap(Col,Row: integer);
begin
  RemoveCellControl(Col,Row,TCellBitmapName);
  RemoveCellControl(Col,Row,TCellBitmap);
end;

procedure TTMSFMXGridData.SetBitmapName(Col,Row: integer; AName: string);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellBitmapName) then
  begin
    (cp.Control as TCellBitmapName).BitmapName := AName;
    UpdateGridCells;
  end;
end;

procedure TTMSFMXGridData.SetBitmaps(Col, Row: Integer; const Value: TBitmap);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellBitmap.Create(Self);
  (cp.Control as TCellBitmap).Bitmap.Assign(Value);
  IntObjects[Col,Row] := cp;
end;

procedure TTMSFMXGridData.SetBooleans(Col, Row: Integer; const Value: Boolean);
begin
  AddDataCheckBox(Col, Row, Value);
end;

function TTMSFMXGridData.GetBitmapName(Col,Row: integer): string;
var
  cp: TCellProperty;
begin
  Result := '';
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellBitmapName) then
  begin
    Result := (cp.Control as TCellBitmapName).BitmapName;
  end;
end;

function TTMSFMXGridData.GetBitmaps(Col, Row: Integer): TBitmap;
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellBitmap) then
    Result := (cp.Control as TCellBitmap).Bitmap
  else
    Result := CreateBitmap(Col, Row);
end;

function TTMSFMXGridData.GetBooleans(Col, Row: Integer): Boolean;
begin
  Result := CheckBoxState[Col, Row]
end;

function TTMSFMXGridData.IsBitmap(Col: Integer; Row: Integer): boolean;
begin
  Result := IsCellControl(Col,Row, TCellBitmapName);
end;

function TTMSFMXGridData.GetBitmap(Col: Integer; Row: Integer): TBitmap;
var
  cp: TCellProperty;
begin
  Result := nil;

  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellBitmapName) then
  begin
    // get bitmap via container
    //Result := (cp.Control as TCellBitmapName).BitmapName;
  end;

  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellBitmap) then
  begin
    Result := (cp.Control as TCellBitmap).Bitmap;
  end;
end;

procedure TTMSFMXGridData.AddNode(Row, Span: Integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[FixedColumns - 1,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(FixedColumns - 1,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellNode.Create;
  (cp.Control as TCellNode).Span := Span;
  (cp.Control as TCellNode).State := nsOpen;
  IntObjects[FixedColumns - 1,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddProgressBar(Col, Row: Integer; Value: single);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellProgressBar.Create;
  (cp.Control as TCellProgressBar).Value := Value;
  (cp.Control as TCellProgressBar).Data := false;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetProgressBarValue(Col,Row: Integer; Value: single);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    Exit;

  if (cp.Control is TCellProgressBar) then
  begin
    (cp.Control as TCellProgressBar).Value := Value;
    UpdateGridCells;
  end;
end;

function TTMSFMXGridData.GetProgressBarValue(Col,Row: integer): single;
var
  cp: TCellProperty;
begin
  Result := 0;

  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    Exit;

  if (cp.Control is TCellProgressBar) then
    Result := (cp.Control as TCellProgressBar).Value;
end;

procedure TTMSFMXGridData.AddDataProgressBar(Col, Row: Integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellProgressBar.Create;
  (cp.Control as TCellProgressBar).Value := 0;
  (cp.Control as TCellProgressBar).Data := true;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;


procedure TTMSFMXGridData.RemoveNode(Row: Integer);
begin
  RemoveCellControl(FixedColumns - 1, Row, TCellNode);
end;

procedure TTMSFMXGridData.RemoveProgressBar(Col, Row: Integer);
begin
  RemoveCellControl(Col,Row,TCellProgressBar);
end;

function TTMSFMXGridData.IsNode(Row: Integer): boolean;
begin
  Result := IsCellControl(FixedColumns - 1, Row, TCellNode);
end;

function TTMSFMXGridData.IsNormalFixed(Col, Row: Integer): Boolean;
var
  ACellFixed: Boolean;
begin
  ACellFixed := False;
  DoGetCellIsFixed(Col, Row, ACellFixed);
  Result := ACellFixed;
end;

function TTMSFMXGridData.IsProgressBar(Col, Row: Integer): boolean;
begin
  Result := IsCellControl(Col, Row, TCellProgressBar);
end;

procedure TTMSFMXGridData.SetNodeState(Row: Integer; State: TNodeState);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[FixedColumns - 1,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellNode) then
  begin
    (cp.Control as TCellNode).State := State;
  end;
end;

procedure TTMSFMXGridData.OpenAllNodes;
var
  i,rr: integer;
begin
  i := FixedRows;

  while  i < RowCount - FixedFooterRows do
  begin
    rr := DisplToRealRow(i);
    if IsNode(rr) then
      OpenNode(rr);
    inc(i);
  end;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.OpenNode(Row: Integer);
var
  cn: TCellNode;
begin
  cn := GetNode(Row);

  if not Assigned(cn) then
    Exit;

  if cn.Span <> -1 then
  begin
    if cn.State = nsClosed then
    begin
      UnHideRows(Row + 1, cn.Span - 1);
      cn.State := nsOpen;
    end;
  end;
end;

procedure TTMSFMXGridData.CloseAllNodes;
var
  i,rr: integer;
begin
  i := FixedRows;
  while i < RowCount - FixedFooterRows do
  begin
    rr := DisplToRealRow(i);
    if IsNode(rr) then
      CloseNode(rr);
    inc(i);
  end;
  UpdateGridCells;
end;


procedure TTMSFMXGridData.CloseNode(Row: Integer);
var
  cn: TCellNode;
begin
  cn := GetNode(Row);

  if not Assigned(cn) then
    Exit;

  if cn.Span <> -1 then
  begin
    if cn.State = nsOpen then
    begin
      HideRows(Row + 1, cn.Span );
      cn.State := nsClosed;
    end
  end;
end;

procedure TTMSFMXGridData.SetNodeSpan(Row: Integer; Span: Integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[FixedColumns - 1,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellNode) then
  begin
    (cp.Control as TCellNode).Span := Span;
  end;
end;

function TTMSFMXGridData.GetNodeSpan(Row: Integer): Integer;
var
  cn: TCellNode;
begin
  Result := -1;

  cn := GetNode(Row);
  if Assigned(cn) then
    Result := cn.Span;
end;

function TTMSFMXGridData.GetNodeState(Row: Integer): TNodeState;
var
  cn: TCellNode;
begin
  Result := nsOpen;

  cn := GetNode(Row);
  if Assigned(cn) then
    Result := cn.State;
end;

function TTMSFMXGridData.GetObjects(Col, Row: integer): TObject;
var
  cp: TCellProperty;
begin
  Result := nil;

  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) then
    Result := cp.CellObject;
end;

function TTMSFMXGridData.GetNode(Row: Integer): TCellNode;
var
  cp: TCellProperty;
begin
  Result := nil;
  cp := TCellProperty(IntObjects[FixedColumns - 1,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellNode) then
  begin
    Result := (cp.Control as TCellNode);
  end;
end;

procedure TTMSFMXGridData.AddCheckBoxColumn(Col: integer);
var
  i:integer;
begin
  for i := FixedRows to RowCount - FixedFooterRows - 1 do
  begin
    if (GroupColumn <> -1) then
    begin
      if IsNode(i) then
      begin
        if Options.Grouping.AutoCheckGroup then
          AddCheckBox(Col,i);
      end
      else
        if not IsSummary(i) then
          AddCheckBox(Col,i);
    end
    else
      AddCheckBox(Col,i);
  end;
end;


procedure TTMSFMXGridData.AddComboBox(Col, Row: Integer; Items: TStrings;
  Editable: boolean);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellCombo.Create;
  (cp.Control as TCellCombo).Editable := Editable;
  (cp.Control as TCellCombo).Items.Assign(Items);
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddCheckBox(Col,Row: integer; State: boolean = false);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellCheckBox.Create;
  (cp.Control as TCellCheckBox).Checked := state;
  (cp.Control as TCellCheckBox).Header := false;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.AddHeaderCheckBox(Col,Row: integer; State: boolean = false);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellCheckBox.Create;
  (cp.Control as TCellCheckBox).Checked := state;
  (cp.Control as TCellCheckBox).Header := true;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;


procedure TTMSFMXGridData.AddDataCheckBox(Col,Row: integer; State: boolean = false);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellDataCheckBox.Create;
  IntObjects[Col,Row] := cp;
  if State then
    Cells[Col,Row] := CheckTrue
  else
    Cells[Col,Row] := CheckFalse;

  UpdateGridCells;
end;


procedure TTMSFMXGridData.AddDataCheckBoxColumn(Col: integer);
var
  i:integer;
begin
  for i := FixedRows to RowCount - FixedFooterRows - 1 do
    AddDataCheckBox(Col,i);
end;

procedure TTMSFMXGridData.RemoveCheckBox(Col,Row: integer);
begin
  RemoveCellControl(Col,Row, TCellCheckBox);
end;

procedure TTMSFMXGridData.RemoveComboBox(Col, Row: Integer);
begin
  RemoveCellControl(Col,Row, TCellCombo);
end;

procedure TTMSFMXGridData.RemoveFilter;
begin
  UnHideRowsAll;
end;

function TTMSFMXGridData.IsCheckBox(Col,Row: integer): boolean;
begin
  Result := IsCellControl(Col,Row, TCellCheckBox) or IsCellControl(Col,Row, TCellDataCheckBox);
end;

function TTMSFMXGridData.IsComboBox(Col, Row: integer): boolean;
begin
  Result := IsCellControl(Col,Row, TCellCombo)
end;

function TTMSFMXGridData.GetComboIndex(Col,Row: integer): integer;
var
  cp: TCellProperty;
begin
  Result := -1;
  if IsCellControl(Col,Row,TCellCombo) then
  begin
    cp := TCellProperty(IntObjects[Col,Row]);
    if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellCombo) then
      Result := (cp.Control as TCellCombo).Items.IndexOf(Cells[Col,Row]);
  end;
end;

function TTMSFMXGridData.GetCommentColors(Col, Row: Integer): TAlphaColor;
var
  CellProp: TCellProperty;
begin
  Result := claNull;

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
   Result := CellProp.CommentColor;

end;

function TTMSFMXGridData.GetComments(Col, Row: Integer): string;
var
  CellProp: TCellProperty;
begin
  Result := '';

  CellProp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(CellProp) then
   Result := CellProp.Comment;
end;

procedure TTMSFMXGridData.SetComboIndex(Col,Row,Value: integer);
var
  cp: TCellProperty;
begin
  if Value = -1 then
    Cells[Col,Row] := ''
  else
    if IsCellControl(Col,Row,TCellCombo) then
    begin
      cp := TCellProperty(IntObjects[Col,Row]);
      if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellCombo) then
      begin
        if Value < (cp.Control as TCellCombo).Items.Count then
          Cells[Col,Row] := (cp.Control as TCellCombo).Items[Value];
      end;
    end;
end;


procedure TTMSFMXGridData.SetCommentColors(Col, Row: Integer;
  const Value: TAlphaColor);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.CommentColor := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.CommentColor := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SetComments(Col, Row: Integer; const Value: string);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.Comment := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.Comment := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

function TTMSFMXGridData.IsCellControl(Col,Row: Integer;  AClass: TClass): boolean;
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  Result := Assigned(cp) and Assigned(cp.Control) and (cp.Control is AClass);
end;

procedure TTMSFMXGridData.RemoveCellControl(Col, Row: Integer; AClass: TClass);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is AClass) then
  begin
    cp.FreeControl;
    UpdateGridCells;
  end;
end;

procedure TTMSFMXGridData.AddRadioButtonColumn(Col,Index: integer);
var
  i:integer;
begin
  for i := FixedRows to RowCount - FixedFooterRows - 1 do
  begin
    AddRadioButton(Col,i,Index);
  end;
end;

procedure TTMSFMXGridData.AddSummary(Row: integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[FixedColumns - 1,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(FixedColumns - 1,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellSummary.Create;
  IntObjects[FixedColumns - 1,Row] := cp;
end;

procedure TTMSFMXGridData.AddRadioButton(Col,Row,Index: integer; State: boolean = false);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellRadioButton.Create;
  (cp.Control as TCellRadioButton).Checked := state;
  (cp.Control as TCellRadioButton).Index := Index;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.RemoveRadioButton(Col,Row: integer);
begin
  RemoveCellControl(Col,Row,TCellRadioButton);
end;

procedure TTMSFMXGridData.RemoveSummary(Row: integer);
begin
  RemoveCellControl(FixedColumns - 1, Row, TCellSummary);
end;

function TTMSFMXGridData.IsRadioButton(Col,Row: integer): boolean;
begin
  Result := IsCellControl(Col,Row,TCellRadioButton);
end;

procedure TTMSFMXGridData.AddButton(Col,Row: integer; AText: string; AWidth: integer = 24);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  if Assigned(cp.Control) then
    cp.FreeControl;

  cp.Control := TCellButton.Create;
  (cp.Control as TCellButton).Text := AText;
  (cp.Control as TCellButton).Width := AWidth;
  IntObjects[Col,Row] := cp;
  UpdateGridCells;
end;

function TTMSFMXGridData.IsButton(Col,Row: integer): boolean;
begin
  Result := IsCellControl(Col,Row, TCellButton);
end;

procedure TTMSFMXGridData.RemoveButton(Col: Integer; Row: Integer);
begin
  RemoveCellControl(Col,Row,TCellButton);
end;

function TTMSFMXGridData.IsReadOnly(Col, Row: integer): boolean;
begin
  Result := false;
  DoGetCellReadOnly(Col,Row,Result);
end;

function TTMSFMXGridData.IsSummary(Row: integer): boolean;
begin
  Result := IsCellControl(0,Row,TCellSummary);
end;

function TTMSFMXGridData.RadioButtonState(Col,Row: integer): boolean;
var
  cp: TCellProperty;
begin
  Result := false;
  cp := TCellProperty(IntObjects[Col,Row]);
  if Assigned(cp) and Assigned(cp.Control) and (cp.Control is TCellRadioButton) then
  begin
    Result := (cp.Control as TCellRadioButton).Checked;
  end;
end;

procedure TTMSFMXGridData.RandomFill(DoFixed: Boolean; rnd: Integer);
var
  i,j: Integer;
  ro,co,re,ce: Integer;
begin
  if DoFixed then
  begin
    ro := 0;
    co := 0;
    re := RowCount - 1;
    ce := ColumnCount - 1;
  end
  else
  begin
    ro := FixedRows;
    co := FixedColumns;
    re := RowCount - 1 - FixedFooterRows;
    ce := ColumnCount - 1 - FixedRightColumns;
  end;

  for i := ro to re do
    for j := co to ce do
      Cells[j,i] := IntToStr(Random(rnd));
end;

procedure TTMSFMXGridData.SetColors(Col, Row: integer; const Value: TAlphaColor);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.Color := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.Color := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SetColumnCalculation(Col: Integer;
  const Value: TColumnCalculation);
begin
  while (FCellCalcList.Count <= Col) do
    FCellCalcList.Add(integer(ccNone));

  FCellCalcList.Items[Col] := integer(Value);

  UpdateColumnCalc(Col, Value);
end;

procedure TTMSFMXGridData.SetColumnCount(const Value: Integer);
var
  i: integer;
begin
  FirstCellApply := False;
  while (Value > FColumns.Count) do
    FColumns.Add;

  BeginUpdate;

  if (Value < FColumnCount) then
    ClearCells(CellRange(Value, 0, FColumnCount - 1, RowCount - 1));

  FColumnCount := Value;

  if Value < FColumnDisplayList.Count then
  begin
    while (FColumnDisplayList.Count > Value) do
    begin
      FColumnDisplayList.Delete(FColumnDisplayList.Count - 1);
    end;
  end
  else
  begin
    i := 0;
    if  FColumnDisplayList.Count > 0 then
      i := integer(FColumnDisplayList.Items[FColumnDisplayList.Count - 1]) + 1;

    while (FColumnDisplayList.Count < Value) do
    begin
      FColumnDisplayList.Add(i);
      inc(i);
    end;
  end;

  EndUpdate;
end;

procedure TTMSFMXGridData.SetColumnOrder;
var
  i: Integer;
begin
  FColumnOrder.Clear;
  for i := 1 to ColumnCount do
    FColumnOrder.Add(i - 1);
end;

procedure TTMSFMXGridData.SetColumns(const Value: TTMSFMXGridColumns);
begin
  FColumns.Assign(Value);
end;

procedure TTMSFMXGridData.SetColumnSelect(Col: integer; const Value: boolean);
var
  idx: integer;
begin
  idx := FColSelect.IndexOfValue(Col);

  if (idx = -1) and Value then
    FColSelect.Add(Col);

  if (idx <> -1) and not Value then
    FColSelect.Delete(idx);

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetColumnW(const Value: TSingleList);
begin
  if FColumnW <> Value then
  begin
    BeginUpdate;
    FColumnW.Assign(Value);
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetColWidths(Col: Integer; const Value: Single);
var
  idx: Integer;
  c: TSingleListItem;
begin
  idx := ColumnW.IndexOf(Col);
  if (idx >= 0) and (idx <= ColumnW.Count - 1) then
  begin
    if Value = DefaultColumnWidth then
      ColumnW.Delete(idx)
    else
    begin
      ColumnW[idx].Value := Max(0, Value);
      ColumnW[idx].CellVal := Col;
    end;
  end
  else if Value <> DefaultColumnWidth then
  begin
    c := ColumnW.Add;
    c.Value := Max(0, Value);
    c.CellVal := Col;
  end;
end;

procedure TTMSFMXGridData.SetCellControls(Col, Row: integer;
  const Value: TFMXObject);
var
  cp: TCellProperty;
begin
  if Value = nil then
    RemoveCellControl(Col,Row, TCellControl)
  else
  begin
    cp := TCellProperty(IntObjects[Col,Row]);

    if not Assigned(cp) then
      cp := TCellProperty.Create(GetDefaultFont(Col,Row));

    if Assigned(cp.Control) then
      cp.FreeControl;

    cp.Control := TCellControl.Create;
    (cp.Control as TCellControl).Control := Value;
    IntObjects[Col,Row] := cp;
  end;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetDefaultColumnWidth(const Value: Single);
begin
  if FDefaultColumnWidth <> Value then
  begin
    BeginUpdate;
    FDefaultColumnWidth := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetDefaultRowHeight(const Value: Single);
begin
  if FDefaultRowHeight <> Value then
  begin
    BeginUpdate;
    FDefaultRowHeight := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetFixedColumns(const Value: Integer);
begin
  if (FFixedColumns <> Value) and (Value >= 0) then
  begin
    BeginUpdate;
    FFixedColumns := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetFixedFooterRows(const Value: Integer);
begin
  if (FFixedFooterRows <> Value) and (Value >= 0) then
  begin
    BeginUpdate;
    FFixedFooterRows := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetFixedRightColumns(const Value: Integer);
begin
  if (FFixedRightColumns <> Value) and (Value >= 0) then
  begin
    BeginUpdate;
    FFixedRightColumns := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetFixedRows(const Value: Integer);
begin
  if (FFixedRows <> Value) and (Value >= 0) then
  begin
    BeginUpdate;
    FFixedRows := Value;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetFloats(Col, Row: Integer; const Value: double);
begin
  AllCells[Col,Row] := Format(FloatFormat,[Value]);
end;

procedure TTMSFMXGridData.SetFocusCell(ACol, ARow: Integer);
begin
  FFocusedCell.Col := ACol;
  FFocusedCell.Row := ARow;
  FFocusedCell.Col := Max(FixedColumns, Min(ColumnCount - 1 - FixedRightColumns, FFocusedCell.Col));
  FFocusedCell.Row := Max(FixedRows, Min(RowCount - 1 - FixedFooterRows, FFocusedCell.Row));
end;

procedure TTMSFMXGridData.SetFocusedCell(const Value: TCell);
var
  val: TCell;
begin
  SetFocusCell(Value.Col, Value.Row);
  if IsNormalFixed(FocusedCell.Col, FocusedCell.Row) then
  begin
    val := NextSelectableColumn(FocusedCell.Col, FocusedCell.Row);
    SetFocusCell(val.Col, val.Row);
  end;
  SelectCell(FFocusedCell, []);
end;

procedure TTMSFMXGridData.SetFontColors(Col, Row: integer; const Value: TAlphaColor);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.FontColor := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.FontColor := Value;
    IntObjects[Col,Row] := cp;
  end;

end;

procedure TTMSFMXGridData.SetFontNames(Col, Row: integer; const Value: string);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.FontName := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.FontName := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SetFontSizes(Col, Row: integer; const Value: single);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.FontSize := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.FontSize := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SetFontStyles(Col, Row: integer;
  const Value: TFontStyles);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.FontStyle := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.FontStyle := Value;
    IntObjects[Col,Row] := cp;
  end;

end;

procedure TTMSFMXGridData.SetIntObjects(Col, Row: integer; const Value: TObject);
var
  sl: TRowInfo;
begin
  //
  while (FRowList.Count <= Row) do
  begin
    FRowList.Add(nil);
    FRowDisplayList.Add(FRowDisplayList.Count);
  end;

  if Assigned(FRowList.Items[Row]) then
  begin
    sl := TRowInfo(FRowList.Items[Row]);
  end
  else
  begin
    sl := TRowInfo.Create;
    FRowList.Items[Row] := sl;
  end;

  while sl.Data.Count <= Col do
    sl.Data.AddObject('',nil);

  sl.Data.Objects[col] := Value;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetMergeCellPrintPageNr(Col, Row: integer;
  const Value: integer);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) and (cp.BaseCol <> -1) then
  begin
    cp.FPrintPageNr := Value;
  end;
end;

procedure TTMSFMXGridData.SetObjects(Col, Row: integer; const Value: TObject);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  cp.CellObject := Value;
  IntObjects[Col,Row] := cp;
end;

procedure TTMSFMXGridData.SetOptions(const Value: TTMSFMXGridOptions);
begin
  if (FOptions <> Value) then
    FOptions.Assign(Value);
end;

procedure TTMSFMXGridData.SetReadOnlys(Col, Row: integer; const Value: boolean);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if not Assigned(cp) then
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));

  cp.ReadOnly := Value;
  IntObjects[Col,Row] := cp;
end;

procedure TTMSFMXGridData.SetRowCount(const Value: integer);
var
  ri: TRowInfo;
begin
  FirstCellApply := False;
  BeginUpdate;

  if (Value < FRowCount) then
    ClearCells(CellRange(0, Value, ColumnCount - 1, FRowCount - 1));

  FRowCount := Value;

  if Value < FRowList.Count then
  begin
    while (FRowList.Count > Value) do
    begin
      if Assigned(FRowList.Items[FRowList.Count - 1]) then
      begin
        ri := TRowInfo(FRowList.Items[FRowList.Count - 1]);
        ri.Free;
      end;
      FRowList.Delete(FRowList.Count - 1);
    end;
  end
  else
    while (FRowList.Count < Value) do
      FRowList.Add(nil);

  EndUpdate;
end;

procedure TTMSFMXGridData.SetRowH(const Value: TSingleList);
begin
  if FRowH <> Value then
  begin
    BeginUpdate;
    FRowH.Assign(Value);
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SetRowHeights(Row: Integer; const Value: Single);
var 
  idx: Integer;
  r: TSingleListItem;
begin
  idx := RowH.IndexOf(Row);
  if (idx >= 0) and (idx <= RowH.Count - 1) then
  begin
    if Value = DefaultRowHeight then
      RowH.Delete(idx)
    else
    begin
      RowH[idx].Value := Max(0, Value);
      RowH[idx].CellVal := Row;
    end;
  end
  else if Value <> DefaultRowHeight then
  begin       
    r := RowH.Add;
    r.Value := Max(0, Value);
    r.CellVal := Row;
  end;
end;

procedure TTMSFMXGridData.UnSelectCells(ARange: TCellRange);
var
  i,j: integer;
begin
  for i := ARange.StartCol to ARange.EndCol do
    for j := ARange.StartRow to ARange.EndRow do
      CellSelect[i,j] := false;
end;

procedure TTMSFMXGridData.UnSelectColumns(FromColumn, ToColumn: integer);
var
  i,idx: integer;
begin
  for i := FromColumn to ToColumn do
  begin
    idx := FColSelect.IndexOfValue(i);
    if idx <> -1 then
      FColSelect.Delete(idx);
  end;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnSelectRows(FromRow, ToRow: integer);
var
  i: integer;
begin
  for i := FromRow to ToRow do
    RowSelect[i] := false;
end;

procedure TTMSFMXGridData.SelectRows(FromRow, ToRow: integer);
begin
  SelectCell(Cell(0, FromRow));
  SelectCell(Cell(0, ToRow), [ssCtrl, ssShift]);
end;

procedure TTMSFMXGridData.ClearRowSelect;
var
  i: integer;
begin
  for i := FixedRows to RowCount - FixedFooterRows - 1 do
  begin
    if i >= FRowList.Count then
      Break;

    if Assigned(FRowList[i]) then
      TRowInfo(FRowList[i]).State := rsDefault;
  end;
end;


procedure TTMSFMXGridData.ClearSortColumn;
begin
  FSortColumn := -1;
end;

procedure TTMSFMXGridData.SetRowSelect(Row: integer; const Value: boolean);
var
  sl: TRowInfo;
begin
  EnsureRow(Row);

  if not Assigned(FRowList[Row]) then
  begin
    sl := TRowInfo.Create;
    FRowList.Items[Row] := sl;
  end;

  if Value then
    TRowInfo(FRowList[Row]).State := rsSelected
   else
    TRowInfo(FRowList[Row]).State := rsDefault;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetSelection(const Value: TCellRange);
var
  start, stop: TCell;
  val: TCellRange;
  vl: TCell;
begin
  if not FirstCellApply then
  begin
    BeginUpdate;
    NeedStyleLookUp;
    Repaint;
    EndUpdate;
  end;

  FFirstCellApply := False;
  val := Value;

  if IsNormalFixed(val.StartCol, val.StartRow) then
  begin
    vl := NextSelectableColumn(val.StartCol, val.StartRow);
    val.StartCol := vl.Col;
    val.StartRow := vl.Row;
  end;

  FStartCell.Col := val.StartCol;
  FStartCell.Row := val.StartRow;
  FStopCell.Col := val.EndCol;
  FStopCell.Row := val.EndRow;

  start := FStartCell;
  stop := FStopCell;
  SelectCell(start, [], True);
  SelectCell(stop, [], True);

  FFocusedCell := FStartCell;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SetSelectionMode(
  const Value: TTMSFMXGridSelectionMode);
begin
  if FSelectionMode <> Value then
  begin
    FSelectionMode := Value;
    UpdateGrid(False);
  end;
end;

procedure TTMSFMXGridData.SetSortColumn(const Value: integer);
begin
  if Value <> FSortColumn then
    SortData(Value, SortDirection);
end;

procedure TTMSFMXGridData.SetSortDirection(const Value: TSortDirection);
begin
  if Value <> FSortDirection then
    SortData(SortColumn, Value);
end;

procedure TTMSFMXGridData.SetStartCell(ACol, ARow: Integer);
begin
  FStartCell.Col := ACol;
  FStartCell.Row := ARow;

  FStartCell.Col := Max(FixedColumns, Min(ColumnCount - 1 - FixedRightColumns, FStartCell.Col));
  FStartCell.Row := Max(FixedRows, Min(RowCount - 1 - FixedFooterRows, FStartCell.Row));
end;

procedure TTMSFMXGridData.SetStopCell(ACol, ARow: Integer);
begin
  FStopCell.Col := ACol;
  FStopCell.Row := ARow;

  FStopCell.Col := Max(FixedColumns, Min(ColumnCount - 1 - FixedRightColumns, FStopCell.Col));
  FStopCell.Row := Max(FixedRows, Min(RowCount - 1 - FixedFooterRows, FStopCell.Row));
end;

procedure TTMSFMXGridData.SetVertAlignments(Col, Row: integer;
  const Value: TTextAlign);
var
  cp: TCellProperty;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) then
    cp.AlignVert := Value
  else
  begin
    cp := TCellProperty.Create(GetDefaultFont(Col,Row));
    cp.AlignVert := Value;
    IntObjects[Col,Row] := cp;
  end;
end;

procedure TTMSFMXGridData.SortData(Column: integer; Direction: TSortDirection);
var
  ri: TRowInfo;
begin
  BlockUpdate := True;
  FSortColumn := Column;
  FSortDirection := Direction;

  // allocate space for pivot row
  FRowCount := RowCount + 1;

  QuickSortRows(FSortColumn, FIOOffset.Y, FRowCount - 2 - FixedFooterRows);

  // remove pivot row space
  ClearCells(CellRange(0,RowCount - 1, ColumnCount - 1, RowCount - 1));
  FRowCount := FRowCount - 1;

  if Assigned(FRowList.Items[FRowList.Count - 1]) then
  begin
    ri := TRowInfo(FRowList.Items[FRowList.Count - 1]);
    ri.Free;
  end;

  FRowList.Delete(FRowList.Count - 1);

  BlockUpdate := False;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.SortGrouped(Column: integer; Direction: TSortDirection);
begin
  FSortColumn := Column;
  FSortDirection := Direction;
  SortGroupedInt(false);
end;

procedure TTMSFMXGridData.SortGroupedIndexed;
begin
  SortGroupedInt(true);
end;

procedure TTMSFMXGridData.SortGroupedInt(Indexed: boolean);
var
  i,r1,r2: Integer;
  hr: integer;
begin
  BeginUpdate;

  hr := HiddenRowCount;

  try
    FRowCount := FRowCount + 1 + hr;  // pivot row

    for i := FixedRows to FRowCount - FixedFooterRows - 3 do
    begin
      if IsNode(i) then
      begin
        r1 := i + 1;
        r2 := i + GetNodeSpan(i);

        if r2 - r1 > 1 then
          if Indexed then
            QuickSortRowsIndexed(0,r1,r2 )
          else
            QuickSortRows(FSortColumn,r1,r2  );
      end;
    end;

  finally
    FRowCount := FRowCount - 1 - hr;
    EndUpdate;
  end;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SortIndexed;
var
  ri: TRowInfo;
begin
  if FSortIndexes.Count = 0 then
    raise Exception.Create('No indexes specified for indexed sort');

  BlockUpdate := True;

  // allocate space for pivot row
  FRowCount := FRowCount + 1;

  QuickSortRowsIndexed(0,FixedRows, FRowCount - 2 - FixedFooterRows);

  // remove pivot row space
  FRowCount := FRowCount - 1;

  if Assigned(FRowList.Items[FRowList.Count - 1]) then
  begin
    ri := TRowInfo(FRowList.Items[FRowList.Count - 1]);
    ri.Free;
  end;

  FRowList.Delete(FRowList.Count - 1);

  BlockUpdate := False;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.SplitCell(Col, Row: integer);
var
  cp: TCellProperty;
  i,j: integer;
  fromc,toc,fromr,tor: integer;
begin
  cp := TCellProperty(IntObjects[Col,Row]);

  if Assigned(cp) and cp.IsBaseCell(Col,Row)then
  begin
    fromc := cp.BaseCol;
    fromr := cp.BaseRow;
    toc := cp.BaseCol + cp.ColSpan;
    tor := cp.BaseRow + cp.RowSpan;

    for i := fromc to toc - 1 do
    begin
      for j := fromr to tor - 1 do
      begin
        cp := TCellProperty(IntObjects[i,j]);
        if Assigned(cp) then
        begin
          cp.BaseCol := -1;
          cp.BaseRow := -1;
          cp.ColSpan := 0;
          cp.RowSpan := 0;
        end;

        if not ((j = fromr) and (i = fromc)) then
          IntObjects[i,j] := nil;
      end;
    end;

    UpdateGridCells;
  end;
end;

procedure TTMSFMXGridData.StringToColumnStates(Value: string);
// sample: 5#27,64,22,64,64#4,0,1,2,3#1,1,1,1,1
var
  i,j: integer;
  il: TIntList;
  sl: TStringList;
  s: string;
  NewPos: integer;
begin
  if pos('#',value) = 0 then
    Exit;

  BeginUpdate;
  sl := TStringList.Create;

  try
    FColumnOrder.Clear;

    UnHideColumnsAll;

    s := copy(value, 1, pos('#',value) - 1);

    ColumnCount := StrToInt(s);

    delete(value,1, pos('#',value));

    // order + visible part
    s := copy(value,pos('#',value) + 1,length(value));

    // order part
    s := copy(s,1, pos('#',s) - 1);

    sl.CommaText := s;

    if s <> '' then
    begin
      for i := 0 to ColumnCount - 1 do
      begin
        if i < sl.Count then
        begin
          NewPos := StrToInt(sl.Strings[i]);
          if (NewPos <> -1) then
            FColumnOrder.Add(NewPos);
        end;
      end;

      // prepare reorganisation
      il := TIntList.Create;
      for i := 0 to ColumnCount - 1 do
      begin
        for j := 0 to FColumnOrder.Count - 1 do
        begin
          if (FColumnOrder[j] = i) then
            il.Add(j);
        end;
      end;

      FColumnOrder.Clear;
      for i := 0 to ColumnCount - 1 do
      begin
        FColumnOrder.Add(il.Items[i]);
      end;

      il.Free;

      // do reorganisation
      if FColumnOrder.Count > 0 then
        ResetColumnOrder;

      FColumnOrder.Clear;
      for i := 0 to ColumnCount - 1 do
      begin
        NewPos := StrToInt(sl.Strings[i]);
        if (NewPos <> -1) then
          FColumnOrder.Add(NewPos);
      end;
    end;

    // order + visible part
    s := copy(value,pos('#',value)+1,length(value));

    // visible part
    s := copy(s,pos('#',s)+1,length(s));

    sl.CommaText := s;

    for i := ColumnCount - 1 downto 0 do
    begin
      if sl.Strings[i] = '0' then
        HideColumn(i);
    end;

    s := copy(value,1,pos('#',value)-1);

    sl.CommaText := s;

    for i := 0 to ColumnCount - 1 do
    begin
      ColumnWidths[i] := StrToInt(sl.Strings[i]);
    end;

  finally
    sl.Free;
    EndUpdate;
  end;
end;

procedure TTMSFMXGridData.SwapCells(Col, Row1, Row2: integer);
var
  v: TCellData;
  o: TObject;
begin
  v := Cells[Col,Row1];
  o := IntObjects[Col,Row1];
  Cells[Col,Row1] := Cells[Col,Row2];
  IntObjects[Col,Row1] := IntObjects[Col,Row2];
  Cells[Col,Row2] := v;
  IntObjects[Col,Row2] := o;
end;

procedure TTMSFMXGridData.SwapNormalCells(Row1, Row2: integer);
var
  i: integer;
  v: TCellData;
  o: TObject;
begin
  for i := FixedColumns to ColumnCount - FixedRightColumns - 1 do
  begin
    v := Cells[i,Row1];
    o := IntObjects[i,Row1];
    Cells[i,Row1] := Cells[i,Row2];
    IntObjects[i,Row1] := IntObjects[i,Row2];
    Cells[i,Row2] := v;
    IntObjects[i,Row2] := o;
  end;
end;

procedure TTMSFMXGridData.SwapRows(Row1, Row2: integer);
var
  item: pointer;
  val1, val2: Single;
  rr1,rr2: integer;
  sel1, sel2: Boolean;
begin
  rr1 := DisplToRealRow(row1);
  rr2 := DisplToRealRow(row2);

  item := FRowList[rr1];
  FRowList[rr1] := FRowList[rr2];
  FRowList[rr2] := item;

  val1 := RowHeights[Row1];
  val2 := RowHeights[Row2];

  RowHeights[Row1] := val2;
  RowHeights[Row2] := val1;

  sel1 := RowSelect[Row1];
  sel2 := RowSelect[Row2];

  RowSelect[Row1] := sel2;
  RowSelect[Row2] := sel1;

  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnHideColumn(Col: integer);
var
  i: integer;
  fnd: boolean;
begin
  if not IsHiddenColumn(Col) or (FHiddenColumnList.Count = 0) then
    Exit;

  i := 0;
  fnd := FHiddenColumnList.IndexOf(Col) <> - 1;

  while i < FColumnDisplayList.Count do
  begin
    if integer(FColumnDisplayList.Items[i]) >= Col then
    begin
      fnd := true;
      break;
    end
    else
      inc(i);
  end;

  if fnd then
  begin
    FColumnDisplayList.Insert(i, Col);
    FHiddenColumnList.Delete(FHiddenColumnList.IndexOf(Col));
    ColumnCount := ColumnCount + 1;
    UpdateGridCells;
  end;
end;

procedure TTMSFMXGridData.UnHideColumns(Col: integer; Count: integer);
var
  i: integer;
begin
  for i := Col to Col + Count - 1 do
    UnHidecolumn(i);
end;

procedure TTMSFMXGridData.UnHideColumnsAll;
var
  i: integer;
begin
  BeginUpdate;
  while FColumnDisplayList.Count < TotalColCount do
  begin
    FColumnDisplayList.Add(0);
  end;

  for i := 0 to TotalColCount - 1 do
  begin
    FColumnDisplayList.Items[i] := i;
  end;

  FColumnCount := TotalColCount;
  FHiddenColumnList.Clear;

  EndUpdate;
end;

procedure TTMSFMXGridData.UnHideRow(Row: integer);
var
  i: integer;
  fnd: boolean;
begin
  if not IsHiddenRow(Row) then
    Exit;

  i := 0;
  fnd := FHiddenRowList.Count > 0;

  while i < FRowDisplayList.Count do
  begin
    if integer(FRowDisplayList.Items[i]) >= Row then
    begin
      fnd := true;
      break;
    end
    else
      inc(i);
  end;

  if fnd then
  begin
    FRowDisplayList.Insert(i, Row);
    FHiddenRowList.Delete(FHiddenRowList.IndexOf(Row));
    FRowCount := FRowCount + 1;
    UpdateGridCells;
  end;
end;

procedure TTMSFMXGridData.UnHideRows(Row: integer; Count: Integer);
var
  i,j,nr,ri: integer;
  fnd: boolean;
begin
  if FHiddenRowList.Count = 0 then
    Exit;

  i := 0;
  nr := 0;

  if (FRowDisplayList.Count = 0) or (Row > FRowDisplayList.Items[FRowDisplayList.Count - 1]) then
  begin
    for i := 0 to Max(0, Count - 1) do
    begin
      if FHiddenRowList.IndexOf(i + Row) <> -1 then
      begin
        inc(nr);
        FRowDisplayList.Add(i + Row);
        FHiddenRowList.Delete(FHiddenRowList.IndexOf(i + Row));
      end;
    end;
    FRowCount := FRowCount + nr;
    UpdateGrid(true);
    Exit;
  end;


  BeginUpdate;

  fnd := false;

  while i < FRowDisplayList.Count do
  begin
    if integer(FRowDisplayList.Items[i]) >= Row then
    begin
      fnd := true;
      break;
    end
    else
      inc(i);
  end;

  if fnd then
  begin
    ri := FHiddenRowList.IndexOf(Row);

    if (ri >= 0) then
    begin
      FRowDisplayList.Insert(i, Row);
      FHiddenRowList.Delete(ri);
      FRowCount := FRowCount + 1;
      j := 1;
      inc(i);

      while (j <= Count) do
      begin
        if integer(FRowDisplayList.Items[i]) <> Row + j then
        begin
          ri := FHiddenRowList.IndexOf(Row + j);
          if (ri >= 0) then
          begin
            FRowDisplayList.Insert(i, Row + j);
            FHiddenRowList.Delete(ri);
            FRowCount := FRowCount + 1;
          end;
        end;

        inc(i);
        inc(j);
      end;
    end;
  end;

  ResetUpdate;
  UpdateGrid(true);
end;

procedure TTMSFMXGridData.UnHideRowsAll;
var
  i: integer;
begin
  while FRowDisplayList.Count < FRowList.Count do
  begin
    FRowDisplayList.Add(0);
  end;

  for i := 0 to FRowList.Count - 1 do
  begin
    FRowDisplayList.Items[i] := i;
  end;

  FRowCount := FRowList.Count;

  UpdateGrid(true);
end;

procedure TTMSFMXGridData.UnHighlightAll;
var
  i: integer;
begin
  for i := FMarkList.Count - 1 downto 0 do
  begin
    if FMarkList.Items[i].MarkType = mtHighlight then
      FMarkList.Delete(i);
  end;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnHighlightInCell(Col, Row: Integer);
begin
  FMarkList.RemoveMarker(GetCellRange(Col,Row), mtHighlight);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnHighlightInCol(DoFixed: Boolean; Col: Integer);
begin
  FMarkList.RemoveMarker(GetColumnRange(Col,DoFixed), mtHighlight);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnHighlightInGrid(DoFixed: Boolean);
begin
  FMarkList.RemoveMarker(GetGridRange(DoFixed), mtHighlight);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnHighlightInRow(DoFixed: Boolean; Row: Integer);
begin
  FMarkList.RemoveMarker(GetRowRange(Row,DoFixed), mtHighlight);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnMarkAll;
var
  i: integer;
begin
  for i := FMarkList.Count - 1 downto 0 do
  begin
    if FMarkList.Items[i].MarkType = mtError then
      FMarkList.Delete(i);
  end;
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnMarkInCell(Col, Row: Integer);
begin
  FMarkList.RemoveMarker(GetCellRange(Col,Row), mtError);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnMarkInCol(DoFixed: Boolean; Col: Integer);
begin
  FMarkList.RemoveMarker(GetColumnRange(Col,DoFixed), mtError);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnMarkInGrid(DoFixed: Boolean);
begin
  FMarkList.RemoveMarker(GetGridRange(DoFixed), mtError);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UnMarkInRow(DoFixed: Boolean; Row: Integer);
begin
  FMarkList.RemoveMarker(GetRowRange(Row,DoFixed), mtError);
  UpdateGridCells;
end;

procedure TTMSFMXGridData.UpdateCalculation(ACol: integer);
var
  cc: TColumnCalculation;
begin
  cc := ColumnCalculation[ACol];
  if cc <> ccNone then
    UpdateColumnCalc(ACol,cc);
end;

procedure TTMSFMXGridData.UpdateCalculations;
var
  i: integer;
  cc: TColumnCalculation;
begin
  for i := 0 to ColumnCount - 1 do
  begin
    cc := ColumnCalculation[i];
    if cc <> ccNone then
      UpdateColumnCalc(i,cc);
  end;
end;

procedure TTMSFMXGridData.UpdateColumnCalc(Col: Integer;
  Value: TColumnCalculation);
var
  str: string;
  res: double;
begin
  //
  if FixedFooterRows = 0 then
    Exit;

  case Value of
    ccNone: str := '';
    ccSUM: str := Format(Options.Editing.CalcFormat,[ColumnSum(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccAVG: str := Format(Options.Editing.CalcFormat,[ColumnAvg(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccCOUNT:
      begin
        res := RowCount - FixedFooterRows - FixedRows;
        str := Format(Options.Editing.CalcFormat,[res]);
      end;
    ccMIN: str := Format(Options.Editing.CalcFormat,[ColumnMin(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccMAX: str := Format(Options.Editing.CalcFormat,[ColumnMax(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccCUSTOM: str := Format(Options.Editing.CalcFormat,[ColumnCustomCalc(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccDISTINCT: str := Format(Options.Editing.CalcFormat,[ColumnDistinct(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
    ccSTDDEV: str := Format(Options.Editing.CalcFormat,[ColumnStdDev(Col,FixedRows, RowCount - FixedFooterRows - 1)]);
  end;

  Cells[Col, RowCount - FixedFooterRows] := str;
end;

procedure TTMSFMXGridData.UpdateGrid(Update: Boolean);
begin

end;

procedure TTMSFMXGridData.UpdateGridCells;
begin

end;

{ TFileStringList }

procedure TFileStringList.Reset;
begin
  fp := 0;
  cache := '';
end;

function TFileStringList.GetEOF;
begin
  Result := fp >= Count;
end;

procedure TFileStringList.ReadLn(var s: string);
begin
  s := Strings[fp];
  inc(fp);
end;

procedure TFileStringList.Write(s: string);
begin
  cache := cache + s;
end;

procedure TFileStringList.WriteLn(s: string);
begin
  Add(cache + s);
  cache := '';
end;


{ TFilterData }

procedure TFilterData.Assign(Source: TPersistent);
var
  ASrcFilterData: TFilterData;
begin
  ASrcFilterData := Source as TFilterData;
  if Assigned(ASrcFilterData) then
  begin
    FColumn  := ASrcFilterData.Column;
    FCondition := ASrcFilterData.Condition;
    FCaseSensitive := ASrcFilterData.CaseSensitive;
    FData := ASrcFilterData.Data;
    FPrefix := ASrcFilterData.Prefix;
    FSuffix := ASrcFilterData.Suffix;
    FOperation := ASrcFilterData.Operation;
  end;

end;

constructor TFilterData.Create(ACollection: TCollection);
begin
  inherited;
  FCaseSensitive := True;
  FData := fcVirtual;
end;

{ TFilter }

function TFilter.Add: TFilterData;
begin
  Result := TFilterData(inherited Add);
  if Count = 1 then
    Result.Operation := foNone
  else
    Result.Operation := foAND;
end;

constructor TFilter.Create(AOwner: TTMSFMXGridData);
begin
  inherited Create(TFilterData);
  FOwner := AOwner;
end;

function TFilter.GetColFilter(Col: Integer): TFilterData;
var
  i: Integer;
begin
  for i := 1 to Count do
  begin
    if Items[i - 1].Column = Col then
    begin
      Result := Items[i - 1];
      Exit;
    end;
  end;
  Result := Add;
  Result.Column := Col;
end;

function TFilter.GetItem(Index: Integer): TFilterData;
begin
  Result := TFilterData(inherited GetItem(Index));
end;

function TFilter.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

procedure TFilter.RemoveColumnFilter(Col: integer);
var
  i: integer;
begin
  i := Count;

  while i > 0 do
  begin
    dec(i);
    if Items[i].Column = Col then
      Delete(i);
  end;

  if Assigned(FOwner) then
  begin
    //FOwner.RepaintCell(Col,0);
  end;
end;

function TFilter.HasFilter(Col: integer): Boolean;
var
  i: Integer;
begin
  Result := false;

  for i := 1 to Count do
  begin
    if Items[i - 1].Column = Col then
    begin
      Result := true;
      Break;
    end;
  end;
end;

function TFilter.Insert(index: Integer): TFilterData;
begin
  Result := TFilterData(inherited Add);
end;

procedure TFilter.SetItem(Index: Integer; Value: TFilterData);
begin
  inherited SetItem(Index, Value);
end;


{ TRowInfo }

constructor TRowInfo.Create;
begin
  inherited;
  FData := TDataList.Create;
  FState := rsDefault;
end;

destructor TRowInfo.Destroy;
begin
  FData.Free;
  inherited;
end;

procedure TRowInfo.SetState(const Value: TRowState);
begin
  FState := Value;
end;

{ TCellProperty }

procedure TCellProperty.Assign(Source: TCellProperty);
begin
  FColSpan := Source.ColSpan;
  FRowSpan := Source.RowSpan;
  FBaseCol := Source.BaseCol;
  FBaseRow := Source.BaseRow;
  FColor := Source.Color;
  FAlignHorz := Source.AlignHorz;
  FAlignVert := Source.AlignVert;
  FFontStyle := Source.FontStyle;
  FReadOnly := false;
end;

constructor TCellProperty.Create(DefaultFont: TFont);
begin
  inherited Create;
  FColSpan := -1;
  FRowSpan := -1;
  FBaseCol := -1;
  FBaseRow := -1;
  FColor := claNull;
  FControl := nil;
  FPrintPageNr := -1;
  FBorderWidth := 1;
  FAngle := 0;
  {$IFDEF DELPHIXE6_LVL}
  FAlignHorz := TTextAlign.Leading;
  FAlignVert := TTextAlign.Center;
  {$ELSE}
  FAlignHorz := TTextAlign.taLeading;
  FAlignVert := TTextAlign.taCenter;
  {$ENDIF}
  FFontSize := DefaultFont.Size;
  FFontStyle := DefaultFont.Style;
  FCommentColor := claNull;
  FComment := '';
end;

destructor TCellProperty.Destroy;
begin
  if Assigned(FControl) then
    FreeAndNil(FControl);
  inherited;
end;

procedure TCellProperty.FreeControl;
begin
  FreeAndNil(FControl);
end;

function TCellProperty.IsBaseCell(Col, Row: integer): boolean;
begin
  Result := ((Col = BaseCol) and (Row = BaseRow)) or ((BaseCol = -1) and (BaseRow = -1));
end;

{ TSingleListItem }

constructor TSingleListItem.Create(Collection: TCollection);
begin
  inherited;
  FOwner := (Collection as TSingleList).FOwner;
end;

procedure TSingleListItem.SeTCellData(const Value: Single);
begin
  FValue := Value;
end;

{ TSingleList }

function TSingleList.Add: TSingleListItem;
begin
  Result := TSingleListItem(inherited Add);
end;

procedure TSingleList.Clear;
begin
  if Count > 0 then
  begin
    while Count > 0 do
    begin
      {$IFDEF DELPHI_LLVM}
      TCollectionItem(Items[Count - 1]).Release;
      {$ELSE}
      TCollectionItem(Items[Count - 1]).Free;
      {$ENDIf}
    end;
  end;
end;

constructor TSingleList.Create(AOwner: TTMSFMXGridData);
begin
  inherited Create(TSingleListItem);
  FOwner := AOwner;
end;

function TSingleList.GetItem(
  Index: Integer): TSingleListItem;
begin
  Result := TSingleListItem(inherited Items[Index]);
end;

function TSingleList.GetOwner: TPersistent;
begin
  Result := FOwner;
end;

function TSingleList.IndexOf(ACellVal: Integer): Integer;
begin
  for Result := 0 to Count - 1 do
    if Items[Result].CellVal = ACellVal then Exit;
  Result := -1;
end;

function TSingleList.Insert(
  Index: Integer): TSingleListItem;
begin
  Result := TSingleListItem(inherited Insert(Index));
end;

procedure TSingleList.SetItem(Index: Integer;
  const Value: TSingleListItem);
begin
  inherited Items[Index] := Value;
end;

{ TCellBitmap }

constructor TCellBitmap.Create(AOwner: TTMSFMXGridData);
begin
  FBitmap := TBitmapEx.Create(1,1);
  FBitmap.FOwner := AOwner;
end;

destructor TCellBitmap.Destroy;
begin
  FBitmap.Free;
  inherited;
end;

procedure TCellBitmap.SetBitmap(const Value: TBitmapEx);
begin
  FBitmap.Assign(Value);
end;

{ TCellCombo }

constructor TCellCombo.Create;
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TCellCombo.Destroy;
begin
  FItems.Free;
  inherited;
end;

procedure TCellCombo.SetItems(const Value: TStringList);
begin
  FItems.Assign(Value);
end;

{ TCellBitmapName }

constructor TCellBitmapName.Create;
begin
  inherited Create;
  FBitmapName := '';
  FData := false;
end;

{ TCellMarkList }

function TCellMarkList.Add: TCellMarkItem;
begin
  Result := TCellMarkItem(inherited Add);
end;

constructor TCellMarkList.Create(AOwner: TTMSFMXGridData);
begin
  inherited Create(TCellMarkItem);
end;

function TCellMarkList.GetItem(Index: Integer): TCellMarkItem;
begin
  Result := TCellMarkItem(inherited Items[Index]);
end;

function TCellMarkList.Insert(Index: Integer): TCellMarkItem;
begin
  Result := TCellMarkItem(inherited Insert(Index));
end;

procedure TCellMarkList.RemoveMarker(CellRange: TCellRange; MarkerType: TTMSFMXMarkType);
var
  i: integer;
begin
  for i := Count - 1 downto 0 do
  begin
    if EqualsCellRange(Items[i].CellRange,CellRange) and (MarkerType = Items[i].MarkType) then
      Delete(i);
  end;
end;

procedure TCellMarkList.SetItem(Index: Integer; const Value: TCellMarkItem);
begin
  inherited Items[Index] := Value;
end;

{ TCellList }

procedure TCellList.AddCell(Cell: TCell);
begin
  Add(Cell);
end;

procedure TCellList.Clear;
var
  p: TCell;
begin
  while Count > 0 do
  begin
    p := Items[Count - 1];
    Delete(Count - 1);
  end;
end;

constructor TCellList.Create;
begin
  inherited Create;
end;

procedure TCellList.DeleteCell(Cell: TCell);
var
  i: integer;
  p: TCell;
begin
  for i := Count - 1 downto 0 do
  begin
    p := Items[i];
    if (p.Col  = Cell.Col) and (p.Row = Cell.Row) then
      Delete(i);
  end;
end;

procedure TCellList.DeleteCell(index: integer);
var
  p: TCell;
begin
  if (index >= 0) and (index < Count) then
  begin
    p := Items[index];
    Delete(index);
  end;
end;


destructor TCellList.Destroy;
begin
  Clear;
  inherited;
end;

function TCellList.Find(Cell: TCell): integer;
var
  i: integer;
  p: TCell;
begin
  Result := -1;

  for i := Count - 1 downto 0 do
  begin
    p := Items[i];
    //if ((p^.Col  = Cell.Col) and (p^.Row = Cell.Row)) OR ((Cell.Col = 0) and (p^.Row = Cell.Row)) OR ((p^.Col  = Cell.Col) and (Cell.Row = 0)) then
    if (p.Col  = Cell.Col) and (p.Row = Cell.Row) then
    begin
      Result := i;
      Break;
    end;
  end;
end;

function TCellList.FindFixed(Cell: TCell): integer;
var
  i: integer;
  p: TCell;
begin
  Result := -1;
  for i := Count - 1 downto 0 do
  begin
    p := Items[i];
    if (p.Row = Cell.Row) OR (p.Col  = Cell.Col) then
    begin
      Result := i;
      Break;
    end;
  end;
end;

{ TBitmapEx }

procedure TBitmapEx.Assign(Source: TPersistent);
begin
  inherited;
  if Assigned(FOwner) then
    FOwner.AssignBitmapEx(Source, Self);
end;

{ TTMSFMXGridColumns }

function TTMSFMXGridColumns.Add: TTMSFMXGridColumn;
begin
  Result := TTMSFMXGridColumn(inherited Add);
end;

function TTMSFMXGridColumns.ByID(Id: string): TTMSFMXGridColumn;
var
  i: integer;
begin
  Result := nil;
  for i := 0 to Count - 1 do
  begin
    if Items[i].Id = Id then
    begin
      Result := Items[i];
      break;
    end;
  end;
end;

function TTMSFMXGridColumns.ColumnByID(AID: string): TTMSFMXGridColumn;
var
  i: integer;
begin
  Result := nil;
  for i := 0 to Count - 1 do
  begin
    if (Items[i].ID = AID) then
    begin
      Result := Items[i];
      Break;
    end;
  end;
end;

function TTMSFMXGridColumns.ColumnByName(AName: string): TTMSFMXGridColumn;
var
  i: integer;
begin
  Result := nil;
  for i := 0 to Count - 1 do
  begin
    if (Items[i].Name = AName) then
    begin
      Result := Items[i];
      Break;
    end;
  end;
end;

constructor TTMSFMXGridColumns.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner,TTMSFMXGridColumn);
end;

function TTMSFMXGridColumns.GetItem(Index: integer): TTMSFMXGridColumn;
begin
  Result := TTMSFMXGridColumn(inherited Items[Index]);
end;

function TTMSFMXGridColumns.Insert(Index: integer): TTMSFMXGridColumn;
begin
  Result := TTMSFMXGridColumn(inherited Insert(Index));
end;

procedure TTMSFMXGridColumns.SetItem(Index: integer;
  const Value: TTMSFMXGridColumn);
begin
  inherited Items[Index] := Value;
end;

{ TTMSFMXGridColumn }

procedure TTMSFMXGridColumn.Assign(Source: TPersistent);
begin
  if (Source is TTMSFMXGridColumn) then
  begin
    FBorderColor := (Source as TTMSFMXGridColumn).BorderColor;
    FBorderWidth := (Source as TTMSFMXGridColumn).BorderWidth;
    FColor := (Source as TTMSFMXGridColumn).Color;
    FFixed := (Source as TTMSFMXGridColumn).Fixed;
    FFontColor := (Source as TTMSFMXGridColumn).FontColor;
    FFont.Assign((Source as TTMSFMXGridColumn).Font);
    FHorzAlignment := (Source as TTMSFMXGridColumn).HorzAlignment;
    FID := (Source as TTMSFMXGridColumn).ID;
    FVertAlignment := (Source as TTMSFMXGridColumn).VertAlignment;
    FEditor := (Source as TTMSFMXGridColumn).Editor;
    FReadOnly := (Source as TTMSFMXGridColumn).ReadOnly;
    FSortFormat := (Source as TTMSFMXGridColumn).SortFormat;
    FSortSuffix := (Source as TTMSFMXGridColumn).SortSuffix;
    FSortPrefix := (Source as TTMSFMXGridColumn).SortPrefix;
    FWordWrap := (Source as TTMSFMXGridColumn).WordWrap;
    FColumnType := (Source as TTMSFMXGridColumn).ColumnType;
    FComboItems.Assign((Source as TTMSFMXGridColumn).ComboItems);
  end;
end;

procedure TTMSFMXGridColumn.Changed;
begin
  ((Collection as TTMSFMXGridColumns).Owner as TTMSFMXGridData).UpdateGridCells;
end;

constructor TTMSFMXGridColumn.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FColor := claNull;
  FFont := TFont.Create;
  FBorderColor := claSilver;
  FBorderWidth := 1;
  FComboItems := TStringList.Create;
  FColumnType := ctDefault;
  {$IFDEF DELPHIXE6_LVL}
  FHorzAlignment := TTextAlign.Leading;
  {$ELSE}
  FHorzAlignment := TTextAlign.taLeading;
  {$ENDIF}
end;

destructor TTMSFMXGridColumn.Destroy;
begin
  FComboItems.Free;
  FFont.Free;
  inherited;
end;

function TTMSFMXGridColumn.GetDisplayName: string;
begin
  if Name <> '' then
    Result := Name
  else
    Result := 'Column ' + inttostr(Index);
end;

procedure TTMSFMXGridColumn.SetBorderColor(const Value: TAlphaColor);
begin
  if (FBorderColor <> Value) then
  begin
    FBorderColor := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetBorderWidth(const Value: single);
begin
  if (FBorderWidth <> Value) then
  begin
    FBorderWidth := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetBrushKind(const Value: TBrushKind);
begin
  if (FBrushKind <> Value) then
  begin
    FBrushKind := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetColor(const Value: TAlphaColor);
begin
  if (FColor <> Value) then
  begin
    FColor := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetColumnType(const Value: TTMSFMXGridColumnType);
begin
  if (FColumnType <> Value) then
  begin
    FColumnType := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetComboItems(const Value: TStringList);
begin
  FComboItems.Assign(Value);
end;

procedure TTMSFMXGridColumn.SetFixed(const Value: boolean);
begin
  if (FFixed <> Value) then
  begin
    FFixed := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetFont(const Value: TFont);
begin
  FFont.Assign(Value);
  Changed;
end;

procedure TTMSFMXGridColumn.SetFontColor(const Value: TAlphaColor);
begin
  if (FFontColor <> Value) then
  begin
    FFontColor := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetHorzAlignment(const Value: TTextAlign);
begin
  if (FHorzAlignment <> Value) then
  begin
    FHorzAlignment := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetVertAlignment(const Value: TTextAlign);
begin
  if (FVertAlignment <> Value) then
  begin
    FVertAlignment := Value;
    Changed;
  end;
end;

procedure TTMSFMXGridColumn.SetWordWrap(const Value: boolean);
begin
  if (FWordWrap <> Value) then
  begin
    FWordWrap := Value;
    Changed;
  end;
end;

end.
