{ ------------------------------------------------------------- }
{ Purpose : NextDBGrid 6                                        }
{ By      : Bojan Nikolic                                       }
{ For     : BergSoft                                            }
{ ------------------------------------------------------------- }
{ Copyright (C) BergSoft.net 2019. All Rights Reserved.         }
{ ------------------------------------------------------------- }
{$I '..\NxSuite6.inc'}
unit NxDBGrid6;

interface

uses
  { Standard }
  Classes, Types, Windows, Messages, Graphics, SysUtils,
  { Data }
  DB,
  { Next Standard 6 }
  NxIntf6, NxClasses6, NxConsts6, NxTypes6,
  { NextGrid 6 }
  NxVirtualGrid6, NxCustomGrid6, NxColumns6, NxDBColumns6, NxCells6, NxDBCells6;

type
  TNextDBGrid6 = class;

  { TNxDBGridDataLink6 }

  TNxDBGridDataLink6 = class(TDataLink)
  private
    FActiveField: TField;
    FDataGrid: TNextDBGrid6;
    FRecordCount: Integer;
    FRecNo: Integer;
    FSelectedIndex: Integer;
    function GetCurrentRecord: TBookmark;
    procedure SetActiveField(const Value: TField);
  protected
    procedure ActiveChanged; override;
    procedure DataEvent(Event: TDataEvent; Info: {$IFDEF NATIVE_INT}NativeInt{$ELSE}Longint{$ENDIF}); override;
    procedure DataSetScrolled(Distance: Integer); override;
    procedure DataSetChanged; override;
    {  }
    procedure EditingChanged; override;
    { `Post` called }
    procedure UpdateData; override;
  public
    constructor Create(ADataGrid: TNextDBGrid6);
    function LocateTo(const RowIndex: Integer): Boolean;
    procedure MoveByRecord(const Delta: Integer);
    function Recount: Boolean;
    procedure Relocate;
    { Properties }
    property ActiveField: TField read FActiveField write SetActiveField;
    property CurrentRecord: TBookmark read GetCurrentRecord;
    property RectNo: Integer read FRecNo;
  end;

  { TNxDBRecordList }

  TNxDBRecordList = class(TNxSortableObject)
  private
    FDataLink: TNxDBGridDataLink6;
    FDataGrid: TNextDBGrid6;
    FList: TInterfaceList;
    FSorted: Boolean;
    function GetBookmark(const ARow: Integer): TBookmark;
    function GetRecordIndex(const ARow: Integer): Integer;
    procedure SetRecordIndex(const ARow, Value: Integer);
    procedure SetSorted(const Value: Boolean);
  protected
    function Compare(const Item1, Item2: TBookmark): Integer;
  public
    constructor Create(ACells: INxCells; AHeight: Integer); override;
    destructor Destroy; override;
    { Methods }
    procedure Clear; virtual;
    procedure Compact(RowHeight: Integer);
    function Add(ActiveIndex: Integer; Bookmark: TBookmark): INxRecord;
    function IndexOfBookmark(const Bookmark: TBookmark): Integer;
    procedure AssignIndexes;
    function GotoRecord(const Index: Integer): Boolean;
    { Properties }
    property Bookmark[const ARow: Integer]: TBookmark read GetBookmark;
    property RecordIndex[const ARow: Integer]: Integer read GetRecordIndex write
      SetRecordIndex;
    property Sorted: Boolean read FSorted write SetSorted;
  end;

  { TNxDBCellSource6 }

  TNxDBCellSource6 = class(TNxDBRecordList, INxCells, INxDBCells)
  private
    FCell: TNxFieldCell;
    FColIndex: Integer;
    FRow: INxRow;
    FRowIndex: Integer;
    FSortedCol: Integer;
    FSortedColumn: TNxColumn6;
    FSortKind: TNxSortKind;
    function GetDataBinding(Index: Integer): TNxDataBinding;
  protected
    { INxDBCells }
    procedure MoveTo(const Index: Integer);
    procedure RevertLocation;

    function AbsoluteIndexOf(Row: INxRow): Integer;
    procedure AddColumn;
    function AddRow(const Count: Integer; IgnoreDefaults: Boolean): INxCellsRow;
    procedure CellChanged(Cell: INxCell); virtual;
    procedure DeleteColumn(const Index: Integer);
    procedure DeleteRow(const Index: Integer);
    procedure FindFirstVisible;
    procedure FindLastVisible;
    function GetCell(ACol, ARow: Integer): INxCell;
    function GetChildRow(const Index: Integer): INxRow;
    function GetChildRowCount: Integer;
    function GetControl: INxCellsControl;
    function GetField(ACol, ARow: Integer): TField;
    function GetFirstVisible: Integer;
    function GetLastVisible: Integer;
    function GetLocation: TPoint;
    function GetRow(const Index: Integer): INxRow;
    function GetRowCount: Integer;
    function GetRowsBetween(FromPos, ToPos: Integer): Integer;
    function GetShowing(const Index: Integer): Boolean;
    function GetShowingCount: Integer;
    function HasLocation: Boolean;
    procedure IncRowCount(const Delta: Integer);
    function IndexOf(Row: INxRow): Integer;
    function InsertRow(const Index, Count: Integer; IgnoreDefaults: Boolean): INxCellsRow;
    function IsDestroying: Boolean;
    function IsUpdating: Boolean;
    procedure MoveRow(const FromPos, ToPos: Integer);
    procedure SetControl(const Value: INxCellsControl);
    procedure SetFirstVisible(const Value: Integer);
    procedure SetLocation(const Value: TPoint);
  public
    constructor Create(ACells: INxCells; AHeight: Integer); override;
    destructor Destroy; override;
    { Methods }
    procedure AssignDefaultValues(RecordIndex: Integer);
    procedure Clear; override;
    function CreateCell(const ACol, ARow: Integer): INxCell;
    procedure Sort(const Index: Integer; SortKind: TNxSortKind; Resort: Boolean);
    { Properties }
    property DataBinding[Index: Integer]: TNxDataBinding read GetDataBinding;
    property Row[const Index: Integer]: INxRow read GetRow;
    property RowCount: Integer read GetRowCount;
  end;

  { TNxDBMultiSelectCellSource6 }

  TNxDBMultiSelectCellSource6 = class(TNxDBCellSource6, INxSelectable)
  private
    function GetSelected(const Index: Integer): Boolean;
    function GetSelectedCount: Integer;
    procedure SetSelected(const Index: Integer; const Value: Boolean);
    procedure SelectNone;
  protected
    procedure DoCellCompare(Cell1, Cell2: INxBase; var Compare: Integer); override;
  end;

  { TNextDBGrid6 }

  TNxAddFieldQueryEvent = procedure(Sender: TObject; Field: TField;
    var ColumnClass: TNxColumnClass; var Accept: Boolean) of object;

  TNxFieldAddedEvent = procedure(Sender: TObject; Field: TField;
    Column: TNxColumn6) of object;

  TNxGetFieldEvent = procedure(Sender: TObject; ACol, ARow: Integer;
    var Field: TField) of object;

  TNxActivationOptions = set of (aoAutoAddFields, aoAddAutoIncCol, aoAutoSetFields);
  TNxDeactivationOptions = set of (doDeleteAutoAddedFields);

  TNxCellsDBGrid6 = class(TNextVirtualGrid6, INxCellsControl)
  protected
    procedure CellChanged(Cell: INxCell; UpdateSpread: TNxCellUpdateSpread);
    procedure ChildDeleted(ARow: INxRow; Hide: Boolean);
    procedure ChildsCleared(ARow: INxRow);
    procedure DoCellCompare(Cell1, Cell2: INxCell; var Compare: Integer);
    procedure ExpandedChanged(Row: INxRow; Expanded: Boolean);
    procedure HeightChanged(Index: Integer; Delta: Integer);
    procedure RowMoved(const CurIndex, NewIndex: Integer);
    procedure SetLastAddedRow(const Value: Integer);
    procedure ShowChildRows(ARow: INxRow; Count: Integer; Hide: Boolean);
    procedure VisibilityChanged(Index: Integer; Show: Boolean);
  end;

  ERecordBufferRangeException = class(Exception)

  end;

  INxDBDesignNotificationRecipient = interface(INxDesignNotificationRecipient)
    ['{EBDC2A6D-3780-452C-8990-77EE94D1BDF1}']
    procedure DataLinkActiveChanged(Active: Boolean);
  end;

  TNextDBGrid6 = class(TNxCellsDBGrid6, INxDataOwner, INxRecordOwner)
  private
    { Property Fields }
    FActivationOptions: TNxActivationOptions;
    FAutoPost: Boolean;
    FBufferRecords: Boolean;
    FCells: TNxDBMultiSelectCellSource6;
    FDataLink: TNxDBGridDataLink6;
    FDataLocked: Boolean;
    FDataScrolling: Boolean;
    FDataSource: TDataSource;
    FDeactivationOptions: TNxDeactivationOptions;
    FEditingLockCount: Integer;
    FOnAddFieldQuery: TNxAddFieldQueryEvent;
    FOnFieldAdded: TNxFieldAddedEvent;
    FOnGetField: TNxGetFieldEvent;
    FScrollBarTracking: Boolean;
    { Property Accessors }
    function GetDataBinding(Index: Integer): TNxDataBinding;
    function GetSelectedField: TField;
    function GetShowingRecordCount: Integer;
    procedure SetBufferRecords(const Value: Boolean);
    procedure SetDataSource(const Value: TDataSource);
  protected
    { INxRecordOwner }
    procedure AddRecordToList(ARecord: INxRecord);
    procedure RemoveRecordFromList(ARecord: INxRecord);
  protected
    { Event Handlers }
    procedure DoAddFieldQuery(Field: TField; var ColumnClass: TNxColumnClass;
      var Accept: Boolean); dynamic;
    procedure DoGetField(ACol, ARow: Integer; var Field: TField); dynamic;
    procedure DoFieldAdded(Field: TField; Column: TNxColumn6); dynamic;
    { Overriden Handlers }
    procedure DoBeforePostEdit(Cell: INxCell); override;
    procedure DoBeginEditing; override;
    procedure DoColumnSort(Sender: TNxColumn6); override;
    procedure DoSelectionChanged(ACol, ARow: Integer); override;
    procedure LocateCell(ACol, ARow: Integer); override;
    { Methods }
    function CanAutoScroll: Boolean; override;
    procedure DataLayoutChanged;
    procedure DeselectSelected; override;
    function GetDataSet: TDataSet;
    function GetFetchedRowCount: Integer; override;
    function GetFirstFetchedRow: Integer; override;
    function GetRowCount: Integer; override;
    function GetSelected(Index: Integer): Boolean; override;
    function GetSelectedRow: Integer; override;
    function GetShown(Index: Integer): Boolean;
    function GetVisibleRowCount: Integer; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure NotityActiveChanged(Active: Boolean);
    procedure Paint; override;
    procedure SetActiveRow(const Index: Integer); override;
    procedure SetCells(ACol, ARow: Integer; const Value: WideString); override;
    procedure SetSelected(Index: Integer; const Value: Boolean); override;
    function IsIntegralHeight: Boolean; override;
    { Windows Messages }
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
    procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
  public
    procedure AssignFields; virtual;
    procedure SetFields;
    procedure UnassignFields;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Test;
    { Methods }
    procedure AddCells(Values: array of WideString; Cols: array of Integer); override;
    function CanEdit(const ACol, ARow: Integer): Boolean; override;
    function CanSelect(const ACol, ARow: Integer): Boolean; override;
    function CheckBuffered(const ACol, ARow: Integer): Boolean;
    procedure ChangeRowHeight(const Value: Integer); override;
    procedure CreateCols;
    procedure ClearAutoDelete;
    function IsBuffered(const Index: Integer): Boolean;
    function MoveToCell(ACol, ARow: Integer): INxCell;
    procedure Recalculate; override;
    procedure Resort;
    function RowPersist(const Index: Integer): Boolean; override;
    procedure SelectNone(DelayedRefresh: Boolean = True); override;
    { Properties }
    property DataBinding[Index: Integer]: TNxDataBinding read GetDataBinding;
    property DataScrolling: Boolean read FDataScrolling;
    property SelectedField: TField read GetSelectedField;
    property ShowingRecordCount: Integer read GetShowingRecordCount;
  published
    { Properties }
    property ActivationOptions: TNxActivationOptions read FActivationOptions
      write FActivationOptions default [aoAutoAddFields];
    property AutoPost: Boolean read FAutoPost write FAutoPost default False;
    property BufferRecords: Boolean read FBufferRecords write SetBufferRecords default False;
    property ScrollBarTracking: Boolean read FScrollBarTracking write FScrollBarTracking default True;
    property DeactivationOptions: TNxDeactivationOptions read FDeactivationOptions
      write FDeactivationOptions default [doDeleteAutoAddedFields];
    property DataSource: TDataSource read FDataSource write SetDataSource;
    { Events }
    property OnAddFieldQuery: TNxAddFieldQueryEvent read FOnAddFieldQuery write FOnAddFieldQuery;
    property OnFieldAdded: TNxFieldAddedEvent read FOnFieldAdded write FOnFieldAdded;
    property OnGetField: TNxGetFieldEvent read FOnGetField write FOnGetField;
  end;

  function GetColumnClass(DataType: TFieldType): TNxColumnClass;

