{*************************************************************************************
  This file is part of Transmission Remote GUI.
  Copyright (c) 2008-2019 by Yury Sidorov and Transmission Remote GUI working group.

  Transmission Remote GUI is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  Transmission Remote GUI is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Transmission Remote GUI; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*************************************************************************************}

unit VarGrid;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Grids, VarList, Graphics, Controls, LMessages, Forms, StdCtrls, LCLType, ExtCtrls,LazUTF8, LCLVersion;

type
  TVarGrid = class;

  TCellOption = (coDrawCheckBox, coDrawTreeButton);
  TCellOptions = set of TCellOption;

  TCellAttributes = record
    Text: string;
    ImageIndex: integer;
    Indent: integer;
    Options: TCellOptions;
    State: TCheckBoxState;
    Expanded: boolean;
  end;

  TOnCellAttributes = procedure (Sender: TVarGrid; ACol, ARow, ADataCol: integer; AState: TGridDrawState; var CellAttribs: TCellAttributes) of object;
  TOnDrawCellEvent = procedure (Sender: TVarGrid; ACol, ARow, ADataCol: integer; AState: TGridDrawState; const R: TRect; var ADefaultDrawing: boolean) of object;
  TOnSortColumnEvent = procedure (Sender: TVarGrid; var ASortCol: integer) of object;
  TCellNotifyEvent = procedure (Sender: TVarGrid; ACol, ARow, ADataCol: integer) of object;
  TOnQuickSearch = procedure (Sender: TVarGrid; var SearchText: string; var ARow: integer) of object;

  { TVarGridStringEditor }

  TVarGridStringEditor = class(TStringCellEditor)
  protected
    procedure msg_SetGrid(var Msg: TGridMessage); message GM_SETGRID;
    procedure msg_SetBounds(var Msg: TGridMessage); message GM_SETBOUNDS;
  end;

  { TVarGrid }

  TVarGrid = class(TCustomDrawGrid)
  private
    FFirstVisibleColumn: integer;
    FHideSelection: boolean;
    FImages: TImageList;
    FItems: TVarList;
    FItemsChanging: boolean;
    FColumnsMap: array of integer;
    FMultiSelect: boolean;
    FOnAfterSort: TNotifyEvent;
    FOnCellAttributes: TOnCellAttributes;
    FOnCheckBoxClick: TCellNotifyEvent;
    FOnDrawCell: TOnDrawCellEvent;
    FOnEditorHide: TNotifyEvent;
    FOnEditorShow: TNotifyEvent;
    FOnQuickSearch: TOnQuickSearch;
    FOnTreeButtonClick: TCellNotifyEvent;
    FSelCount: integer;
    FAnchor: integer;
    FSortColumn: integer;
    FOnSortColumn: TOnSortColumnEvent;
    FRow: integer;
    FHintCell: TPoint;
    FCurSearch: string;
    FSearchTimer: TTimer;
    FOldOpt: TGridOptions;
    FNoDblClick: boolean;
    FStrEditor: TVarGridStringEditor;

    function GetRow: integer;
    function GetRowSelected(RowIndex: integer): boolean;
    function GetRowVisible(RowIndex: integer): boolean;
    function GetSortOrder: TSortOrder;
    procedure ItemsChanged(Sender: TObject);
    procedure SetHideSelection(const AValue: boolean);
    procedure SetRow(const AValue: integer);
    procedure SetRowSelected(RowIndex: integer; const AValue: boolean);
    procedure SetRowVisible(RowIndex: integer; const AValue: boolean);
    procedure SetSortColumn(const AValue: integer);
    procedure SetSortOrder(const AValue: TSortOrder);
    procedure UpdateColumnsMap;
    procedure UpdateSelCount;
    procedure SelectRange(OldRow, NewRow: integer);
    procedure CMHintShow(var Message: TCMHintShow); message CM_HINTSHOW;
    function CellNeedsCheckboxBitmaps(const aCol,aRow: Integer): boolean;
    procedure DrawCellCheckboxBitmaps(const aCol,aRow: Integer; const aRect: TRect);
    function FindRow(const SearchStr: string; StartRow: integer): integer;
    procedure DoSearchTimer(Sender: TObject);

  protected
    procedure SizeChanged(OldColCount, OldRowCount: Integer); override;
    procedure DrawCell(aCol,aRow: Integer; aRect: TRect; aState:TGridDrawState); override;
    procedure ColRowMoved(IsColumn: Boolean; FromIndex,ToIndex: Integer); override;
    procedure PrepareCanvas(aCol,aRow: Integer; aState:TGridDrawState); override;
    procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
    procedure MouseMove(Shift: TShiftState; X,Y: Integer);override;
    procedure KeyDown(var Key : Word; Shift : TShiftState); override;
    procedure UTF8KeyPress(var UTF8Key: TUTF8Char); override;
    procedure DoOnCellAttributes(ACol, ARow, ADataCol: integer; AState: TGridDrawState; var CellAttribs: TCellAttributes);
    procedure HeaderClick(IsColumn: Boolean; index: Integer); override;
    procedure AutoAdjustColumn(aCol: Integer); override;
    procedure VisualChange; override;
    procedure DrawColumnText(aCol,aRow: Integer; aRect: TRect; aState:TGridDrawState); override;
    procedure DblClick; override;
    procedure Click; override;
    procedure GetCheckBoxState(const aCol, aRow:Integer; var aState:TCheckboxState); override;
    procedure SetCheckboxState(const aCol, aRow:Integer; const aState: TCheckboxState); override;
    procedure SetupCell(ACol, ARow: integer; AState: TGridDrawState; out CellAttribs: TCellAttributes);
    procedure DoOnCheckBoxClick(ACol, ARow: integer);
    procedure DoOnTreeButtonClick(ACol, ARow: integer);
    function  DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; override;
    function  DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; override;
    function  DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
    procedure DrawRow(aRow: Integer); override;
    function  GetCells(ACol, ARow: Integer): string; override;
    function  GetEditText(ACol, ARow: Longint): string; override;
    procedure SetEditText(ACol, ARow: Longint; const Value: string); override;
    procedure DoEditorShow; override;
    procedure DoEditorHide; override;

  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function EditorByStyle(Style: TColumnButtonStyle): TWinControl; override;
    procedure RemoveSelection;
    procedure SelectAll;
    procedure Sort; reintroduce;
    function ColToDataCol(ACol: integer): integer;
    function DataColToCol(ADataCol: integer): integer;
    procedure EnsureSelectionVisible;
    procedure EnsureRowVisible(ARow: integer);
    procedure BeginUpdate; reintroduce;
    procedure EndUpdate(aRefresh: boolean = true); reintroduce;
    procedure EditCell(ACol, ARow: integer);

    property Items: TVarList read FItems;
    property RowSelected[RowIndex: integer]: boolean read GetRowSelected write SetRowSelected;
    property RowVisible[RowIndex: integer]: boolean read GetRowVisible write SetRowVisible;
    property SelCount: integer read FSelCount;
    property Row: integer read GetRow write SetRow;
    property FirstVisibleColumn: integer read FFirstVisibleColumn;
  published
    property Align;
    property AlternateColor;
    property Anchors;
    property BorderSpacing;
    property BorderStyle;
    property Color;
    property Columns;
    property Constraints;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Enabled;
    property FixedCols;
    property FixedRows;
    property Font;
    property GridLineWidth;
    property Options;
    property ParentColor default false;
    property ParentFont;
    property ParentShowHint default false;
    property PopupMenu;
    property RowCount;
    property ScrollBars;
    property ShowHint default True;
    property TabOrder;
    property TabStop;
    property TitleFont;
    property TitleImageList;
    property TitleStyle default tsNative;
    property Visible;

    property OnClick;
    property OnDblClick;
    property OnEnter;
    property OnExit;
    property OnHeaderClick;
    property OnHeaderSized;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnContextPopup;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDock;
    property OnEndDrag;
    property OnStartDock;
    property OnStartDrag;
    property OnUTF8KeyPress;
    property OnResize;
    property OnGetEditText;
    property OnSetEditText;

    property Images: TImageList read FImages write FImages;
    property MultiSelect: boolean read FMultiSelect write FMultiSelect default False;
    property SortColumn: integer read FSortColumn write SetSortColumn default -1;
    property SortOrder: TSortOrder read GetSortOrder write SetSortOrder default soAscending;
    property HideSelection: boolean read FHideSelection write SetHideSelection default False;

    property OnCellAttributes: TOnCellAttributes read FOnCellAttributes write FOnCellAttributes;
    property OnDrawCell: TOnDrawCellEvent read FOnDrawCell write FOnDrawCell;
    property OnSortColumn: TOnSortColumnEvent read FOnSortColumn write FOnSortColumn;
    property OnAfterSort: TNotifyEvent read FOnAfterSort write FOnAfterSort;
    property OnCheckBoxClick: TCellNotifyEvent read FOnCheckBoxClick write FOnCheckBoxClick;
    property OnTreeButtonClick: TCellNotifyEvent read FOnTreeButtonClick write FOnTreeButtonClick;
    property OnQuickSearch: TOnQuickSearch read FOnQuickSearch write FOnQuickSearch;
    property OnEditorShow: TNotifyEvent read FOnEditorShow write FOnEditorShow;
    property OnEditorHide: TNotifyEvent read FOnEditorHide write FOnEditorHide;
  end;

