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

unit IWAdvWebGrid;

{$I TMSDEFS.INC}

interface                                                   

uses
  {$IFDEF LINUX}
  QControls, QGraphics,
  {$ELSE}
  Controls, Windows, Graphics,
  {$ENDIF}
  {$IFDEF DELPHI6_LVL}
  Types,
  {$ENDIF}
  Classes,
  IWControl, IWTypes, SysUtils, IWCompButton,
  IWCompEdit, IWCompLabel, IWCompCheckbox, IWExtCtrls, IWHTMLTag,
  IWFont, IWWebGrid, Math;

const
  MAJ_VER = 3; // Major version nr.
  MIN_VER = 6; // Minor version nr.
  REL_VER = 0; // Release nr.
  BLD_VER = 0; // Build nr.
  DATE_VER = 'May, 2009'; // Month version

  // version history
  // 2.5.3.0 : move row selection in msRowCheck mode during sorting
  //         : added InitSortedIndexes method
  // 2.7.6.0 : Improved hover color with active color combined
  //         : Fixed issue with pager pagelist
  //         : Fixed issue with Post event
  // 2.8.0.0 : IW9 compatibility added

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

type
  TTIWCustomAdvWebGrid = class;

  TTIWSortFormat = (sfAlphabetic, sfNumeric, sfDate, sfFinancial, sfCustom);

  TTIWCustomCompare = procedure(Sender: TObject; Column: Integer; ARow1,ARow2: Integer; AValue1,AValue2: string; var CompareResult: Integer) of object;

  TTIWAdvWebGridColumn = class(TTIWWebGridColumn)
  private
    FSortFormat: TTIWSortFormat;
    FSortSuffix: string;                  
    FSortPrefix: string;
  public
    procedure Assign(Source: TPersistent); override;
  published
    property SortFormat: TTIWSortFormat read FSortFormat write FSortFormat;
    property SortPrefix: string read FSortPrefix write FSortPrefix;
    property SortSuffix: string read FSortSuffix write FSortSuffix;
  end;

  TTIWAdvWebGridColumns = class(TTIWWebGridColumns)
  private
    function GetItem(Index: Integer): TTIWAdvWebGridColumn;
    procedure SetItem(Index: Integer; const Value: TTIWAdvWebGridColumn);
  public
    function GetItemClass: TCollectionItemClass; override;
    function Add: TTIWAdvWebGridColumn;
    function Insert(index: Integer): TTIWAdvWebGridColumn;
    property Items[Index: Integer]: TTIWAdvWebGridColumn read GetItem write SetItem; default;
  end;

  TTIWCustomAdvWebGrid = class(TTIWCustomWebGrid)
  private
    FEditList: TStringList;
    FCellList: TList;
    FTotalRows: Integer;
    FActiveRow: Integer;
    FGridState: TTIWGridState;
    FSortDir: Integer;
    FDelimiter: char;
    FOemConvert: Boolean;
    FQuoteEmptyCells: Boolean;
    FAlwaysEdit: Boolean;
    FRowIndexList: TList;
    FOnCustomCompare: TTIWCustomCompare;
    function GetCells(ACol, ARow: Integer): string;
    procedure SetCells(ACol, ARow: Integer; const Value: string);
    procedure UpdateCellList(Num: Integer);
    procedure SetTotalRows(const Value: Integer);
    procedure SetActiveRow(const Value: Integer);
    function GetActiveRow: Integer;
    function GetColumns: TTIWAdvWebGridColumns;
    procedure SetColumns(const Value: TTIWAdvWebGridColumns);
    function GetState: TTIWGridState;
  protected
    procedure UpdateRowCount(const Value: Integer); override;
    procedure UpdateTotalRows(const Value: Integer); override;    
    function GetCellData(ACol,ARow: Integer): string; override;
    procedure SetCellData(ACol,ARow: Integer; Value: string;postalways:boolean=false); override;
    function GetCellImage(ACol,ARow: Integer): TPicture; override;
    function GetLookupValue(ACol:Integer; ACellValue:string): string; override;
    function GetLookupValues(ACol:Integer; ACellValue:string): string; override;
    function GetColumnTitle(ACol: Integer): string; override;
    function GetGridState: TTIWGridState; override;
    procedure GetRowInfo(var NumRows,RowOffset,PageOffset: Integer); override;
    function GetCurrentRecord: integer; override;
    function IsValidRow(ARow: integer): boolean; override;
    function RenderPrepare: Boolean; override;
    procedure RenderDone; override;
    procedure DoEdit; override;
    procedure DoCancel; override;
    function DoPost(postalways:boolean=false): Boolean; override;
    procedure DoInsert; override;
    procedure GotoRecord(RecordIndex: Integer); override;
    function NextRecord: boolean; override;
    function DirectEditUpdate(ACol,ARow: Integer;var Value: string): Boolean; override;
    procedure SwapRows(ARow1,ARow2: Integer);
    procedure QuickSortRows(Col,Left,Right: Integer);
    function CompareRows(ACol,ARow1,ARow2: Integer): Integer;
    procedure InputFromCSV(FileName: string; InsertMode: Boolean);
    procedure InputFromStream(AStream: TStream; InsertMode: Boolean);
    procedure OutputToCSV(FileName: string; AppendMode: Boolean; Unicode: boolean);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function CreateColumns: TTIWWebGridColumns; override;
    property Cells[ACol,ARow: Integer]: string read GetCells write SetCells;
    function SortedToRowIndex(RowIndex: Integer): Integer;
    function RowToSortedIndex(RowIndex: Integer): Integer;
    procedure InitSortedIndexes;
    procedure ClearCells; override;
    procedure InsertRows(RowIndex,RowCount: Integer); override;
    procedure DeleteRows(RowIndex,RowCount: Integer); override;
    procedure DeleteSelectedRows;
    procedure InsertColumns(ColIndex, ColCount: Integer);
    procedure DeleteColumns(ColIndex, ColCount: Integer);
    procedure ClearRows(RowIndex,RowCount: Integer);
    procedure ClearColumns(ColIndex,ColCount: Integer);
    procedure InsertFromCSV(FileName: string);
    procedure LoadFromCSV(FileName: string);
    procedure SaveToCSV(FileName: string; Unicode: boolean = true);
    procedure AppendToCSV(FileName: string; Unicode: boolean = true);
    procedure SaveToStream(AStream: TStream);
    procedure LoadFromStream(AStream: TStream);
    procedure Edit; override;
    procedure Post;
    procedure Cancel;
    procedure ColumnHeaderClick(AColumn: Integer); override;
    procedure QSort; virtual;
    property State: TTIWGridState read GetState;
    property Delimiter: char read FDelimiter write FDelimiter;
    property OemConvert: Boolean read FOemConvert write FOemConvert;
    property QuoteEmptyCells: Boolean read FQuoteEmptyCells write FQuoteEmptyCells;
    function GetVersionNr: Integer; override;
    function GetVersionString: string; override;
  published
    property ActiveRow: Integer read GetActiveRow write SetActiveRow;
    property AlwaysEdit: Boolean read FAlwaysEdit write FAlwaysEdit;
    property TotalRows: Integer read FTotalRows write SetTotalRows;
    property Columns: TTIWAdvWebGridColumns read GetColumns write SetColumns;
    property Color;
    property OnCustomCompare: TTIWCustomCompare read FOnCustomCompare write FOnCustomCompare;
  end;

  TTIWAdvWebGrid = class(TTIWCustomAdvWeBGrid)
  end;

  TTIWAdvDetailWebGrid = class(TTIWCustomAdvWeBGrid)
  public
    constructor Create(AOwner: TComponent); override;
  published
    property FullHeight;
  end;