implementation

uses
  { Standard }
  Dialogs, Math, Jpeg, Controls;

function GetBlobGraphic(Field: TBlobField): TGraphic;
var
  Picture: TPicture;
  Stream: TMemoryStream;
begin
  if Pos('JFIF', Copy(Field.AsString, 1, 10)) = 7 then { Jpeg? }
  begin
    Stream := TMemoryStream.Create;
    Field.SaveToStream(Stream);
    Stream.Seek(soFromBeginning, 0);
    Result := TJPEGImage.Create;
    Result.LoadFromStream(Stream);
  end else
  begin
    Picture := TPicture.Create;
    Picture.Assign(TBlobField(Field));
    Result := Picture.Graphic;
  end;
end;

function JpegStartsInBlob(PicField: TBlobField): Integer;
var
  BlobStream: TStream;
  buffer: Word;
  hx: string;
begin
  Result := -1;
  BlobStream := PicField.DataSet.CreateBlobStream(PicField, bmRead);
  try
    while (Result = -1) and (BlobStream.Position + 1 < BlobStream.Size) do
    begin
      BlobStream.ReadBuffer(buffer, 1);
      hx := IntToHex(buffer, 2);
      if hx = 'FF' then
      begin
        BlobStream.ReadBuffer(buffer, 1);
        hx := IntToHex(buffer, 2);
        if hx = 'D8' then Result := BlobStream.Position - 2
        else if hx = 'FF' then BlobStream.Position := BlobStream.Position - 1;
      end;
    end;
  finally
    BlobStream.Free;
  end;
end;

function GetColumnClass(DataType: TFieldType): TNxColumnClass;
begin
  { NOTE: This should be only function! }
  case DataType of
    ftBoolean: Result := TNxDBCheckBoxColumn6;

    ftDate,
    ftDateTime,
    ftTime,
    ftTimeStamp: Result := TNxDBDateColumn6;

    ftString,
    ftWideString: Result := TNxDBTextColumn6;

    ftAutoInc,
    ftBytes,
    ftFloat,
    ftCurrency,
    ftInteger,
    ftSmallint,
    ftLargeint,
    ftWord: Result := TNxDBNumberColumn6;

    ftGraphic: Result := TNxDBGraphicColumn6;

    ftMemo{$IFDEF WIDE_MEMO}, ftWideMemo{$ENDIF}: Result := TNxDBMemoColumn6;

    else Result := TNxDBTextColumn6;
  end;