procedure Register;

implementation

uses Variants, Math, GraphType, lclintf, Themes, types, lclproc
    {$ifdef LCLcarbon} , carbonproc {$endif LCLcarbon};

const
  roSelected = 1;
  roCurRow   = 2;

procedure Register;
begin
  RegisterComponents('TransGUI', [TVarGrid]);
end;

{ TVarGridStringEditor }

procedure TVarGridStringEditor.msg_SetGrid(var Msg: TGridMessage);
begin
  inherited;
  Msg.Options:=Msg.Options and not EO_AUTOSIZE;
end;

procedure TVarGridStringEditor.msg_SetBounds(var Msg: TGridMessage);
var
  ca: TCellAttributes;
begin
  with Msg do begin
    TVarGrid(Grid).SetupCell(Col, Row, [], ca);
    with CellRect do begin
      Inc(Left, ca.Indent);
      if coDrawTreeButton in ca.Options then
        Inc(Left, Bottom - Top);
      if coDrawCheckBox in ca.Options then
        Inc(Left, Bottom - Top);
      if (ca.ImageIndex <> -1) and Assigned(TVarGrid(Grid).Images) then
        Inc(Left, TVarGrid(Grid).Images.Width + 2);
      Dec(Left, 3);
      Dec(Top, 1);
      SetBounds(Left, Top, Right-Left, Bottom-Top);
    end;
  end;
end;

{ TVarGrid }

procedure TVarGrid.ItemsChanged(Sender: TObject);
var
  i, OldRows, OldCols: integer;
  pt: TPoint;
begin
  FItemsChanging:=True;
  try
    Perform(CM_MouseLeave, 0, 0);  // Hack to call ResetHotCell to workaround a bug
    OldRows:=RowCount;
    OldCols:=Columns.Count;
    i:=FItems.RowCnt + FixedRows;
    if (FRow = -1) and (inherited Row >= i) and (i > FixedRows) then
      inherited Row:=i - 1;
    RowCount:=i;
    if FRow <> -1 then begin
      Row:=FRow;
      FRow:=-1;
    end;
    UpdateSelCount;
    while Columns.Count > FItems.ColCnt do
      Columns.Delete(Columns.Count - 1);
    if Columns.Count <> FItems.ColCnt then begin
      Columns.BeginUpdate;
      try
        for i:=Columns.Count to FItems.ColCnt - 1 do
          Columns.Add;
      finally
        Columns.EndUpdate;
      end;
    end;
    if (OldRows <> RowCount) or (OldCols <> Columns.Count) then begin
      if Parent <> nil then
        HandleNeeded;
      ResetSizes;
    end
    else
      Invalidate;
    pt:=ScreenToClient(Mouse.CursorPos);
    if PtInRect(ClientRect, pt) then
      MouseMove([], pt.x, pt.y);
  finally
    FItemsChanging:=False;
  end;
end;

procedure TVarGrid.SetHideSelection(const AValue: boolean);
begin
  if FHideSelection=AValue then exit;
  FHideSelection:=AValue;
  Invalidate;
end;

procedure TVarGrid.SetRow(const AValue: integer);
var
  i, r: integer;
begin
  if FItems.IsUpdating then
    FRow:=AValue
  else begin
    r:=AValue + FixedRows;
    if r <> inherited Row then begin
      i:=LeftCol;
      inherited Row:=r;
      LeftCol:=i;
    end;
  end;