implementation

uses
  {$IFDEF LINUX}
  QImgList,
  {$ELSE}
  ImgList, ShellAPI, CommCtrl,
  {$ENDIF}
  IWApplication, IWServerControllerBase,
  SWStrings, SWSystem, IWAppForm;

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

procedure TFileStringList.Reset;
begin
  fp := 0;
  cache := '';
end;

function TFileStringList.GetEOF;
begin
  Result := fp >= Count;
end;

procedure TFileStringList.ReadLn(var s: string);
begin
  s := Strings[fp];
  inc(fp);
end;

procedure TFileStringList.Write(s: string);
begin
  cache := cache + s;
end;

procedure TFileStringList.WriteLn(s: string);
begin
  Add(cache + s);
  cache := '';
end;

  

{ TTIWDBAdvWebGrid }

constructor TTIWCustomAdvWebGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FCellList := TList.Create;
  FEditList := TStringList.Create;
  FTotalRows := 10;
  UpdateCellList(10);
  FGridState := gsBrowse;
  FDelimiter := #0;
  FOemConvert := False;
  FRowIndexList := TList.Create;
end;

function TTIWCustomAdvWebGrid.CreateColumns: TTIWWebGridColumns;
begin
  Result := TTIWAdvWebGridColumns.Create(Self);
end;

destructor TTIWCustomAdvWebGrid.Destroy;
begin
  UpdateCellList(0);
  FEditList.Free;
  FCellList.Free;
  FRowIndexList.Free;  
  inherited;
end;

function TTIWCustomAdvWebGrid.GetVersionNr: Integer;
begin
  Result := MakeLong(MakeWord(BLD_VER,REL_VER),MakeWord(MIN_VER,MAJ_VER));
end;

function TTIWCustomAdvWebGrid.GetVersionString: string;
var
  vn: Integer;
begin
  vn := GetVersionNr;
  Result := IntToStr(Hi(Hiword(vn)))+'.'+IntToStr(Lo(Hiword(vn)))+'.'+IntToStr(Hi(Loword(vn)))+'.'+IntToStr(Lo(Loword(vn)))+' '+DATE_VER;
end;


procedure TTIWCustomAdvWebGrid.DoCancel;
begin                            
  inherited;
  Cancel;
end;

procedure TTIWCustomAdvWebGrid.DoEdit;
begin
  inherited;
  Edit;
end;

procedure TTIWCustomAdvWebGrid.DoInsert;
begin
end;

function TTIWCustomAdvWebGrid.DoPost(postalways:boolean=false): Boolean;
begin
  Result := inherited DoPost(postalways);

 // if Result then
 //   Post;
  FGridState := gsBrowse;
end;

procedure TTIWCustomAdvWebGrid.Edit;
begin
  inherited;
  FGridState := gsEdit;
  FEditList.Assign(TStringList(FCellList.Items[FActiveRow + RowOffset]));
end;

function TTIWCustomAdvWebGrid.GetCellData(ACol, ARow: Integer): string;
begin
  if AutoHTMLAdapt then
    Result := HTMLAdapt(Cells[ACol,RowOffset + ARow])
  else
  begin
    if (RowOffset + ARow >= TotalRows) then
      Result := ''
    else
      Result := Cells[ACol,RowOffset + ARow];
  end;

  if Assigned(OnGetCellData) then
    OnGetCellData(Self,ARow,ACol,Result);

  if Result = '' then
    Result := '&nbsp;';
end;

function TTIWCustomAdvWebGrid.GetCellImage(ACol, ARow: Integer): TPicture;
var
  bmp: TBitmap;
  idx,err: integer;
  pic: TPicture;

begin
  Result := nil;
  bmp := nil;

  if (Columns[ACol].ColumnType = ctImage) and Assigned(Images) and (Columns[ACol].ImageIndex >=0) then
  begin
    if (Columns[ACol].ImageIndex < Images.Count) then
    begin
      bmp := TBitmap.Create;
      Images.GetBitmap(Columns[ACol].ImageIndex,bmp);
    end;
  end;

  if (Columns[ACol].ColumnType = ctDataImage) and Assigned(Images) then
  begin
    val(GetCellData(ACol,ARow),idx,err);

    if (err=0) then
    begin
      if (idx >= 0) and (idx < Images.Count) then
      begin
        bmp := TBitmap.Create;
        Images.GetBitmap(idx,bmp);
      end;
    end;
  end;

  if Assigned(bmp) then
  begin
    pic := TPicture.Create;
    pic.Assign(bmp);
    bmp.Free;
    Result := pic;
  end;