end;

{ TNxDBRecordList }

function TNxDBRecordList.Add(ActiveIndex: Integer; Bookmark: TBookmark): INxRecord;
var
  Index: Integer;
begin
  Index := IndexOfBookmark(Bookmark);

  { Not already added? }
  if Index = -1 then
  begin
    Result := TNxRecordRow6.Create(FDataLink.FDataGrid);
    Result.Bookmark := Bookmark;
    Result.RecordIndex := ActiveIndex;

    FList.Add(Result);
  end else
  begin
    with FList[Index] as INxRecord do
    begin
      RecordIndex := ActiveIndex;
    end;
  end;
end;

procedure TNxDBRecordList.Clear;
begin
  FList.Clear;
end;

procedure TNxDBRecordList.Compact(RowHeight: Integer);
begin
  FList.Clear;
end;

function TNxDBRecordList.Compare(const Item1, Item2: TBookmark): Integer;
begin
  with FDataLink.DataSet do
    Result := CompareBookmarks(TBookmark(Item1), TBookmark(Item2));
end;

constructor TNxDBRecordList.Create;
begin
  inherited;
  FList := TInterfaceList.Create;
  FSorted := False;
end;

destructor TNxDBRecordList.Destroy;
begin
  FreeAndNil(FList);
  inherited;
end;

function TNxDBRecordList.GetBookmark(const ARow: Integer): TBookmark;
begin
  Result := INxRecord(FList[ARow]).Bookmark;
end;

function TNxDBRecordList.GetRecordIndex(const ARow: Integer): Integer;
begin
  { Default }
  Result := ARow;

  if FSorted then
  begin
    if InRange(ARow, 0, Pred(FList.Count)) then
    begin
      Result := INxRecord(FList[ARow]).RecordIndex;
    end;
  end;
end;

function TNxDBRecordList.GotoRecord(const Index: Integer): Boolean;
var
  Bookmark: TBookmark;
begin
  Result := False;
  if FDataLink.Active then
  begin
    Bookmark := Self.Bookmark[Index];
    if Bookmark <> nil then
    begin
      FDataLink.DataSet.GotoBookmark(Bookmark);
      Result := True;
    end;
  end;
end;

function TNxDBRecordList.IndexOfBookmark(const Bookmark: TBookmark): Integer;
var
  i: Integer;
begin
  Result := -1;
  for i := 0 to Pred(FList.Count) do
  begin
    if Compare(INxRecord(FList[i]).Bookmark, Bookmark) = 0 then
    begin
      Result := i;
      Exit;
    end;
  end;
end;

procedure TNxDBRecordList.AssignIndexes;
var
  i: Integer;
begin
  i := 0;
  FDataLink.DataSet.DisableControls;

  while i < FDataLink.RecordCount do
  begin
    FDataLink.ActiveRecord := i;
    Add(i, FDataLink.DataSet.GetBookmark);
    Inc(i)
  end;
  FSorted := True;
  FDataLink.DataSet.EnableControls;
end;

procedure TNxDBRecordList.SetRecordIndex(const ARow, Value: Integer);
begin
  INxRecord(FList[ARow]).RecordIndex := Value;
end;

procedure TNxDBRecordList.SetSorted(const Value: Boolean);
begin
  FSorted := Value;
end;

{ TNxDBGridDataLink6 }

procedure TNxDBGridDataLink6.ActiveChanged;
begin
  inherited;

  if Active then
  begin

    { Link Field & Col. }
    if FDataGrid.HasCols then FDataGrid.AssignFields else
    begin

      if csDesigning in FDataGrid.ComponentState then
      begin

        FDataGrid.NotityActiveChanged(Active);

      end else
      begin

        { Create Col. & link Field }
        if aoAutoAddFields in FDataGrid.ActivationOptions then
        begin


          FDataGrid.CreateCols;
        end;

      end;
    end;

    { Cache for speed }
    FRecordCount := DataSet.RecordCount;

    { Required, ADO scroll to 1 (?) }
    DataSet.First;

    { Can be set only if `Active` }
    ActiveRecord := 0;

    { Set `BufferCount` of TDataLink, depending
      of `BufferRecords` property. }
    Recount;

    { TODO: Do it better }
    if FDataGrid.FCells.FSorted then
    begin
      FDataGrid.Resort;
    end;

  end else
  begin
    if Assigned(FDataGrid.ActiveView) then
    begin
      FDataGrid.ActiveView.Reset;
    end;
    FDataGrid.FCells.Clear;

    { Clear all `dboAutoDeleteOnDeactivate` }
    if doDeleteAutoAddedFields in FDataGrid.DeactivationOptions then
    begin
      FDataGrid.ClearAutoDelete;
    end;

    { Set `Field` to nil }
    FDataGrid.UnassignFields;

    FRecordCount := 0;

    { It should be 0 already, but ok }
    BufferCount := 0;
  end;

  FRecNo := 0;

  FDataGrid.UpdateScrollBars;
  FDataGrid.Invalidate;
end;

constructor TNxDBGridDataLink6.Create(ADataGrid: TNextDBGrid6);
begin
  inherited Create;
  FDataGrid := ADataGrid;
  FRecNo := -1;
  FRecordCount := 0;
end;

procedure TNxDBGridDataLink6.DataEvent(Event: TDataEvent;
  Info: {$IFDEF NATIVE_INT}NativeInt{$ELSE}Longint{$ENDIF});
var
  ActualRecordCount: Integer;