end;

function TVarGrid.GetRowSelected(RowIndex: integer): boolean;
begin
  Result:=LongBool(FItems.RowOptions[RowIndex] and roSelected);
end;

function TVarGrid.GetRowVisible(RowIndex: integer): boolean;
begin
  Result:=RowHeights[RowIndex + FixedRows] > 0;
end;

function TVarGrid.GetSortOrder: TSortOrder;
begin
  Result:=inherited SortOrder;
end;

function TVarGrid.GetRow: integer;
begin
  if FItems.IsUpdating and (FRow <> -1) then
    Result:=FRow
  else begin
    Result:=inherited Row - FixedRows;
  end;
end;

procedure TVarGrid.SetRowSelected(RowIndex: integer; const AValue: boolean);
var
  i, j: integer;
begin
  i:=FItems.RowOptions[RowIndex];
  if AValue then begin
    j:=i or roSelected;
    if j <> i then
      Inc(FSelCount);
  end
  else begin
    j:=i and not roSelected;
    if j <> i then
      Dec(FSelCount);
  end;
  FItems.RowOptions[RowIndex]:=j;
  InvalidateRow(RowIndex + FixedRows);
  if FSelCount <= 1 then
    InvalidateRow(inherited Row);
end;

procedure TVarGrid.SetRowVisible(RowIndex: integer; const AValue: boolean);
begin
  if AValue then
    RowHeights[RowIndex + FixedRows]:=DefaultRowHeight
  else
    RowHeights[RowIndex + FixedRows]:=0;
end;

procedure TVarGrid.SetSortColumn(const AValue: integer);
begin
  if FSortColumn=AValue then exit;
  FSortColumn:=AValue;
  if FSortColumn >= 0 then
    Options:=Options + [goHeaderPushedLook, goHeaderHotTracking]
  else
    Options:=Options - [goHeaderPushedLook, goHeaderHotTracking];
  Sort;
end;

procedure TVarGrid.SetSortOrder(const AValue: TSortOrder);
begin
  if SortOrder = AValue then exit;
  inherited SortOrder:=AValue;
  Sort;
end;

procedure TVarGrid.UpdateColumnsMap;
var
  i, j: integer;
begin
  FFirstVisibleColumn:=-1;
  SetLength(FColumnsMap, Columns.Count);
  j:=0;
  for i:=0 to Columns.Count - 1 do
    with Columns[i] do begin
      if (FFirstVisibleColumn < 0) and Visible then
        FFirstVisibleColumn:=i;
      FColumnsMap[j]:=ID - 1;
      Inc(j);
    end;
  SetLength(FColumnsMap, j);
end;

procedure TVarGrid.UpdateSelCount;
var
  i: integer;
begin
  FSelCount:=0;
  for i:=0 to FItems.Count - 1 do
    if RowSelected[i] then
      Inc(FSelCount);
end;

procedure TVarGrid.SelectRange(OldRow, NewRow: integer);
var
  dir: integer;
  sel: boolean;
begin
  if OldRow = NewRow then
    exit;
  if FAnchor = -1 then
    FAnchor:=OldRow;
  dir:=Sign(NewRow - OldRow);
  if Sign(FAnchor - OldRow) <> Sign(FAnchor - NewRow) then
    while OldRow <> FAnchor do begin
      RowSelected[OldRow]:=False;
      Inc(OldRow, dir);
    end;
  sel:=Abs(FAnchor - OldRow) < Abs(FAnchor - NewRow);
  while OldRow <> NewRow do begin
    RowSelected[OldRow]:=sel;
    Inc(OldRow, dir);
  end;
  RowSelected[NewRow]:=True;
end;

procedure TVarGrid.CMHintShow(var Message: TCMHintShow);
var
  ca: TCellAttributes;
  pt: TPoint;
  wd: integer;
  R: TRect;
begin
  with Message.HintInfo^ do begin
    pt:=MouseToCell(CursorPos);
    if (pt.x >= FixedCols) and (pt.y >= 0) then begin
      R:=CellRect(pt.x, pt.y);
      if PtInRect(R, CursorPos) then begin
        SetupCell(pt.x, pt.y, [], ca);
        if ca.Text <> '' then begin
          wd:=Canvas.TextWidth(ca.Text);
          Inc(R.Left, ca.Indent);
          if coDrawTreeButton in ca.Options then
            Inc(R.Left, R.Bottom - R.Top);
          if coDrawCheckBox in ca.Options then
            Inc(R.Left, R.Bottom - R.Top);
          if (ca.ImageIndex <> -1) and Assigned(FImages) then
            Inc(R.Left, FImages.Width + 2);
          if (R.Right <= R.Left) or (R.Right - R.Left < wd + 5) then begin
            HintStr:=ca.Text;
            R.Top:=(R.Top + R.Bottom - Canvas.TextHeight(ca.Text)) div 2 - 4;
            Dec(R.Left);
            HintPos:=ClientToScreen(R.TopLeft);
          end;
          FHintCell:=pt;
        end
        else
          Message.Result:=1;
      end
      else
        Message.Result:=1;
    end;
  end;
end;

function TVarGrid.CellNeedsCheckboxBitmaps(const aCol, aRow: Integer): boolean;
var
  C: TGridColumn;
begin
  Result := false;
  if (aRow>=FixedRows) and Columns.Enabled then begin
    C := ColumnFromGridColumn(aCol);
    result := (C<>nil) and (C.ButtonStyle=cbsCheckboxColumn)
  end;
end;

procedure TVarGrid.DrawCellCheckboxBitmaps(const aCol, aRow: Integer; const aRect: TRect);
var
  AState: TCheckboxState;
begin
  AState := cbUnchecked;
  GetCheckBoxState(aCol, aRow, aState);
  DrawGridCheckboxBitmaps(aCol, aRow, aRect, aState);
end;

function TVarGrid.FindRow(const SearchStr: string; StartRow: integer): integer;
var
  i, c: integer;
  s, ss: string;
  v: variant;
begin
  Result:=-1;
  if Columns.Count = 0 then
    exit;
  c:=SortColumn;
  if (c < 0) or (c >= Items.ColCnt) then
    c:=0;
  ss:= LazUTF8.UTF8UpperCase(SearchStr);
  for i:=StartRow to Items.Count - 1 do begin
    v:=Items[c, i];
    if VarIsNull(v) or VarIsEmpty(v) then
      s:= ''
    else
      s:= LazUTF8.UTF8UpperCase(UTF8Encode(widestring(v)));
    if Copy(s, 1, Length(ss)) = ss then begin
      Result:=i;
      break;
    end;
  end;
