
{*******************************************************}
{                                                       }
{       RichView                                        }
{       Table rows sorting.                             }
{                                                       }
{       Copyright (c) Sergey Tkachenko                  }
{       svt@trichview.com                               }
{       http://www.trichview.com                        }
{                                                       }
{*******************************************************}


unit TableSort;

{$I RV_Defs.inc}

interface
uses
 SysUtils, Windows, Messages, Classes, Graphics,
 RVClasses, RVItem, RVTable,
 {$IFDEF RVUNICODESTR}
 RVGetTextW,
 {$ELSE}
 RVGetText,
 {$ENDIF}
 RVStyle, RichView, RVEdit;

// Can this table be sorted? Yes, if it does not have cells merged across rows
function CanSortTable(table: TRVTableItemInfo): Boolean;

// Returns a new table, a sorted copy of table.
// Table must be inserted in rv.
// Sorting is performed by the content of the Column-th column
// (must be in range 0..table.ColCount-1).
// Content is compared as ANSI string

function CreateSortedTable(rv: TCustomRichView; table: TRVTableItemInfo;
  Column: Integer; Ascending, IgnoreCase: Boolean): TRVTableItemInfo;

// Sorts the current table in rve, as an editing operation (can be undone)
function SortCurrentTable(rve: TCustomRichViewEdit;
  Column: Integer; Ascending, IgnoreCase: Boolean): Boolean;

implementation

function CanSortTable(table: TRVTableItemInfo): Boolean;
var r,c: Integer;
begin
  Result := False;
  for r := 0 to table.RowCount-1 do
    for c := 0 to table.ColCount-1 do
      if (table.Cells[r,c]<>nil) and
         (table.Cells[r,c].RowSpan>1) then
        exit;
  Result := True;
end;
{------------------------------------------------------------------------------}
type
  TCellSortInfo = class
    Row: Integer;
    Text: String;
  end;

function CompareCellsA(Item1, Item2: Pointer): Integer;
begin
  Result := AnsiCompareText(TCellSortInfo(Item1).Text, TCellSortInfo(Item2).Text);
end;

function CompareCellsACS(Item1, Item2: Pointer): Integer;
begin
  Result := AnsiCompareStr(TCellSortInfo(Item1).Text, TCellSortInfo(Item2).Text);
end;

function CompareCellsD(Item1, Item2: Pointer): Integer;
begin
  Result := -AnsiCompareText(TCellSortInfo(Item1).Text, TCellSortInfo(Item2).Text);
end;

function CompareCellsDCS(Item1, Item2: Pointer): Integer;
begin
  Result := -AnsiCompareStr(TCellSortInfo(Item1).Text, TCellSortInfo(Item2).Text);
end;
{------------------------------------------------------------------------------}
function CreateSortedTable(rv: TCustomRichView; table: TRVTableItemInfo;
  Column: Integer; Ascending, IgnoreCase: Boolean): TRVTableItemInfo;
var r,c: Integer;
    SortList: TRVList;
    SortItem: TCellSortInfo;
    Stream: TMemoryStream;
    SourceCell, DestCell: TRVTableCellData;
    Color: TColor;
    RVFOptions: TRVFOptions;
begin
  Result := nil;
  if (Column<0) or (Column>=table.ColCount) or not CanSortTable(table) then
    exit;
  SortList := TRVList.Create;
  try
    // preparing sort list
    SortList.Capacity := table.RowCount;
    for r := 0 to table.RowCount-1 do begin
      SortItem := TCellSortInfo.Create;
      SortItem.Row := r;
      if table.Cells[r, Column]<>nil then
        SortItem.Text := GetRVDataText(table.Cells[r, Column]);
      SortList.Add(SortItem);
    end;
    // sorting
    if Ascending then
      if IgnoreCase then
        SortList.Sort(CompareCellsA)
      else
        SortList.Sort(CompareCellsACS)
    else
      if IgnoreCase then
        SortList.Sort(CompareCellsD)
      else
        SortList.Sort(CompareCellsDCS);
    // creating new table
    Result := TRVTableItemInfo.CreateEx(table.RowCount, table.ColCount,
      table.Cells[0,0].GetParentData);
    Result.AssignProperties(table);
    // copying rows from the old table in the sort order
    Color := clNone;
    RVFOptions := rv.RVFOptions;
    rv.RVFOptions := RVFOptions-[rvfoSaveTextStyles, rvfoSaveParaStyles];
    try
      for r := 0 to SortList.Count-1 do begin
        SortItem := TCellSortInfo(SortList.Items[r]);
        for c := 0 to table.ColCount-1 do begin
          SourceCell := table.Cells[SortItem.Row, c];
          if SourceCell<>nil then begin
            if SourceCell.ColSpan>1 then
              Result.MergeCells(r, c, SourceCell.ColSpan, 1, True);
            DestCell := Result.Cells[r, c];
            DestCell.AssignAttributesFrom(table.Cells[SortItem.Row, c], True, 1, 1);
            Stream := TMemoryStream.Create;
            try
              SourceCell.GetRVData.SaveRVFToStream(Stream, False, clNone, nil, nil);
              Stream.Position := 0;
              DestCell.LoadRVFFromStream(Stream, Color, nil, nil);
            finally
              Stream.Free;
            end;
          end;
        end;
      end;
    finally
      rv.RVFOptions := RVFOptions;
    end;
  finally
    SortList.Free;
  end;
end;
{------------------------------------------------------------------------------}
function SortCurrentTable(rve: TCustomRichViewEdit;
  Column: Integer; Ascending, IgnoreCase: Boolean): Boolean;
var rvet: TCustomRichViewEdit;
    table: TRVTableItemInfo;
    ItemNo, p: Integer;
begin
  Result := False;
  if not rve.GetCurrentItemEx(TRVTableItemInfo, rvet, TCustomRVItemInfo(table)) then
    exit;
  ItemNo := table.GetMyItemNo;
  table := CreateSortedTable(rvet, table, Column, Ascending, IgnoreCase);
  if table=nil then
    exit;
  rvet.BeginUndoGroup(rvutModifyItem);
  rvet.SetUndoGroupMode(True);
  try
    p := rve.RootEditor.VScrollPos;
    SendMessage(rve.Handle, WM_SETREDRAW, 0, 0);
    rvet.SetSelectionBounds(ItemNo, 0, ItemNo, 1);
    rvet.InsertItemR(rvet.GetItemTextR(ItemNo), table);
    rve.RootEditor.VScrollPos := p;
    SendMessage(rve.Handle, WM_SETREDRAW, 1, 0);
    rve.RefreshAll;
  finally
    rvet.SetUndoGroupMode(False);
  end;
  Result := True;
end;

end.