begin
  inherited;

  { `Post` occur }
  if Event in [deUpdateRecord] then
  begin
    { Issue here. InplaceEdit didn't get value! }

    { Assign editing (don't end) }
    FDataGrid.AssignEditing;
  end;

  if Event in [deLayoutChange] then
  begin
    WritelnDebug('deLayoutChange');
  end;

  { Editing or Active changed }
  if Event in [deUpdateState] then
  begin
//    WritelnDebug('deUpdateState');
  end;

  if Event in [deDataSetChange] then
  begin
//    WritelnDebug('deDataSetChange: ' + IntToStr(Info));

    ActualRecordCount := DataSet.RecordCount;

    { Add one for insert record }
    if DataSet.State = dsInsert
      then Inc(ActualRecordCount);

    { Append or Deleted? }
    if ActualRecordCount <> FRecordCount then
    begin
      WritelnDebug('RecordCount changed');

      { Record Deleted? }
      if ActualRecordCount < FRecordCount then
      begin
        FDataGrid.Recount;

      end;

      FRecordCount := ActualRecordCount;

      { Set BufferCount }
      Recount;

      FDataGrid.UpdateScrollBars;
      FDataGrid.Invalidate;
    end;

  end;
end;

procedure TNxDBGridDataLink6.DataSetChanged;
begin
  inherited;

  { ActiveRecord is changed too often }
  FSelectedIndex := ActiveRecord;

  { Already scroll via ScrollBar? }
  if not FDataGrid.DataScrolling then
  begin

    if FDataGrid.FCells.Sorted then
    begin

    end else
    begin

      { Note: RecNo is NOT zero-based }
      if DataSet.RecNo <> -1 then
      begin
        FDataGrid.FDataLocked := True;

        { Note: Data is already scrolled!
                Records on top are now NOT
                available. }
        FDataGrid.ScrollToCell(FDataGrid.SelectedCol, DataSet.RecNo - 1);

        FDataGrid.FDataLocked := False;
      end;

    end;
  end;
end;

procedure TNxDBGridDataLink6.DataSetScrolled(Distance: Integer);
begin
  { Note: MoveBy() call this, but
          ActiveRecord change NOT! }
  { Note: Distance is ONLY set when view
          is scrolled too! }

  inherited;

  if Active then
  begin
    if FDataGrid.IsBuffered(FDataGrid.FSelectedRow) then
    begin
      FDataGrid.RefreshRow(FDataGrid.FSelectedRow);
    end;

//    FDataGrid.VertScrollBar.Lock;
//    FDataGrid.VertScrollBar.MoveBy(Distance);
//    FDataGrid.VertScrollBar.Unlock(False);

    FDataGrid.RefreshSelectedCell;
  end;
end;

procedure TNxDBGridDataLink6.EditingChanged;
begin
  inherited;
  if FDataGrid.FEditingLockCount = 0 then
    if Editing then
    begin
      { DataSet start Editing, but Grid is not in
        edit state yet }
      if not FDataGrid.Editing
        and FDataGrid.CellExists(FDataGrid.SelectedLocation) then
      begin

        { Append will trigger this code,
          and Field will eventualy be changed,
          but inplaceedit will not be updated! }

        if DataSet.State <> dsInsert then
        begin
          FDataGrid.EditCell(FDataGrid.SelectedLocation);
        end;

      end;
    end else
    begin
      { DataSet ended editing, but Grid is still
        in editing state }
      if FDataGrid.Editing then
      begin
        FDataGrid.EndEditing(False);
      end;
    end;
end;

function TNxDBGridDataLink6.GetCurrentRecord: TBookmark;
begin
  Result := DataSet.GetBookmark;
end;

function TNxDBGridDataLink6.LocateTo(const RowIndex: Integer): Boolean;
var
  Index: Integer;
begin
  Result := False;
  FRecNo := ActiveRecord;
  if FDataGrid.BufferRecords then
  begin
    ActiveRecord := FDataGrid.FCells.RecordIndex[RowIndex];
    Result := True;
  end else
  begin
    Index := RowIndex - FDataGrid.VertScrollBar.SnapshotPosition;
    if Index > -1 then
    begin
      ActiveRecord := Index;
      Result := True;
    end;
  end;
end;

procedure TNxDBGridDataLink6.MoveByRecord(const Delta: Integer);
begin
  if Active then
  begin
    if Delta <> 0 then MoveBy(Delta);
  end;
end;

function TNxDBGridDataLink6.Recount: Boolean;
var
  Count: Integer;
begin
  Count := BufferCount;
  BufferCount := FDataGrid.ShowingRecordCount;
  Result := Count <> BufferCount;
end;

procedure TNxDBGridDataLink6.Relocate;
begin
  ActiveRecord := FRecNo;
end;

procedure TNxDBGridDataLink6.SetActiveField(const Value: TField);
begin
  FActiveField := Value;
end;

procedure TNxDBGridDataLink6.UpdateData;
begin
  inherited;

end;

{ TNxDBCellSource6 }

function TNxDBCellSource6.AbsoluteIndexOf(Row: INxRow): Integer;
begin
  Result := -1;
end;

procedure TNxDBCellSource6.AddColumn;
begin

end;

function TNxDBCellSource6.AddRow(const Count: Integer;
  IgnoreDefaults: Boolean): INxCellsRow;
var
  i, RecordCount: Integer;
begin
  RecordCount := FDataLink.FRecordCount;

  Inc(FDataGrid.FEditingLockCount);

  FDataLink.DataSet.DisableControls;
  try
    { Append & Post each record }
    for i := 0 to Pred(Count) do
    begin
      if FDataLink.Editing then
        FDataLink.DataSet.Post;

      FDataLink.DataSet.Append;
      FDataLink.DataSet.Post;

      if not IgnoreDefaults
        then AssignDefaultValues(RecordCount + i);
    end;

  finally
    FDataLink.DataSet.EnableControls;
    Dec(FDataGrid.FEditingLockCount);
    FDataLink.FRecordCount := FDataLink.DataSet.RecordCount;
  end;
end;

procedure TNxDBCellSource6.AssignDefaultValues;
var
  i: Integer;
  Cell: INxCell;
begin
  for i := 0 to Pred(FDataGrid.Columns.Count) do
  begin
    with FDataGrid.Columns[i] do
    begin
      { Inc. RefCount }
      Cell := CreateCell(i, RecordIndex);

      { Each Col will take care for setting value }
      AssignDefaultValue(Cell);
    end;
  end;
end;

procedure TNxDBCellSource6.CellChanged(Cell: INxCell);
begin

end;

procedure TNxDBCellSource6.Clear;
begin
  inherited;
  FColIndex := -1;
  FRowIndex := -1;
end;

constructor TNxDBCellSource6.Create(ACells: INxCells; AHeight: Integer);
begin
  inherited;
  FRowIndex := -1;
  FColIndex := -1;
  FRow := TNxCellsRow6.Create(nil, AHeight);
end;

function TNxDBCellSource6.CreateCell(const ACol, ARow: Integer): INxCell;
var
  DataField: TField;

  BlobContent: INxTextBlobContent;
  GraphicBlob: INxGraphicBlobContent;
begin
  FDataLink.ActiveField := DataBinding[ACol].Field;

  DataField := FDataLink.ActiveField;

  { Trigger event }
  FDataLink.FDataGrid.DoGetField(ACol, ARow, DataField);

  { Create cell & set field }
  FCell := nil;

  case FDataLink.FDataGrid.Columns[ACol].DataType of
    dtAutoInc:
    begin
      FCell := TNxAutoIncFieldCell.Create(Self);
      FCell.AsInteger := ARow;
    end
    else
      { Does it Col. have Field? }
      if DataField <> nil then
      begin

        case DataField.DataType of
          ftBoolean: FCell := TNxBooleanFieldCell.Create(Self);
          ftDate, ftDateTime, ftTime, ftTimeStamp: FCell := TNxDateTimeFieldCell.Create(Self);
          ftString, ftWideString, ftMemo: FCell := TNxStringFieldCell.Create(Self);
          ftFloat, ftCurrency: FCell := TNxFloatFieldCell.Create(Self);
          ftInteger, ftSmallint, ftLargeint, ftWord, {ftSingle, }ftAutoInc: FCell := TNxIntegerFieldCell.Create(Self);
          else FCell := TNxStringFieldCell.Create(Self);
        end;

      end
      else FCell := TNxStringFieldCell.Create(Self);

  end;

  if Assigned(FCell) then
  begin
    AutoPostEdit := True;
    FCell.ActiveRecord := FDataLink.ActiveRecord;
    FCell.NullText := DataBinding[ACol].NullText;
    FCell.Data := nil;

    { Blob }
    if Assigned(DataField)
      and DataField.IsBlob and not DataField.IsNull then
    begin

      { (MEMO) }
      if Supports(FDataGrid.Columns[ACol], INxTextBlobContent, BlobContent) then
      begin
        FCell.ExpandContent := BlobContent.ExpandContent;
      end;

      { (GRAPHIC) }
      if Supports(FDataGrid.Columns[ACol], INxGraphicBlobContent, GraphicBlob) then
      begin
        if DataField.DataType = ftGraphic then
        begin
          FCell.Data := GetBlobGraphic(DataField as TBlobField);
        end;
      end;

    end;

    { Link Field & assign Value }
    FCell.Field := DataField;
  end;

  { Inc. ReferenceCount to Cell }
  Result := FCell;
end;

procedure TNxDBCellSource6.DeleteColumn(const Index: Integer);
begin

end;

procedure TNxDBCellSource6.DeleteRow(const Index: Integer);
begin

end;

destructor TNxDBCellSource6.Destroy;
begin
  inherited;

end;

procedure TNxDBCellSource6.FindFirstVisible;
begin

end;

procedure TNxDBCellSource6.FindLastVisible;
begin

end;

function TNxDBCellSource6.GetCell(ACol, ARow: Integer): INxCell;
var
  Relocated: Boolean;
begin
  Result := nil;
  Relocated := False;
  try
    FDataGrid.CheckBuffered(ACol, ARow);

    Relocated := FDataLink.LocateTo(ARow);

    FColIndex := ACol;
    FRowIndex := ARow;

    Result := CreateCell(ACol, ARow);
  finally
    { Set ActiveRecord back }
    if Relocated then FDataLink.Relocate;
  end;
end;

function TNxDBCellSource6.GetChildRow(const Index: Integer): INxRow;
begin

end;

function TNxDBCellSource6.GetChildRowCount: Integer;
begin
  Result := -1;
end;

function TNxDBCellSource6.GetControl: INxCellsControl;
begin
  Result := FDataGrid as INxCellsControl;
end;

function TNxDBCellSource6.GetDataBinding(Index: Integer): TNxDataBinding;
var
  Ref: INxDataBinding;
begin
  Result := nil;
  if Supports(FDataGrid.Columns[Index], INxDataBinding, Ref) then
  begin
    Result := Ref.DataBinding;
  end;
end;

function TNxDBCellSource6.GetField(ACol, ARow: Integer): TField;
var
  Relocated: Boolean;
begin
  Relocated := False;
  try
    Relocated := FDataLink.LocateTo(ARow);

    FDataLink.ActiveField := DataBinding[ACol].Field;

    Result := FDataLink.ActiveField;

    { Trigger event }
    FDataLink.FDataGrid.DoGetField(ACol, ARow, Result);
  finally
    { Set ActiveRecord back }
    if Relocated then FDataLink.Relocate;
  end;
end;

function TNxDBCellSource6.GetFirstVisible: Integer;
begin
  Result := 0;
end;

function TNxDBCellSource6.GetLastVisible: Integer;
begin
  Result := Pred(GetRowCount);
end;

function TNxDBCellSource6.GetLocation: TPoint;
begin
  Result := Point(FColIndex, FRowIndex);
end;

function TNxDBCellSource6.GetRow(const Index: Integer): INxRow;
var
  Ref: INxRecord;
  Bookmark: TBookmark;
  RecordIndex: Integer;
  Relocated: Boolean;
begin
  Relocated := False;
  try
    Relocated := FDataLink.LocateTo(Index);

    { Book. of current record }
    Bookmark := FDataLink.DataSet.GetBookmark;

    RecordIndex := IndexOfBookmark(Bookmark);

    if RecordIndex <> -1 then
    begin
      Result := FList[RecordIndex] as INxRow;
    end else
    begin
      Result := TNxRecordRow6.Create(FDataGrid);

      if Supports(Result, INxRecord, Ref) then
      begin
        Ref.Bookmark := FDataLink.DataSet.GetBookmark;
      end;
    end;

    (Result as INxRecordRow).SetIndex(Index);

  finally
    { Don't forget }
    if Relocated then FDataLink.Relocate;
  end;
end;

function TNxDBCellSource6.GetRowCount: Integer;
begin
  if FDataLink.Active then
  begin
    Result := FDataLink.FRecordCount;

//    if FDataLink.DataSet.State = dsInsert
//      then Inc(Result);

  end
  else Result := 0;
end;

function TNxDBCellSource6.GetRowsBetween(FromPos, ToPos: Integer): Integer;
begin
  Result := ToPos - FromPos;
end;

function TNxDBCellSource6.GetShowing(const Index: Integer): Boolean;
begin
  Result := True;
end;

function TNxDBCellSource6.GetShowingCount: Integer;
begin
  Result := GetRowCount;
end;

function TNxDBCellSource6.HasLocation: Boolean;
begin
  Result := False;
end;

procedure TNxDBCellSource6.IncRowCount(const Delta: Integer);
begin

end;

function TNxDBCellSource6.IndexOf(Row: INxRow): Integer;
begin
  Result := -1;
end;

function TNxDBCellSource6.InsertRow(const Index, Count: Integer;
  IgnoreDefaults: Boolean): INxCellsRow;
begin

end;

function TNxDBCellSource6.IsDestroying: Boolean;
begin
  Result := csDestroying in FDataGrid.ComponentState;
end;

function TNxDBCellSource6.IsUpdating: Boolean;
begin
  Result := False;
end;

procedure TNxDBCellSource6.MoveRow(const FromPos, ToPos: Integer);
begin

end;

procedure TNxDBCellSource6.MoveTo(const Index: Integer);
begin
  { Note: ActiveRecord actualy used }
  FDataLink.ActiveRecord := Index;
end;

procedure TNxDBCellSource6.RevertLocation;
begin
  FDataLink.Relocate;
end;

procedure TNxDBCellSource6.SetControl(const Value: INxCellsControl);
begin

end;

procedure TNxDBCellSource6.SetFirstVisible(const Value: Integer);
begin

end;

procedure TNxDBCellSource6.SetLocation(const Value: TPoint);
begin

end;

procedure TNxDBCellSource6.Sort(const Index: Integer; SortKind: TNxSortKind;
  Resort: Boolean);
var
	ALessCompFunc: TNxCompareFunc;
	AGreaterCompFunc: TNxCompareFunc;

  function GetChildCell(ACol, ARow: Integer): INxBase;
  begin
    Result := FDataGrid.Cell[ACol, ARow];
  end;

  procedure Exchange(Pos1, Pos2: Integer);
  var
    P: INxRecord;
  begin
    P := INxRecord(FList[Pos1]);
    FList[Pos1] := FList[Pos2];
    FList[Pos2] := P;
  end;

  procedure InverseSort;
  var
    i, Middle, Count: Integer;
  begin
    if RowCount > 0 then
    begin
      Middle := Pred(RowCount div 2);

      { Optimize }
      Count := RowCount;

      for i := 0 to Middle do Exchange(i, Pred(Count) - i);
    end;
  end;

  function DivideAsc(l, r: Integer; LessCompFunc,
  	GreaterCompFunc: TNxCompareFunc): Integer;
  var
    i, j: Integer;
    pivot: INxBase;
  begin
  	pivot := GetChildCell(Index, l + 1 + Random(r - l));

    i := l - 1;
    j := r + 1;

    repeat

      repeat Inc(i) until LessCompFunc(pivot, GetChildCell(Index, i));
	    repeat Dec(j) until GreaterCompFunc(pivot, GetChildCell(Index, j));

      if GetChildCell(Index, j) <> GetChildCell(Index, i)
        then Exchange(i, j)

		until j <= i;

    if GetChildCell(Index, j) <> GetChildCell(Index, i)
      then Exchange(i, j);

    Result := i;
  end;

  function DivideDesc(l, r: Integer; LessCompFunc,
  	GreaterCompFunc: TNxCompareFunc): Integer;
  var
    i, j: Integer;
    pivot: INxBase;
  begin
  	pivot := GetChildCell(Index, l + 1 + Random(r - l));
    i := l - 1;
    j := r + 1;
    repeat
      repeat Inc(i) until GreaterCompFunc(pivot, GetChildCell(Index, i));
	    repeat Dec(j) until LessCompFunc(pivot, GetChildCell(Index, j));
      if (GetChildCell(Index, j) <> GetChildCell(Index, i)) then Exchange(i, j)
		until j <= i;
    if (GetChildCell(Index, j) <> GetChildCell(Index, i)) then Exchange(i, j);
    Result := i;
  end;

  procedure Quicksort(Lft, Rgt: Integer);
  var
    Middle: Integer;
  begin
    if Lft < Rgt then
    begin
	    if FSortedColumn.SortKind = skAscending
      	then Middle := DivideAsc(Lft, Rgt, ALessCompFunc, AGreaterCompFunc)
      	else Middle := DivideDesc(Lft, Rgt, ALessCompFunc, AGreaterCompFunc);

      Quicksort(Lft, Middle - 1);
      Quicksort(Middle, Rgt);
    end;
  end;

begin
  AssignIndexes;
  FSortedColumn := FDataGrid.Columns[Index];

  case FSortedColumn.SortType of
    stAlphabetic:
    begin
      ALessCompFunc := AlphabeticLessCompare;
      AGreaterCompFunc := AlphabeticGreaterCompare;
    end;
    stBoolean:
    begin
      ALessCompFunc := BooleanLessCompare;
      AGreaterCompFunc := BooleanGreaterCompare;
    end;
    stCaseInsensitive:
    begin
      ALessCompFunc := CaseInsensitiveLessCompare;
      AGreaterCompFunc := CaseInsensitiveGreaterCompare;
    end;
    stCustom:
    begin
      ALessCompFunc := CustomLessCompare;
      AGreaterCompFunc := CustomGreaterCompare;
    end;
    stNumeric:
    begin
      ALessCompFunc := NumericLessCompare;
      AGreaterCompFunc := NumericGreaterCompare;
    end;
    stDate:
    begin
      ALessCompFunc := DateLessCompare;
      AGreaterCompFunc := DateGreaterCompare;
    end;
    stIP:
    begin
      ALessCompFunc := IPLessCompare;
      AGreaterCompFunc := IPGreaterCompare;
    end;
    stUnicodeAlphabetic:
    begin
      ALessCompFunc := AlphabeticUnicodeLessCompare;
      AGreaterCompFunc := AlphabeticUnicodeGreaterCompare;
    end;
  end;

  if (Index = FSortedCol) and FSorted and (SortKind <> FSortKind)
    and not Resort then
  begin
    FSortKind := SortKind;
    InverseSort;
  end else
  begin
    FSortKind := SortKind;
    Quicksort(0, Pred(RowCount));
  end;

  FSortedCol := Index;
  FSortKind := SortKind;
  FSorted := True;
end;

{ TNxDBMultiSelectCellSource6 }

procedure TNxDBMultiSelectCellSource6.DoCellCompare(Cell1, Cell2: INxBase; var Compare: Integer);
begin
  inherited;

end;

function TNxDBMultiSelectCellSource6.GetSelected(const Index: Integer): Boolean;
begin
  Result := Row[Index].Selected;
end;

function TNxDBMultiSelectCellSource6.GetSelectedCount: Integer;
begin
  Result := 0;
end;

procedure TNxDBMultiSelectCellSource6.SelectNone;
var
  i: Integer;
  RecordRow: INxRecordRow;
begin
  i := 0;
  while i < FList.Count do
  begin
    if Supports(FList[i], INxRecordRow, RecordRow) then
    begin
      RecordRow.Selected := False;
      if not RecordRow.IsStoreNeeded then FList.Delete(i)
        else Inc(i);
    end;
  end;
end;

procedure TNxDBMultiSelectCellSource6.SetSelected(const Index: Integer; const Value: Boolean);
begin
  Row[Index].Selected := Value;
end;

{ TNextDBGrid6 }

procedure TNextDBGrid6.AddCells(Values: array of WideString;
  Cols: array of Integer);
var
  i, j: Integer;
  Field: TField;
begin
  if FDataLink.Active and FDataLink.DataSet.CanModify then
    with FDataLink.DataSet do
    try
      Inc(FEditingLockCount);

      DisableControls;

      i := 0;

      { Add record & start
        edit record fields }
      AddRow;
      Edit;

      while i < Length(Values) do
      begin

        for j := Low(Cols) to High(Cols) do
        begin

          Field := DataBinding[Cols[j]].Field;

          { Set value }
          if Assigned(Field) and Field.CanModify then
          begin
            Field.AsString := Values[i];
          end;

          Inc(i);

          { Done adding all }
          if i >= Length(Values) then Exit;
        end;

        { Need to add record? }
        if i < Length(Values) then
        begin
          { Finish with fields in
            previous record }
          Post;

          { Add record & start
            edit record fields }
          AddRow;
          Edit;
        end;
      end;

    finally
      Post;
      EnableControls;
      Dec(FEditingLockCount);
      FDataLink.Recount;
    end;
end;

procedure TNextDBGrid6.AddRecordToList(ARecord: INxRecord);
var
  Index: Integer;
begin
  Index := FCells.IndexOfBookmark(ARecord.Bookmark);

  { Doesn't exists already? }
  if Index = -1 then
  begin
    FCells.FList.Add(ARecord);
  end else
  begin
    FCells.FList[Index] := ARecord;
  end;
end;

procedure TNextDBGrid6.AssignFields;
var
  i: Integer;
begin
  for i := 0 to Pred(Columns.Count) do
  begin
    DataBinding[i].AssignField;
  end;
end;

function TNextDBGrid6.CanAutoScroll: Boolean;
begin
  Result := BufferRecords;
end;

function TNextDBGrid6.CanEdit(const ACol, ARow: Integer): Boolean;
var
  Field: TField;
begin
  Field := FCells.GetField(ACol, ARow);
  Result := inherited CanEdit(ACol, ARow) and Assigned(Field) and Field.CanModify;
end;

function TNextDBGrid6.CanSelect(const ACol, ARow: Integer): Boolean;
begin
  { Default }
  Result := True;

  if CellExists(ACol, ARow)
    then Result := Columns[ACol].CanSelect;

//
//      and Shown[ARow]
//      and Cell[ACol, ARow].Enabled;

  DoSelectQuery(ACol, ARow, Result);
end;

function TNextDBGrid6.IsBuffered(const Index: Integer): Boolean;
var
  ActiveRecord: Integer;
begin
  if BufferRecords then Result := True else
  begin
    ActiveRecord := Index - VertScrollBar.SnapshotPosition;
    Result := InRange(ActiveRecord, 0, Pred(FDataLink.BufferCount));
  end;
end;

procedure TNextDBGrid6.ChangeRowHeight(const Value: Integer);
begin
  inherited;
  FCells.Compact(Value);

  { Required for big changes in Data layout/content }
  DataLayoutChanged;
end;

function TNextDBGrid6.CheckBuffered(const ACol, ARow: Integer): Boolean;
var
  Index: Integer;
begin
  if BufferRecords
    then Index := ARow
    else Index := ARow - VertScrollBar.SnapshotPosition;

  if (FDataLink.BufferCount > 0)
    and not InRange(Index, 0, Pred(FDataLink.BufferCount)) then
  begin
    Result := False;
    //raise ERecordBufferRangeException.CreateFmt('Record %d out of buffer', [Index]);
  end
  else Result := True;
end;

procedure TNextDBGrid6.ClearAutoDelete;
var
  i: Integer;
begin
  i := 0;
  while i < Columns.Count do
  begin
    if dboAutoDeleteOnDeactivate in DataBinding[i].Options then
    begin
      Columns[i].Free;
    end else Inc(i);
  end;
end;

constructor TNextDBGrid6.Create(AOwner: TComponent);
begin
  inherited;
  FActivationOptions := [aoAutoAddFields];
  FAutoPost := False;
  FDataLink := TNxDBGridDataLink6.Create(Self);
  FDataLocked := False;
  FDeactivationOptions := [doDeleteAutoAddedFields];
  FDataScrolling := False;
  FDataSource := nil;
  FEditingLockCount := 0;
  FCells := TNxDBMultiSelectCellSource6.Create(nil, RowHeight);
  FCells.FDataLink := FDataLink;
  FCells.FDataGrid := Self;
  FCellSource := FCells;
  FScrollBarTracking := True;
end;

procedure TNextDBGrid6.CreateCols;
var
  i: Integer;
  Col: TNxColumn6;
  DataField: TField;
  ColClass: TNxColumnClass;
  DataBinding: INxDataBinding;
  Accept: Boolean;
begin
  if FDataLink.Active then
  begin

    for i := 0 to Pred(FDataLink.DataSet.FieldCount) do
    begin
      DataField := FDataLink.DataSet.Fields[i];

      ColClass := GetColumnClass(DataField.DataType);

      Accept := True;

      { Ask for adding & class to create }
      DoAddFieldQuery(DataField, ColClass, Accept);

      { Can add? }
      if Accept then
      begin
        { Add & set Caption }
        Col := Columns.Add(ColClass, DataField.FieldName);

        if Supports(Col, INxDataBinding, DataBinding) then
        begin
          DataBinding.DataBinding.Options := [
            dboAutoDeleteOnDeactivate,
            dboUseFieldProperties];

          DataBinding.DataBinding.FieldName := DataField.FieldName;
        end;

        { Event }
        DoFieldAdded(DataField, Col);
      end;

    end;
  end;
end;

procedure TNextDBGrid6.DataLayoutChanged;
begin
  Recount;
  UpdateScrollBars;
  Invalidate;
end;

procedure TNextDBGrid6.DeselectSelected;
begin
  if gtMultiSelected in FState then
  begin
    FCells.SelectNone;
    Exclude(FState, gtMultiSelected);
    if Assigned(ActiveView) then ActiveView.RefreshData;
  end;
end;

destructor TNextDBGrid6.Destroy;
begin
  FCells := nil;
  FreeAndNil(FDataLink);
  inherited;
end;

procedure TNextDBGrid6.DoAddFieldQuery(Field: TField; var ColumnClass:
  TNxColumnClass; var Accept: Boolean);
begin
  if Assigned(FOnAddFieldQuery) then FOnAddFieldQuery(Self, Field, ColumnClass, Accept);
end;

procedure TNextDBGrid6.DoBeforePostEdit(Cell: INxCell);
begin
  AutoPostEdit := Self.AutoPost;
end;

procedure TNextDBGrid6.DoBeginEditing;
begin
  inherited;
  if FDataLink.Active then
  begin
    FDataLink.Edit;
  end;
end;

procedure TNextDBGrid6.DoColumnSort(Sender: TNxColumn6);
var
  SelectedRowIntf: INxRow;
begin
  if FDataLink.Active and BufferRecords then
  begin

    { Ignore Prop. Loading }
    if not (csLoading in ComponentState) then
    begin

      if Sender.Sorted then
      begin
        SelectedRowIntf := nil;

        if RowExists(SelectedRow)
          then SelectedRowIntf := Row[SelectedRow];

        try
          FDataLink.DataSet.DisableControls;
          FCells.Sort(Sender.Index, Sender.SortKind, False);
        finally
          FDataLink.DataSet.EnableControls;
        end;

        { Set SelectedRow Back to prev. selected }
        if Assigned(SelectedRowIntf) then
        begin
          { 09/20/13: It is fine to set Field only since SelectCell
                      events and etc. are not needed because same row is
                      selected anyway }
          FSelectedRow := FCells.IndexOf(SelectedRowIntf);

        end;

        if Assigned(ActiveView) then
        begin
          { Invalidate Data & Indicators }
          ActiveView.RefreshData;

          { ToDo: Update LastFixedRow }
          { Update FirstIndex }
          ActiveView.AfterSort;

          ScrollToSelected;
        end;

      end;

      { OnAfterSort call inside }
      inherited;
    end;
  end;

end;

procedure TNextDBGrid6.DoFieldAdded(Field: TField; Column: TNxColumn6);
begin
  if Assigned(FOnFieldAdded) then FOnFieldAdded(Self, Field, Column);  
end;

procedure TNextDBGrid6.DoGetField(ACol, ARow: Integer; var Field: TField);
begin
  if Assigned(FOnGetField) then FOnGetField(Self, ACol, ARow, Field);
end;

procedure TNextDBGrid6.DoSelectionChanged(ACol, ARow: Integer);
var
  ScrollDelta: Integer;
  Index: Integer;
begin
  if FDataLink.Active then
  begin

    if not BufferRecords then
    begin
      Index := FDataLink.ActiveRecord + FSelectDelta.Y;

      if FSelectDelta.Y > 0 then
      begin
        ScrollDelta := Index - Pred(FDataLink.BufferCount);

        if ScrollDelta > 0 then
        begin
          { Set FirstIndex }
          ScrollContentBy(0, ScrollDelta);

          if ScrollDelta = 1 then
          begin
            FDataLink.MoveBy(1);
          end else
          begin
            FDataLink.ActiveRecord := Pred(FDataLink.RecordCount);
            FDataLink.MoveBy(ScrollDelta);
          end;

          VertScrollBar.Lock;
          VertScrollBar.MoveBy(ScrollDelta);
          VertScrollBar.Unlock;

          Exit;
        end;

      end else if FSelectDelta.Y < 0 then
      begin
        // Not needed for up
      end;

    end else
    begin
      { BufferRecords }

      if FCells.Sorted then
      begin
        FCells.GotoRecord(ARow);
        Exit;
      end;
    end;

    FDataLink.DataSet.DisableControls;
    FDataLink.MoveByRecord(FSelectDelta.Y);
    FDataLink.DataSet.EnableControls;
  end;

  { Trigger event }
  inherited DoSelectionChanged(ACol, ARow);
end;

function TNextDBGrid6.GetDataBinding(Index: Integer): TNxDataBinding;
var
  Ref: INxDataBinding;
begin
  if Supports(Columns[Index], INxDataBinding, Ref) then
  begin
    Result := Ref.DataBinding;
  end else Result := nil;
end;

function TNextDBGrid6.GetDataSet: TDataSet;
begin
  Result := FDataLink.DataSet;
end;

function TNextDBGrid6.GetFetchedRowCount: Integer;
begin
  Result := IfThen(BufferRecords, inherited GetFetchedRowCount, FirstFetchedRow + FDataLink.BufferCount);
end;

function TNextDBGrid6.GetFirstFetchedRow: Integer;
begin
  Result := IfThen(BufferRecords, inherited GetFirstFetchedRow, VertScrollBar.Position);
end;

function TNextDBGrid6.GetRowCount: Integer;
begin
  Result := FCells.GetRowCount;
end;

function TNextDBGrid6.GetSelected(Index: Integer): Boolean;
begin
  if MultiSelect then Result := Row[Index].Selected
    else Result := SelectedRow = Index;
end;

function TNextDBGrid6.GetSelectedField: TField;
begin
  if CellExists(SelectedLocation) then Result := DataBinding[SelectedCol].Field else Result := nil;
end;

function TNextDBGrid6.GetSelectedRow: Integer;
begin
  { Default, in-active DataSet }
  Result := -1;

  if FDataLink.Active then
    case BufferRecords of
      True:
      begin
        if FCells.Sorted then
        begin
          Result := FCells.IndexOfBookmark(FDataLink.DataSet.GetBookmark);
        end else
        begin
          Result := FDataLink.ActiveRecord;
        end;
      end
      else Result := VertScrollBar.Position + FDataLink.ActiveRecord;
    end;

  { Set Prop. field }
  FSelectedRow := Result;
end;

function TNextDBGrid6.GetShowingRecordCount: Integer;
begin
  { Default }
  Result := 0;

  if Assigned(ActiveView) then
  begin
    if BufferRecords then Result := FDataLink.FRecordCount
      else Result := ActiveView.VertPageSize;
  end;
end;

function TNextDBGrid6.GetShown(Index: Integer): Boolean;
begin
  Result := True;
end;

function TNextDBGrid6.GetVisibleRowCount: Integer;
begin
  Result := FCells.GetShowingCount;
end;

function TNextDBGrid6.IsIntegralHeight: Boolean;
begin
  Result := not BufferRecords;
end;

procedure TNextDBGrid6.LocateCell(ACol, ARow: Integer);
begin
//  FDataLink.LocateTo(ARow);
//  Inc(FEditingLockCount);
  AutoPostEdit := Self.AutoPost;
end;

function TNextDBGrid6.MoveToCell(ACol, ARow: Integer): INxCell;
begin
  FDataLink.LocateTo(ARow);
  Result := FCells.CreateCell(ACol, ARow);
end;

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

procedure TNextDBGrid6.NotityActiveChanged(Active: Boolean);
begin
  if Assigned(DesignerDelegate) then
  begin
    INxDBDesignNotificationRecipient(DesignerDelegate).DataLinkActiveChanged(Active);
  end;
end;

procedure TNextDBGrid6.Paint;
begin
  try
    inherited;

  finally

  end;

//  WritelnDebug(IntToStr(FDataLink.FRecordCount));
end;

procedure TNextDBGrid6.Recalculate;
begin
  if FDataLink.Active then
    try
      FDataLink.DataSet.DisableControls;
      inherited Recalculate;
    finally
      FDataLink.DataSet.EnableControls;
    end;
end;

procedure TNextDBGrid6.RemoveRecordFromList(ARecord: INxRecord);
begin
//  FCells.DeleteRecord();
end;

procedure TNextDBGrid6.Resort;
begin
  FCells.Sort(FCells.FSortedCol, FCells.FSortKind, True);
end;

function TNextDBGrid6.RowPersist(const Index: Integer): Boolean;
begin
  Result := IsBuffered(Index);
end;

procedure TNextDBGrid6.SelectNone(DelayedRefresh: Boolean);
begin
  FSelectedCol := -1;
  FSelectedRow := -1;
  if gtMultiSelected in FState then
  begin
    FCells.SelectNone;
    Exclude(FState, gtMultiSelected);
    if not DelayedRefresh and Assigned(ActiveView) then
    begin
      ActiveView.RefreshData;
    end;
  end;
end;

procedure TNextDBGrid6.SetActiveRow(const Index: Integer);
begin
  if FDataLink.Active then FDataLink.LocateTo(Index);
end;

procedure TNextDBGrid6.SetBufferRecords(const Value: Boolean);
begin
  if Value <> FBufferRecords then
  begin
    FBufferRecords := Value;
    if FDataLink.Active then
    begin
      FDataLink.DataSet.First;
      FDataLink.Recount;
    end;
    Invalidate;
  end;
end;

procedure TNextDBGrid6.SetCells(ACol, ARow: Integer; const Value: WideString);
begin
  Cell[ACol, ARow].AsString := Value;
end;

procedure TNextDBGrid6.SetDataSource(const Value: TDataSource);
begin
  if Value <> FDataSource then
  begin
    { Clear buffer }
    FDataLink.BufferCount := 0;

    { Don't notify anymore }
    if Assigned(FDataSource) then
    begin
      FDataLink.DataSource := nil;

      FDataSource.RemoveFreeNotification(Self);

      { Set `Field` to nil }
      UnassignFields;
    end;

    { Assign value }
    FDataSource := Value;

    { Link to TDataLink }
    { This should call ActiveChanged ?! }
    FDataLink.DataSource := FDataSource;

    if Assigned(FDataSource) then
    begin
      { Notify if destroyed }
      FDataSource.FreeNotification(Self);

//      { Link `Field` properties, or add Cols. }
//      if FDataLink.Active then FDataLink.ActiveChanged;
    end;
  end;
end;

procedure TNextDBGrid6.SetFields;
var
  i: Integer;
begin
  for i := 0 to Pred(ColCount) do
  begin

  end;
end;

procedure TNextDBGrid6.SetSelected(Index: Integer; const Value: Boolean);
begin
  Row[Index].Selected := Value;
end;

procedure TNextDBGrid6.Test;
begin
  FDataLink.ActiveRecord := 5;
end;

procedure TNextDBGrid6.UnassignFields;
var
  i: Integer;
begin
  for i := 0 to Pred(Columns.Count) do
  begin
    DataBinding[i].Field := nil;
  end;
end;

procedure TNextDBGrid6.WMSize(var Message: TWMSize);
var
  Recounted: Boolean;
begin
  Recounted := False;
  if FDataLink.Active
    and not BufferRecords then
  begin
    Recounted := FDataLink.Recount;
  end;

  { Invalidation }
  inherited WMSize(Message);

  if not BufferRecords and Recounted then
  begin
    Invalidate;
//    RefreshRow(Pred(LastVisibleRow));
  end;
end;

procedure TNextDBGrid6.WMVScroll(var Message: TWMVScroll);
var
  Delta: Integer;
begin
  if not BufferRecords and not FDataLocked and Assigned(ActiveView) then
  begin
    FDataScrolling := True;

    with Message, FDataLink do

      case ScrollCode of
        SB_LINEDOWN:
        begin
          try
            { Save `Position` into `SnapshotPosition` }
            VertScrollBar.Snapshot;

            { Scroll, offset etc. }
            inherited;
          finally
            VertScrollBar.EraseSnapshot;
          end;

          MoveBy(RecordCount - ActiveRecord);
        end;

        SB_LINEUP:
        begin
          if VertScrollBar.Position > 0 then MoveBy(-Succ(ActiveRecord));

          inherited;
        end;

        SB_PAGEDOWN:
        begin
          try
            { Save `Position` into `SnapshotPosition` }
            VertScrollBar.Snapshot;

            inherited;
          finally
            VertScrollBar.EraseSnapshot;
          end;

          ActiveRecord := Pred(RecordCount);
          MoveBy(VertScrollBar.LargeChange);
        end;

        SB_PAGEUP:
        begin
          ActiveRecord := 0;
          MoveBy(-VertScrollBar.LargeChange);

          inherited;
        end;

        SB_THUMBPOSITION,
        SB_THUMBTRACK:
          if (Message.ScrollCode = SB_THUMBPOSITION)
            or ScrollBarTracking then
          begin
            { Note: ThumbPosition property return large/correct values
                    than Message.Pos }

            Delta := VertScrollBar.ThumbPosition - VertScrollBar.Position;

            { Scroll down }
            if Delta > 0 then
            begin
              try
                { Save `Position` into `SnapshotPosition` }
                VertScrollBar.Snapshot;

                { Scroll, offset etc. }
                inherited;
              finally
                VertScrollBar.EraseSnapshot;
              end;

              ActiveRecord := Pred(RecordCount);
              MoveBy(Delta);
            end

            { Scroll up }
            else if Delta < 0 then
            begin
              ActiveRecord := 0;
              MoveBy(Delta);

              inherited;
            end

            { No scroll }
            else Exit;

          end;

        SB_BOTTOM:
        begin
          DataSet.Last;
          Invalidate;

          inherited;
        end;

        SB_TOP:
        begin
          DataSet.First;
          Invalidate;

          inherited;
        end;
      end;

    FDataScrolling := False;
  end
  else inherited;
end;

{ TNxCellsDBGrid6 }

procedure TNxCellsDBGrid6.CellChanged(Cell: INxCell;
  UpdateSpread: TNxCellUpdateSpread);
begin
  RefreshCell(FCellSource.Location.X, FCellSource.Location.Y);
end;

procedure TNxCellsDBGrid6.ChildDeleted(ARow: INxRow; Hide: Boolean);
begin

end;

procedure TNxCellsDBGrid6.ChildsCleared(ARow: INxRow);
begin

end;

procedure TNxCellsDBGrid6.DoCellCompare(Cell1, Cell2: INxCell;
  var Compare: Integer);
begin

end;

procedure TNxCellsDBGrid6.ExpandedChanged(Row: INxRow; Expanded: Boolean);
begin

end;

procedure TNxCellsDBGrid6.HeightChanged(Index, Delta: Integer);
begin

end;

procedure TNxCellsDBGrid6.RowMoved(const CurIndex, NewIndex: Integer);
begin

end;

procedure TNxCellsDBGrid6.SetLastAddedRow(const Value: Integer);
begin

end;

procedure TNxCellsDBGrid6.ShowChildRows(ARow: INxRow; Count: Integer;
  Hide: Boolean);
begin

end;

procedure TNxCellsDBGrid6.VisibilityChanged(Index: Integer; Show: Boolean);
begin

end;

end.