end;

procedure TVarGrid.DoSearchTimer(Sender: TObject);
begin
  FSearchTimer.Enabled:=False;
  FCurSearch:='';
end;

procedure TVarGrid.SizeChanged(OldColCount, OldRowCount: Integer);
begin
  if not FItemsChanging and (FItems <> nil) then begin
    FItems.ColCnt:=Columns.Count;
    FItems.RowCnt:=RowCount - FixedRows;
    UpdateColumnsMap;
  end;
  inherited;
end;

procedure TVarGrid.DrawCell(aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState);
var
  ca: TCellAttributes;
//  ts: TTextStyle;
  dd, IsHeader: boolean;
  R, RR: TRect;
  det: TThemedElementDetails;
  sz: TSize;
  i: integer;
begin
  RR:=aRect;
  IsHeader:=(gdFixed in aState) and (aRow=0) and (aCol>=FirstGridColumn);
  if not IsHeader and MultiSelect and (FSelCount > 0) then
    if (aRow >= FixedRows) and (aCol >= FixedCols) and RowSelected[aRow - FixedRows] then
      Include(aState, gdSelected)
    else
      Exclude(aState, gdSelected);

  PrepareCanvas(aCol, aRow, aState);
  if DefaultDrawing then
    SetupCell(aCol, aRow, aState, ca);
  if not IsHeader or (TitleStyle<>tsNative) then
    Canvas.FillRect(aRect);

  if not IsHeader then begin
    dd:=True;
    if Assigned(FOnDrawCell) then begin
      R:=CellRect(aCol, aRow);
      if goVertLine in Options then
        Dec(R.Right, 1);
      if goHorzLine in Options then
        Dec(R.Bottom, 1);
      FOnDrawCell(Self, aCol, aRow - FixedRows, ColToDataCol(aCol), aState, R, dd);
    end;

    if DefaultDrawing and dd then begin
      if CellNeedsCheckboxBitmaps(aCol,aRow) then
        DrawCellCheckboxBitmaps(aCol,aRow,aRect)
      else begin
        Inc(aRect.Left, ca.Indent);
        if coDrawTreeButton in ca.Options then begin
          R:=aRect;
          R.Right:=R.Left + (R.Bottom - R.Top);
          aRect.Left:=R.Right;
          if ThemeServices.ThemesEnabled then begin
            if ca.Expanded then
              det:=ThemeServices.GetElementDetails(ttGlyphOpened)
            else
              det:=ThemeServices.GetElementDetails(ttGlyphClosed);
            sz:=ThemeServices.GetDetailSize(det);
            with R do begin
              Left:=(Left + Right - sz.cx) div 2;
              Top:=(Top + Bottom - sz.cy) div 2;
              R:=Bounds(Left, Top, sz.cx, sz.cy);
            end;
            ThemeServices.DrawElement(Canvas.Handle, det, R, nil);
          end
          else
            with Canvas do begin
              i:=(R.Bottom - R.Top) div 4;
              InflateRect(R, -i, -i);
              if (R.Right - R.Left) and 1 = 0 then
                Dec(R.Right);
              if (R.Bottom - R.Top) and 1 = 0 then
                Dec(R.Bottom);
              Pen.Color:=clWindowText;
              Rectangle(R);
              InflateRect(R, -1, -1);
              Brush.Color:=clWindow;
              FillRect(R);
              InflateRect(R, -1, -1);
              i:=(R.Top + R.Bottom) div 2;
              MoveTo(R.Left, i);
              LineTo(R.Right, i);
              if not ca.Expanded then begin
                i:=(R.Left + R.Right) div 2;
                MoveTo(i, R.Top);
                LineTo(i, R.Bottom);
              end;
            end;
        end;
        if coDrawCheckBox in ca.Options then begin
          R:=aRect;
          R.Right:=R.Left + (R.Bottom - R.Top);
          aRect.Left:=R.Right;
          DrawGridCheckboxBitmaps(aCol, aRow, R, ca.State);
        end;
        if (ca.ImageIndex <> -1) and Assigned(FImages) then begin
          FImages.Draw(Canvas, aRect.Left + 2, (aRect.Bottom + aRect.Top - FImages.Height) div 2, ca.ImageIndex, gdeNormal);
          Inc(aRect.Left, FImages.Width + 2);
        end;
        if ca.Text <> '' then begin
{
          if Canvas.TextStyle.Alignment <> taLeftJustify then
            if (aRect.Right <= aRect.Left) or (aRect.Right - aRect.Left < Canvas.TextWidth(ca.Text) + 9) then begin
              ts:=Canvas.TextStyle;
              ts.Alignment:=taLeftJustify;
              Canvas.TextStyle:=ts;
            end;
          DrawCellText(aCol, aRow, aRect, aState, ca.Text);
}
          with aRect do begin
            Inc(Top, 2);
            Inc(Left, constCellPadding);
            Dec(Right, constCellPadding);
            if Right<Left then
              Right:=Left;
            if Left>Right then
              Left:=Right;
            if Bottom<Top then
              Bottom:=Top;
            if Top>Bottom then
              Top:=Bottom;

            if (Left <> Right) and (Top <> Bottom) then begin
              if Canvas.TextStyle.Alignment <> taLeftJustify then begin
                i:=Canvas.TextWidth(ca.Text);
                if i < Right - Left then
                  case Canvas.TextStyle.Alignment of
                    taRightJustify:
                      Left:=Right - i;
                    taCenter:
                      Left:=(Left + Right - i) div 2;
                  end;
              end;
              ExtUTF8Out(Canvas.Handle, Left, Top, ETO_OPAQUE or ETO_CLIPPED, @aRect, PChar(ca.Text), Length(ca.Text), nil);
            end;
          end;

        end;
      end;
    end;
  end;
  if gdFixed in aState then
    DefaultDrawCell(aCol, aRow, RR, aState)
  else
    DrawCellGrid(aCol, aRow, RR, aState);
end;

procedure TVarGrid.ColRowMoved(IsColumn: Boolean; FromIndex, ToIndex: Integer);
begin
  inherited ColRowMoved(IsColumn, FromIndex, ToIndex);
  UpdateColumnsMap;
