git-svn-id: https://192.168.0.254/svn/Componentes.Terceros.FastReport@9 475b051d-3a53-6940-addd-820bf0cfe0d7
1791 lines
45 KiB
ObjectPascal
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> |