Componentes.Terceros.FastRe.../official/3.23/Source/frxCrossMatrix.pas
2007-09-10 15:54:09 +00:00

1791 lines
45 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport v3.0 }
{ Cross classes }
{ }
{ Copyright (c) 1998-2006 }
{ by Alexander Tzyganenko, }
{ Fast Reports Inc. }
{ }
{******************************************}
unit frxCrossMatrix;
interface
{$I frx.inc}
uses
Windows, SysUtils, Classes, Controls, Graphics, frxClass, frxDMPClass
{$IFDEF Delphi6}
, Variants
{$ENDIF};
const
CROSS_DIM_SIZE = 16;
type
{ the record represents one cell of cross matrix }
PfrCrossCell = ^TfrxCrossCell;
TfrxCrossCell = packed record
Value: Variant;
Count: Integer;
Next: PfrCrossCell; { pointer to the next value in the same cell }
end;
TfrxCrossSortOrder = (soAscending, soDescending, soNone);
TfrxCrossFunction = (cfNone, cfSum, cfMin, cfMax, cfAvg, cfCount);
TfrxVariantArray = array of Variant;
TfrxMemoArray = array[0..CROSS_DIM_SIZE - 1] of TfrxCustomMemoView;
TfrxSortArray = array[0..CROSS_DIM_SIZE - 1] of TfrxCrossSortOrder;
{ the base class for column/row item. Contains Indexes array that
identifies a column/row }
TfrxIndexItem = class(TCollectionItem)
private
FIndexes: TfrxVariantArray;
public
destructor Destroy; override;
property Indexes: TfrxVariantArray read FIndexes write FIndexes;
end;
{ the base collection for column/row items. Contains methods for working
with Indexes and sorting them }
TfrxIndexCollection = class(TCollection)
private
FIndexesCount: Integer;
FSortOrder: TfrxSortArray;
function GetItems(Index: Integer): TfrxIndexItem;
public
function Find(const Indexes: array of Variant; var Index: Integer): Boolean;
function InsertItem(Index: Integer; const Indexes: array of Variant): TfrxIndexItem; virtual;
property Items[Index: Integer]: TfrxIndexItem read GetItems; default;
end;
{ the class representing a single row item }
TfrxCrossRow = class(TfrxIndexItem)
private
FCellLevels: Integer;
FCells: TList;
procedure CreateCell(Index: Integer);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
function GetCell(Index: Integer): PfrCrossCell;
function GetCellValue(Index1, Index2: Integer): Variant;
procedure SetCellValue(Index1, Index2: Integer; const Value: Variant);
end;
{ the class representing row items }
TfrxCrossRows = class(TfrxIndexCollection)
private
FCellLevels: Integer;
function GetItems(Index: Integer): TfrxCrossRow;
public
constructor Create;
function InsertItem(Index: Integer; const Indexes: array of Variant): TfrxIndexItem; override;
function Row(const Indexes: array of Variant): TfrxCrossRow;
property Items[Index: Integer]: TfrxCrossRow read GetItems; default;
end;
{ the class representing a single column item }
TfrxCrossColumn = class(TfrxIndexItem)
private
FCellIndex: Integer;
public
property CellIndex: Integer read FCellIndex write FCellIndex;
end;
{ the class representing column items }
TfrxCrossColumns = class(TfrxIndexCollection)
private
function GetItems(Index: Integer): TfrxCrossColumn;
public
constructor Create;
function Column(const Indexes: array of Variant): TfrxCrossColumn;
function InsertItem(Index: Integer; const Indexes: array of Variant): TfrxIndexItem; override;
property Items[Index: Integer]: TfrxCrossColumn read GetItems; default;
end;
{ TfrxCrossHeader represents one cell of a cross header. The cell has a value,
position, size and list of subcells }
TfrxCrossHeader = class(TObject)
private
FBounds: TfrxRect; { bounds of the cell }
FMemos: TfrxMemoArray;
FTotalMemos: TfrxMemoArray;
FCounts: TfrxVariantArray;
FCellLevels: Integer;
FFuncValues: TfrxVariantArray;
FIsTotal: Boolean; { is this cell a total cell }
FItems: TList; { subcells }
FLevelsCount: Integer; { number of header levels }
FMemo: TfrxCustomMemoView; { memo for this cell }
FParent: TfrxCrossHeader; { parent of the cell }
FSize: TfrxPoint;
FValue: Variant; { value (text) of the cell }
FVisible: Boolean; { visibility of the cell }
FCellSizes: array[0..CROSS_DIM_SIZE - 1] of Extended;
procedure AddFuncValues(const Values, Counts: array of Variant;
const CellFunctions: array of TfrxCrossFunction);
procedure AddValues(const Values: array of Variant);
procedure Reset(const CellFunctions: array of TfrxCrossFunction);
procedure CalcSizes(MaxWidth, MinWidth: Integer);
procedure CalcBounds; virtual; abstract;
function GetCount: Integer;
function GetItems(Index: Integer): TfrxCrossHeader;
function GetLevel: Integer;
function GetHeight: Extended;
function GetWidth: Extended;
function GetCellSizes(Index: Integer): Extended;
procedure SetCellSizes(Index: Integer; const Value: Extended);
public
constructor Create(CellLevels: Integer);
destructor Destroy; override;
function AllItems: TList;
function Find(Value: Variant): Integer;
function GetIndexes: Variant;
function GetValues: Variant;
function TerminalItems: TList;
property Bounds: TfrxRect read FBounds write FBounds;
property CellSizes[Index: Integer]: Extended read GetCellSizes write SetCellSizes;
property Count: Integer read GetCount;
property Height: Extended read GetHeight;
property IsTotal: Boolean read FIsTotal;
property Items[Index: Integer]: TfrxCrossHeader read GetItems; default;
property Level: Integer read GetLevel;
property Memo: TfrxCustomMemoView read FMemo;
property Parent: TfrxCrossHeader read FParent;
property Value: Variant read FValue write FValue;
property Visible: Boolean read FVisible write FVisible;
property Width: Extended read GetWidth;
end;
{ the cross columns }
TfrxCrossColumnHeader = class(TfrxCrossHeader)
private
procedure CalcBounds; override;
end;
{ the cross rows }
TfrxCrossRowHeader = class(TfrxCrossHeader)
private
procedure CalcBounds; override;
end;
TfrxCrossCalcSizeEvent = procedure (Index: Integer; var Size: Extended) of object;
{ the cross matrix. Contains cross body (matrix), row and column headers }
TfrxCrossMatrix = class(TObject)
private
FCellFunctions: array[0..CROSS_DIM_SIZE - 1] of TfrxCrossFunction;
FCellLevels: Integer;
FColumnHeader: TfrxCrossColumnHeader;
FColumns: TfrxCrossColumns;
FColumnSort: TfrxSortArray;
FDefHeight: Integer;
FGapX: Integer;
FGapY: Integer;
FMaxWidth: Integer;
FMinWidth: Integer;
FNoColumns: Boolean;
FNoRows: Boolean;
FPlainCells: Boolean;
FRowHeader: TfrxCrossRowHeader;
FRows: TfrxCrossRows;
FRowSort: TfrxSortArray;
FCellMemos: TfrxMemoArray;
FColumnMemos: TfrxMemoArray;
FColumnTotalMemos: TfrxMemoArray;
FRowMemos: TfrxMemoArray;
FRowTotalMemos: TfrxMemoArray;
FOnCalcHeight: TfrxCrossCalcSizeEvent;
FOnCalcWidth: TfrxCrossCalcSizeEvent;
procedure CalcTotal(Header: TfrxCrossHeader; Source: TfrxIndexCollection);
procedure CreateHeader(Header: TfrxCrossHeader; Source: TfrxIndexCollection;
const Totals: TfrxMemoArray; TotalVisible: Boolean);
procedure SetCellFunctions(Index: Integer; const Value: TfrxCrossFunction);
procedure SetColumnSort(Index: Integer; Value: TfrxCrossSortOrder);
procedure SetRowSort(Index: Integer; Value: TfrxCrossSortOrder);
function GetCellFunctions(Index: Integer): TfrxCrossFunction;
function GetCellMemos(Index: Integer): TfrxCustomMemoView;
function GetColumnMemos(Index: Integer): TfrxCustomMemoView;
function GetColumnSort(Index: Integer): TfrxCrossSortOrder;
function GetColumnTotalMemos(Index: Integer): TfrxCustomMemoView;
function GetRowMemos(Index: Integer): TfrxCustomMemoView;
function GetRowSort(Index: Integer): TfrxCrossSortOrder;
function GetRowTotalMemos(Index: Integer): TfrxCustomMemoView;
public
constructor Create;
destructor Destroy; override;
procedure Init(RowLevels, ColumnLevels, CellLevels: Integer);
procedure InitMemos(DotMatrix: Boolean);
procedure Clear;
procedure ClearMemos;
procedure AddValue(const Rows, Columns, Cells: array of Variant);
function GetValue(ARow, AColumn, ACell: Integer): Variant;
function GetColumnIndexes(AColumn: Integer): Variant;
function GetRowIndexes(ARow: Integer): Variant;
procedure CreateHeaders;
procedure CalcTotals;
procedure CalcBounds;
function ColCount: Integer;
function RowCount: Integer;
function IsGrandTotalColumn(Index: Integer): Boolean;
function IsGrandTotalRow(Index: Integer): Boolean;
function IsTotalColumn(Index: Integer): Boolean;
function IsTotalRow(Index: Integer): Boolean;
function GetDrawSize: TfrxPoint;
property ColumnHeader: TfrxCrossColumnHeader read FColumnHeader;
property RowHeader: TfrxCrossRowHeader read FRowHeader;
property NoColumns: Boolean read FNoColumns;
property NoRows: Boolean read FNoRows;
property CellFunctions[Index: Integer]: TfrxCrossFunction read GetCellFunctions
write SetCellFunctions;
property CellMemos[Index: Integer]: TfrxCustomMemoView read GetCellMemos;
property ColumnMemos[Index: Integer]: TfrxCustomMemoView read GetColumnMemos;
property ColumnSort[Index: Integer]: TfrxCrossSortOrder read GetColumnSort
write SetColumnSort;
property ColumnTotalMemos[Index: Integer]: TfrxCustomMemoView read GetColumnTotalMemos;
property DefHeight: Integer read FDefHeight write FDefHeight;
property GapX: Integer read FGapX write FGapX;
property GapY: Integer read FGapY write FGapY;
property MaxWidth: Integer read FMaxWidth write FMaxWidth;
property MinWidth: Integer read FMinWidth write FMinWidth;
property PlainCells: Boolean read FPlainCells write FPlainCells;
property RowMemos[Index: Integer]: TfrxCustomMemoView read GetRowMemos;
property RowSort[Index: Integer]: TfrxCrossSortOrder read GetRowSort
write SetRowSort;
property RowTotalMemos[Index: Integer]: TfrxCustomMemoView read GetRowTotalMemos;
property OnCalcHeight: TfrxCrossCalcSizeEvent read FOnCalcHeight write FOnCalcHeight;
property OnCalcWidth: TfrxCrossCalcSizeEvent read FOnCalcWidth write FOnCalcWidth;
end;
implementation
uses frxUtils, frxFormUtils;
function CalcSize(m: TfrxCustomMemoView): TfrxPoint;
var
e: Extended;
begin
m.Height := 10000;
Result.X := m.CalcWidth;
Result.Y := m.CalcHeight;
if m is TfrxDMPMemoView then
begin
Result.X := Result.X + fr1CharX;
Result.Y := Result.Y + fr1CharY;
end;
if (m.Rotation = 90) or (m.Rotation = 270) then
begin
e := Result.X;
Result.X := Result.Y;
Result.Y := e;
end;
end;
{ TfrxIndexItem }
destructor TfrxIndexItem.Destroy;
begin
FIndexes := nil;
inherited;
end;
{ TfrxIndexCollection }
function TfrxIndexCollection.GetItems(Index: Integer): TfrxIndexItem;
begin
Result := TfrxIndexItem(inherited Items[Index]);
end;
function TfrxIndexCollection.Find(const Indexes: array of Variant;
var Index: Integer): Boolean;
var
i, i0, i1, c: Integer;
Item: TfrxIndexItem;
function Compare: Integer;
var
i: Integer;
begin
Result := 0;
for i := 0 to FIndexesCount - 1 do
if Item.Indexes[i] = Indexes[i] then
Result := 0
else if Item.Indexes[i] > Indexes[i] then
begin
if FSortOrder[i] = soAscending then
Result := 1 else
Result := -1;
break;
end
else if Item.Indexes[i] < Indexes[i] then
begin
if FSortOrder[i] = soAscending then
Result := -1 else
Result := 1;
break;
end;
end;
begin
Result := False;
if FSortOrder[0] = soNone then
begin
for i := 0 to Count - 1 do
begin
Item := TfrxIndexItem(Items[i]);
if Compare = 0 then
begin
Result := True;
Index := i;
Exit;
end;
end;
Index := Count;
Exit;
end;
{ quick find }
i0 := 0;
i1 := Count - 1;
while i0 <= i1 do
begin
i := (i0 + i1) div 2;
Item := TfrxIndexItem(Items[i]);
c := Compare;
if c < 0 then
i0 := i + 1
else
begin
i1 := i - 1;
if c = 0 then
begin
Result := True;
i0 := i;
end;
end;
end;
Index := i0;
end;
function TfrxIndexCollection.InsertItem(Index: Integer;
const Indexes: array of Variant): TfrxIndexItem;
var
i: Integer;
begin
if Index < Count then
Result := TfrxIndexItem(Insert(Index)) else
Result := TfrxIndexItem(Add);
SetLength(Result.FIndexes, FIndexesCount);
for i := 0 to FIndexesCount - 1 do
Result.FIndexes[i] := Indexes[i];
end;
{ TfrxCrossRow }
constructor TfrxCrossRow.Create;
begin
inherited;
FCells := TList.Create;
end;
destructor TfrxCrossRow.Destroy;
var
i: Integer;
c, c1: PfrCrossCell;
begin
for i := 0 to FCells.Count - 1 do
begin
c := FCells[i];
while c <> nil do
begin
c1 := c;
c := c.Next;
VarClear(c1.Value);
Dispose(c1);
end;
end;
FCells.Free;
inherited;
end;
procedure TfrxCrossRow.CreateCell(Index: Integer);
var
i: Integer;
c, c1: PfrCrossCell;
begin
while Index >= FCells.Count do
begin
c1 := nil;
for i := 0 to FCellLevels - 1 do
begin
New(c);
c.Value := Null;
c.Count := 1;
c.Next := nil;
if c1 <> nil then
c1.Next := c else
FCells.Add(c);
c1 := c;
end;
end;
end;
function TfrxCrossRow.GetCellValue(Index1, Index2: Integer): Variant;
var
c: PfrCrossCell;
begin
Result := Null;
if (Index1 < 0) or (Index1 >= FCells.Count) then Exit;
c := FCells[Index1];
while (c <> nil) and (Index2 > 0) do
begin
c := c.Next;
Dec(Index2);
end;
if c <> nil then
Result := c.Value;
end;
procedure TfrxCrossRow.SetCellValue(Index1, Index2: Integer; const Value: Variant);
var
c: PfrCrossCell;
begin
if Index1 < 0 then Exit;
if Index1 >= FCells.Count then
CreateCell(Index1);
c := FCells[Index1];
while (c <> nil) and (Index2 > 0) do
begin
c := c.Next;
Dec(Index2);
end;
if c <> nil then
if c.Value = Null then
c.Value := Value else
c.Value := c.Value + Value;
end;
function TfrxCrossRow.GetCell(Index: Integer): PfrCrossCell;
begin
Result := nil;
if Index < 0 then Exit;
if Index >= FCells.Count then
CreateCell(Index);
Result := FCells[Index];
end;
{ TfrxCrossRows }
constructor TfrxCrossRows.Create;
begin
inherited Create(TfrxCrossRow);
end;
function TfrxCrossRows.GetItems(Index: Integer): TfrxCrossRow;
begin
Result := TfrxCrossRow(inherited Items[Index]);
end;
function TfrxCrossRows.InsertItem(Index: Integer;
const Indexes: array of Variant): TfrxIndexItem;
begin
Result := inherited InsertItem(Index, Indexes);
TfrxCrossRow(Result).FCellLevels := FCellLevels;
end;
function TfrxCrossRows.Row(const Indexes: array of Variant): TfrxCrossRow;
var
i: Integer;
begin
if Find(Indexes, i) then
Result := Items[i] else
Result := TfrxCrossRow(InsertItem(i, Indexes));
end;
{ TfrxCrossColumns }
constructor TfrxCrossColumns.Create;
begin
inherited Create(TfrxCrossColumn);
end;
function TfrxCrossColumns.GetItems(Index: Integer): TfrxCrossColumn;
begin
Result := TfrxCrossColumn(inherited Items[Index]);
end;
function TfrxCrossColumns.Column(const Indexes: array of Variant): TfrxCrossColumn;
var
i: Integer;
begin
if Find(Indexes, i) then
Result := Items[i] else
Result := TfrxCrossColumn(InsertItem(i, Indexes));
end;
function TfrxCrossColumns.InsertItem(Index: Integer;
const Indexes: array of Variant): TfrxIndexItem;
begin
Result := inherited InsertItem(Index, Indexes);
TfrxCrossColumn(Result).FCellIndex := Count - 1;
end;
{ TfrxCrossHeader }
constructor TfrxCrossHeader.Create(CellLevels: Integer);
begin
FItems := TList.Create;
FCellLevels := CellLevels;
FValue := Null;
FVisible := True;
SetLength(FFuncValues, FCellLevels);
SetLength(FCounts, FCellLevels);
end;
destructor TfrxCrossHeader.Destroy;
begin
FFuncValues := nil;
FCounts := nil;
while FItems.Count > 0 do
begin
TfrxCrossHeader(FItems[0]).Free;
FItems.Delete(0);
end;
FItems.Free;
inherited;
end;
function TfrxCrossHeader.GetItems(Index: Integer): TfrxCrossHeader;
begin
Result := TfrxCrossHeader(FItems[Index]);
end;
function TfrxCrossHeader.GetCount: Integer;
begin
Result := FItems.Count;
end;
function TfrxCrossHeader.GetLevel: Integer;
var
h: TfrxCrossHeader;
begin
Result := -2;
h := Self;
while h <> nil do
begin
h := h.Parent;
Inc(Result);
end;
end;
function TfrxCrossHeader.Find(Value: Variant): Integer;
var
i: Integer;
begin
{ find the cell containing the given value }
Result := -1;
for i := 0 to Count - 1 do
// if AnsiCompareText(VarToStr(Items[i].Value), VarToStr(Value)) = 0 then
if VarToStr(Items[i].Value) = VarToStr(Value) then
begin
Result := i;
Exit;
end;
end;
procedure TfrxCrossHeader.AddValues(const Values: array of Variant);
var
i, j: Integer;
Header, Header1: TfrxCrossHeader;
v: Variant;
s: String;
begin
{ create the header tree. For example, subsequent calls
AddValues([1998,1]);
AddValues([1998,2]);
AddValues([1999,1]);
will create the header
1998 | 1999
--+--+-----
1 |2 | 1 }
Header := Self;
for i := Low(Values) to High(Values) do
begin
j := Header.Find(Values[i]);
if j <> -1 then
Header := Header.Items[j] { find existing item... }
else
begin
{ ...or create new one }
Header1 := TfrxCrossHeader(NewInstance);
Header1.Create(FCellLevels);
{ link it to the parent }
Header.FItems.Add(Header1);
Header1.FParent := Header;
v := Values[i];
s := VarToStr(v);
{ this is subtotal item }
if Pos('@@@', s) = 1 then
begin
{ remove @@@ }
s := Copy(s, 4, Length(s) - 5);
v := s;
Header1.FIsTotal := True;
Header1.FMemo := FTotalMemos[i];
end
else
Header1.FMemo := FMemos[i];
Header1.FValue := v;
Header := Header1;
if Header.FIsTotal then break;
end;
end;
end;
procedure TfrxCrossHeader.Reset(const CellFunctions: array of TfrxCrossFunction);
var
i: Integer;
h: TfrxCrossHeader;
begin
{ reset aggregate values for this cell and all its parent cells }
h := Self;
while h <> nil do
begin
for i := 0 to FCellLevels - 1 do
begin
case CellFunctions[i] of
cfNone, cfMin, cfMax:
h.FFuncValues[i] := Null;
cfSum, cfAvg, cfCount:
h.FFuncValues[i] := 0;
end;
h.FCounts[i] := 0;
end;
h := h.Parent;
end;
end;
procedure TfrxCrossHeader.AddFuncValues(const Values, Counts: array of Variant;
const CellFunctions: array of TfrxCrossFunction);
var
i: Integer;
h: TfrxCrossHeader;
begin
{ add aggregate values for this cell and all its parent cells }
h := Self;
while h <> nil do
begin
for i := 0 to FCellLevels - 1 do
if Values[i] <> Null then
case CellFunctions[i] of
cfNone:;
cfSum:
h.FFuncValues[i] := h.FFuncValues[i] + Values[i];
cfMin:
if (h.FFuncValues[i] = Null) or (Values[i] < h.FFuncValues[i]) then
h.FFuncValues[i] := Values[i];
cfMax:
if (h.FFuncValues[i] = Null) or (Values[i] > h.FFuncValues[i]) then
h.FFuncValues[i] := Values[i];
cfAvg:
begin
h.FFuncValues[i] := h.FFuncValues[i] + Values[i];
h.FCounts[i] := h.FCounts[i] + Counts[i];
end;
cfCount:
h.FFuncValues[i] := h.FFuncValues[i] + Values[i];// + Counts[i];
end;
h := h.Parent;
end;
end;
function TfrxCrossHeader.AllItems: TList;
procedure EnumItems(Item: TfrxCrossHeader);
var
i: Integer;
begin
if Item.Parent <> nil then
Result.Add(Item);
for i := 0 to Item.Count - 1 do
EnumItems(Item[i]);
end;
begin
{ list all items in the header }
Result := TList.Create;
EnumItems(Self);
end;
function TfrxCrossHeader.TerminalItems: TList;
var
i: Integer;
begin
{ list all terminal items in the header }
Result := AllItems;
i := 0;
while i < Result.Count do
if TfrxCrossHeader(Result[i]).Count <> 0 then
Result.Delete(i) else
Inc(i);
end;
function TfrxCrossHeader.GetIndexes: Variant;
var
ar: array[0..CROSS_DIM_SIZE - 1] of Variant;
i, n: Integer;
h, h1: TfrxCrossHeader;
begin
n := 0;
h := Parent;
h1 := Self;
while h <> nil do
begin
ar[n] := h.FItems.IndexOf(h1);
Inc(n);
h1 := h;
h := h.Parent;
end;
Result := VarArrayCreate([0, CROSS_DIM_SIZE - 1], varVariant);
for i := 0 to CROSS_DIM_SIZE - 1 do
if i < n then
Result[i] := ar[n - i - 1] else
Result[i] := Null;
end;
function TfrxCrossHeader.GetValues: Variant;
var
ar: array[0..CROSS_DIM_SIZE - 1] of Variant;
i, n: Integer;
h: TfrxCrossHeader;
begin
n := 0;
h := Self;
while h.Parent <> nil do
begin
ar[n] := h.Value;
Inc(n);
h := h.Parent;
end;
Result := VarArrayCreate([0, CROSS_DIM_SIZE - 1], varVariant);
for i := 0 to CROSS_DIM_SIZE - 1 do
if i < n then
Result[i] := ar[n - i - 1] else
Result[i] := Null;
end;
procedure TfrxCrossHeader.CalcSizes(MaxWidth, MinWidth: Integer);
var
i: Integer;
Items: TList;
Item: TfrxCrossHeader;
s: String;
begin
Items := AllItems;
for i := 0 to Items.Count - 1 do
begin
Item := Items[i];
Item.FMemo.Width := MaxWidth;
s := Item.FMemo.Text;
Item.FMemo.Text := Item.FMemo.FormatData(Item.Value);
Item.FSize := CalcSize(Item.FMemo);
Item.FMemo.Text := s;
if Item.FSize.X < MinWidth then
Item.FSize.X := MinWidth;
if Item.FSize.X > MaxWidth then
Item.FSize.X := MaxWidth;
end;
Items.Free;
end;
function TfrxCrossHeader.GetHeight: Extended;
var
Items: TList;
begin
Items := TerminalItems;
if (Items.Count > 0) and FVisible then
Result := TfrxCrossHeader(Items[Items.Count - 1]).Bounds.Top +
TfrxCrossHeader(Items[Items.Count - 1]).Bounds.Bottom else
Result := 0;
Items.Free;
end;
function TfrxCrossHeader.GetWidth: Extended;
var
Items: TList;
begin
Items := TerminalItems;
if (Items.Count > 0) and FVisible then
Result := TfrxCrossHeader(Items[Items.Count - 1]).Bounds.Left +
TfrxCrossHeader(Items[Items.Count - 1]).Bounds.Right else
Result := 0;
Items.Free;
end;
function TfrxCrossHeader.GetCellSizes(Index: Integer): Extended;
begin
Result := FCellSizes[Index];
end;
procedure TfrxCrossHeader.SetCellSizes(Index: Integer;
const Value: Extended);
begin
FCellSizes[Index] := Value;
end;
{ TfrxCrossColumnHeader }
procedure TfrxCrossColumnHeader.CalcBounds;
var
i, j, l: Integer;
h: Extended;
Items: TList;
Item: TfrxCrossHeader;
LevelHeights: array of Extended;
function DoAdjust(Item: TfrxCrossHeader): Extended;
var
i: Integer;
Width: Extended;
begin
if Item.Count = 0 then
begin
Result := Item.FSize.X;
Exit;
end;
Width := 0;
for i := 0 to Item.Count - 1 do
Width := Width + DoAdjust(Item[i]);
if Item.FSize.X < Width then
Item.FSize.X := Width
else
begin
Item[Item.Count - 1].FSize.X := Item[Item.Count - 1].FSize.X + Item.FSize.X - Width;
DoAdjust(Item[Item.Count - 1]);
end;
Result := Item.FSize.X;
end;
procedure FillBounds(Item: TfrxCrossHeader; Offset: TfrxPoint);
var
i, j, l: Integer;
h: Extended;
begin
l := Item.Level;
if l <> -1 then
h := LevelHeights[l] else
h := 0;
if Item.IsTotal then
for j := l + 1 to FLevelsCount - 1 do
h := h + LevelHeights[j];
Item.FBounds := frxRect(Offset.X, Offset.Y, Item.FSize.X, h);
Offset.Y := Offset.Y + h;
for i := 0 to Item.Count - 1 do
begin
FillBounds(Item[i], Offset);
Offset.X := Offset.X + Item[i].FSize.X;
end;
end;
begin
DoAdjust(Self);
SetLength(LevelHeights, FLevelsCount);
Items := AllItems;
// calculate height of each row
for i := 0 to Items.Count - 1 do
begin
Item := Items[i];
l := Item.Level;
if Item.IsTotal then
if l <> FLevelsCount - 1 then continue;
if l >= 0 then
if Item.FSize.Y > LevelHeights[l] then
LevelHeights[l] := Item.FSize.Y;
end;
// adjust totals
for i := 0 to Items.Count - 1 do
begin
Item := Items[i];
l := Item.Level;
if Item.IsTotal and (l < FLevelsCount - 1) then
begin
h := 0;
for j := l to FLevelsCount - 1 do
h := h + LevelHeights[j];
if Item.FSize.Y > h then
LevelHeights[FLevelsCount - 1] := LevelHeights[FLevelsCount - 1] + Item.FSize.Y - h;
end;
end;
FillBounds(Self, frxPoint(0, 0));
Items.Free;
LevelHeights := nil;
end;
{ TfrxCrossRowHeader }
procedure TfrxCrossRowHeader.CalcBounds;
var
i, j, l: Integer;
h: Extended;
Items: TList;
Item: TfrxCrossHeader;
LevelHeights: array of Extended;
function DoAdjust(Item: TfrxCrossHeader): Extended;
var
i: Integer;
Width: Extended;
begin
if Item.Count = 0 then
begin
Result := Item.FSize.Y;
Exit;
end;
Width := 0;
for i := 0 to Item.Count - 1 do
Width := Width + DoAdjust(Item[i]);
if Item.FSize.Y < Width then
Item.FSize.Y := Width
else
begin
Item[Item.Count - 1].FSize.Y := Item[Item.Count - 1].FSize.Y + Item.FSize.Y - Width;
DoAdjust(Item[Item.Count - 1]);
end;
Result := Item.FSize.Y;
end;
procedure FillBounds(Item: TfrxCrossHeader; Offset: TfrxPoint);
var
i, j, l: Integer;
h: Extended;
begin
l := Item.Level;
if l <> -1 then
h := LevelHeights[l] else
h := 0;
if Item.IsTotal then
for j := l + 1 to FLevelsCount - 1 do
h := h + LevelHeights[j];
Item.FBounds := frxRect(Offset.X, Offset.Y, h, Item.FSize.Y);
Offset.X := Offset.X + h;
for i := 0 to Item.Count - 1 do
begin
FillBounds(Item[i], Offset);
Offset.Y := Offset.Y + Item[i].FSize.Y;
end;
end;
begin
DoAdjust(Self);
SetLength(LevelHeights, FLevelsCount);
Items := AllItems;
// calculate height of each row
for i := 0 to Items.Count - 1 do
begin
Item := Items[i];
l := Item.Level;
if Item.IsTotal then
if l <> FLevelsCount - 1 then continue;
if l >= 0 then
if Item.FSize.X > LevelHeights[l] then
LevelHeights[l] := Item.FSize.X;
end;
// adjust totals
for i := 0 to Items.Count - 1 do
begin
Item := Items[i];
l := Item.Level;
if Item.IsTotal and (l < FLevelsCount - 1) then
begin
h := 0;
for j := l to FLevelsCount - 1 do
h := h + LevelHeights[j];
if Item.FSize.X > h then
LevelHeights[FLevelsCount - 1] := LevelHeights[FLevelsCount - 1] + Item.FSize.X - h;
end;
end;
FillBounds(Self, frxPoint(0, 0));
Items.Free;
LevelHeights := nil;
end;
{ TfrxCrossMatrix }
constructor TfrxCrossMatrix.Create;
begin
FGapX := 3;
FGapY := 3;
InitMemos(False);
end;
destructor TfrxCrossMatrix.Destroy;
begin
Clear;
ClearMemos;
inherited;
end;
procedure TfrxCrossMatrix.InitMemos(DotMatrix: Boolean);
var
i: Integer;
s: String;
procedure SetDefProps(m: TfrxCustomMemoView);
begin
m.HAlign := haCenter;
m.VAlign := vaCenter;
m.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
end;
function CreateMemo: TfrxCustomMemoView;
begin
if DotMatrix then
Result := TfrxDMPMemoView.Create(nil) else
Result := TfrxMemoView.Create(nil);
end;
begin
ClearMemos;
for i := 0 to CROSS_DIM_SIZE - 1 do
begin
FCellMemos[i] := CreateMemo;
FColumnMemos[i] := CreateMemo;
FColumnTotalMemos[i] := CreateMemo;
FRowMemos[i] := CreateMemo;
FRowTotalMemos[i] := CreateMemo;
FCellFunctions[i] := cfSum;
FColumnSort[i] := soAscending;
FRowSort[i] := soAscending;
SetDefProps(FCellMemos[i]);
FCellMemos[i].HAlign := haRight;
FCellMemos[i].Style := 'cell';
FCellMemos[i].Tag := i;
SetDefProps(FColumnMemos[i]);
FColumnMemos[i].Style := 'column';
FColumnMemos[i].Tag := 100 + i;
SetDefProps(FColumnTotalMemos[i]);
if i = 0 then
begin
s := 'Grand Total';
FColumnTotalMemos[i].Style := 'colgrand';
end
else
begin
s := 'Total';
FColumnTotalMemos[i].Style := 'coltotal';
end;
FColumnTotalMemos[i].Text := s;
FColumnTotalMemos[i].Font.Style := [fsBold];
FColumnTotalMemos[i].Tag := 300 + i;
SetDefProps(FRowMemos[i]);
FRowMemos[i].Style := 'row';
FRowMemos[i].Tag := 200 + i;
SetDefProps(FRowTotalMemos[i]);
if i = 0 then
begin
s := 'Grand Total';
FRowTotalMemos[i].Style := 'rowgrand';
end
else
begin
s := 'Total';
FRowTotalMemos[i].Style := 'rowtotal';
end;
FRowTotalMemos[i].Text := s;
FRowTotalMemos[i].Font.Style := [fsBold];
FRowTotalMemos[i].Tag := 400 + i;
end;
end;
procedure TfrxCrossMatrix.ClearMemos;
var
i: Integer;
begin
for i := 0 to CROSS_DIM_SIZE - 1 do
begin
FCellMemos[i].Free;
FCellMemos[i] := nil;
FColumnMemos[i].Free;
FColumnMemos[i] := nil;
FColumnTotalMemos[i].Free;
FColumnTotalMemos[i] := nil;
FRowMemos[i].Free;
FRowMemos[i] := nil;
FRowTotalMemos[i].Free;
FRowTotalMemos[i] := nil;
end;
end;
procedure TfrxCrossMatrix.Init(RowLevels, ColumnLevels, CellLevels: Integer);
var
i: Integer;
begin
Clear;
FNoRows := RowLevels = 0;
if RowLevels = 0 then
RowLevels := 1;
FNoColumns := ColumnLevels = 0;
if ColumnLevels = 0 then
ColumnLevels := 1;
FCellLevels := CellLevels;
FRows := TfrxCrossRows.Create;
FRows.FIndexesCount := RowLevels;
FRows.FSortOrder := FRowSort;
FRows.FCellLevels := FCellLevels;
FColumns := TfrxCrossColumns.Create;
FColumns.FIndexesCount := ColumnLevels;
FColumns.FSortOrder := FColumnSort;
FRowHeader := TfrxCrossRowHeader.Create(FCellLevels);
FRowHeader.FMemos := FRowMemos;
FRowHeader.FTotalMemos := FRowTotalMemos;
FRowHeader.FLevelsCount := RowLevels;
FColumnHeader := TfrxCrossColumnHeader.Create(FCellLevels);
FColumnHeader.FMemos := FColumnMemos;
FColumnHeader.FTotalMemos := FColumnTotalMemos;
FColumnHeader.FLevelsCount := ColumnLevels;
for i := 0 to CROSS_DIM_SIZE - 1 do
begin
FCellMemos[i].GapX := FGapX;
FCellMemos[i].GapY := FGapY;
FCellMemos[i].AllowExpressions := False;
FColumnMemos[i].GapX := FGapX;
FColumnMemos[i].GapY := FGapY;
FColumnMemos[i].AllowExpressions := False;
FColumnTotalMemos[i].GapX := FGapX;
FColumnTotalMemos[i].GapY := FGapY;
FColumnTotalMemos[i].AllowExpressions := False;
FRowMemos[i].GapX := FGapX;
FRowMemos[i].GapY := FGapY;
FRowMemos[i].AllowExpressions := False;
FRowTotalMemos[i].GapX := FGapX;
FRowTotalMemos[i].GapY := FGapY;
FRowTotalMemos[i].AllowExpressions := False;
end;
end;
procedure TfrxCrossMatrix.Clear;
begin
if FRows = nil then Exit;
FRows.Free;
FRows := nil;
FColumns.Free;
FColumns := nil;
FRowHeader.Free;
FRowHeader := nil;
FColumnHeader.Free;
FColumnHeader := nil;
end;
procedure TfrxCrossMatrix.SetCellFunctions(Index: Integer;
const Value: TfrxCrossFunction);
begin
FCellFunctions[Index] := Value;
end;
function TfrxCrossMatrix.GetCellFunctions(Index: Integer): TfrxCrossFunction;
begin
Result := FCellFunctions[Index];
end;
function TfrxCrossMatrix.GetCellMemos(Index: Integer): TfrxCustomMemoView;
begin
Result := FCellMemos[Index];
end;
function TfrxCrossMatrix.GetColumnMemos(Index: Integer): TfrxCustomMemoView;
begin
Result := FColumnMemos[Index];
end;
function TfrxCrossMatrix.GetColumnTotalMemos(Index: Integer): TfrxCustomMemoView;
begin
Result := FColumnTotalMemos[Index];
end;
function TfrxCrossMatrix.GetRowMemos(Index: Integer): TfrxCustomMemoView;
begin
Result := FRowMemos[Index];
end;
function TfrxCrossMatrix.GetRowTotalMemos(Index: Integer): TfrxCustomMemoView;
begin
Result := FRowTotalMemos[Index];
end;
function TfrxCrossMatrix.GetColumnSort(Index: Integer): TfrxCrossSortOrder;
begin
Result := FColumnSort[Index];
end;
function TfrxCrossMatrix.GetRowSort(Index: Integer): TfrxCrossSortOrder;
begin
Result := FRowSort[Index];
end;
procedure TfrxCrossMatrix.SetColumnSort(Index: Integer; Value: TfrxCrossSortOrder);
begin
FColumnSort[Index] := Value;
end;
procedure TfrxCrossMatrix.SetRowSort(Index: Integer; Value: TfrxCrossSortOrder);
begin
FRowSort[Index] := Value;
end;
function TfrxCrossMatrix.ColCount: Integer;
begin
Result := FColumns.Count;
end;
function TfrxCrossMatrix.RowCount: Integer;
begin
Result := FRows.Count;
end;
function TfrxCrossMatrix.IsGrandTotalColumn(Index: Integer): Boolean;
begin
Result := Index = FColumns.Count - 1;
end;
function TfrxCrossMatrix.IsGrandTotalRow(Index: Integer): Boolean;
begin
Result := Index = FRows.Count - 1;
end;
function TfrxCrossMatrix.IsTotalColumn(Index: Integer): Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to FColumns.FIndexesCount - 1 do
if VarToStr(FColumns[Index].Indexes[i]) = '@@@' then
Result := True;
end;
function TfrxCrossMatrix.IsTotalRow(Index: Integer): Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to FRows.FIndexesCount - 1 do
if VarToStr(FRows[Index].Indexes[i]) = '@@@' then
Result := True;
end;
function TfrxCrossMatrix.GetDrawSize: TfrxPoint;
var
ColumnItems, RowItems: TList;
ColumnItem, RowItem: TfrxCrossHeader;
begin
ColumnItems := ColumnHeader.TerminalItems;
RowItems := RowHeader.TerminalItems;
ColumnItem := ColumnItems[ColumnItems.Count - 1];
RowItem := RowItems[RowItems.Count - 1];
Result.X := ColumnItem.Bounds.Left + ColumnItem.Bounds.Right + RowHeader.Width;
Result.Y := RowItem.Bounds.Top + RowItem.Bounds.Bottom + ColumnHeader.Height;
ColumnItems.Free;
RowItems.Free;
end;
procedure TfrxCrossMatrix.AddValue(const Rows, Columns, Cells: array of Variant);
var
i: Integer;
Row: TfrxCrossRow;
Column: TfrxCrossColumn;
Cell: PfrCrossCell;
Value, v: Variant;
begin
if FRows = nil then Exit;
if FNoColumns then
Column := FColumns.Column([Null]) else
Column := FColumns.Column(Columns);
if FNoRows then
Row := FRows.Row([Null]) else
Row := FRows.Row(Rows);
Cell := Row.GetCell(Column.CellIndex);
for i := 0 to FCellLevels - 1 do
begin
Value := Cell.Value;
v := Cells[i];
if FCellFunctions[i] = cfCount then
begin
v := Cells[i];
if v = Null then
v := 0
else
v := 1;
end;
if Value = Null then
Cell.Value := v
else if (TVarData(Value).VType = varString) or (TVarData(Value).VType = varOleStr) then
Cell.Value := Value + #13#10 + v
else
Cell.Value := Value + v;
Cell := Cell.Next;
end;
end;
function TfrxCrossMatrix.GetValue(ARow, AColumn, ACell: Integer): Variant;
var
Row: TfrxCrossRow;
Column: TfrxCrossColumn;
Cell: PfrCrossCell;
begin
Result := Null;
Column := FColumns[AColumn];
Row := FRows[ARow];
Cell := Row.GetCell(Column.CellIndex);
while (Cell <> nil) and (ACell > 0) do
begin
Cell := Cell.Next;
Dec(ACell);
end;
if Cell <> nil then
Result := Cell.Value;
end;
function TfrxCrossMatrix.GetColumnIndexes(AColumn: Integer): Variant;
begin
Result := FColumns[AColumn].Indexes;
end;
function TfrxCrossMatrix.GetRowIndexes(ARow: Integer): Variant;
begin
Result := FRows[ARow].Indexes;
end;
procedure TfrxCrossMatrix.CreateHeader(Header: TfrxCrossHeader;
Source: TfrxIndexCollection; const Totals: TfrxMemoArray; TotalVisible: Boolean);
var
i, j, IndexesCount: Integer;
LastValues, CurValues: TfrxVariantArray;
function ExpandVariable(s: String; const Value: Variant): String;
var
i: Integer;
begin
{ expand the [Value] macro if any (eg. if total memo contains
the text: 'Total of [Value]' }
i := Pos('[VALUE]', AnsiUppercase(s));
if i <> 0 then
begin
Delete(s, i, 7);
Insert(VarToStr(Value), s, i);
end;
Result := s;
end;
procedure AddTotals;
var
j, k: Integer;
begin
for j := 0 to IndexesCount - 1 do
{ if value changed... }
if LastValues[j] <> CurValues[j] then
begin
{ ...create subtotals for all down-level values }
for k := IndexesCount - 1 downto j + 1 do
if Totals[k].Visible then
begin
{ '@@@' means that this is subtotal cell }
LastValues[k] := '@@@' + ExpandVariable(Totals[k].Text, LastValues[k - 1]);
{ create header cells... }
Header.AddValues(LastValues);
LastValues[k] := '@@@';
{ ...and row/column item }
Source.InsertItem(i, LastValues);
Inc(i);
end;
break;
end;
end;
begin
if Source.Count = 0 then Exit;
IndexesCount := Source.FIndexesCount;
{ copy first indexes to lastvalues }
LastValues := Copy(Source.Items[0].Indexes, 0, IndexesCount);
i := 0;
while i < Source.Count do
begin
{ copy current indexes to curvalues }
CurValues := Copy(Source.Items[i].Indexes, 0, IndexesCount);
{ if lastvalues <> curvalues, make a subtotal item }
AddTotals;
{ add header cells }
Header.AddValues(CurValues);
LastValues := CurValues;
Inc(i);
end;
{ create last subtotal item }
CurValues := Copy(Source.Items[0].Indexes, 0, IndexesCount);
for j := 0 to IndexesCount - 1 do
CurValues[j] := Null;
AddTotals;
{ create grand total }
if Totals[0].Visible and TotalVisible then
begin
LastValues[0] := '@@@' + Totals[0].Text;
Header.AddValues(LastValues);
LastValues[0] := '@@@';
Source.InsertItem(i, LastValues);
end;
end;
procedure TfrxCrossMatrix.CreateHeaders;
begin
CreateHeader(FColumnHeader, FColumns, FColumnTotalMemos, not FNoColumns);
CreateHeader(FRowHeader, FRows, FRowTotalMemos, not FNoRows);
end;
procedure TfrxCrossMatrix.CalcTotal(Header: TfrxCrossHeader;
Source: TfrxIndexCollection);
var
i, j: Integer;
Items: TList;
Values, Counts: TfrxVariantArray;
Item: TfrxCrossHeader;
p: PfrCrossCell;
FinalPass: Boolean;
procedure CellToArrays(p: PfrCrossCell);
var
i: Integer;
begin
for i := 0 to FCellLevels - 1 do
begin
Values[i] := p.Value;
Counts[i] := p.Count;
if (FCellFunctions[i] = cfAvg) and FinalPass and (p.Count <> 0) then
p.Value := p.Value / p.Count;
p := p.Next;
end;
end;
procedure ArraysToCell(p: PfrCrossCell);
var
i: Integer;
begin
for i := 0 to FCellLevels - 1 do
begin
p.Value := Item.FFuncValues[i];
p.Count := Item.FCounts[i];
if (FCellFunctions[i] = cfAvg) and FinalPass then
if p.Count <> 0 then
p.Value := p.Value / p.Count else
p.Value := 0;
if (FCellFunctions[i] = cfCount) and not FinalPass then
p.Count := p.Value;
p := p.Next;
end;
end;
begin
Items := Header.TerminalItems;
SetLength(Values, FCellLevels);
SetLength(Counts, FCellLevels);
FinalPass := Source = FColumns;
{ scan the matrix }
for i := 0 to Source.Count - 1 do
begin
for j := 0 to Items.Count - 1 do
TfrxCrossHeader(Items[j]).Reset(FCellFunctions);
for j := 0 to Items.Count - 1 do
begin
Item := Items[j];
if Source = FRows then
p := FRows[i].GetCell(FColumns[j].CellIndex) else
p := FRows[j].GetCell(FColumns[i].CellIndex);
if not Item.IsTotal then
begin
{ convert cell values to Values and Counts arrays }
CellToArrays(p);
{ accumulate values in the header items }
Item.AddFuncValues(Values, Counts, FCellFunctions);
end
else
begin
{ get the accumulated values from the item's parent }
Item := Item.Parent;
{ and convert it to the cell }
ArraysToCell(p);
end;
end;
end;
Items.Free;
Values := nil;
Counts := nil;
end;
procedure TfrxCrossMatrix.CalcTotals;
begin
{ scan the matrix from left to right, then from top to bottom }
CalcTotal(FColumnHeader, FRows);
{ final pass, scan the matrix from top to bottom, then from left to right }
CalcTotal(FRowHeader, FColumns);
end;
procedure TfrxCrossMatrix.CalcBounds;
var
i, j, k: Integer;
ColumnItems, RowItems: TList;
ColumnItem, RowItem: TfrxCrossHeader;
Cell: PfrCrossCell;
m: TfrxCustomMemoView;
sz, totalSz, NewHeight: Extended;
function DoCalc(const Value: Variant): Extended;
var
Size: TfrxPoint;
r: Integer;
s: String;
Width, NewWidth: Extended;
WidthChanged: Boolean;
begin
s := m.Text;
m.Text := m.FormatData(Value, FCellMemos[k].DisplayFormat);
r := m.Rotation;
m.Rotation := 0;
Width := FMaxWidth;
NewWidth := Width;
if Assigned(FOnCalcWidth) then
FOnCalcWidth(j, NewWidth);
m.Width := NewWidth;
WidthChanged := NewWidth <> Width;
Size := CalcSize(m);
if Size.X > FMaxWidth then
Size.X := FMaxWidth;
if Size.X < FMinWidth then
Size.X := FMinWidth;
if WidthChanged then
Size.X := NewWidth;
if FDefHeight <> 0 then
Size.Y := FDefHeight;
if NewWidth = 0 then
Size.Y := 0;
m.Rotation := r;
m.Text := s;
if (ColumnItem.FSize.X < Size.X) or WidthChanged then
ColumnItem.FSize.X := Size.X;
if FPlainCells then
Result := Size.X
else
Result := Size.Y;
end;
begin
ColumnItems := FColumnHeader.TerminalItems;
RowItems := FRowHeader.TerminalItems;
{ calculate the widths of columns and the heights of rows }
FColumnHeader.CalcSizes(FMaxWidth, FMinWidth);
FRowHeader.CalcSizes(FMaxWidth, FMinWidth);
{ scanning the matrix cells and update calculated widths and heights }
for i := 0 to RowItems.Count - 1 do
begin
RowItem := RowItems[i];
for j := 0 to ColumnItems.Count - 1 do
begin
ColumnItem := ColumnItems[j];
Cell := FRows[i].GetCell(FColumns[j].CellIndex);
totalSz := 0;
for k := 0 to FCellLevels - 1 do
begin
if ColumnItem.IsTotal then
m := ColumnItem.Memo
else if RowItem.IsTotal then
m := RowItem.Memo else
m := FCellMemos[k];
sz := DoCalc(Cell.Value);
totalSz := totalSz + sz;
if FPlainCells then
ColumnItem.FCellSizes[k] := sz;
Cell := Cell.Next;
end;
if FPlainCells then
begin
if ColumnItem.FSize.X < totalSz then
ColumnItem.FSize.X := totalSz
else
ColumnItem.FCellSizes[FCellLevels - 1] :=
ColumnItem.FCellSizes[FCellLevels - 1] + (ColumnItem.FSize.X - totalSz);
end
else
begin
if RowItem.FSize.Y < totalSz then
RowItem.FSize.Y := totalSz;
end;
end;
NewHeight := RowItem.FSize.Y;
if Assigned(FOnCalcHeight) then
FOnCalcHeight(i, NewHeight);
RowItem.FSize.Y := NewHeight;
end;
{ calculate the positions and sizes of the header cells }
FColumnHeader.CalcBounds;
FRowHeader.CalcBounds;
ColumnItems.Free;
RowItems.Free;
end;
end.
//<censored>