end;

procedure TVarGrid.PrepareCanvas(aCol, aRow: Integer; aState: TGridDrawState);
var
  F: TCustomForm;
begin
  if FHideSelection and (FSelCount = 0) then begin
    F:=GetParentForm(Self);
    if (F <> nil) and (F.ActiveControl <> Self) then
      aState:=aState - [gdSelected];
  end;
  inherited PrepareCanvas(aCol, aRow, aState);
  with Canvas do
    if (Font.Color = clWindow) and (Brush.Color = clHighlight) then begin
      Font.Color:=clHighlightText;
{$ifdef LCLgtk2}
      Brush.Color:=ColorToRGB(Brush.Color); // Workaround for LCL bug
{$endif LCLgtk2}
  end;
end;

procedure TVarGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  pt: TPoint;
  IsCtrl, CheckBoxClicked: boolean;
  ca: TCellAttributes;
  R, RR: TRect;
begin
{$ifdef LCLcarbon}
  IsCtrl:=ssMeta in GetCarbonShiftState;
{$else}
  IsCtrl:=ssCtrl in Shift;
{$endif LCLcarbon}
  CheckBoxClicked:=False;
  pt:=MouseToCell(Point(X,Y));
  if ssLeft in Shift then begin
    SetupCell(pt.x, pt.y, [], ca);
    RR:=CellRect(pt.x, pt.y);
    Inc(RR.Left, ca.Indent);
    if (RR.Left <= RR.Right) and (coDrawTreeButton in ca.Options) then begin
      R:=RR;
      R.Right:=R.Left + (R.Bottom - R.Top);
      if R.Right > RR.Right then
        R.Right:=RR.Right;
      if PtInRect(R, Point(X,Y)) then begin
        DoOnTreeButtonClick(pt.x, pt.y);
        InvalidateCell(pt.x, pt.y);
        if Assigned(OnDblClick) and (ssDouble in Shift) then
          FNoDblClick:=True;
      end;
      Inc(RR.Left, RR.Bottom - RR.Top);
    end;
    if (RR.Left <= RR.Right) and (coDrawCheckBox in ca.Options) then begin
      R:=RR;
      R.Right:=R.Left + (R.Bottom - R.Top);
      if R.Right > RR.Right then
        R.Right:=RR.Right;
      if PtInRect(R, Point(X,Y)) then begin
        DoOnCheckBoxClick(pt.x, pt.y);
        InvalidateCell(pt.x, pt.y);
        CheckBoxClicked:=True;
        if Assigned(OnDblClick) and (ssDouble in Shift) then
          FNoDblClick:=True;
      end;
    end;
  end;
  if (ssRight in Shift) {$ifdef darwin} or (Shift*[ssLeft, ssCtrl] = [ssLeft, ssCtrl]) {$endif} then begin
    SetFocus;
    if (pt.x >= FixedCols) and (pt.y >= FixedRows) then begin
      if MultiSelect and (SelCount > 0) and not RowSelected[pt.y - FixedRows] then
        RemoveSelection;
      Row:=pt.y - FixedRows;
    end;
  end
  else
    if MultiSelect and (ssLeft in Shift) and (pt.x >= FixedCols) and (pt.y >= FixedRows) then begin
      if IsCtrl then begin
        if SelCount = 0 then
          RowSelected[Row]:=True;
        RowSelected[pt.y - FixedRows]:=not RowSelected[pt.y - FixedRows];
        FAnchor:=-1;
      end
      else
        if ssShift in Shift then
          SelectRange(Row, pt.y - FixedRows)
        else begin
          if (SelCount > 0) and not CheckBoxClicked then
            RemoveSelection;
          FAnchor:=-1;
        end;
    end;
  inherited MouseDown(Button, Shift, X, Y);
end;

procedure TVarGrid.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  pt: TPoint;
begin
  NullStrictConvert := False;
  inherited MouseMove(Shift, X, Y);
  pt:=MouseToCell(Point(x, y));
  if (FHintCell.x <> -1) and ((FHintCell.x <> pt.x) or (FHintCell.y <> pt.y)) then begin
    Application.CancelHint;
    FHintCell.x:=-1;
  end;
end;

procedure TVarGrid.KeyDown(var Key: Word; Shift: TShiftState);
var
  r, k: integer;
  ca: TCellAttributes;
begin
  if EditorMode then begin
    if Key = VK_ESCAPE then begin
      EditorHide;
      SetFocus;
    end;
    exit;
  end;

  r:=Row;
  k:=Key;

  if (Shift = []) and ( (k = VK_SPACE) or (k = VK_LEFT) or (k = VK_RIGHT) or (k = VK_ADD) or (k = VK_SUBTRACT) ) then begin
    SetupCell(FixedCols, inherited Row, [], ca);
    case k of
      VK_SPACE:
        if coDrawCheckBox in ca.Options then begin
          DoOnCheckBoxClick(FixedCols, inherited Row);
          Key:=0;
          exit;
        end;
      VK_LEFT, VK_SUBTRACT:
        if (coDrawTreeButton in ca.Options) and ca.Expanded then begin
          DoOnTreeButtonClick(FixedCols, inherited Row);
          Key:=0;
          exit;
        end;
      VK_RIGHT, VK_ADD:
        if (coDrawTreeButton in ca.Options) and not ca.Expanded then begin
          DoOnTreeButtonClick(FixedCols, inherited Row);
          Key:=0;
          exit;
        end;
    end;
  end;

  inherited KeyDown(Key, Shift);

  if MultiSelect then begin
    if ssCtrl in Shift then begin
      if k = VK_SPACE then
        RowSelected[Row]:=not RowSelected[Row];
      FAnchor:=-1;
    end
    else
      if ssShift in Shift then begin
        SelectRange(r, Row);
      end
      else
        if k in [VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_HOME, VK_END, VK_NEXT, VK_PRIOR] then begin
          if SelCount > 0 then
            RemoveSelection;
          FAnchor:=-1;
        end;
  end;
  if (Key = VK_RETURN) and (Shift = []) and Assigned(OnDblClick) then
    OnDblClick(Self);
end;

procedure TVarGrid.UTF8KeyPress(var UTF8Key: TUTF8Char);
var
  i, r: integer;