end;

function TTIWCustomAdvWebGrid.GetCells(ACol, ARow: Integer): string;
var
  sl: TStringList;
begin
  if TotalRows = 0 then
  begin
    Result := '';
    Exit;
  end;

  if (ARow < 0) or (ARow >= TotalRows) then
    raise Exception.Create('Row index out of bounds '#13+InttoStr(ARow)+' is not in range 0..'+inttostr(FTotalRows-1));

  if (ACol < 0) or (ACol >= Columns.Count) then
    raise Exception.Create('Column index out of bounds '#13+InttoStr(ACol)+' is not in range 0..'+inttostr(Columns.Count-1));

  sl := TStringList(FCellList.Items[ARow]);

  if sl.Count > ACol then
    Result := sl.Strings[ACol]
  else
    Result := '';
end;

function TTIWCustomAdvWebGrid.GetColumnTitle(ACol: Integer): string;
begin
  Result := Columns[ACol].Title;
end;

function TTIWCustomAdvWebGrid.GetGridState: TTIWGridState;
begin
  Result := FGridState;
end;

function TTIWCustomAdvWebGrid.GetLookupValue(ACol: Integer;
  ACellValue: string): string;
begin
  Result := ACellValue;
end;

function TTIWCustomAdvWebGrid.GetLookupValues(ACol: Integer;
  ACellValue: string): string;
var
  k: Integer;
  cval:string;
begin
  {$IFDEF TMSDEBUG}
  outputdebugstring('In lookup values');
  {$ENDIF}
  Result := '';

  for k := 1 to Columns[ACol].ComboItems.Count do
  begin
    {$IFDEF TMSDEBUG}
    outputdebugstring(pchar(Columns[ACol].ComboItems[k - 1]));
    {$ENDIF}
    if Columns[ACol].ComboValues.Count  >= k then
      cval := Columns[ACol].ComboValues[k - 1]
    else
      cval := Columns[ACol].ComboItems[k - 1];

    Result := Result + '<OPTION VALUE="'+cval+'"';
    if cval = ACellValue then
      Result := Result + ' SELECTED';

    Result := Result + '>'+Columns[ACol].ComboItems[k - 1];
  end;
end;

function TTIWCustomAdvWebGrid.GetCurrentRecord: integer;
begin
  Result := FActiveRow; 
end;

procedure TTIWCustomAdvWebGrid.GetRowInfo(var NumRows, RowOffset,PageOffset: Integer);
begin
  NumRows := FTotalRows;

  if RowCount > 0 then
  begin
    RowOffset := (self.RowOffset div RowCount) * RowCount;
    PageOffset := FActiveRow mod RowCount;
  end
  else
  begin
    RowOffset := 0;
    PageOffset := 0;
  end;
end;

function TTIWCustomAdvWebGrid.IsValidRow(ARow: Integer): boolean;
begin
  Result := (RowOffset + ARow < TotalRows);
end;

procedure TTIWCustomAdvWebGrid.GotoRecord(RecordIndex: Integer);
begin
  if RowCount > 0 then
    FActiveRow := RecordIndex mod RowCount
  else
    FActiveRow := RecordIndex;

  FGridState := gsBrowse;
end;

function TTIWCustomAdvWebGrid.NextRecord: boolean;
begin
  Result := false;
end;

procedure TTIWCustomAdvWebGrid.RenderDone;
begin

end;

function TTIWCustomAdvWebGrid.RenderPrepare: Boolean;
begin
  Result := True;
  if AlwaysEdit then
    FGridState := gsEdit;
end;

procedure TTIWCustomAdvWebGrid.SetActiveRow(const Value: Integer);
begin
  if (Value >= TotalRows) or (Value < -1) then
    raise Exception.Create('Active row should be in range -1..'+IntToStr(TotalRows-1));

  if RowCount <> 0 then
  begin
    FActiveRow := Value mod RowCount;
    SetRowOffset((Value div RowCount) * RowCount);
  end
  else
  begin
    FActiveRow := Value;
    SetRowOffset(0);
  end;
end;

function TTIWCustomAdvWebGrid.GetActiveRow: Integer;
begin
  Result := FActiveRow + RowOffset;
end;

procedure TTIWCustomAdvWebGrid.SetCellData(ACol, ARow: Integer; Value: string;postalways:boolean=false);
begin
  {$IFDEF TMSDEBUG}
  outputdebugstring(pchar('SETCELLDATA - col:'+inttostr(acol)+' row:'+inttostr(FActiveRow)+' offset:'+IntToStr(RowOffset)+':'+Value));
  {$ENDIF}

  if GetGridState <> gsBrowse then
  begin
    if (Columns[ACol].Editor = edCheckBox) then
    begin
      if (Value = 'true') or (Value = Columns[ACol].CheckTrue) then
        Value := Columns[ACol].CheckTrue
      else
        Value := Columns[ACol].CheckFalse;
    end;

    if (Columns[ACol].Editor <> edNone) or postalways then
    begin
      if Assigned(OnSetCellData) then
        OnSetCellData(Self,ACol,FActiveRow + RowOffset,Value);

      Cells[ACol,FActiveRow + RowOffset] := Value;
    end;
  end;

end;


procedure TTIWCustomAdvWebGrid.SetCells(ACol, ARow: Integer; const Value: string);
var
  sl: TStringList;
begin
  if (ARow < 0) or (ARow >= TotalRows) then
    raise Exception.Create('Row index out of bounds');

  if (ACol < 0) or (ACol >= Columns.Count) then
    raise Exception.Create('Column index out of bounds');


  sl := TStringList(FCellList.Items[ARow]);

  if sl.Count > ACol then
    sl.Strings[ACol] := Value
  else
  begin
    while (sl.Count < ACol + 1) do
    begin
      sl.Add('');
    end;
    sl.Strings[ACol] := Value;
  end;
  Invalidate;
end;


procedure TTIWCustomAdvWebGrid.SetTotalRows(const Value: Integer);
begin
  FTotalRows := Value;

  if (RowCount > 0) and (FTotalRows > 0) then
  begin
    if ((FTotalRows - 1) div RowCount < Page) then
      Page := (FTotalRows - 1) div RowCount;
  end
  else
    Page := 0;

  UpdateCellList(FTotalRows);
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.UpdateCellList(Num: Integer);
begin
  while FCellList.Count > Num do
  begin
    {$IFDEF TMSDEBUG}
    outputdebugstring(pchar('destroy list  :'+inttostr(num)));
    {$ENDIF}
    TStringList(FCellList.Items[FCellList.Count - 1]).Free;
    FCellList.Delete(FCellList.Count - 1);
  end;

  while FCellList.Count < Num do
  begin
    {$IFDEF TMSDEBUG}
    outputdebugstring(pchar('create list  :'+inttostr(num)));
    {$ENDIF}
    FCellList.Add(TStringList.Create);
  end;
end;

procedure TTIWCustomAdvWebGrid.Post;
//var
//  i: Integer;
begin
  DoPost;
  {
  for i := 1 to PostValues.Count do
  begin
    SetCellData(i - 1, 0, PostValues.Strings[i - 1]);
  end;

  FGridState := gsBrowse;
  }
end;

procedure TTIWCustomAdvWebGrid.Cancel;
begin
  if FGridState <> gsBrowse then
  begin
    FGridState := gsBrowse;
    // restore original value
    TStringList(FCellList.Items[FActiveRow + RowOffset]).Assign(FEditList);
  end;
end;

procedure TTIWCustomAdvWebGrid.ColumnHeaderClick(AColumn: Integer);
begin
  inherited;                                    

  if SortSettings.Show and (FGridState = GSBrowse) then
  begin
    if (SortSettings.Column = AColumn) then
    begin
      if (SortSettings.Direction = sdAscending) then
        SortSettings.Direction := sdDescending
      else
        SortSettings.Direction := sdAscending;
    end
    else
      SortSettings.Direction := SortSettings.InitSortDir;

    SortSettings.Column := AColumn;
    QSort;
  end;
end;

function VarPos(su,s: string; var p: integer): integer;
begin
  p := pos(su,s);
  Result := p;
end;

function TTIWCustomAdvWebGrid.CompareRows(ACol,ARow1, ARow2: Integer): Integer;
var
  v1,v2: double;
  err,sp: Integer;
  dt1,dt2: TDateTime;
  s1,s2:string;
begin

  if (Columns[ACol].ColumnType in [ctDynText,ctDynCheckBox,ctDynEdit]) then
  begin
    s1 := DynEdits[ACol,ARow1];
    s2 := DynEdits[ACol,ARow2];
  end
  else
  begin
    s1 := Cells[ACol,ARow1];
    s2 := Cells[ACol,ARow2];
  end;

  if (Columns[ACol].SortPrefix <> '') then
  begin
    if pos(Columns[ACol].SortPrefix,s1) = 1 then
      Delete(s1,1,length(Columns[ACol].SortPrefix));
    if pos(Columns[ACol].SortPrefix,s2) = 1 then
      Delete(s2,1,length(Columns[ACol].SortPrefix));
  end;

  if (Columns[ACol].SortSuffix <> '') then
  begin
    if pos(Columns[ACol].SortSuffix,s1) = length(s1) - length(Columns[ACol].SortSuffix) then
      Delete(s1,1,length(Columns[ACol].SortSuffix));
    if pos(Columns[ACol].SortSuffix,s2) = length(s2) - length(Columns[ACol].SortSuffix) then
      Delete(s2,1,length(Columns[ACol].SortSuffix));
  end;

  case Columns[ACol].SortFormat of
  sfAlphabetic: Result := AnsiCompareStr(s1,s2);
  sfNumeric:
    begin
      val(s1,v1,err);
      val(s2,v2,err);

      if v1 > v2 then
        Result := 1
      else
        if v1 = v2 then
          Result := 0
        else
          Result := -1;
    end;
  sfFinancial:
    begin
      {delete the thousandseparator}
      while VarPos(ThousandSeparator,s1,sp) > 0 do
        Delete(s1,sp,1);
      while VarPos(ThousandSeparator,s2,sp) > 0 do
        Delete(s2,sp,1);

      if DecimalSeparator <> '.' then
      begin
        if Varpos(Decimalseparator,s1,sp) > 0 then
          s1[sp] := '.';
        if VarPos(DecimalSeparator,s2,sp) > 0 then
          s2[sp] := '.';
      end;
      
      val(s1,v1,err);
      val(s2,v2,err);

      if v1 > v2 then
        Result := 1
      else
        if v1 = v2 then
          Result := 0
        else
          Result := -1;
    end;
  sfDate:
    begin
      dt1 := DateStrToDate(s1);
      if s1 = '' then
        dt1 := 0;
      dt2 := DateStrToDate(s2);
      if s2 = '' then
        dt2 := 0;
      if dt1 > dt2 then
        Result := 1
      else
        if dt1 = dt2 then
          Result := 0
        else
          Result := -1;
    end;
  sfCustom:
    begin
      Result := 0;
      if Assigned(OnCustomCompare) then
        OnCustomCompare(Self, ACol,ARow1, ARow2, s1,s2,Result);
    end
  else
    Result := 0;
  end;
end;

procedure TTIWCustomAdvWebGrid.QuickSortRows(Col,Left,Right: Integer);
var
  i,j,k,mid: Integer;

begin
  if SortSettings.Direction = sdAscending then
    FSortDir := 1
  else
    FSortDir := -1;

  i := Left;
  j := Right;
  mid := (Left + Right) shr 1;

  //create temporary reference row
  for k :=  1 to Columns.Count do
  begin
    Cells[k - 1,TotalRows - 1] := Cells[k - 1,mid];
    if Columns[k - 1].ColumnType in [ctDynEdit,ctDynCombo,ctDynCheckbox] then
      DynEdits[k - 1, TotalRows - 1] := DynEdits[k - 1,mid];
  end;

  repeat
    while (CompareRows(Col,TotalRows - 1,i) = FSortDir) and (i < Right) do Inc(i);
    while (CompareRows(Col,j,TotalRows - 1) = FSortDir) and (j > Left) do Dec(j);

    if i <= j then
    begin
      if i <> j then
      begin
        if CompareRows(Col,i,j) <> 0 then
          SwapRows(i,j);
      end;
      Inc(i);
      Dec(j);
    end;
  until i > j;

  if Left < j then
    QuickSortRows(Col,Left,j);
  if i < Right then
    QuickSortRows(Col,i,Right);
end;


procedure TTIWCustomAdvWebGrid.QSort;
var
  i: Integer;
begin
  if TotalRows <= 1 then
    Exit;

  if FRowIndexList.Count <> TotalRows then
    for i := 1 to TotalRows do
      FRowIndexList.Add(TObject(i - 1));

  TotalRows := TotalRows + 1;
  QuickSortRows(SortSettings.Column,0,TotalRows - 2);
  TotalRows := TotalRows - 1;
  ActiveRow := 0;
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.SwapRows(ARow1, ARow2: Integer);
var
  sl: TStringList;
  a,i: Integer;
  s: string;
  chk: boolean;

begin
  sl := FCellList.Items[ARow1];
  FCellList.Items[ARow1] := FCellList.Items[ARow2];
  FCellList.Items[ARow2] := sl;

  a := Integer(FRowIndexList.Items[ARow1]);
  FRowIndexList.Items[ARow1] := FRowIndexList.Items[ARow2];
  FRowIndexList.Items[ARow2] := TObject(a);

  if MouseSelect in [msRowCheck, msSingleRowCheck] then
  begin
    chk := RowSelect[ARow1];
    RowSelect[ARow1] := RowSelect[ARow2];
    RowSelect[ARow2] := chk;
  end;

  for i := 1 to Columns.Count do
  begin
    if (Columns[i - 1].ColumnType in [ctDynEdit,ctDynCombo,ctDynCheckBox]) then
    begin
      s := DynEdits[i - 1,ARow1];
      DynEdits[i - 1,ARow1] := DynEdits[i - 1,ARow2];
      DynEdits[i - 1,ARow2] := s;
    end;
  end;
end;

{ TTIWAdvWebGridColumns }

function TTIWAdvWebGridColumns.Add: TTIWAdvWebGridColumn;
begin
  Result := TTIWAdvWebGridColumn(inherited Add);
end;

function TTIWAdvWebGridColumns.GetItem(
  Index: Integer): TTIWAdvWebGridColumn;
begin
  Result := TTIWAdvWebGridColumn(inherited Items[Index]);
end;

function TTIWAdvWebGridColumns.GetItemClass: TCollectionItemClass;
begin
  Result := TTIWAdvWebGridColumn;
end;

function TTIWAdvWebGridColumns.Insert(
  index: Integer): TTIWAdvWebGridColumn;
begin
  Result := TTIWAdvWebGridColumn(inherited Insert(Index));
end;

procedure TTIWAdvWebGridColumns.SetItem(Index: Integer;
  const Value: TTIWAdvWebGridColumn);
begin
  inherited Items[Index] := Value;
end;

function TTIWCustomAdvWebGrid.GetColumns: TTIWAdvWebGridColumns;
begin
  Result := TTIWAdvWebGridColumns(inherited Columns);
end;

procedure TTIWCustomAdvWebGrid.SetColumns(const Value: TTIWAdvWebGridColumns);
begin
  inherited Columns := Value;
end;

function TTIWCustomAdvWebGrid.GetState: TTIWGridState;
begin
  Result := GetGridState;
end;

procedure TTIWCustomAdvWebGrid.ClearCells;
var
  i: Integer;
begin
  for i := 1 to FCellList.Count do
    TStringList(FCellList.Items[i - 1]).Clear;
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.ClearRows(RowIndex, RowCount: Integer);
var
  i: Integer;
begin
  for i := RowIndex to RowIndex + RowCount - 1 do
  begin
    if i < FCellList.Count then
      TStringList(FCellList.Items[i - 1]).Clear;
  end;
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.ClearColumns(ColIndex, ColCount: Integer);
var
  i,j: Integer;
begin
  for i := 1 to FCellList.Count do
  begin
    for j := ColIndex to ColIndex + ColCount do
      Cells[j,i - 1] := '';
  end;
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.DeleteRows(RowIndex, RowCount: Integer);
var
  i,j: Integer;
  sl: TStringList;
begin
  inherited;
  j := 0;
  for i := RowIndex to RowIndex + RowCount - 1 do
  begin
    if RowIndex < FCellList.Count then
    begin
      sl := TStringList(FCellList.Items[RowIndex]);
      sl.Free;
      FCellList.Delete(RowIndex);
      DeleteSelection(RowIndex);
      inc(j);
    end;
  end;
  TotalRows := TotalRows - j;

  //changed : go to previous page when no rows on current page
  if (Page > 0) then
  begin
    if (Self.RowCount*Page) >= TotalRows then
      Page := Page - 1;
  end;

  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.InsertRows(RowIndex, RowCount: Integer);
var
  i: Integer;
begin
  inherited;
  for i := RowIndex to RowIndex + RowCount - 1 do
  begin
    FCellList.Insert(i,pointer(TStringList.Create));
  end;
  TotalRows := TotalRows + RowCount;
  Invalidate;
end;

function SinglePos(p:char;s:string;var sp: Integer):Integer;
var
  i: Integer;
  QuoteCount: Integer;
begin
  i := 1;
  QuoteCount:= 0;
  while i <= Length(s) do
  begin
    if s[i] = p then
    begin
      if i < Length(s) then
        Inc(QuoteCount)
      else
        if i = Length(s) then
        begin
          Result := i;
          sp := i;
          Exit;
        end;
    end
    else
    begin
      if (Odd(QuoteCount)) then
      begin
        Result := i - 1;
        sp := i - 1;
        Exit;
      end
      else
        QuoteCount := 0;
    end;
    Inc(i);
  end;
  Result := 0;
  sp := 0;
end;


function NumSingleChar(p:char;s:string):Integer;
var
  Res,sp: Integer;
begin
  Res := 0;
  while SinglePos(p,s,sp) > 0 do
  begin
    Delete(s,1,sp);
    Inc(Res);
  end;
  Result := Res;
end;

function NumChar(p:char;s:string):Integer;
var
  Res,vp: Integer;
begin
  Res := 0;
  while VarPos(p,s,vp) > 0 do
  begin
    Delete(s,1,vp);
    Inc(Res);
  end;
  Result := Res;
end;

procedure StringToOem(var s:string);
{$IFDEF WIN32}
var
  pin: PChar;
  {$IFDEF DELPHI_UNICODE}
  pout: PAnsiChar;
  {$ENDIF}
  {$IFNDEF DELPHI_UNICODE}
  pout: PChar;
  {$ENDIF}
{$ENDIF}
begin
  //DELPHI_UNICODE
  {$IFDEF WIN32}
  GetMem(pin,Length(s) + 1);
  GetMem(pout,Length(s) + 1);
  StrLCopy(pin,PChar(s),Length(s));
  CharToOem(pin,pout);
  s := string(StrPas(pout));
  FreeMem(pin);
  FreeMem(pout);
 {$ENDIF}
end;

procedure OemToString(var s:string);
{$IFDEF WIN32}
var
  {$IFDEF DELPHI_UNICODE}
  pin: PAnsiChar;
  sa: ansistring;
  {$ENDIF}
  {$IFNDEF DELPHI_UNICODE}
  pin: PChar;
  sa: string;
  {$ENDIF}
  pout: PChar;
{$ENDIF}
begin
  //DELPHI_UNICODE
  {$IFDEF WIN32}
  sa := ansistring(s);
  GetMem(pin,Length(sa) + 1);
  GetMem(pout,Length(sa) + 1);
  StrPLCopy(pin,sa,Length(sa));
  OemToChar(pin,pout);
  s := StrPas(pout);
  FreeMem(pin);
  FreeMem(pout);
  {$ENDIF}
end;


function VarCharPos(ch: Char; const s: string; var Res: Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 1 to Length(s) do
    if s[i] = ch then
    begin
      Res := i;
      Result := i;
      Break;
    end;
end;

function CharPos(ch: Char; const s: string): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := 1 to Length(s) do
    if s[i] = ch then
    begin
      Result := i;
      Break;
    end;
end;



function DoubleToSingleChar(ch: Char;const s:string):string;
var
  Res: string;
  i: Integer;
begin
  if (s = '') or (CharPos(ch,s) = 0) then
  begin
    DoubleToSingleChar := s;
    Exit;
  end;
  res := s[1];
  for i := 2 to Length(s) do
  begin
   if s[i] <> ch then
     Res := Res + s[i]
   else
     if ((s[i] = ch) and (s[i - 1] <> ch)) then
       Res := Res + s[i];
  end;
  DoubleToSingleChar := Res;
end;

procedure LineFeedsToCSV(var s:string);
var
  vp: Integer;
begin
  while VarPos(#13#10,s,vp)>0 do
    Delete(s,vp,1);
  s := '"' + s + '"';
end;

procedure CSVToLineFeeds(var s:string);
var
  res: string;
  i: Integer;
begin
  if CharPos(#10,s) = 0 then
    Exit;
  Res := '';
  for i := 1 to Length(s) do
    if s[i] = #10 then
      Res := Res + #13#10
    else
      Res := Res + s[i];
  s := Res;
end;


procedure TTIWCustomAdvWebGrid.InputFromCSV(Filename:string;insertmode: Boolean);

var
  buffer,celltext: string;
  s,z: Integer;
  strtCol,strtRow: Integer;
  c1,c2,cm: Integer;
  OldDelimiter: Char;
  delimiterpos,quotepos: Integer;
  lr: TStringList;
  sl: TFileStringList;

begin
  StrtCol := 0;
  StrtRow := 0;


  (*
  AssignFile(f, FileName);
  {$i-}
  Reset(f);
  {$i+}

  if (IOResult<>0) then
    raise EAdvGridError.Create('Cannot open file ' + FileName);
  *)

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

  z := StrtRow;

  lr := TStringList.Create;

  if InsertMode then
  begin
    z := TotalRows;
  end;

  OldDelimiter := FDelimiter;

  // do intelligent estimate of the separator
  if FDelimiter = #0 then
  begin
    CellText := '';

    sl.ReadLn(buffer);

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

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

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

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

  sl.Reset;

  while (not sl.Eof) do
  begin
    sl.ReadLn(buffer);

    if FOemConvert then
      OemToString(Buffer);

    s := StrtCol;

    if z >= RowCount then
      TotalRows := z + 1000;

    while VarCharPos(FDelimiter,Buffer,DelimiterPos) > 0 do
    begin
      if Buffer[1] = '"' then
      begin
        Delete(buffer,1,1);   //delete first quote from buffer

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


      CSVToLineFeeds(CellText);

      while s >= Columns.Count do
        Columns.Add;

      Cells[s,z] := CellText;

      Delete(buffer,1,DelimiterPos);

      Inc(s);
    end;

    if Length(Buffer) > 0 then
    begin
      if Buffer[1] = '"' then
        Delete(buffer,1,1);
      if Length(Buffer) > 0 then
      begin
        if Buffer[Length(Buffer)] = '"' then
          Delete(Buffer,Length(Buffer),1);
      end;

      CellText := DoubleToSingleChar('"',Buffer);

      CSVToLineFeeds(CellText);

      while (s >= Columns.Count) do
        Columns.Add;

      Cells[s,z] := CellText;
    end;
    Inc(z);
  end;
  
  sl.Free;
  TotalRows := z;
  lr.Free;
  FDelimiter := OldDelimiter;
  Invalidate;
end;



procedure TTIWCustomAdvWebGrid.LoadFromCSV(Filename:string);
begin
  InputFromCSV(Filename,False);
end;

procedure TTIWCustomAdvWebGrid.InsertFromCSV(Filename:string);
begin
  InputFromCSV(FileName,True);
end;

function CSVQuotes(const s: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to length(s) do
  begin
    Result := Result + s[i];
    if s[i]='"' then
      Result := Result + '"';
  end;
end;

function LinesInText(s:string;multiline:boolean):Integer;
var
  vp: Integer;
begin
  Result := 1;
  if not Multiline then Exit;
  while VarPos(LINEFEED,s,vp)>0 do
  begin
    Inc(Result);
    Delete(s,1,vp);
  end;
end;

procedure TTIWCustomAdvWebGrid.SaveToStream(AStream: TStream);
var
  z,s: Integer;
  Line: string;
  Delim,CellText: string;

  procedure Writestring(s:string);
  var
    buf:PChar;
    c: array[0..1] of char;
  begin
    GetMem(buf,Length(s) + 1);
    StrPLCopy(buf,s,Length(s));
    AStream.Writebuffer(buf^,Length(s));
    c[0] := #13;
    c[1] := #10;
    AStream.Writebuffer(c,2);
    FreeMem(buf);
  end;

begin
  if FDelimiter = #0 then
    Delim := ','
  else
    Delim := FDelimiter;

  for z := 0 to Pred(TotalRows) do
  begin
    Line := '';
    for s := 0 to Pred(Columns.Count) do
    begin
      if s > 0 then
        Line := Line + Delim;

      CellText := Cells[s,z];
      CellText := CSVQuotes(CellText);

      if FOemConvert then
        StringToOem(CellText);

      if CellText = '' then
      begin
        if QuoteEmptyCells then
          CellText := '""';
      end;

      if (Pos(Delim,CellText) > 0) or (LinesInText(CellText,True) > 1) then
      begin
        LinefeedsToCSV(CellText);
      end;
      Line := Line + CellText;
    end;
    WriteString(Line);
  end;
end;

procedure TTIWCustomAdvWebGrid.LoadFromStream(AStream: TStream);
begin
  InputFromStream(AStream,False);
end;

procedure TTIWCustomAdvWebGrid.InputFromStream(AStream: TStream; InsertMode: Boolean);
var
  buffer,celltext: string;
  s,z: Integer;
  strtCol,strtRow: Integer;
  c1,c2,cm: Integer;
  OldDelimiter: Char;
  delimiterpos,quotepos: Integer;
  lr: TStringList;

  function ReadString(var s:string): Integer;
  var
    c: char;
  begin
    c := '0';
    s := '';
    while (AStream.Position < AStream.Size) and (c <> #13) do
    begin
      AStream.Read(c,1);
      if (c <> #13) then s := s + c;
    end;
    AStream.Read(c,1); {read the #10 newline marker}
    Result := Length(s);
  end;

begin
  StrtCol := 0;
  StrtRow := 0;

  z := StrtRow;

  lr := TStringList.Create;

  if InsertMode then
    z := TotalRows;

  OldDelimiter := FDelimiter;

  // do intelligent estimate of the separator
  if FDelimiter = #0 then
  begin
    CellText := '';
    ReadString(buffer);

    if (AStream.Position < AStream.Size) then ReadString(CellText);

    cm := 0;
    for s := 1 to 5 do
    begin
      c1 := NumSingleChar(CSVSeparators[s],Buffer);
      c2 := NumSingleChar(CSVSeparators[s],Celltext);
      if (c1 = c2) and (c1 > cm) then
      begin
        FDelimiter := CSVSeparators[s];
        cm := c1;
      end;
    end;

    if cm = 0 then
      for s := 1 to 5 do
      begin
        c1 := NumChar(CSVSeparators[s],Buffer);
        c2 := NumChar(CSVSeparators[s],Celltext);
        if (c1 = c2) and (c1 > cm) then
        begin
          FDelimiter := CSVSeparators[s];
          cm := c1;
        end;
      end;

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

  AStream.Position := 0;

  while (AStream.Position < AStream.Size) do
  begin
    ReadString(buffer);
    if FOemConvert then
      OemToString(Buffer);

    s := StrtCol;

    if z >= RowCount then
      TotalRows := z + 1000;

    while VarCharPos(FDelimiter,Buffer,DelimiterPos) > 0 do
    begin
      if Buffer[1] = '"' then
      begin
        Delete(buffer,1,1);   //delete first quote from buffer
        if SinglePos('"',Buffer,QuotePos) > 0 then  //search for next single quote
        begin
          CellText := Copy(buffer,1,QuotePos-1);
          CellText := DoubleToSingleChar('"',CellText);
          Delete(buffer,1,QuotePos);
        end
        else
          CellText := '';
        VarCharPos(FDelimiter,buffer,DelimiterPos);
      end
      else
      begin
        CellText := Copy(buffer,1,DelimiterPos - 1);
        CellText := DoubleToSingleChar('"',CellText);
      end;

      CSVToLineFeeds(CellText);

      while s >= Columns.Count do
        Columns.Add;

      Cells[s,z] := CellText;

      Delete(buffer,1,DelimiterPos);
      inc(s);

    end;

    if Length(Buffer) > 0 then
    begin
      if Buffer[1] = '"' then
        Delete(buffer,1,1);
      if Length(Buffer) > 0 then
      begin
        if Buffer[Length(Buffer)] = '"' then
          Delete(Buffer,Length(Buffer),1);
      end;

      CellText := DoubleToSingleChar('"',Buffer);

      CSVToLineFeeds(CellText);

      while s >= Columns.Count do
        Columns.Add;

      Cells[s,z] := CellText;
    end;

    Inc(z);
  end;


  TotalRows := z;
  lr.Free;
  FDelimiter := OldDelimiter;
  Invalidate;
end;


procedure TTIWCustomAdvWebGrid.OutputToCSV(FileName:String;appendmode: Boolean; Unicode: boolean);
var
  z,s: Integer;
  CellText: String;
  Delim: Char;
  sl: TFileStringList;

begin
  if FDelimiter = #0 then
    Delim := ','
  else
    Delim := FDelimiter;

  sl := TFileStringList.Create;
  if AppendMode then
    sl.LoadFromFile(FileName);

  for z := 0 to TotalRows - 1 do
  begin
    for s := 0 to Columns.Count - 1 do
    begin
      if s > 0 then
        sl.write(Delim);

      CellText := Cells[s,z];

      CellText := CSVQuotes(CellText);

      if FOemConvert then
        StringToOem(CellText);

      if CellText = '' then
      begin
        if QuoteEmptyCells then
          CellText := '""';
      end;

      if (Pos(Delim,CellText) > 0) or (LinesInText(CellText,True) > 1) then
      begin
        LinefeedsToCSV(CellText)
      end;

      sl.Write(CellText);
    end;
    sl.Writeln('');

  end;

  {$IFDEF DELPHI_UNICODE}
  if Unicode then
    sl.SaveToFile(FileName, TEncoding.Unicode)
  else
    sl.SaveToFile(FileName);
  {$ENDIF}

  {$IFNDEF DELPHI_UNICODE}
  sl.SaveToFile(FileName);
  {$ENDIF}

  sl.Free;
end;
(*
var
  f: TextFile;
  z,s: Integer;
  CellText: String;
  Delim: Char;

begin
  if FDelimiter = #0 then
    Delim := ','
  else
    Delim := FDelimiter;

  AssignFile(f,FileName);

  if AppendMode then
  begin
    {$i-}
    Reset(f);
    {$i+}
    if IOResult <> 0 then
    begin
      {$i-}
      Rewrite(f);
      {$i+}
      if IOResult <> 0 then raise Exception.Create('Cannot Create file '+FileName);
    end
    else
      Append(f);
  end
  else
  begin
    {$i-}
    Rewrite(f);
    {$i+}
    if IOResult<>0 then raise Exception.Create('Cannot Create file '+FileName);
  end;

  for z := 0 to Pred(TotalRows) do
  begin
    for s := 0 to Pred(Columns.Count) do
    begin
      if s > 0 then
        write(f,Delim);

      CellText := Cells[s,z];

      CellText := CSVQuotes(CellText);

      if FOemConvert then
        StringToOem(CellText);

      if CellText = '' then
      begin
        if QuoteEmptyCells then
          CellText := '""';
      end;

      if (Pos(Delim,CellText) > 0) or (LinesInText(CellText,True) > 1) then
      begin
        LinefeedsToCSV(CellText);
      end;
      Write(f,CellText);
    end;
    Writeln(f);
  end;
  CloseFile(f)
end;
*)

procedure TTIWCustomAdvWebGrid.SaveToCSV(FileName:String; Unicode: boolean = true);
begin
  OutputToCSV(FileName,False,Unicode);
end;

procedure TTIWCustomAdvWebGrid.AppendToCSV(FileName:String; Unicode: boolean = true);
begin
  OutputToCSV(FileName,True, Unicode);
end;

procedure TTIWCustomAdvWebGrid.UpdateRowCount(const Value: Integer);
begin
  inherited;
  if Value > TotalRows then
   TotalRows := Value;
end;

procedure TTIWCustomAdvWebGrid.UpdateTotalRows(const Value: Integer);
begin
  TotalRows := Value;
end;

procedure TTIWCustomAdvWebGrid.DeleteColumns(ColIndex, ColCount: Integer);
var
  i,j: Integer;
begin
  for i := 1 to FCellList.Count do
  begin
    for j := 1 to ColCount do
      TStringList(FCellList.Items[i - 1]).Delete(ColIndex);
  end;
  Invalidate;
end;

procedure TTIWCustomAdvWebGrid.InsertColumns(ColIndex, ColCount: Integer);
var
  i,j: Integer;
begin
  for i := 1 to FCellList.Count do
  begin
    for j := 1 to ColCount do
      TStringList(FCellList.Items[i - 1]).Insert(ColIndex,'');
  end;
  Invalidate;
end;

{ TTIWAdvDetailWebGrid }

constructor TTIWAdvDetailWebGrid.Create(AOwner: TComponent);
begin
  inherited;
  IsDetail := True;
end;

function TTIWCustomAdvWebGrid.DirectEditUpdate(ACol, ARow: Integer;
  var Value: string): Boolean;
begin
  Result := inherited DirectEditUpdate(ACol,ARow,Value);
  if Result then
    Cells[ACol,ARow] := Value;
end;

procedure TTIWCustomAdvWebGrid.DeleteSelectedRows;
var
  i: Integer;
begin
  i := 0;
  while (i < TotalRows) do
  begin
    if RowSelect[i] then
      DeleteRows(i,1)
    else
      inc(i);
  end;
end;

function TTIWCustomAdvWebGrid.SortedToRowIndex(RowIndex: Integer): Integer;
begin
  if FRowIndexList.Count > RowIndex then
    Result := Integer(FRowIndexList.Items[RowIndex])
  else
    Result := RowIndex;
end;

function TTIWCustomAdvWebGrid.RowToSortedIndex(RowIndex: Integer): Integer;
begin
  Result := FRowIndexList.IndexOf(TObject(RowIndex));
end;

procedure TTIWCustomAdvWebGrid.InitSortedIndexes;
begin
  FRowIndexList.Clear;
end;

{ TTIWAdvWebGridColumn }

procedure TTIWAdvWebGridColumn.Assign(Source: TPersistent);
begin
  inherited Assign(Source);
  FSortFormat := (Source as TTIWAdvWebGridColumn).SortFormat;
  FSortPrefix := (Source as TTIWAdvWebGridColumn).SortPrefix;
  FSortSuffix := (Source as TTIWAdvWebGridColumn).SortSuffix;
end;

end.