begin
  inherited UTF8KeyPress(UTF8Key);
  if UTF8Key = #0 then
    exit;
  FSearchTimer.Enabled:=False;
  FSearchTimer.Enabled:=True;
  if FCurSearch = '' then
    i:=0
  else
    i:=Row;
  FCurSearch:=FCurSearch + UTF8Key;
  if Assigned(FOnQuickSearch) then begin
    r:=i;
    FOnQuickSearch(Self, FCurSearch, r);
    if r <> i then
      Row:=r;
  end
  else begin
    i:=FindRow(FCurSearch, i);
    if i >= 0 then
      Row:=i;
  end;
end;

procedure TVarGrid.DoOnCellAttributes(ACol, ARow, ADataCol: integer; AState: TGridDrawState; var CellAttribs: TCellAttributes);
begin
  if Assigned(FOnCellAttributes) then
    FOnCellAttributes(Self, ACol, ARow, ADataCol, AState, CellAttribs);
end;

procedure TVarGrid.HeaderClick(IsColumn: Boolean; index: Integer);
var
  i: integer;
begin
  inherited HeaderClick(IsColumn, index);
  if IsColumn and (FSortColumn >= 0) then begin
    fGridState:=gsNormal;
    i:=ColToDataCol(index);
    if FSortColumn = i then begin
      if SortOrder = soAscending then
        SortOrder:=soDescending
      else
        SortOrder:=soAscending;
    end
    else begin
      SortOrder:=soAscending;
      SortColumn:=i;
    end;
  end;
end;

procedure TVarGrid.AutoAdjustColumn(aCol: Integer);
var
  i, j, wd, h, fr: integer;
  ca: TCellAttributes;
begin
  wd:=4;
  fr:=FixedRows;
  for i:=0 to FItems.Count - 1 do begin
    h:=RowHeights[i + fr];
    if h > 0 then begin
      SetupCell(aCol, i + fr, [], ca);
      j:=Canvas.TextWidth(ca.Text) + 6;
      Inc(j, ca.Indent);
      if coDrawTreeButton in ca.Options then
        Inc(j, h);
      if coDrawCheckBox in ca.Options then
        Inc(j, h);
      if (ca.ImageIndex <> -1) and Assigned(FImages) then
        Inc(j, FImages.Width + 2);
      if j > wd then
        wd:=j;
    end;
  end;
  ColumnFromGridColumn(aCol).Width:=wd;
end;

procedure TVarGrid.VisualChange;
begin
  inherited VisualChange;
  if HandleAllocated then
    DefaultRowHeight:=Canvas.TextHeight('Xy') + 5;
  UpdateColumnsMap;
end;

procedure TVarGrid.DrawColumnText(aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState);
var
  R: TRect;
  i: integer;
begin
  if (gdFixed in aState) and (aRow=0) and (aCol>=FirstGridColumn) then begin
    R:=aRect;
    if FSortColumn = ColToDataCol(aCol) then begin
      R.Right:=R.Left + R.Bottom - R.Top;
      InflateRect(R, -5, -5);
      OffsetRect(R, -3, 0);
      Dec(R.Bottom, 2);
      aRect.Left:=R.Right + 2;
    end;
    inherited DrawColumnText(aCol, aRow, aRect, aState);
    if FSortColumn = ColToDataCol(aCol) then
      with Canvas do begin
        Pen.Color:=clGrayText;
        i:=(R.Left + R.Right ) div 2;
        if SortOrder = soAscending then begin
          MoveTo(i + (i - R.Left) - 1, R.Bottom - 1);
          LineTo(i, R.Top - 1);
          MoveTo(i, R.Top);
          LineTo(i - (R.Right - i) + 1, R.Bottom);
          LineTo(R.Right, R.Bottom);
        end
        else begin
          MoveTo(i + (i - R.Left) - 1, R.Top + 1);
          LineTo(i, R.Bottom + 1);
          MoveTo(i, R.Bottom);
          LineTo(i - (R.Right - i) + 1, R.Top);
          LineTo(R.Right, R.Top);
        end;
      end;
  end;
end;


procedure TVarGrid.DblClick;
var
  pt: TPoint;
begin
  if FNoDblClick then begin
    FNoDblClick:=False;
    exit;
  end;
  pt:=MouseToCell(ScreenToClient(Mouse.CursorPos));
  if (pt.y < FixedRows) and (pt.y = 0) and (Cursor <> crHSplit) then
    exit;
  inherited DblClick;
end;

procedure TVarGrid.Click;
begin
  if Assigned(OnClick) then
    OnClick(Self);
end;

procedure TVarGrid.GetCheckBoxState(const aCol, aRow: Integer; var aState: TCheckboxState);
var
  s: string;
begin
  if (aCol >= FixedCols) and (aRow >= FixedRows) then begin
    s:=Items[ColToDataCol(aCol), aRow - FixedRows];
    with Columns[GridColumnFromColumnIndex(aCol)] do
      if s = ValueChecked then
        aState:=cbChecked
      else
        if s = ValueUnchecked then
          aState:=cbUnchecked
        else
          aState:=cbGrayed;
  end;
  inherited GetCheckBoxState(aCol, aRow, aState);
end;

procedure TVarGrid.SetCheckboxState(const aCol, aRow: Integer; const aState: TCheckboxState);
var
  s: string;
begin
  if (aCol >= FixedCols) and (aRow >= FixedRows) then begin
    with Columns[GridColumnFromColumnIndex(aCol)] do
      case aState of
        cbUnchecked:
          s:=ValueUnchecked;
        cbChecked:
          s:=ValueChecked;
        else
          s:='?';
      end;
    Items[ColToDataCol(aCol), aRow - FixedRows]:=s;
  end;
  inherited SetCheckboxState(aCol, aRow, aState);
end;

procedure TVarGrid.SetupCell(ACol, ARow: integer; AState: TGridDrawState; out CellAttribs: TCellAttributes);
var
  v: variant;
  dc: integer;
begin
  if (ACol < 0) or (ARow < 0) then
    exit;
  CellAttribs.ImageIndex:=-1;
  CellAttribs.Indent:=0;
  CellAttribs.Options:=[];
  CellAttribs.State:=cbUnchecked;
  CellAttribs.Expanded:=True;
  if ACol >= FixedCols then begin
    dc:=ColToDataCol(ACol);
    if ARow >= FixedRows then begin
      v:=Items[dc, ARow - FixedRows];
      if not VarIsNull(v) and not VarIsEmpty(v) then
        CellAttribs.Text:=UTF8Encode(WideString(v))
      else
        CellAttribs.Text:='';
    end
    else
      CellAttribs.Text:=ColumnFromGridColumn(ACol).Title.Caption;
  end
  else
    dc:=-1;
  DoOnCellAttributes(ACol - FixedCols, ARow - FixedRows, dc, AState, CellAttribs);
end;

procedure TVarGrid.DoOnCheckBoxClick(ACol, ARow: integer);
var
  i, dc, c: integer;
  ca: TCellAttributes;
  st: TCheckBoxState;
begin
  if Assigned(FOnCheckBoxClick) then begin
    dc:=ColToDataCol(ACol);
    c:=ACol - FixedCols;
    FOnCheckBoxClick(Self, c, ARow - FixedRows, dc);
    if (SelCount > 0) and RowSelected[ARow - FixedRows] then begin
      SetupCell(ACol, ARow, [], ca);
      st:=ca.State;
      for i:=0 to Items.Count - 1 do
        if RowSelected[i] then begin
          SetupCell(ACol, i + FixedRows, [], ca);
          if (coDrawCheckBox in ca.Options) and (ca.State <> st) then
            FOnCheckBoxClick(Self, c, i, dc);
        end;
    end;
  end;
end;

procedure TVarGrid.DoOnTreeButtonClick(ACol, ARow: integer);
begin
  if Assigned(FOnTreeButtonClick) then
    FOnTreeButtonClick(Self, ACol - FixedCols, ARow - FixedRows, ColToDataCol(ACol));
end;

function TVarGrid.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
  Result := False;
  if Assigned(OnMouseWheelDown) then
    OnMouseWheelDown(Self, Shift, MousePos, Result);
end;

function TVarGrid.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
begin
  Result := False;
  if Assigned(OnMouseWheelUp) then
    OnMouseWheelUp(Self, Shift, MousePos, Result);
end;

function TVarGrid.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean;
begin
  Result:=inherited DoMouseWheel(Shift, WheelDelta, MousePos);
  if not Result then begin
    if Mouse.WheelScrollLines = -1 then
  {$IF LCL_FULLVERSION < 1080000}
      GridMouseWheel(Shift, -WheelDelta*VisibleRowCount div 120)
    else
      GridMouseWheel(Shift, -WheelDelta*Mouse.WheelScrollLines div 120);
  {$ENDIF}
  {$IF LCL_FULLVERSION >= 1080000}
      GridMouseWheel(Shift, WheelDelta*VisibleRowCount div 120)
    else
      GridMouseWheel(Shift, -WheelDelta div 120);
  {$ENDIF}
      Result := True;
  end;
end;

constructor TVarGrid.Create(AOwner: TComponent);
begin
  FRow:=-1;
  FHintCell.x:=-1;
  inherited Create(AOwner);
  FixedRows:=1;
  FixedCols:=0;
  Options:=[goRowSelect, goThumbTracking, goVertLine, goHorzLine, goColSizing, goColMoving, goDblClickAutoSize, goFixedHorzLine, goFixedVertLine];
  MouseWheelOption:=mwGrid;
  FItems:=TVarList.Create(1, 0);
  FItems.OnDataChanged:=@ItemsChanged;
  ItemsChanged(nil);
  TitleStyle:=tsNative;
  FAnchor:=-1;
  FSortColumn:=-1;
  ShowHint:=True;
  SetLength(FColumnsMap, 1);
  FColumnsMap[0]:=0;
  FSearchTimer:=TTimer.Create(Self);
  with FSearchTimer do begin
    Enabled:=False;
    Interval:=1500;
    OnTimer:=@DoSearchTimer;
  end;
  FastEditing:=False;
  EditorBorderStyle:=bsSingle;
end;

destructor TVarGrid.Destroy;
begin
  inherited Destroy;
  FItems.Free;
end;

function TVarGrid.EditorByStyle(Style: TColumnButtonStyle): TWinControl;
begin
  if Style = cbsAuto then begin
    if FStrEditor = nil then begin
      FStrEditor:=TVarGridStringEditor.Create(Self);
      FStrEditor.Name :='VGStringEditor';
      FStrEditor.Text:='';
      FStrEditor.Visible:=False;
      FStrEditor.Align:=alNone;
      FStrEditor.BorderStyle:=bsSingle;
    end;
    Result:=FStrEditor;
  end
  else
    Result:=inherited EditorByStyle(Style);
end;

procedure TVarGrid.RemoveSelection;
var
  i: integer;
begin
  for i:=0 to FItems.Count - 1 do
    RowSelected[i]:=False;
  FSelCount:=0;
end;

procedure TVarGrid.SelectAll;
var
  i: integer;
begin
  for i:=0 to FItems.Count - 1 do
    RowSelected[i]:=True;
end;

procedure TVarGrid.Sort;
var
  i, c: integer;
begin
  if (FSortColumn >= 0) and (FItems.Count > 0) then begin
    c:=FSortColumn;
    if Assigned(FOnSortColumn) then
      FOnSortColumn(Self, c);
    if not FItems.IsUpdating and (Row >= 0) and (Row < FItems.Count) then
      FItems.RowOptions[Row]:=FItems.RowOptions[Row] or roCurRow;
    FItems.Sort(c, SortOrder = soDescending);
    if not FItems.IsUpdating then begin
      if Assigned(FOnAfterSort) then
        FOnAfterSort(Self);
      for i:=0 to FItems.Count - 1 do
        if LongBool(FItems.RowOptions[i] and roCurRow) then begin
          FItems.RowOptions[i]:=FItems.RowOptions[i] and not roCurRow;
          Row:=i;
          break;
        end;
      Invalidate;
    end;
  end;
end;

function TVarGrid.ColToDataCol(ACol: integer): integer;
begin
  if (ACol >= FixedCols) and (ACol <= High(FColumnsMap)) then
    Result:=FColumnsMap[ACol]
  else
    Result:=-1;
end;

function TVarGrid.DataColToCol(ADataCol: integer): integer;
var
  i: integer;
begin
  for i:=FixedCols to High(FColumnsMap) do
    if FColumnsMap[i] = ADataCol then begin
      Result:=i;
      exit;
    end;
  Result:=-1;
end;

procedure TVarGrid.EnsureSelectionVisible;
var
  i: integer;
begin
  if FSelCount > 0 then
    for i:=0 to FItems.Count - 1 do
      if RowSelected[i] then begin
        Row:=i;
        break;
      end;
  EnsureRowVisible(Row);
end;

procedure TVarGrid.EnsureRowVisible(ARow: integer);
begin
  ARow:=ARow + FixedRows;
  if ARow < TopRow then
    TopRow:=ARow
  else
    if ARow > GCache.FullVisibleGrid.Bottom then
      TopRow:=ARow - (GCache.FullVisibleGrid.Bottom - GCache.FullVisibleGrid.Top);
end;

procedure TVarGrid.BeginUpdate;
begin
  inherited BeginUpdate;
  Items.BeginUpdate;
end;

procedure TVarGrid.EndUpdate(aRefresh: boolean);
begin
  inherited EndUpdate(aRefresh);
  Items.EndUpdate;
end;

procedure TVarGrid.EditCell(ACol, ARow: integer);
begin
  SetFocus;
  FOldOpt:=Options;
  Options:=Options + [goEditing];
  EditorShowInCell(DataColToCol(ACol), ARow + FixedRows);
end;

procedure TVarGrid.DrawRow(aRow: Integer);
var
  Gds: TGridDrawState;
  aCol: Integer;
  Rs: Boolean;
  R: TRect;
  ClipArea: Trect;
{$ifdef LCLgtk2}
  Rgn: HRGN;
{$endif LCLgtk2}

  procedure DoDrawCell;
  begin
    with GCache do begin
      if (aCol=HotCell.x) and (aRow=HotCell.y) and not ((PushedCell.X<>-1) and (PushedCell.Y<>-1)) then begin
        Include(gds, gdHot);
        HotCellPainted:=True;
      end;
      if ClickCellPushed and (aCol=PushedCell.x) and (aRow=PushedCell.y) then begin
        Include(gds, gdPushed);
      end;
    end;
{$ifdef LCLgtk2}
    Rgn := CreateRectRgn(R.Left, R.Top, R.Right, R.Bottom);
    SelectClipRgn(Canvas.Handle, Rgn);
    DeleteObject(Rgn);
{$endif LCLgtk2}
    DrawCell(aCol, aRow, R, gds);
  end;

  function HorizontalIntersect(const aRect,bRect: TRect): boolean;
  begin
    result := (aRect.Left < bRect.Right) and (aRect.Right > bRect.Left);
  end;

begin
  Rs := false;
  // Upper and Lower bounds for this row
  R.Left:=0;
  ColRowToOffSet(False, True, aRow, R.Top, R.Bottom);
  if R.Bottom <= R.Top then
    exit;
  // is this row within the ClipRect?
  ClipArea := Canvas.ClipRect;
  if (R.Top >= ClipArea.Bottom) or (R.Bottom < ClipArea.Top) then
    exit;
  // Draw columns in this row
  with GCache.VisibleGrid do begin
    for aCol:=left to Right do begin
      ColRowToOffset(True, True, aCol, R.Left, R.Right);
      if (R.Right <= R.Left) or not HorizontalIntersect(R, ClipArea) then
        continue;
      gds := [];
      Rs := (goRowSelect in Options);
      if ARow<FixedRows then
        include(gds, gdFixed)
      else begin
        if (aCol=Col)and(aRow=inherited Row) then
          gds := gds + [gdFocused, gdSelected]
        else
        if IsCellSelected[aCol, aRow] then
          include(gds, gdSelected);
      end;

      DoDrawCell;
    end;

    // Draw Fixed Columns
    For aCol:=0 to FixedCols-1 do begin
      gds:=[gdFixed];
      ColRowToOffset(True, True, aCol, R.Left, R.Right);
      // is this column within the ClipRect?
      if (R.Right > R.Left) and HorizontalIntersect(R, ClipArea) then
        DoDrawCell;
    end;

{$ifdef LCLgtk2}
    with ClipArea do
      Rgn := CreateRectRgn(Left, Top, Right, Bottom);
    SelectClipRgn(Canvas.Handle, Rgn);
    DeleteObject(Rgn);
{$endif LCLgtk2}

    // Draw the focus Rect
    if FocusRectVisible and (ARow=inherited Row) and
      ((Rs and (ARow>=Top) and (ARow<=Bottom)) or IsCellVisible(Col,ARow))
    then begin
      if EditorMode then begin
      //if EditorAlwaysShown and (FEditor<>nil) and FEditor.Visible then begin
        //DebugLn('No Draw Focus Rect');
      end else begin
        ColRowToOffset(True, True, Col, R.Left, R.Right);
        // is this column within the ClipRect?
        if HorizontalIntersect(R, ClipArea) then
          DrawFocusRect(Col,inherited Row, R);
      end;
    end;
  end;
end;

function TVarGrid.GetCells(ACol, ARow: Integer): string;
var
  dc: integer;
  v: variant;
begin
  Result:='';
  dc:=ColToDataCol(ACol);
  if ARow >= FixedRows then begin
    v:=Items[dc, ARow - FixedRows];
    if not VarIsNull(v) and not VarIsEmpty(v) then
      Result:=UTF8Encode(WideString(v));
  end;
end;

function TVarGrid.GetEditText(ACol, ARow: Longint): string;
begin
  Result:=GetCells(ACol, ARow);
  if Assigned(OnGetEditText) then
    OnGetEditText(self, aCol - FixedCols, aRow - FixedRows, Result);
end;

procedure TVarGrid.SetEditText(ACol, ARow: Longint; const Value: string);
var
  dc: integer;
begin
  if not (gfEditingDone in GridFlags) then
    exit;
  if Assigned(OnSetEditText) then
    OnSetEditText(Self, aCol - FixedCols, aRow - FixedRows, Value)
  else begin
    dc:=ColToDataCol(ACol);
    if ARow >= FixedRows then
      Items[dc, ARow - FixedRows]:=UTF8Decode(Value);
  end;
end;

procedure TVarGrid.DoEditorShow;
begin
  inherited DoEditorShow;
  if Assigned(OnEditorShow) then
    OnEditorShow(Self);
end;

procedure TVarGrid.DoEditorHide;
begin
  try
    inherited DoEditorHide;
  finally
    Options:=FOldOpt;
  end;
  if Assigned(OnEditorHide) then
    OnEditorHide(Self);
end;

end.

