3722 lines
106 KiB
ObjectPascal
3722 lines
106 KiB
ObjectPascal
{******************************************}
|
|
{ }
|
|
{ FastReport VCL }
|
|
{ Report engine }
|
|
{ }
|
|
{ Copyright (c) 1998-2021 }
|
|
{ by Fast Reports Inc. }
|
|
{ }
|
|
{******************************************}
|
|
|
|
unit frxEngine;
|
|
|
|
interface
|
|
|
|
{$I frx.inc}
|
|
|
|
uses
|
|
SysUtils, {$IFNDEF FPC}Windows, Messages,{$ENDIF}
|
|
Types, Classes, Graphics, Controls, Forms, Dialogs,
|
|
frxClass, frxAggregate, frxXML, frxDMPClass, frxStorage
|
|
{$IFDEF Delphi6}
|
|
, Variants
|
|
{$ENDIF};
|
|
|
|
type
|
|
{ TfrxHeaderList holds a set of bands that should appear on each new page.
|
|
This includes page header, column header and header bands with
|
|
"Reprint on new page" setting }
|
|
|
|
TfrxHeaderListItem = class(TObject)
|
|
public
|
|
Band: TfrxBand;
|
|
Left: Extended;
|
|
IsInKeepList: Boolean;
|
|
end;
|
|
|
|
TfrxShiftEngine = class(TObject)
|
|
private
|
|
FContainers: TList;
|
|
FDestroyQueue: TList;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure ClearDestroyQueue;
|
|
procedure ClearContainer(Container: TfrxReportComponent);
|
|
procedure PrepareShiftTree(Container: TfrxReportComponent);
|
|
procedure ShiftObjects(Container: TfrxReportComponent);
|
|
procedure InitShiftAmount(AObject: TfrxReportComponent; ShiftAmount: Extended);
|
|
procedure ContainerToDestroyQueue(AContainer: TObject);
|
|
end;
|
|
|
|
TfrxHeaderList = class(TObject)
|
|
private
|
|
FList: TList;
|
|
function GetCount: Integer;
|
|
function GetItems(Index: Integer): TfrxHeaderListItem;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure AddItem(ABand: TfrxBand; ALeft: Extended; AInKeepList: Boolean);
|
|
procedure RemoveItem(ABand: TfrxBand);
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TfrxHeaderListItem read GetItems; default;
|
|
end;
|
|
|
|
TfrxEngine = class(TfrxCustomEngine)
|
|
private
|
|
FAggregates: TfrxAggregateList;
|
|
FCallFromAddPage: Boolean;
|
|
FCallFromEndPage: Boolean;
|
|
FCurBand: TfrxBand;
|
|
FLastBandOnPage: TfrxBand;
|
|
FDontShowHeaders: Boolean;
|
|
FHeaderList: TfrxHeaderList; { list of header bands }
|
|
FFirstReportPage: Boolean; { needed for correct setting of PreviewPages.FirstPage }
|
|
FFirstColumnY: Extended; { position of the first column }
|
|
FIsFirstBand: Boolean; { needed for KeepTogether }
|
|
FIsFirstPage: Boolean; { first and last page flags }
|
|
FIsLastPage: Boolean; { }
|
|
FTitlePrinted: Boolean;
|
|
FHBandNamesTree: TStrings; { need for correct work with drill down in master-detail-subtetail-... }
|
|
FKeepBand: TfrxBand;
|
|
FKeepFooter: Boolean;
|
|
FKeeping: Boolean;
|
|
FKeepHeader: Boolean;
|
|
FKeepCurY: Extended; { need when group doesn't fit on the whole page}
|
|
FPrevFooterHeight: Extended; {need for correct freespace calculation when use printOnPreviousPage}
|
|
FKeepOutline: TfrxXMLItem;
|
|
FKeepPosition: Integer;
|
|
FKeepAnchor: Integer;
|
|
FCallFromPHeader: Boolean; { endless loop fix }
|
|
FOutputTo: TfrxNullBand; { used in the subreports }
|
|
FPage: TfrxReportPage; { currently proceeded page }
|
|
FPageCurX: Extended;
|
|
FStartNewPageBand: TfrxBand; { needed in addpage }
|
|
FVHeaderList: TList; { list of vheader bands }
|
|
FVMasterBand: TfrxBand; { master hband for vbands }
|
|
FVPageList: TList; { list of page breaks for vbands }
|
|
|
|
{ need for subreports and keep }
|
|
FSubSavePageN: Integer;
|
|
FSubSaveCurY: Extended;
|
|
{ Bands sequance started with mcmTillPageEnds doesn't fit on the page }
|
|
{ and we need to brake bands output }
|
|
FBreakShowBandTree: Boolean;
|
|
{ in case of mcmTillPageEnds footers printed before addition sequence }
|
|
{ so we need to reset Aggregates for footer anly after this sequence }
|
|
FLockResetAggregates: Boolean;
|
|
FShiftEngine: TfrxShiftEngine;
|
|
procedure AddBandOutline(Band: TfrxBand);
|
|
procedure AddColumn;
|
|
procedure AddPage;
|
|
procedure AddPageOutline;
|
|
procedure AddToHeaderList(Band: TfrxBand);
|
|
procedure AddToVHeaderList(Band: TfrxBand);
|
|
procedure CheckBandColumns(Band: TfrxDataBand; ColumnKeepPos: Integer; var HeaderKeepPos: Integer;
|
|
SaveCurY, SaveHeaderY: Extended);
|
|
procedure CheckDrill(Master: TfrxDataBand; Band: TfrxGroupHeader);
|
|
procedure CheckGroups(Master: TfrxDataBand; Band: TfrxGroupHeader;
|
|
ColumnKeepPos: Integer; SaveCurY: Extended);
|
|
procedure CheckSubReports(Band: TfrxBand);
|
|
procedure CheckSuppress(Band: TfrxBand);
|
|
procedure DoShow(Band: TfrxBand);
|
|
procedure DrawSplit(Band: TfrxBand);
|
|
procedure EndColumn;
|
|
procedure EndKeep(Band: TfrxBand);
|
|
procedure InitGroups(Master: TfrxDataBand; Band: TfrxGroupHeader;
|
|
Index: Integer; ResetLineN: Boolean = False);
|
|
procedure InitPage;
|
|
procedure NotifyObjects(Band: TfrxBand);
|
|
procedure OutlineRoot;
|
|
procedure OutlineUp(Band: TfrxBand);
|
|
procedure PreparePage(ErrorList: TStrings; PrepareVBands: Boolean);
|
|
procedure RemoveFromHeaderList(Band: TfrxBand);
|
|
procedure RemoveFromVHeaderList(Band: TfrxBand);
|
|
procedure ResetSuppressValues(Band: TfrxBand);
|
|
procedure RunPage(Page: TfrxReportPage);
|
|
procedure RunReportPages(APage: TfrxReportPage);
|
|
procedure ShowGroupFooters(Band: TfrxGroupHeader; Index: Integer; Master: TfrxDataBand);
|
|
procedure ShowVBands(HBand: TfrxBand);
|
|
procedure StartKeep(Band: TfrxBand; Position: Integer = 0);
|
|
function CanShow(Obj: TObject; PrintIfDetailEmpty: Boolean): Boolean;
|
|
function FindBand(Band: TfrxBandClass): TfrxBand;
|
|
function RunDialogs: Boolean;
|
|
procedure RestoreVBandsObjects;
|
|
function IsMaxHeightMode(View: TfrxView): Boolean;
|
|
protected
|
|
function GetPageHeight: Extended; override;
|
|
procedure DoProcessState(aBand: TfrxBand; aState: TfrxProcessAtMode);
|
|
function GetAggregates: TfrxCustomAggregateList; override;
|
|
public
|
|
constructor Create(AReport: TfrxReport); override;
|
|
destructor Destroy; override;
|
|
procedure EndPage; override;
|
|
procedure NewColumn; override;
|
|
procedure NewPage; override;
|
|
procedure InitializeSplit(Container: TfrxReportComponent; SavedObjects: TList; SplitObject: TList); override;
|
|
function SplitObjects(Container: TfrxReportComponent; SavedObjects: TList; SplitObject: TList; AHeight: Extended): Boolean; override;
|
|
procedure FinalizeSplit(Container: TfrxReportComponent; SavedObjects: TList; SplitObject: TList; SplitHeight: Extended); override;
|
|
function Run(ARunDialogs: Boolean; AClearLast: Boolean = False; APage: TfrxPage = nil): Boolean; override;
|
|
function ShowBand(Band: TfrxBand): TfrxBand; overload; override;
|
|
procedure ShowBand(Band: TfrxBandClass); overload; override;
|
|
procedure Stretch(Container: TfrxReportComponent; SkipGetData: Boolean = False); override;
|
|
procedure UnStretch(Container: TfrxReportComponent); override;
|
|
function HeaderHeight(AddReprintOnNewPage: Boolean = False): Extended; override;
|
|
function FooterHeight: Extended; override;
|
|
function FreeSpace: Extended; override;
|
|
procedure BreakAllKeep; override; { used in crosstab }
|
|
procedure PrepareShiftTree(Container: TfrxReportComponent); override;
|
|
procedure ProcessObject(ReportObject: TfrxView); override;
|
|
function GetAggregateValue(const Name, Expression: String;
|
|
DataRow: TfrxComponent; Flags: Integer): Variant; override;
|
|
function Initialize: Boolean;
|
|
procedure Finalize;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses frxUtils, frxPreviewPages, frxRes, Math;
|
|
|
|
type
|
|
THackComponent = class(TfrxComponent);
|
|
THackMemoView = class(TfrxCustomMemoView);
|
|
|
|
TfrxShiftItem = class(TObject)
|
|
private
|
|
function GetTop: Extended;
|
|
function GetHeight: Extended;
|
|
function GetLeft: Extended;
|
|
function GetWidth: Extended;
|
|
function GetItems(Index: Integer): TfrxShiftItem;
|
|
public
|
|
//FParents: TList;
|
|
FShiftAmount: Extended;
|
|
FShiftedTo: Extended;
|
|
FMinDist: Extended;
|
|
FShiftChildren: TList;
|
|
FShifted: Boolean;
|
|
FReportObject: TfrxReportComponent;
|
|
FRefCount: Integer;
|
|
constructor Create(AParent: TfrxShiftItem); overload;
|
|
constructor Create(AParent: TfrxShiftItem; AReportObject: TfrxReportComponent); overload;
|
|
destructor Destroy; override;
|
|
function Add(AReportObject: TfrxReportComponent): TfrxShiftItem;
|
|
function Count: Integer;
|
|
procedure AddExist(Item: TfrxShiftItem);
|
|
procedure DeleteClildren(Item: TfrxShiftItem);
|
|
procedure DefaultHandler(var Message); override;
|
|
property Top: Extended read GetTop;
|
|
property Left: Extended read GetLeft;
|
|
property Width: Extended read GetWidth;
|
|
property Height: Extended read GetHeight;
|
|
property Items[Index: Integer]: TfrxShiftItem read GetItems; default;
|
|
end;
|
|
|
|
{ these classes handle synchronization with report objects }
|
|
{ when containers createad/destroyed dynamically from code }
|
|
{ RootItem or List bound to base container using FShiftObject }
|
|
{ Report component destructor uses DefaultHandler to send FShiftObject }
|
|
{ object that original object is destroying }
|
|
{ and later we clear them in ClearDestroyQueue }
|
|
{ current architecture does not allow to un-bind report object from ShiftItem }
|
|
|
|
TfrxShiftRootItem = class(TfrxShiftItem)
|
|
private
|
|
FShiftEngine: TfrxShiftEngine;
|
|
public
|
|
constructor Create(AParent: TfrxShiftItem; AReportObject: TfrxReportComponent; AShiftEngine: TfrxShiftEngine);
|
|
procedure DefaultHandler(var Message); override;
|
|
end;
|
|
|
|
TfrxShiftedObjectList = class(TfrxExtendedObjectList)
|
|
private
|
|
FShiftEngine: TfrxShiftEngine;
|
|
FParentContainer: TfrxReportComponent;
|
|
public
|
|
constructor Create(AShiftEngine: TfrxShiftEngine);
|
|
destructor Destroy; override;
|
|
procedure DefaultHandler(var Message); override;
|
|
end;
|
|
|
|
{ TfrxHeaderList }
|
|
|
|
constructor TfrxHeaderList.Create;
|
|
begin
|
|
FList := TList.Create;
|
|
end;
|
|
|
|
destructor TfrxHeaderList.Destroy;
|
|
begin
|
|
Clear;
|
|
FList.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TfrxHeaderList.Clear;
|
|
begin
|
|
while FList.Count > 0 do
|
|
begin
|
|
TObject(FList[0]).Free;
|
|
FList.Delete(0);
|
|
end;
|
|
end;
|
|
|
|
function TfrxHeaderList.GetCount: Integer;
|
|
begin
|
|
Result := FList.Count;
|
|
end;
|
|
|
|
function TfrxHeaderList.GetItems(Index: Integer): TfrxHeaderListItem;
|
|
begin
|
|
Result := FList[Index];
|
|
end;
|
|
|
|
procedure TfrxHeaderList.AddItem(ABand: TfrxBand; ALeft: Extended; AInKeepList: Boolean);
|
|
var
|
|
Item: TfrxHeaderListItem;
|
|
begin
|
|
Item := TfrxHeaderListItem.Create;
|
|
Item.Band := ABand;
|
|
Item.Left := ALeft;
|
|
Item.IsInKeepList := AInKeepList;
|
|
FList.Add(Item);
|
|
end;
|
|
|
|
procedure TfrxHeaderList.RemoveItem(ABand: TfrxBand);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Count - 1 do
|
|
if Items[i].Band = ABand then
|
|
begin
|
|
Items[i].Free;
|
|
FList.Delete(i);
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TfrxEngine }
|
|
|
|
constructor TfrxEngine.Create(AReport: TfrxReport);
|
|
begin
|
|
inherited;
|
|
FHeaderList := TfrxHeaderList.Create;
|
|
FVHeaderList := TList.Create;
|
|
FVPageList := TList.Create;
|
|
FAggregates := TfrxAggregateList.Create(AReport);
|
|
FHBandNamesTree := TStringList.Create;
|
|
FLastBandOnPage := nil;
|
|
FShiftEngine := TfrxShiftEngine.Create;
|
|
end;
|
|
|
|
destructor TfrxEngine.Destroy;
|
|
begin
|
|
FreeAndNil(FHeaderList);
|
|
FreeAndNil(FVHeaderList);
|
|
FreeAndNil(FVPageList);
|
|
FreeAndNil(FAggregates);
|
|
FreeAndNil(FHBandNamesTree);
|
|
FreeAndNil(FShiftEngine);
|
|
inherited;
|
|
end;
|
|
|
|
function TfrxEngine.Initialize: Boolean;
|
|
var
|
|
i, j: Integer;
|
|
b: TfrxDataBand;
|
|
begin
|
|
FPrevFooterHeight := 0;
|
|
PreviewPages.Initialize;
|
|
PreviewPages.AddPageAction := apAdd;
|
|
StartDate := Date;
|
|
StartTime := Time;
|
|
Running := True;
|
|
FKeeping := False;
|
|
CurVColumn := 0;
|
|
FOutputTo := nil;
|
|
|
|
{ clear all aggregate items }
|
|
FAggregates.Clear;
|
|
|
|
{ add all report pages to the PreviewPages }
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if Report.Pages[i] is TfrxReportPage then
|
|
begin
|
|
{ set the current page }
|
|
FPage := TfrxReportPage(Report.Pages[i]);
|
|
{ create band tree for the current page }
|
|
PreparePage(Report.Errors, False);
|
|
PreparePage(Report.Errors, True);
|
|
end;
|
|
|
|
{ check datasets used }
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if Report.Pages[i] is TfrxReportPage then
|
|
begin
|
|
FPage := TfrxReportPage(Report.Pages[i]);
|
|
if (Report.DataSet <> nil) and (Report.DataSet = FPage.DataSet) then
|
|
begin
|
|
Report.Errors.Add('Cannot use the same dataset for Report.DataSet and Page.DataSet');
|
|
break;
|
|
end;
|
|
for j := 0 to FPage.FSubBands.Count - 1 do
|
|
begin
|
|
b := FPage.FSubBands[j];
|
|
if (b <> nil) and (b.DataSet <> nil) then
|
|
if Report.DataSet = b.DataSet then
|
|
begin
|
|
Report.Errors.Add('Cannot use the same dataset for Report.DataSet and Band.DataSet');
|
|
break;
|
|
end
|
|
else if FPage.DataSet = b.DataSet then
|
|
begin
|
|
Report.Errors.Add('Cannot use the same dataset for Page.DataSet and Band.DataSet');
|
|
break;
|
|
end
|
|
end;
|
|
end;
|
|
|
|
Result := Report.Errors.Count = 0;
|
|
end;
|
|
|
|
procedure TfrxEngine.InitializeSplit(Container: TfrxReportComponent;
|
|
SavedObjects: TList; SplitObject: TList);
|
|
var
|
|
i: Integer;
|
|
View: TfrxView;
|
|
begin
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
View := Container.Objects[i];
|
|
if not (View is TfrxSubreport) then
|
|
SplitObject.Add(View);
|
|
SavedObjects.Add(View);
|
|
if View is TfrxStretcheable then
|
|
begin
|
|
TfrxStretcheable(View).InitPart;
|
|
TfrxStretcheable(View).FSaveHeight := View.Height;
|
|
end;
|
|
end;
|
|
Container.Objects.Clear;
|
|
end;
|
|
|
|
procedure TfrxEngine.Finalize;
|
|
begin
|
|
try
|
|
RestoreVBandsObjects;
|
|
Report.DataSets.Finalize;
|
|
finally
|
|
Running := False;
|
|
PreviewPages.Finish;
|
|
FShiftEngine.Clear;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.FinalizeSplit(Container: TfrxReportComponent; SavedObjects,
|
|
SplitObject: TList; SplitHeight: Extended);
|
|
var
|
|
i: Integer;
|
|
View: TfrxView;
|
|
begin
|
|
{ correct the top coordinate of remained objects }
|
|
Container.Objects.Clear;
|
|
for i := 0 to SplitObject.Count - 1 do
|
|
begin
|
|
View := SplitObject[i];
|
|
View.Top := View.Top - SplitHeight;
|
|
{ restore the height of stretched objects }
|
|
if View is TfrxStretcheable then
|
|
begin
|
|
if View.Top < 0 then
|
|
View.Top := 0;
|
|
View.Height := TfrxStretcheable(View).FSaveHeight;
|
|
|
|
if View.Align = baBottom then
|
|
View.Top := Container.Height - View.Height
|
|
else if IsMaxHeightMode(View) then
|
|
View.Height := Container.Height - View.Top;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TfrxEngine.Run(ARunDialogs: Boolean; AClearLast: Boolean = False; APage: TfrxPage = nil): Boolean;
|
|
var
|
|
i: Integer;
|
|
aSaveCurX: Extended;
|
|
aCurColumn: Integer;
|
|
begin
|
|
Result := False;
|
|
aCurColumn := 0;
|
|
aSaveCurX := 0;
|
|
try
|
|
if Initialize then
|
|
try
|
|
Report.DataSets.Initialize;
|
|
Report.DoNotifyEvent(Report, Report.OnStartReport);
|
|
if not ARunDialogs or RunDialogs then
|
|
begin
|
|
Result := True;
|
|
|
|
{ add all report pages to the PreviewPages }
|
|
if APage <> nil then
|
|
begin
|
|
FPage := TfrxReportPage(APage);
|
|
PreviewPages.AddSourcePage(FPage);
|
|
{ find aggregates }
|
|
FAggregates.AddItems(FPage);
|
|
end
|
|
else
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if Report.Pages[i] is TfrxReportPage then
|
|
begin
|
|
FPage := TfrxReportPage(Report.Pages[i]);
|
|
PreviewPages.AddSourcePage(FPage);
|
|
{ find aggregates }
|
|
FAggregates.AddItems(FPage);
|
|
end;
|
|
|
|
{ start the report }
|
|
FinalPass := not DoublePass;
|
|
TotalPages := 0;
|
|
PreviewPages.BeginPass;
|
|
if (PreviewPages.Count > 0) and DoublePass then
|
|
TfrxPreviewPages(PreviewPages).GetLastColumnPos(aCurColumn, aSaveCurX);
|
|
RunReportPages(TfrxReportPage(APage));
|
|
if DoublePass then
|
|
begin
|
|
TotalPages := PreviewPages.Count;
|
|
PreviewPages.CurPage := PreviewPages.Count - 1;
|
|
PreviewPages.ClearFirstPassPages;
|
|
if PreviewPages.CurPage > -1 then
|
|
TfrxPreviewPages(PreviewPages).UpdatePageLastColumn(aCurColumn, aSaveCurX, True);
|
|
FAggregates.ClearValues;
|
|
FinalPass := True;
|
|
RunReportPages(TfrxReportPage(APage));
|
|
end;
|
|
end
|
|
finally
|
|
Report.DoNotifyEvent(Report, Report.OnStopReport);
|
|
end;
|
|
finally
|
|
Finalize;
|
|
end;
|
|
end;
|
|
|
|
{$HINTS OFF}
|
|
function TfrxEngine.RunDialogs: Boolean;
|
|
var
|
|
i: Integer;
|
|
p: TfrxDialogPage;
|
|
v: Variant;
|
|
begin
|
|
Result := True;
|
|
{$IFNDEF FR_VER_BASIC}
|
|
if Trim(Report.OnRunDialogs) <> '' then
|
|
begin
|
|
v := VarArrayOf([True]);
|
|
Report.DoParamEvent(Report.OnRunDialogs, v);
|
|
Result := v[0];
|
|
end
|
|
else
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if (Report.Pages[i] is TfrxDialogPage) and Report.Pages[i].Visible then
|
|
begin
|
|
p := TfrxDialogPage(Report.Pages[i]);
|
|
{ refresh the border style - it was bsSizeable in the designer }
|
|
p.DialogForm.BorderStyle := p.BorderStyle;
|
|
{ don't show empty form }
|
|
if p.DialogForm.ControlCount <> 0 then
|
|
begin
|
|
if Assigned(OnRunDialog) then
|
|
OnRunDialog(p) else
|
|
p.ShowModal;
|
|
if p.ModalResult = mrCancel then
|
|
begin
|
|
Result := False;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
{$HINTS ON}
|
|
|
|
procedure TfrxEngine.RunReportPages(APage: TfrxReportPage);
|
|
|
|
procedure DoPages;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if (APage <> nil) then
|
|
begin
|
|
FPage := APage;
|
|
RunPage(FPage);
|
|
FFirstReportPage := False;
|
|
Exit;
|
|
end;
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if Report.Pages[i] is TfrxReportPage then
|
|
begin
|
|
FPage := TfrxReportPage(Report.Pages[i]);
|
|
{ ignore subreport pages and invisible pages }
|
|
if not FPage.IsSubReport and FPage.Visible then
|
|
RunPage(FPage);
|
|
if Report.Terminated then break;
|
|
FFirstReportPage := False;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
FFirstReportPage := True;
|
|
if Report.DataSet = nil then
|
|
DoPages
|
|
else
|
|
begin
|
|
Report.DataSet.First;
|
|
while not Report.DataSet.Eof do
|
|
begin
|
|
if Report.Terminated then break;
|
|
DoPages;
|
|
Report.DataSet.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.PreparePage(ErrorList: TStrings; PrepareVBands: Boolean);
|
|
var
|
|
i, j, k: Integer;
|
|
t, c: TfrxComponent;
|
|
b: TfrxBand;
|
|
Bands: TList;
|
|
SortBands: {$IFNDEF NONWINFPC}TStringList{$ELSE}TfrxStringList{$ENDIF};
|
|
|
|
procedure ClearNils;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
i := 0;
|
|
while i < Bands.Count do
|
|
if Bands[i] = nil then
|
|
Bands.Delete(i) else
|
|
Inc(i);
|
|
end;
|
|
|
|
procedure MakeTree(Obj: TObject; From: Integer);
|
|
var
|
|
i: Integer;
|
|
b: TfrxBand;
|
|
begin
|
|
if Obj is TfrxReportPage then
|
|
begin
|
|
{ fill the first level - TfrxReportPage.FMasterBands }
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b := Bands[i];
|
|
if b = nil then continue;
|
|
if b is TfrxMasterData then
|
|
begin
|
|
if TfrxDataBand(b).DataSet <> nil then { ignore empty datasets }
|
|
if PrepareVBands then
|
|
TfrxReportPage(Obj).FVSubBands.Add(b)
|
|
else
|
|
TfrxReportPage(Obj).FSubBands.Add(b);
|
|
Bands[i] := nil;
|
|
MakeTree(b, i + 1);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
{ fill next levels - TfrxBand.FSubBands }
|
|
for i := From to Bands.Count - 1 do
|
|
begin
|
|
b := Bands[i];
|
|
if b = nil then continue;
|
|
{ looking for sub-level bands }
|
|
if b.BandNumber = TfrxBand(Obj).BandNumber + 1 then
|
|
begin
|
|
if TfrxDataBand(b).DataSet <> nil then { ignore empty datasets }
|
|
TfrxBand(Obj).FSubBands.Add(b);
|
|
Bands[i] := nil;
|
|
if not (b is TfrxDataBand6) then
|
|
MakeTree(b, i + 1);
|
|
end
|
|
else if b.BandNumber <= TfrxBand(Obj).BandNumber then
|
|
break; { found higher-level data band }
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure ConnectHeaders;
|
|
var
|
|
i: Integer;
|
|
b1, b2: TfrxBand;
|
|
begin
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b1 := Bands[i];
|
|
{ looking for data band }
|
|
if b1 is TfrxDataBand then
|
|
begin
|
|
if i > 0 then
|
|
begin
|
|
b2 := Bands[i - 1];
|
|
if b2 is TfrxHeader then { if top band is header, connect it }
|
|
begin
|
|
b1.FHeader := b2;
|
|
Bands[i - 1] := nil;
|
|
end;
|
|
end;
|
|
|
|
if i < Bands.Count - 1 then { if bottom band is footer, connect it }
|
|
begin
|
|
b2 := Bands[i + 1];
|
|
if b2 is TfrxFooter then
|
|
begin
|
|
b1.FFooter := b2;
|
|
Bands[i + 1] := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
ClearNils;
|
|
{ now all headers/footers must be connected. If not, add an error }
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b1 := Bands[i];
|
|
if (b1 is TfrxHeader) or (b1 is TfrxFooter) then
|
|
begin
|
|
ErrorList.Add(frxResources.Get('enUnconnHeader') + ' ' + b1.Name);
|
|
Bands[i] := nil;
|
|
end;
|
|
end;
|
|
|
|
ClearNils;
|
|
end;
|
|
|
|
procedure ConnectGroups;
|
|
var
|
|
i, j: Integer;
|
|
b1, b2: TfrxBand;
|
|
begin
|
|
{ connect group headers }
|
|
i := 0;
|
|
while i < Bands.Count do
|
|
begin
|
|
b1 := Bands[i];
|
|
if b1 is TfrxGroupHeader then
|
|
begin
|
|
b1.FSubBands.Add(b1);
|
|
Inc(i);
|
|
{ add all subsequent headers to the first header's FSubBands }
|
|
while (i < Bands.Count) and (TfrxBand(Bands[i]) is TfrxGroupHeader) do
|
|
begin
|
|
b1.FSubBands.Add(Bands[i]);
|
|
Inc(i);
|
|
end;
|
|
|
|
{ search for databand }
|
|
while (i < Bands.Count) and not (TfrxBand(Bands[i]) is TfrxDataBand) do
|
|
Inc(i);
|
|
|
|
{ now we expect to see the databand }
|
|
if (i = Bands.Count) or not (TObject(Bands[i]) is TfrxDataBand) then
|
|
ErrorList.Add(frxResources.Get('enUnconnGroup') + ' ' + b1.Name)
|
|
else
|
|
TfrxBand(Bands[i]).FGroup := b1;
|
|
end
|
|
else
|
|
Inc(i);
|
|
end;
|
|
|
|
{ connect group footers }
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b1 := Bands[i];
|
|
if b1 is TfrxGroupFooter then
|
|
for j := i - 1 downto 0 do
|
|
begin
|
|
b2 := Bands[j];
|
|
if b2 is TfrxGroupHeader then { connect to top-nearest header }
|
|
begin
|
|
b2.FFooter := b1;
|
|
Bands[i] := nil;
|
|
Bands[j] := nil;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ remove header bands from the list }
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b1 := Bands[i];
|
|
if b1 is TfrxGroupHeader then
|
|
Bands[i] := nil;
|
|
end;
|
|
|
|
{ looking for footers w/o corresponding header }
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b1 := Bands[i];
|
|
if b1 is TfrxGroupFooter then
|
|
begin
|
|
ErrorList.Add(frxResources.Get('enUnconnGFooter') + ' ' + b1.Name);
|
|
Bands[i] := nil;
|
|
end;
|
|
end;
|
|
|
|
ClearNils;
|
|
end;
|
|
|
|
begin
|
|
SortBands := {$IFNDEF NONWINFPC}TStringList.Create{$ELSE}TfrxStringList.Create{$ENDIF};
|
|
SortBands.Sorted := True;
|
|
|
|
{ align all objects with Align property <> baNone }
|
|
FPage.AlignChildren;
|
|
|
|
{ clear all page SubBands }
|
|
if PrepareVBands then
|
|
FPage.FVSubBands.Clear
|
|
else
|
|
FPage.FSubBands.Clear;
|
|
|
|
for i := 0 to FPage.Objects.Count - 1 do
|
|
begin
|
|
t := FPage.Objects[i];
|
|
if t is TfrxBand then
|
|
begin
|
|
b := TfrxBand(t);
|
|
if b.Vertical <> PrepareVBands then
|
|
continue;
|
|
if not b.Vertical then
|
|
PrepareShiftTree(b);
|
|
b.FSubBands.Clear;
|
|
b.FHeader := nil;
|
|
b.FFooter := nil;
|
|
b.FGroup := nil;
|
|
b.FHasVBands := False;
|
|
if b is TfrxDataBand then
|
|
if (TfrxDataBand(b).DataSet = nil) and (TfrxDataBand(b).RowCount > 0) then
|
|
begin
|
|
TfrxDataBand(b).DataSet := TfrxDataBand(b).VirtualDataSet;
|
|
TfrxDataBand(b).DataSet.Initialize;
|
|
end;
|
|
|
|
{ connect objects to vertical bands }
|
|
if (not PrepareVBands) and not (b is TfrxOverlay) then
|
|
for j := 0 to FPage.Objects.Count - 1 do
|
|
begin
|
|
t := FPage.Objects[j];
|
|
if (t is TfrxBand) and TfrxBand(t).Vertical then
|
|
begin
|
|
k := 0;
|
|
while k < b.Objects.Count do
|
|
begin
|
|
c := b.Objects[k];
|
|
if (c.Left >= t.Left - 1e-4) and
|
|
(c.Left + c.Width <= t.Left + t.Width + 1e-4) then
|
|
begin
|
|
b.FHasVBands := True;
|
|
c.Parent := t;
|
|
THackComponent(c).FOriginalBand := b;
|
|
c.Left := c.Left - t.Left;
|
|
end
|
|
else
|
|
Inc(k);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ sort bands by position }
|
|
for i := 0 to FPage.Objects.Count - 1 do
|
|
begin
|
|
t := FPage.Objects[i];
|
|
if t is TfrxBand then
|
|
begin
|
|
b := TfrxBand(t);
|
|
if b.Vertical <> PrepareVBands then
|
|
continue;
|
|
if b.BandNumber in [4..13] then
|
|
if b.Vertical then
|
|
SortBands.AddObject(Format('%9.2f', [b.Left]), b)
|
|
else
|
|
SortBands.AddObject(Format('%9.2f', [b.Top]), b);
|
|
end;
|
|
end;
|
|
|
|
{ copy sorted items to TList - it's easier to work with it }
|
|
Bands := TList.Create;
|
|
for i := 0 to SortBands.Count - 1 do
|
|
begin
|
|
t := TfrxComponent(SortBands.Objects[i]);
|
|
Bands.Add(t);
|
|
end;
|
|
|
|
SortBands.Free;
|
|
|
|
ConnectGroups;
|
|
ConnectHeaders;
|
|
MakeTree(FPage, 0);
|
|
|
|
ClearNils;
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
t := Bands[i];
|
|
ErrorList.Add(frxResources.Get('enBandPos') + ' ' + t.Name);
|
|
end;
|
|
|
|
Bands.Free;
|
|
end;
|
|
|
|
procedure TfrxEngine.PrepareShiftTree(Container: TfrxReportComponent);
|
|
begin
|
|
FShiftEngine.PrepareShiftTree(Container);
|
|
end;
|
|
|
|
procedure TfrxEngine.ProcessObject(ReportObject: TfrxView);
|
|
begin
|
|
if ReportObject.Processing.ProcessAt = paCustom then
|
|
PreviewPages.PostProcessor.ProcessObject(Report, ReportObject);
|
|
end;
|
|
|
|
function TfrxEngine.CanShow(Obj: TObject; PrintIfDetailEmpty: Boolean): Boolean;
|
|
var
|
|
i: Integer;
|
|
Bands: TList;
|
|
b: TfrxDataBand;
|
|
res: Boolean;
|
|
begin
|
|
if Obj is TfrxReportPage then
|
|
Bands := TfrxReportPage(Obj).FSubBands else
|
|
Bands := TfrxBand(Obj).FSubBands;
|
|
|
|
Result := True;
|
|
{ Check all subdetail bands to ensure they all have records }
|
|
if not PrintIfDetailEmpty then
|
|
begin
|
|
Result := False;
|
|
if (Bands.Count = 0) and not (Obj is TfrxPage) then
|
|
Result := True;
|
|
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b := Bands[i];
|
|
if b.DataSet <> nil then
|
|
begin
|
|
Report.DoNotifyEvent(b, b.OnMasterDetail);
|
|
b.DataSet.First;
|
|
|
|
while not b.DataSet.Eof do
|
|
begin
|
|
res := CanShow(b, b.PrintIfDetailEmpty);
|
|
if res then
|
|
begin
|
|
Result := True;
|
|
break;
|
|
end
|
|
else
|
|
b.DataSet.Next;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.ResetSuppressValues(Band: TfrxBand);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
PreviewPages.PostProcessor.ResetDuplicates(Band.Name);
|
|
for i := 0 to Band.Objects.Count - 1 do
|
|
if TObject(Band.Objects[i]) is TfrxCustomMemoView then
|
|
THackMemoView(Band.Objects[i]).FLastValue := Null;
|
|
end;
|
|
|
|
procedure TfrxEngine.RestoreVBandsObjects;
|
|
var
|
|
i, j: Integer;
|
|
c: THackComponent;
|
|
aPage: TfrxReportPage;
|
|
cLeft: Extended;
|
|
begin
|
|
for i := 0 to Report.PagesCount - 1 do
|
|
if Report.Pages[i] is TfrxReportPage then
|
|
begin
|
|
aPage := TfrxReportPage(Report.Pages[i]);
|
|
for j := 0 to aPage.FVSubBands.Count - 1 do
|
|
while TfrxBand(aPage.FVSubBands[j]).Objects.Count > 0 do
|
|
begin
|
|
c := THackComponent(TfrxBand(aPage.FVSubBands[j]).Objects[0]);
|
|
cLeft := c.Left;
|
|
c.Parent := c.FOriginalBand;
|
|
c.Left := TfrxBand(aPage.FVSubBands[j]).Left + cLeft;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.InitGroups(Master: TfrxDataBand; Band: TfrxGroupHeader;
|
|
Index: Integer; ResetLineN: Boolean = False);
|
|
var
|
|
i: Integer;
|
|
b: TfrxGroupHeader;
|
|
begin
|
|
for i := Index to Band.FSubBands.Count - 1 do
|
|
begin
|
|
b := Band.FSubBands[i];
|
|
if ResetLineN then
|
|
begin
|
|
b.FLineN := 1;
|
|
b.FLineThrough := 1;
|
|
ResetSuppressValues(b);
|
|
end
|
|
else
|
|
begin
|
|
Inc(b.FLineN);
|
|
if i < Band.FSubBands.Count - 1 then
|
|
TfrxBand(Band.FSubBands[i + 1]).FLineN := 0;
|
|
Inc(b.FLineThrough);
|
|
end;
|
|
end;
|
|
|
|
Master.CurColumn := 1;
|
|
CheckDrill(Master, Band);
|
|
|
|
for i := Index to Band.FSubBands.Count - 1 do
|
|
begin
|
|
b := Band.FSubBands[i];
|
|
CurLine := b.FLineN;
|
|
CurLineThrough := b.FLineThrough;
|
|
Report.CurObject := b.Name;
|
|
b.FLastValue := Report.Calc(b.Condition);
|
|
if b.KeepTogether then
|
|
StartKeep(b);
|
|
if b is TfrxGroupHeader then
|
|
PreviewPages.PostProcessor.EnterGroup;
|
|
ShowBand(b);
|
|
AddBandOutline(b);
|
|
if b.Vertical then
|
|
AddToVHeaderList(b)
|
|
else
|
|
AddToHeaderList(b);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.ShowGroupFooters(Band: TfrxGroupHeader; Index: Integer;
|
|
Master: TfrxDataBand);
|
|
var
|
|
i: Integer;
|
|
b: TfrxGroupHeader;
|
|
begin
|
|
for i := Band.FSubBands.Count - 1 downto Index do
|
|
begin
|
|
b := Band.FSubBands[i];
|
|
if b.FFooter <> nil then
|
|
if not TfrxGroupFooter(b.FFooter).HideIfSingleDataRecord or (Master.FLineN > 2) then
|
|
ShowBand(b.FFooter)
|
|
else
|
|
DoProcessState(b.FFooter, paGroupFinished);
|
|
|
|
OutlineUp(b);
|
|
if b.Vertical then
|
|
RemoveFromVHeaderList(b)
|
|
else
|
|
RemoveFromHeaderList(b);
|
|
if b.KeepTogether then
|
|
EndKeep(b);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.CheckDrill(Master: TfrxDataBand; Band: TfrxGroupHeader);
|
|
var
|
|
i, j: Integer;
|
|
b, b1: TfrxGroupHeader;
|
|
drillVisible: Boolean;
|
|
BandNamesList: TStrings;
|
|
begin
|
|
BandNamesList := FHBandNamesTree;
|
|
|
|
for i := 0 to Band.FSubBands.Count - 1 do
|
|
begin
|
|
b := Band.FSubBands[i];
|
|
if b.DrillDown then
|
|
begin
|
|
b.DrillName := '';
|
|
for j := 0 to BandNamesList.Count - 1 do
|
|
b.DrillName := b.DrillName + BandNamesList[j] + '.' + IntToStr(Integer(BandNamesList.Objects[j])) + '.';
|
|
b.DrillName := b.DrillName + b.Name + '.' + IntToStr(b.FLineThrough);
|
|
drillVisible := Report.DrillState.IndexOf(b.DrillName) <> -1;
|
|
if b.ExpandDrillDown then
|
|
drillVisible := not DrillVisible;
|
|
if (b.Child <> nil) and not Band.ShowChildIfDrillDown then
|
|
b.Child.Visible := drillVisible;
|
|
for j := i + 1 to Band.FSubBands.Count - 1 do
|
|
begin
|
|
b1 := Band.FSubBands[j];
|
|
b1.Visible := drillVisible;
|
|
if b1.FFooter <> nil then
|
|
b1.FFooter.Visible := drillVisible;
|
|
end;
|
|
Master.Visible := drillVisible;
|
|
if not b.ShowFooterIfDrillDown and (b.FFooter <> nil) then
|
|
b.FFooter.Visible := drillVisible;
|
|
if not drillVisible then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.CheckGroups(Master: TfrxDataBand; Band: TfrxGroupHeader;
|
|
ColumnKeepPos: Integer; SaveCurY: Extended);
|
|
var
|
|
i, HeaderPos: Integer;
|
|
b: TfrxGroupHeader;
|
|
NextNeeded: Boolean;
|
|
begin
|
|
HeaderPos := 0;
|
|
CheckDrill(Master, Band);
|
|
|
|
for i := 0 to Band.FSubBands.Count - 1 do
|
|
begin
|
|
b := Band.FSubBands[i];
|
|
|
|
Report.CurObject := b.Name;
|
|
if Report.Calc(b.Condition) <> b.FLastValue then
|
|
begin
|
|
Master.CurColumn := Master.Columns;
|
|
{ avoid exception in uni-directional datasets }
|
|
NextNeeded := True;
|
|
try
|
|
Master.DataSet.Prior;
|
|
except
|
|
NextNeeded := False;
|
|
end;
|
|
CheckBandColumns(Master, ColumnKeepPos, HeaderPos, SaveCurY, 0);
|
|
ShowGroupFooters(Band, i, Master);
|
|
if NextNeeded then
|
|
Master.DataSet.Next;
|
|
|
|
InitGroups(Master, Band, i);
|
|
Master.FLineN := 1;
|
|
ResetSuppressValues(Master);
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.CheckBandColumns(Band: TfrxDataBand; ColumnKeepPos: Integer; var HeaderKeepPos: Integer;
|
|
SaveCurY, SaveHeaderY: Extended);
|
|
begin
|
|
if Band.Columns > 1 then
|
|
begin
|
|
if not Band.Visible then Exit;
|
|
{ collect max position in b.FMaxY }
|
|
if (CurY > Band.FMaxY) then
|
|
Band.FMaxY := CurY;
|
|
{ all columns have been printed }
|
|
if Band.CurColumn >= Band.Columns then
|
|
begin
|
|
{ need page break, don't break if page has Endless Height}
|
|
if ((PageHeight - FooterHeight) - Band.FMaxY <= 1e-4) and (not FPage.EndlessHeight) and (FOutputTo = nil) { #446438 } then
|
|
begin
|
|
{ keep objects doesn't fit on whole page, for columns we need to break keep before start new page }
|
|
{ #446438 }
|
|
if FKeeping and (CurY - FKeepCurY > PageHeight - FooterHeight - HeaderHeight) then
|
|
EndKeep(FKeepBand);
|
|
if FKeeping then { standard keep procedure }
|
|
NewColumn
|
|
else
|
|
begin
|
|
{ emulate keep header for band columns }
|
|
if (HeaderKeepPos > 0) and (SaveCurY - SaveHeaderY + (Band.FMaxY - SaveCurY) < PageHeight - FooterHeight) then
|
|
begin
|
|
ColumnKeepPos := HeaderKeepPos;
|
|
SaveCurY := SaveHeaderY;
|
|
{ #575918 remove cut band for KeepHeader from ReprintOnNewPage list }
|
|
if Band.KeepHeader then
|
|
begin
|
|
RemoveFromHeaderList(Band.FGroup);
|
|
RemoveFromHeaderList(Band.FHeader);
|
|
end;
|
|
end;
|
|
|
|
PreviewPages.CutObjects(ColumnKeepPos);
|
|
NewColumn;
|
|
PreviewPages.PasteObjects(CurX, CurY);
|
|
CurY := CurY + Band.FMaxY - SaveCurY;
|
|
end;
|
|
end
|
|
else
|
|
CurY := Band.FMaxY; { start the new band from saved b.FMaxY }
|
|
HeaderKeepPos := 0;
|
|
end
|
|
else
|
|
CurY := SaveCurY; { start the new band from saved SaveCurY }
|
|
Band.CurColumn := Band.CurColumn + 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.NotifyObjects(Band: TfrxBand);
|
|
var
|
|
i: Integer;
|
|
c: TfrxComponent;
|
|
begin
|
|
for i := 0 to NotifyList.Count - 1 do
|
|
begin
|
|
c := NotifyList[i];
|
|
if c <> nil then
|
|
c.OnNotify(Band);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.RunPage(Page: TfrxReportPage);
|
|
var
|
|
PageCount: Integer;
|
|
|
|
{ "Null" band contains all free-placed objects that don't have a parent band }
|
|
procedure ShowNullBand;
|
|
var
|
|
i: Integer;
|
|
b: TfrxNullBand;
|
|
SaveCurY: Extended;
|
|
begin
|
|
b := TfrxNullBand.Create(nil);
|
|
b.Width := PageWidth;
|
|
b.Height := PageHeight;
|
|
SaveCurY := CurY;
|
|
for i := 0 to FPage.Objects.Count - 1 do
|
|
if not (TObject(FPage.Objects[i]) is TfrxBand) then
|
|
b.Objects.Add(FPage.Objects[i]);
|
|
try
|
|
b.AlignChildren;
|
|
ShowBand(b);
|
|
finally
|
|
CurY := SaveCurY;
|
|
b.Objects.Clear;
|
|
b.Free;
|
|
end;
|
|
end;
|
|
|
|
{ Band tree is the structure that we created in the PreparePage method }
|
|
procedure ShowBandTree(Obj: TObject);
|
|
var
|
|
i, rCount: Integer;
|
|
Bands: TList;
|
|
b: TfrxDataBand;
|
|
ToNRowsBand: TfrxBand;
|
|
ToNRowsChild: TfrxChild;
|
|
FirstTime: Boolean;
|
|
FooterKeepPos, ColumnKeepPos, HeaderKeepPos: Integer;
|
|
SaveCurY, SaveHeaderY: Extended;
|
|
filterOk, HeaderKeeped: Boolean;
|
|
{ TillPageEnds vars }
|
|
TillPageEndsOutline: TfrxXMLItem;
|
|
TillPageEndsAnchor, TillPageEndsCurY: Integer;
|
|
pCount: Integer;
|
|
{ TillPageEnds vars }
|
|
|
|
procedure DoToNRowsBand(bNeedCutObjects: Boolean);
|
|
begin
|
|
FLockResetAggregates := False;
|
|
if PreviewPages.Count = pCount then
|
|
begin
|
|
{ do not cut preview objects when SubReport with PrintOnParent in process }
|
|
if bNeedCutObjects then
|
|
PreviewPages.CutObjects(TillPageEndsCurY);
|
|
FPrevFooterHeight := CurY - PreviewPages.GetLastY + FooterHeight;
|
|
{ do not cut preview objects when SubReport with PrintOnParent in process }
|
|
if bNeedCutObjects then
|
|
CurY := PreviewPages.GetLastY;
|
|
FBreakShowBandTree := False;
|
|
rCount := ToNRowsBand.FLineN;
|
|
ToNRowsChild := ToNRowsBand.Child;
|
|
ToNRowsChild.FLineN := 1;
|
|
if ToNRowsChild.ToNRowsMode = rmAddToCount then
|
|
rCount := 1;
|
|
while (rCount <= ToNRowsChild.ToNRows) or
|
|
(not FBreakShowBandTree and
|
|
(ToNRowsChild.ToNRowsMode = rmTillPageEnds)) do
|
|
begin
|
|
CurLine := ToNRowsBand.FLineN;
|
|
CurLineThrough := ToNRowsBand.FLineThrough;
|
|
DoShow(ToNRowsChild);
|
|
Inc(rCount);
|
|
Inc(ToNRowsBand.FLineN);
|
|
Inc(ToNRowsBand.FLineThrough);
|
|
end;
|
|
{ do not cut preview objects when SubReport with PrintOnParent in process }
|
|
if bNeedCutObjects then
|
|
begin
|
|
PreviewPages.PasteObjects(CurX, CurY);
|
|
PreviewPages.Outline.ShiftItems(TillPageEndsOutline, Round(CurY));
|
|
PreviewPages.ShiftAnchors(TillPageEndsAnchor, Round(CurY));
|
|
CurY := PreviewPages.GetLastY(CurX);
|
|
end;
|
|
FPrevFooterHeight := 0;
|
|
end;
|
|
DoProcessState(b.FFooter, paDataFinished);
|
|
end;
|
|
|
|
begin
|
|
if not Report.EngineOptions.EnableThreadSafe then
|
|
Application.ProcessMessages;
|
|
if Report.Terminated then Exit;
|
|
|
|
FooterKeepPos := 0;
|
|
ColumnKeepPos := 0;
|
|
SaveCurY := CurY;
|
|
if Obj is TfrxReportPage then
|
|
Bands := TfrxReportPage(Obj).FSubBands else
|
|
Bands := TfrxBand(Obj).FSubBands;
|
|
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b := Bands[i];
|
|
if b.DataSet = nil then
|
|
continue;
|
|
b.DataSet.First;
|
|
b.FLineN := 1;
|
|
b.FLineThrough := 1;
|
|
b.CurColumn := 1;
|
|
FirstTime := True;
|
|
HeaderKeeped := False;
|
|
ResetSuppressValues(b);
|
|
HeaderKeepPos := 0;
|
|
SaveHeaderY := 0;
|
|
ToNRowsBand := nil;
|
|
TillPageEndsCurY := 0;
|
|
TillPageEndsOutline := nil;
|
|
TillPageEndsAnchor := 0;
|
|
PreviewPages.PostProcessor.EnterData;
|
|
while not b.DataSet.Eof do
|
|
begin
|
|
if HeaderKeeped then
|
|
if b.KeepHeader and (b.FHeader <> nil) then
|
|
begin
|
|
EndKeep(b);
|
|
HeaderKeeped := False;
|
|
end;
|
|
if Trim(b.Filter) <> '' then
|
|
filterOk := Report.Calc(b.Filter) = True
|
|
else
|
|
filterOk := True;
|
|
if filterOk and CanShow(b, b.PrintIfDetailEmpty) then
|
|
begin
|
|
if FirstTime then
|
|
begin
|
|
if b.KeepHeader and (b.FHeader <> nil) then
|
|
begin
|
|
if FIsFirstBand then
|
|
FIsFirstBand := not FTitlePrinted;
|
|
FKeepHeader := not FIsFirstBand;
|
|
{ we need to handle keepheader separately for multi-column bands }
|
|
if (b.Columns > 1) and (b.CurColumn = 1) then
|
|
begin
|
|
HeaderKeepPos := PreviewPages.GetCurPosition;
|
|
SaveHeaderY := FCurY;
|
|
end;
|
|
StartKeep(b);
|
|
HeaderKeeped := True;
|
|
end;
|
|
AddToHeaderList(b.FHeader);
|
|
ShowBand(b.FHeader);
|
|
if b.KeepTogether and (not HeaderKeeped) then
|
|
StartKeep(b);
|
|
|
|
end
|
|
{ keeping a master-detail differs from keeping a group }
|
|
else if (b.FGroup = nil) and b.KeepTogether and (not HeaderKeeped) then
|
|
StartKeep(b);
|
|
|
|
if b.FGroup <> nil then
|
|
if FirstTime then
|
|
InitGroups(b, TfrxGroupHeader(b.FGroup), 0, True) else
|
|
CheckGroups(b, TfrxGroupHeader(b.FGroup), ColumnKeepPos, SaveCurY);
|
|
|
|
if b.KeepFooter then
|
|
FooterKeepPos := PreviewPages.GetCurPosition;
|
|
if (b.Columns > 1) and (b.CurColumn = 1) then
|
|
ColumnKeepPos := PreviewPages.GetCurPosition;
|
|
|
|
SaveCurY := CurY;
|
|
CurLine := b.FLineN;
|
|
CurLineThrough := b.FLineThrough;
|
|
ToNRowsBand := ShowBand(b);
|
|
FKeepHeader := False;
|
|
NotifyObjects(b);
|
|
|
|
if FirstTime then
|
|
if not (b.KeepFooter and (b.FFooter <> nil)) then
|
|
if b.KeepHeader and (b.FHeader <> nil) then
|
|
begin
|
|
EndKeep(b);
|
|
HeaderKeeped := False;
|
|
end;
|
|
FirstTime := False;
|
|
|
|
FHBandNamesTree.AddObject(b.Name, TObject(b.FLineThrough));
|
|
Inc(b.FLineN);
|
|
Inc(b.FLineThrough);
|
|
CheckBandColumns(b, ColumnKeepPos, HeaderKeepPos, SaveCurY, SaveHeaderY);
|
|
AddBandOutline(b);
|
|
ShowBandTree(b);
|
|
FHBandNamesTree.Delete(FHBandNamesTree.Count - 1);
|
|
OutlineUp(b);
|
|
|
|
FIsFirstBand := False;
|
|
|
|
if b.FooterAfterEach then
|
|
begin
|
|
if not b.KeepFooter then
|
|
ShowBand(b.FFooter)
|
|
else
|
|
begin
|
|
StartKeep(b, FooterKeepPos);
|
|
FKeepFooter := True;
|
|
ShowBand(b.FFooter);
|
|
EndKeep(b);
|
|
FKeepFooter := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ keeping a master-detail differs from keeping a group }
|
|
if (b.FGroup = nil) and b.KeepTogether and (not HeaderKeeped) then
|
|
EndKeep(b);
|
|
b.DataSet.Next;
|
|
if b.RowCount <> 0 then
|
|
if b.FLineN > b.RowCount then break;
|
|
|
|
if Report.Terminated then break;
|
|
end;
|
|
|
|
{ update the CurY if band is multicolumn }
|
|
b.CurColumn := b.Columns;
|
|
CheckBandColumns(b, ColumnKeepPos, HeaderKeepPos, SaveCurY, SaveHeaderY);
|
|
{ need for invisible bands }
|
|
if not(b.Visible) and (b.Columns > 1) and (CurY < B.FMaxY) then
|
|
CurY := B.FMaxY;
|
|
|
|
pCount := PreviewPages.Count;
|
|
if Assigned(ToNRowsBand) then
|
|
if FOutputTo = nil then
|
|
begin
|
|
TillPageEndsCurY := PreviewPages.GetCurPosition;
|
|
TillPageEndsOutline := PreviewPages.Outline.GetCurPosition;
|
|
TillPageEndsAnchor := PreviewPages.GetAnchorCurPosition;
|
|
FLockResetAggregates := True;
|
|
end
|
|
else
|
|
{ procees on Subreport with PrintOnParent }
|
|
DoToNRowsBand(False);
|
|
|
|
if not FirstTime then { some bands have been printed }
|
|
begin
|
|
if b.FGroup <> nil then
|
|
ShowGroupFooters(TfrxGroupHeader(b.FGroup), 0, b);
|
|
|
|
if FKeeping then
|
|
RemoveFromHeaderList(b.FHeader);
|
|
|
|
if not b.FooterAfterEach then
|
|
begin
|
|
if b.KeepFooter and (not HeaderKeeped) then
|
|
StartKeep(b, FooterKeepPos);
|
|
FKeepFooter := True;
|
|
ShowBand(b.FFooter);
|
|
if b.KeepFooter then
|
|
EndKeep(b);
|
|
FKeepFooter := False;
|
|
end;
|
|
RemoveFromHeaderList(b.FHeader);
|
|
if (b.FGroup <> nil) and b.KeepTogether then
|
|
EndKeep(b);
|
|
end;
|
|
|
|
{ TillPageEnds processing , not for PrintOnParent}
|
|
if Assigned(ToNRowsBand) and (FOutputTo = nil) then
|
|
DoToNRowsBand(True);
|
|
PreviewPages.PostProcessor.LeaveData;
|
|
if Report.Terminated then break;
|
|
FIsFirstBand := False;
|
|
end;
|
|
end;
|
|
|
|
procedure ShowPage;
|
|
var
|
|
pgWidth, pgHeight: Extended;
|
|
begin
|
|
if CanShow(FPage, FPage.PrintIfEmpty and Report.EngineOptions.PrintIfEmpty) then
|
|
//if CanShow(FPage, Report.EngineOptions.PrintIfEmpty) then
|
|
begin
|
|
InitPage;
|
|
ShowNullBand;
|
|
|
|
if Assigned(Report.OnManualBuild) then
|
|
Report.OnManualBuild(FPage)
|
|
else if Trim(FPage.OnManualBuild) <> '' then
|
|
Report.DoNotifyEvent(FPage, FPage.OnManualBuild)
|
|
else
|
|
ShowBandTree(FPage);
|
|
FIsLastPage := True;
|
|
if FPage.EndlessHeight or FPage.EndlessWidth then
|
|
begin
|
|
if FPage.EndlessWidth then
|
|
pgWidth := PageWidth + FPage.LeftMargin * fr01cm + FPage.RightMargin * fr01cm
|
|
else
|
|
pgWidth := FPage.PaperWidth * fr01cm;
|
|
if FPage.EndlessHeight then
|
|
begin
|
|
PageHeight := CurY + FooterHeight;
|
|
pgHeight := PageHeight + FPage.TopMargin * fr01cm + FPage.BottomMargin * fr01cm
|
|
end
|
|
else
|
|
pgHeight := FPage.PaperHeight * fr01cm;
|
|
TfrxPreviewPages(PreviewPages).UpdatePageDimensions(FPage, pgWidth, pgHeight);
|
|
end;
|
|
|
|
EndPage;
|
|
FIsLastPage := False;
|
|
DoProcessState(nil, paReportPageFinished);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
{ The Page parameter needed only for subreport pages. General is FPage }
|
|
if Page = nil then exit;
|
|
if Page.IsSubReport then
|
|
begin
|
|
ShowBandTree(Page);
|
|
Exit;
|
|
end;
|
|
|
|
FIsFirstBand := True;
|
|
Report.DoNotifyEvent(FPage, FPage.OnBeforePrint);
|
|
|
|
if FPage.DataSet <> nil then
|
|
begin
|
|
FPage.DataSet.First;
|
|
|
|
while not FPage.DataSet.Eof do
|
|
begin
|
|
if Report.Terminated then break;
|
|
ShowPage;
|
|
FPage.DataSet.Next;
|
|
end;
|
|
end
|
|
else
|
|
for PageCount := 1 to FPage.PageCount do
|
|
begin
|
|
if Report.Terminated then break;
|
|
ShowPage;
|
|
end;
|
|
|
|
Report.DoNotifyEvent(FPage, FPage.OnAfterPrint);
|
|
end;
|
|
|
|
procedure TfrxEngine.ShowVBands(HBand: TfrxBand);
|
|
var
|
|
SavePageNo: Integer;
|
|
procedure ShowBandTree(Bands: TList);
|
|
var
|
|
i: Integer;
|
|
b: TfrxDataBand;
|
|
FirstTime: Boolean;
|
|
begin
|
|
if Report.Terminated then Exit;
|
|
|
|
for i := 0 to Bands.Count - 1 do
|
|
begin
|
|
b := Bands[i];
|
|
if b.DataSet = nil then
|
|
continue;
|
|
b.DataSet.First;
|
|
b.FLineN := 1;
|
|
b.FLineThrough := 1;
|
|
b.CurColumn := 1;
|
|
CurLine := b.FLineN;
|
|
CurLineThrough := b.FLineThrough;
|
|
FirstTime := True;
|
|
ResetSuppressValues(b);
|
|
|
|
while not b.DataSet.Eof do
|
|
begin
|
|
if FirstTime then
|
|
begin
|
|
ShowBand(b.FHeader);
|
|
AddToVHeaderList(b.FHeader);
|
|
end;
|
|
|
|
if b.FGroup <> nil then
|
|
if FirstTime then
|
|
InitGroups(b, TfrxGroupHeader(b.FGroup), 0, True) else
|
|
CheckGroups(b, TfrxGroupHeader(b.FGroup), 0, 0);
|
|
|
|
FirstTime := False;
|
|
|
|
CurLine := b.FLineN;
|
|
CurLineThrough := b.FLineThrough;
|
|
ShowBand(b);
|
|
NotifyObjects(b);
|
|
Inc(b.FLineN);
|
|
Inc(b.FLineThrough);
|
|
ShowBandTree(b.FSubBands);
|
|
|
|
if b.FooterAfterEach then
|
|
ShowBand(b.FFooter);
|
|
|
|
b.DataSet.Next;
|
|
if b.RowCount <> 0 then
|
|
if b.FLineN > b.RowCount then break;
|
|
if Report.Terminated then break;
|
|
end;
|
|
|
|
if b.FGroup <> nil then
|
|
ShowGroupFooters(TfrxGroupHeader(b.FGroup), 0, b);
|
|
|
|
if not FirstTime then { some bands have been printed }
|
|
begin
|
|
RemoveFromVHeaderList(b.FHeader);
|
|
if not b.FooterAfterEach then
|
|
ShowBand(b.FFooter);
|
|
end;
|
|
|
|
if Report.Terminated then break;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
FVMasterBand := HBand;
|
|
FVMasterBand.FOriginalObjectsCount := FVMasterBand.Objects.Count;
|
|
FVMasterBand.AllowSplit := False;
|
|
SavePageNo := PreviewPages.CurPage;
|
|
FVHeaderList.Clear;
|
|
FVPageList.Clear;
|
|
FVPageList.Add(Pointer(0));
|
|
|
|
CurVColumn := 0;
|
|
ShowBandTree(TfrxReportPage(HBand.Page).FVSubBands);
|
|
FVPageList.Add(Pointer(FVMasterBand.Objects.Count));
|
|
PreviewPages.CurPage := SavePageNo;
|
|
end;
|
|
|
|
function TfrxEngine.SplitObjects(Container: TfrxReportComponent; SavedObjects,
|
|
SplitObject: TList; AHeight: Extended): Boolean;
|
|
var
|
|
ShiftedList: TList;
|
|
i, j, Index: Integer;
|
|
View, CorrObj: TfrxView;
|
|
StrView: TfrxStretcheable;
|
|
Corr, SavedHeight: Extended;
|
|
AllowNextPart: Boolean;
|
|
|
|
procedure ShiftObjects(TopView: TfrxView; Delta: Extended);
|
|
var
|
|
i: Integer;
|
|
View: TfrxView;
|
|
TopViewHeight: Extended;
|
|
begin
|
|
{ check split object in full size }
|
|
if TopView is TfrxStretcheable then
|
|
begin
|
|
TopViewHeight := TfrxStretcheable(TopView).FSaveHeight;
|
|
{ this type of object should stretch to the end of the band }
|
|
if IsMaxHeightMode(TopView) then
|
|
TopViewHeight := MaxDouble;
|
|
end
|
|
else
|
|
TopViewHeight := TopView.Height;
|
|
for i := 0 to SplitObject.Count - 1 do
|
|
begin
|
|
View := SplitObject[i];
|
|
if (View <> TopView) and (ShiftedList.IndexOf(View) = -1) and
|
|
(View.Top >= TopView.Top + TopViewHeight) and
|
|
(TopView.Left + TopView.Width - View.Left > 1e-4) and
|
|
(View.Left + View.Width - TopView.Left > 1e-4)
|
|
then
|
|
begin
|
|
View.Top := View.Top + Delta;
|
|
ShiftedList.Add(View);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure CalcBandHeight;
|
|
var
|
|
i: Integer;
|
|
LView: TfrxView;
|
|
LPartMaxHeighList: TList;
|
|
begin
|
|
Container.Height := 0;
|
|
LPartMaxHeighList := TList.Create;
|
|
try
|
|
{ calculate the band's height }
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
LView := Container.Objects[i];
|
|
if LView.Top + LView.Height > Container.Height then
|
|
Container.Height := LView.Top + LView.Height;
|
|
if (SplitObject.Count > 0) and (LView is TfrxStretcheable) and (TfrxStretcheable(LView).StretchMode = smPartMaxHeight) then
|
|
begin
|
|
if SplitObject.IndexOf(LView) = -1 then
|
|
begin
|
|
SplitObject.Add(LView);
|
|
TfrxStretcheable(LView).FSaveHeight := 0;
|
|
end;
|
|
LPartMaxHeighList.Add(LView);
|
|
end;
|
|
end;
|
|
|
|
{ correct objects with smPartMaxHeight }
|
|
for i := 0 to LPartMaxHeighList.Count - 1 do
|
|
begin
|
|
LView := LPartMaxHeighList[i];
|
|
LView.Height := Container.Height - LView.Top;
|
|
end;
|
|
finally
|
|
LPartMaxHeighList.Free;
|
|
end;
|
|
|
|
{ correct objects with StretchToMaxHeight or BandAlign = baBottom }
|
|
if SplitObject.Count = 0 then
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
LView := Container.Objects[i];
|
|
THackComponent(LView).UnlockAnchorsUpdate;
|
|
if LView.Align = baBottom then
|
|
LView.Top := Container.Height - LView.Height
|
|
else if IsMaxHeightMode(LView) then
|
|
LView.Height := Container.Height - LView.Top;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := False;
|
|
ShiftedList := TList.Create;
|
|
|
|
i := 0;
|
|
while i < SplitObject.Count do
|
|
begin
|
|
View := SplitObject[i];
|
|
Corr := 0;
|
|
SavedHeight := View.Height;
|
|
AllowNextPart := (View.Top + SavedHeight <= AHeight);
|
|
{ call DrawPart above to proceess /page tag in rich object }
|
|
if View is TfrxStretcheable then
|
|
begin
|
|
{ Save object top for streched object }
|
|
StrView := SplitObject[i];
|
|
StrView.FSavedTop := StrView.Top;
|
|
if View.Top < AHeight then
|
|
begin
|
|
StrView.Height := AHeight - StrView.Top;
|
|
{ trying to place it }
|
|
{ DrawPart method returns the amount of unused space. If view
|
|
can't fit in the height, this method returns the Height }
|
|
Corr := StrView.DrawPart;
|
|
SavedHeight := StrView.FSaveHeight;
|
|
{ check: does object has new part of data independently from it size}
|
|
AllowNextPart := not StrView.HasNextDataPart(AHeight);
|
|
|
|
{ correction for dynamically changed view }
|
|
{ GetLastShiftAmount should return correction amount between calculated Height }
|
|
{ in CalcHeight method and real height }
|
|
if AllowNextPart then
|
|
begin
|
|
Corr := StrView.GetLastShiftAmount(AHeight);
|
|
if Abs(Corr) > 1e-4 then
|
|
begin
|
|
SavedHeight := SavedHeight + Corr;
|
|
StrView.Height := SavedHeight;
|
|
ShiftObjects(StrView, Corr);
|
|
j := 0;
|
|
while j < i do
|
|
begin
|
|
CorrObj := SplitObject[j];
|
|
Index := ShiftedList.IndexOf(CorrObj);
|
|
if Index <> -1 then
|
|
begin
|
|
SplitObject.Delete(j);
|
|
SplitObject.Add(CorrObj);
|
|
Dec(i);
|
|
end;
|
|
Inc(j);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ whole object fits in the page }
|
|
{ or it can't be split }
|
|
//(View.Top + SavedHeight <= CurHeight) moved to HasNextDataPart method
|
|
if AllowNextPart then
|
|
begin
|
|
View.Height := SavedHeight;
|
|
{ add to band and remove from list }
|
|
Container.Objects.Add(View.GetSaveToComponent);
|
|
SplitObject.Remove(View);
|
|
continue;
|
|
end;
|
|
|
|
if View is TfrxStretcheable then
|
|
begin
|
|
StrView := SplitObject[i];
|
|
|
|
{ view is inside draw area }
|
|
if StrView.Top < AHeight then
|
|
begin
|
|
ShiftObjects(StrView, Corr);
|
|
if Abs(Corr - StrView.Height) < 1e-4 then
|
|
begin
|
|
{ view can't fit, return back the height and correct the top }
|
|
StrView.Top := AHeight;
|
|
{ shift the underlying objects down }
|
|
StrView.Height := StrView.FSaveHeight;
|
|
end
|
|
else
|
|
begin
|
|
{ view can draw something }
|
|
Container.Objects.Add(StrView.GetSaveToComponent);
|
|
{ decrease the remained height }
|
|
StrView.FSaveHeight := StrView.FSaveHeight - StrView.Height + Corr;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
{ non-stretcheable view can't be splitted, draw it in the next page }
|
|
if View.Height > PageHeight - FooterHeight then
|
|
begin
|
|
{ add to band and remove from list }
|
|
Container.Objects.Add(View.GetSaveToComponent);
|
|
SplitObject.Remove(View);
|
|
{ prepare last part of text }
|
|
continue;
|
|
end
|
|
else if View.Top < AHeight then
|
|
begin
|
|
{ shift the underlying objects down }
|
|
ShiftObjects(View, AHeight - View.Top);
|
|
View.Top := AHeight;
|
|
end;
|
|
end;
|
|
Inc(i);
|
|
end;
|
|
CalcBandHeight;
|
|
ShiftedList.Free;
|
|
end;
|
|
|
|
procedure TfrxEngine.InitPage;
|
|
var
|
|
CurColumnRestored: Integer;
|
|
bShowTitle: Boolean;
|
|
begin
|
|
{ fill in the header/footer lists }
|
|
FHeaderList.Clear;
|
|
if FPage.TitleBeforeHeader then
|
|
begin
|
|
FHeaderList.AddItem(FindBand(TfrxReportTitle), 0, False);
|
|
FHeaderList.AddItem(FindBand(TfrxPageHeader), 0, False);
|
|
end
|
|
else
|
|
begin
|
|
FHeaderList.AddItem(FindBand(TfrxPageHeader), 0, False);
|
|
FHeaderList.AddItem(FindBand(TfrxReportTitle), 0, False);
|
|
end;
|
|
|
|
{ calculating the page/footer sizes }
|
|
PageHeight := FPage.PaperHeight * fr01cm - FPage.TopMargin * fr01cm -
|
|
FPage.BottomMargin * fr01cm;
|
|
PageWidth := FPage.PaperWidth * fr01cm - FPage.LeftMargin * fr01cm -
|
|
FPage.RightMargin * fr01cm;
|
|
|
|
{ reset the current position }
|
|
CurX := 0;
|
|
CurY := 0;
|
|
CurColumn := 1;
|
|
FPageCurX := 0;
|
|
FVMasterBand := nil;
|
|
|
|
FIsFirstPage := True;
|
|
FIsLastPage := False;
|
|
OutlineRoot;
|
|
if FPage.ResetPageNumbers then
|
|
PreviewPages.ResetLogicalPageNumber;
|
|
|
|
if (PreviewPages.Count = 0) or not FPage.PrintOnPreviousPage then
|
|
begin
|
|
AddPage;
|
|
FFirstColumnY := CurY;
|
|
end
|
|
else
|
|
begin
|
|
PreviewPages.CurPage := PreviewPages.Count - 1;
|
|
CurColumnRestored := 0;
|
|
TfrxPreviewPages(PreviewPages).GetLastColumnPos(CurColumnRestored, FCurX);
|
|
// don't try to fit full band in smaller column
|
|
bShowTitle := (FPage.Columns <= 1) or ((FindBand(TfrxReportTitle) <> nil) and FPage.ShowTitleOnPreviousPage);
|
|
if bShowTitle then
|
|
begin
|
|
FCurX := 0;
|
|
CurColumnRestored := 0;
|
|
end;
|
|
CurY := PreviewPages.GetLastY(CurX);
|
|
if CurColumnRestored = 0 then
|
|
FFirstColumnY := CurY
|
|
else
|
|
CurColumn := CurColumnRestored;
|
|
RemoveFromHeaderList(FindBand(TfrxReportTitle));
|
|
if bShowTitle{FPage.Columns <= 1} then
|
|
begin
|
|
CurColumn := FPage.Columns;
|
|
ShowBand(TfrxReportTitle);
|
|
CurColumn := 1;
|
|
FFirstColumnY := CurY
|
|
end;
|
|
end;
|
|
|
|
if FFirstReportPage then
|
|
PreviewPages.FirstPage := PreviewPages.CurPage;
|
|
|
|
ShowBand(TfrxColumnHeader);
|
|
FHeaderList.AddItem(FindBand(TfrxColumnHeader), 0, False);
|
|
RemoveFromHeaderList(FindBand(TfrxReportTitle));
|
|
AddPageOutline;
|
|
end;
|
|
|
|
function TfrxEngine.IsMaxHeightMode(View: TfrxView): Boolean;
|
|
var
|
|
SrtView: TfrxStretcheable absolute View;
|
|
begin
|
|
Result := (View is TfrxStretcheable) and (SrtView.StretchMode in [smPartMaxHeight, smMaxHeight]);
|
|
end;
|
|
|
|
function TfrxEngine.HeaderHeight(AddReprintOnNewPage: Boolean): Extended;
|
|
var
|
|
Band: TfrxBand;
|
|
i: Integer;
|
|
begin
|
|
Result := 0;
|
|
|
|
Band := FindBand(TfrxColumnHeader);
|
|
while Band <> nil do
|
|
begin
|
|
Result := Result + Band.Height;
|
|
Band := Band.Child;
|
|
end;
|
|
Band := FindBand(TfrxPageHeader);
|
|
while Band <> nil do
|
|
begin
|
|
Result := Result + Band.Height;
|
|
Band := Band.Child;
|
|
end;
|
|
|
|
{ used in cross tab }
|
|
if AddReprintOnNewPage then
|
|
for i := 0 to FHeaderList.Count - 1 do
|
|
if Assigned(FHeaderList[i].Band) and
|
|
not((FHeaderList[i].Band is TfrxPageHeader) or (FHeaderList[i].Band is TfrxColumnHeader)) then
|
|
Result := Result + FHeaderList[i].Band.Height;
|
|
end;
|
|
|
|
function TfrxEngine.FooterHeight: Extended;
|
|
var
|
|
Band: TfrxBand;
|
|
begin
|
|
Result := 0;
|
|
|
|
Band := FindBand(TfrxColumnFooter);
|
|
if Band <> nil then
|
|
Result := Result + Band.Height;
|
|
Band := FindBand(TfrxPageFooter);
|
|
if Band <> nil then
|
|
Result := Result + Band.Height;
|
|
end;
|
|
|
|
function TfrxEngine.FindBand(Band: TfrxBandClass): TfrxBand;
|
|
begin
|
|
Result := FPage.FindBand(Band);
|
|
end;
|
|
|
|
function TfrxEngine.ShowBand(Band: TfrxBand): TfrxBand;
|
|
var
|
|
chBand: TfrxChild;
|
|
begin
|
|
Result := nil;
|
|
if Band <> nil then
|
|
begin
|
|
if Band.KeepChild then
|
|
StartKeep(Band);
|
|
DoShow(Band);
|
|
chBand := Band.Child;
|
|
if (chBand <> nil) then
|
|
begin
|
|
if (chBand.ToNRowsMode = rmTillPageEnds) or (chBand.ToNRows > 0) then
|
|
Result := Band
|
|
else if (Band.Visible or Band.PrintChildIfInvisible) and
|
|
{ dont show childs for columns}
|
|
not((Band is TfrxDataBand) and (TfrxDataBand(Band).CurColumn > 1)) then
|
|
ShowBand(chBand);
|
|
end;
|
|
|
|
if Band.KeepChild then
|
|
EndKeep(Band);
|
|
if Band is TfrxDataBand then
|
|
FAggregates.AddValue(Band);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.ShowBand(Band: TfrxBandClass);
|
|
begin
|
|
ShowBand(FindBand(Band));
|
|
end;
|
|
|
|
procedure TfrxEngine.AddToHeaderList(Band: TfrxBand);
|
|
begin
|
|
{ only header bands with "Reprint on new page" flag can be added }
|
|
if ((Band is TfrxHeader) and TfrxHeader(Band).ReprintOnNewPage) or
|
|
((Band is TfrxGroupHeader) and TfrxGroupHeader(Band).ReprintOnNewPage) then
|
|
FHeaderList.AddItem(Band, FPageCurX, FKeeping and not (Band is TfrxHeader));
|
|
end;
|
|
|
|
procedure TfrxEngine.AddToVHeaderList(Band: TfrxBand);
|
|
begin
|
|
{ only header bands with "Reprint on new page" flag can be added }
|
|
if ((Band is TfrxHeader) and TfrxHeader(Band).ReprintOnNewPage) or
|
|
((Band is TfrxGroupHeader) and TfrxGroupHeader(Band).ReprintOnNewPage) then
|
|
FVHeaderList.Add(Band);
|
|
end;
|
|
|
|
procedure TfrxEngine.BreakAllKeep;
|
|
begin
|
|
inherited;
|
|
FKeepCurY := 0;
|
|
FKeeping := False;
|
|
FKeepBand := nil;
|
|
FAggregates.EndKeep;
|
|
FKeepHeader := False;
|
|
end;
|
|
|
|
procedure TfrxEngine.RemoveFromHeaderList(Band: TfrxBand);
|
|
begin
|
|
if Band <> nil then
|
|
FHeaderList.RemoveItem(Band);
|
|
end;
|
|
|
|
procedure TfrxEngine.RemoveFromVHeaderList(Band: TfrxBand);
|
|
begin
|
|
if Band <> nil then
|
|
FVHeaderList.Remove(Band);
|
|
end;
|
|
|
|
function TfrxEngine.FreeSpace: Extended;
|
|
begin
|
|
if FPage.EndlessHeight then
|
|
Result := 1e+6
|
|
else
|
|
if FPrevFooterHeight <> 0 then
|
|
Result := PageHeight - FPrevFooterHeight - CurY
|
|
else Result := PageHeight - FooterHeight - CurY;
|
|
end;
|
|
|
|
procedure TfrxEngine.Stretch(Container: TfrxReportComponent; SkipGetData: Boolean);
|
|
var
|
|
i, lCurPage: Integer;
|
|
h, OrgH, maxh: Extended;
|
|
c, maxc: TfrxView;
|
|
HaveSub, IsBand, NeedShift: Boolean;
|
|
SaveCurBand: TfrxBand;
|
|
|
|
procedure DoSubReports;
|
|
var
|
|
i: Integer;
|
|
SaveCurX, SaveCurY, SavePageCurX: Extended;
|
|
Sub: TfrxSubreport;
|
|
MainBand: Boolean;
|
|
AllObjects: TList;
|
|
c: TfrxComponent;
|
|
SaveKeepFooter: Boolean;
|
|
begin
|
|
{ create a band which will accepts all subsequent output }
|
|
MainBand := False;
|
|
if FOutputTo = nil then
|
|
begin
|
|
Container.FOriginalObjectsCount := Container.Objects.Count;
|
|
FOutputTo := TfrxNullBand.Create(nil);
|
|
MainBand := True;
|
|
end;
|
|
|
|
{ save the current position }
|
|
SaveCurX := CurX;
|
|
SaveCurY := CurY;
|
|
SavePageCurX := FPageCurX;
|
|
lCurPage := PreviewPages.CurPage;
|
|
|
|
{ looking for subreport objects }
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
if TObject(Container.Objects[i]) is TfrxSubreport then
|
|
begin
|
|
Sub := TfrxSubreport(Container.Objects[i]);
|
|
if not Sub.Visible or not Sub.PrintOnParent or not MainBand then continue;
|
|
|
|
{ set up all properties... }
|
|
FPageCurX := SavePageCurX + Sub.Left;
|
|
CurX := Sub.Left;
|
|
CurY := Sub.Top;
|
|
{ ...and run the subreport }
|
|
//
|
|
SaveKeepFooter := FKeepFooter;
|
|
RunPage(Sub.Page);
|
|
PreviewPages.CurPage := lCurPage;
|
|
FKeepFooter := SaveKeepFooter;
|
|
end;
|
|
|
|
{ restore saved position }
|
|
CurX := SaveCurX;
|
|
CurY := SaveCurY;
|
|
FPageCurX := SavePageCurX;
|
|
|
|
if MainBand then
|
|
begin
|
|
{ copy all output to the band }
|
|
AllObjects := FOutputTo.AllObjects;
|
|
|
|
for i := 0 to AllObjects.Count - 1 do
|
|
begin
|
|
c := AllObjects[i];
|
|
if (c is TfrxView) and not (c is TfrxSubreport) then
|
|
begin
|
|
c.Left := c.AbsLeft;
|
|
c.Top := c.AbsTop;
|
|
c.ParentFont := False;
|
|
c.Parent := Container;
|
|
if not IsBand then
|
|
c.Name := '';
|
|
end;
|
|
if c is TfrxStretcheable then
|
|
TfrxStretcheable(c).StretchMode := smDontStretch;
|
|
end;
|
|
|
|
{ Clear the FOutputTo property. Extra objects will be freed
|
|
in the Unstretch method. }
|
|
FOutputTo.Free;
|
|
FOutputTo := nil;
|
|
end;
|
|
end;
|
|
begin
|
|
IsBand := (Container is TfrxBand);
|
|
if IsBand then
|
|
FCurBand := TfrxBand(Container);
|
|
HaveSub := False;
|
|
PrepareShiftTree(Container);
|
|
{ it is not necessary for vertical bands }
|
|
if Container <> FVMasterBand then
|
|
begin
|
|
{ firing band OnBeforePrint event }
|
|
Report.CurObject := Container.Name;
|
|
if IsBand then
|
|
begin
|
|
Container.BeforePrint;
|
|
Report.DoBeforePrint(Container);
|
|
end;
|
|
end;
|
|
NeedShift := False;
|
|
{ firing OnBeforePrint events, stretching objects }
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
c := Container.Objects[i];
|
|
if (c is TfrxSubreport) and TfrxSubreport(c).PrintOnParent then
|
|
HaveSub := True;
|
|
|
|
{ skip getdata for vertical bands' objects }
|
|
if ((Container <> FVMasterBand) or (i < Container.FOriginalObjectsCount)) and not SkipGetData then
|
|
begin
|
|
Report.CurObject := c.Name;
|
|
c.BeforePrint;
|
|
if Container.Visible then
|
|
begin
|
|
{ calling NewPage inside OnBeforePrint event changes FCurBand for the current band object }
|
|
{ it affects aggregates calculation. See #4640 }
|
|
SaveCurBand := FCurBand;
|
|
try
|
|
Report.DoBeforePrint(c);
|
|
finally
|
|
FCurBand := SaveCurBand;
|
|
end;
|
|
if (c.Visible) and (c.Processing.ProcessAt = paDefault) then
|
|
begin
|
|
c.GetData;
|
|
Report.DoNotifyEvent(c, c.OnAfterData);
|
|
end;
|
|
end;
|
|
end;
|
|
FShiftEngine.InitShiftAmount(c, 0);
|
|
if not Container.Visible or not c.Visible then continue;
|
|
|
|
if (c is TfrxStretcheable) and
|
|
((TfrxStretcheable(c).StretchMode <> smDontStretch) or TfrxStretcheable(c)
|
|
.CanShrink) then
|
|
begin
|
|
{ some objects can increase height in CalcHeight }
|
|
OrgH := c.Height;
|
|
h := TfrxStretcheable(c).CalcHeight;
|
|
if ((TfrxStretcheable(c).StretchMode <> smDontStretch) and (h > OrgH)) or
|
|
(TfrxStretcheable(c).CanShrink) and (h < OrgH) then
|
|
begin
|
|
{ set shift amount };
|
|
FShiftEngine.InitShiftAmount(c, h - OrgH);
|
|
if Abs(h - c.Height) > 1e-4 then
|
|
c.Height := h; { stretch the object }
|
|
NeedShift := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if not Container.Visible then Exit;
|
|
{ shift objects }
|
|
if NeedShift then
|
|
FShiftEngine.ShiftObjects(Container);
|
|
{ check subreports that have PrintOnParent option }
|
|
if HaveSub then
|
|
DoSubReports;
|
|
|
|
{ calculate the max height of the band }
|
|
maxh := 0;
|
|
maxc := nil;
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
c := Container.Objects[i];
|
|
if c.Top + c.Height > maxh then
|
|
begin
|
|
maxh := c.Top + c.Height;
|
|
maxc := c;
|
|
end;
|
|
end;
|
|
|
|
if (maxc <> nil) and (maxc is TfrxDMPMemoView) and
|
|
(ftBottom in TfrxDMPMemoView(maxc).Frame.Typ) then
|
|
maxh := maxh + fr1CharY;
|
|
if not IsBand or TfrxBand(Container).Stretched then
|
|
Container.Height := maxh;
|
|
|
|
{ fire Band.OnAfterCalcHeight event }
|
|
Report.CurObject := Container.Name;
|
|
if IsBand then
|
|
Report.DoNotifyEvent(Container, TfrxBand(Container).OnAfterCalcHeight);
|
|
|
|
{ set the height of objects that should stretch to max height }
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
c := Container.Objects[i];
|
|
if IsMaxHeightMode(c) then
|
|
begin
|
|
c.Height := maxh - c.Top;
|
|
if (c is TfrxDMPMemoView) and (ftBottom in TfrxDMPMemoView(c).Frame.Typ) then
|
|
c.Height := c.Height - fr1CharY;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.UnStretch(Container: TfrxReportComponent);
|
|
var
|
|
i: Integer;
|
|
c: TfrxView;
|
|
begin
|
|
{ fire OnAfterPrint event }
|
|
if Container.Visible then
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
c := Container.Objects[i];
|
|
Report.CurObject := c.Name;
|
|
Report.DoAfterPrint(c);
|
|
end;
|
|
|
|
{ restore state }
|
|
THackComponent(Container).LockAnchorsUpdate;
|
|
try
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
if (Container <> FVMasterBand) or (i < Container.FOriginalObjectsCount) then
|
|
begin
|
|
c := Container.Objects[i];
|
|
c.AfterPrint;
|
|
end
|
|
else break;
|
|
|
|
|
|
Report.CurObject := Container.Name;
|
|
Report.DoAfterPrint(Container);
|
|
if (Container is TfrxBand) then
|
|
Container.AfterPrint;
|
|
{ free band fill }
|
|
if (Container is TfrxBand) and FinalPass then
|
|
TfrxBand(Container).DisposeFillMemo;
|
|
finally
|
|
THackComponent(Container).UnlockAnchorsUpdate;
|
|
end;
|
|
{ remove extra band objects if any }
|
|
if Container.FOriginalObjectsCount <> -1 then
|
|
begin
|
|
while Container.Objects.Count > Container.FOriginalObjectsCount do
|
|
TObject(Container.Objects[Container.Objects.Count - 1]).Free;
|
|
Container.FOriginalObjectsCount := -1;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.AddPage;
|
|
var
|
|
i: Integer;
|
|
SaveCurX: Extended;
|
|
SaveCurLine, SaveCurLineThrough: Integer;
|
|
Band: TfrxBand;
|
|
IsHeaderBand: Boolean;
|
|
begin
|
|
FPrevFooterHeight := 0;
|
|
PreviewPages.AddPage(FPage);
|
|
CurY := 0;
|
|
Band := FindBand(TfrxOverlay);
|
|
if (Band <> nil) and not TfrxOverlay(Band).PrintOnTop then
|
|
ShowBand(Band);
|
|
|
|
CurY := 0;
|
|
SaveCurX := CurX;
|
|
FFirstColumnY := 0;
|
|
|
|
for i := 0 to FHeaderList.Count - 1 do
|
|
begin
|
|
{ use own CurX - we may be inside subreports now }
|
|
CurX := FHeaderList[i].Left;
|
|
Band := FHeaderList[i].Band;
|
|
if Band = FStartNewPageBand then
|
|
continue;
|
|
|
|
if FIsFirstPage and (Band is TfrxPageHeader) and
|
|
not TfrxPageHeader(Band).PrintOnFirstPage then
|
|
begin
|
|
if Band.PrintChildIfInvisible then
|
|
Band := Band.Child
|
|
else
|
|
continue;
|
|
end;
|
|
|
|
IsHeaderBand := (Band is TfrxHeader);
|
|
if Band <> nil then
|
|
if not (FKeepHeader and (FKeepBand.FHeader = Band)) and not(FHeaderList[i].IsInKeepList and FKeeping) or FKeepFooter
|
|
{or (FKeeping and (FKeepBand.FHeader = Band))} then
|
|
begin
|
|
if (IsHeaderBand and FDontShowHeaders) or (IsHeaderBand and (FLastBandOnPage = Band)) or
|
|
((Band is TfrxGroupHeader) and FDontShowHeaders) then
|
|
continue;
|
|
Band.Overflow := True;
|
|
SaveCurLine := CurLine;
|
|
SaveCurLineThrough := CurLineThrough;
|
|
CurLine := Band.FLineN;
|
|
CurLineThrough := Band.FLineThrough;
|
|
FCallFromAddPage := True;
|
|
FCallFromPHeader := (Band is TfrxPageHeader);
|
|
|
|
{ fix the stack overflow error if call NewPage from ReportTitle }
|
|
if Band is TfrxReportTitle then
|
|
FHeaderList[i].Band := nil;
|
|
|
|
ShowBand(Band);
|
|
{ correct column y position }
|
|
if Band is TfrxPageHeader then
|
|
FFirstColumnY := Band.FStretchedHeight;
|
|
|
|
if (FIsFirstBand) and (Band is TfrxReportTitle) then
|
|
FTitlePrinted := True;
|
|
FCallFromPHeader := False;
|
|
FCallFromAddPage := False;
|
|
Band.Overflow := False;
|
|
CurLine := SaveCurLine;
|
|
CurLineThrough := SaveCurLineThrough;
|
|
end;
|
|
end;
|
|
|
|
CurX := SaveCurX;
|
|
end;
|
|
|
|
procedure TfrxEngine.EndPage;
|
|
var
|
|
Band: TfrxBand;
|
|
Offset: Extended;
|
|
|
|
procedure ShowBand(Band: TfrxBand);
|
|
begin
|
|
if Band = nil then Exit;
|
|
|
|
Stretch(Band);
|
|
try
|
|
if Band.Visible then
|
|
begin
|
|
Band.Left := 0;
|
|
Band.Top := CurY;
|
|
|
|
if Band is TfrxPageFooter then
|
|
if ((FIsFirstPage and not TfrxPageFooter(Band).PrintOnFirstPage) or
|
|
(FIsLastPage and not TfrxPageFooter(Band).PrintOnLastPage and not FCallFromEndPage)) and
|
|
not (TfrxPageFooter(Band).PrintOnSinglePage and FIsFirstPage and FIsLastPage) then
|
|
Exit;
|
|
if FinalPass then
|
|
Band.CreateFillMemo;
|
|
if not PreviewPages.BandExists(Band) then
|
|
PreviewPages.AddObject(Band);
|
|
CurY := CurY + Band.Height;
|
|
end;
|
|
finally
|
|
UnStretch(Band);
|
|
end;
|
|
DoProcessState(Band, paDefault);
|
|
end;
|
|
|
|
begin
|
|
if not FCallFromEndPage then
|
|
EndColumn;
|
|
|
|
if not FIsLastPage then
|
|
begin
|
|
CurX := FPageCurX;
|
|
CurColumn := 1;
|
|
end;
|
|
if FPage.Columns > 1 then
|
|
TfrxPreviewPages(PreviewPages).UpdatePageLastColumn(CurColumn, CurX);
|
|
|
|
if FIsLastPage and not FCallFromEndPage then
|
|
begin
|
|
{ avoid stack overflow if reportsummary does not fit on the page }
|
|
FCallFromEndPage := True;
|
|
try
|
|
Offset := CurY;
|
|
Band := FindBand(TfrxReportSummary);
|
|
Self.ShowBand(Band);
|
|
if Band = nil then
|
|
DoProcessState(nil, paReportFinished);
|
|
if (Band <> nil) and (FPage.EndlessHeight) then
|
|
begin
|
|
Offset := CurY - Offset;
|
|
PageHeight := PageHeight + Offset;
|
|
TfrxPreviewPages(PreviewPages).UpdatePageDimensions(FPage, PageWidth + FPage.LeftMargin * fr01cm + FPage.RightMargin * fr01cm, PageHeight + FPage.TopMargin * fr01cm + FPage.BottomMargin * fr01cm);
|
|
end;
|
|
finally
|
|
FCallFromEndPage := False;
|
|
end;
|
|
end;
|
|
|
|
Band := FindBand(TfrxPageFooter);
|
|
if Band <> nil then
|
|
begin
|
|
CurY := PageHeight - Band.Height;
|
|
if FIsLastPage and TfrxPageFooter(Band).PrintOnLastPage and not FCallFromEndPage then
|
|
FPrevFooterHeight := Band.Height
|
|
else FPrevFooterHeight := 0;
|
|
end;
|
|
ShowBand(Band);
|
|
Band := FindBand(TfrxOverlay);
|
|
if (Band <> nil) and TfrxOverlay(Band).PrintOnTop then
|
|
begin
|
|
CurY := 0;
|
|
ShowBand(Band);
|
|
end;
|
|
|
|
FIsFirstPage := False;
|
|
end;
|
|
|
|
procedure TfrxEngine.AddColumn;
|
|
var
|
|
i: Integer;
|
|
AddX: Extended;
|
|
|
|
procedure DoShow(Band: TfrxBand);
|
|
begin
|
|
Band.Overflow := True;
|
|
Stretch(Band);
|
|
|
|
try
|
|
if Band.Visible then
|
|
begin
|
|
if FinalPass then
|
|
Band.CreateFillMemo;
|
|
Band.Left := CurX;
|
|
Band.Top := CurY;
|
|
PreviewPages.AddObject(Band);
|
|
CurY := CurY + Band.Height;
|
|
end;
|
|
finally
|
|
UnStretch(Band);
|
|
Band.Overflow := False;
|
|
end;
|
|
end;
|
|
|
|
procedure ShowBand(Band: TfrxBand);
|
|
begin
|
|
while Band <> nil do
|
|
begin
|
|
DoShow(Band);
|
|
if Band.Visible or Band.PrintChildIfInvisible then
|
|
Band := Band.Child else
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
PreviewPages.PostProcessor.ResetDuplicates(FCurBand.Name);
|
|
CurColumn := CurColumn + 1;
|
|
AddX := frxStrToFloat(FPage.ColumnPositions[CurColumn - 1]) * fr01cm;
|
|
CurY := FFirstColumnY;
|
|
|
|
for i := 0 to FHeaderList.Count - 1 do
|
|
begin
|
|
CurX := FHeaderList[i].Left + AddX;
|
|
if not (FHeaderList[i].Band is TfrxPageHeader) {and ((FHeaderList[i].Band is TfrxHeader)} and
|
|
(FLastBandOnPage <> FHeaderList[i].Band) and not FHeaderList[i].IsInKeepList and not FKeepHeader then
|
|
ShowBand(FHeaderList[i].Band);
|
|
end;
|
|
|
|
CurX := FPageCurX + AddX;
|
|
end;
|
|
|
|
procedure TfrxEngine.EndColumn;
|
|
var
|
|
Band: TfrxBand;
|
|
begin
|
|
Band := FindBand(TfrxColumnFooter);
|
|
if Band = nil then Exit;
|
|
|
|
Stretch(Band);
|
|
try
|
|
if Band.Visible then
|
|
begin
|
|
Band.Left := CurX - FPageCurX;
|
|
Band.Top := CurY;
|
|
PreviewPages.AddObject(Band);
|
|
{ move the current position }
|
|
CurY := CurY + Band.Height;
|
|
end;
|
|
finally
|
|
UnStretch(Band);
|
|
end;
|
|
|
|
DoProcessState(Band, paColumnFinished);
|
|
end;
|
|
|
|
procedure TfrxEngine.NewPage;
|
|
var
|
|
RepeatedHeader: TfrxBand;
|
|
LastY, kBandHeight: Extended;
|
|
begin
|
|
kBandHeight := 0;
|
|
{ TID#441608 workaround }
|
|
if FKeepBand <> nil then
|
|
begin
|
|
kBandHeight := FKeepBand.Height;
|
|
if FKeepBand is TfrxGroupHeader then
|
|
kBandHeight := FCurBand.Height; // Groups
|
|
end;
|
|
|
|
{ keep objects doesn't fit on whole page, so break keeping and leave the objets }
|
|
if (FKeepBand <> nil) and (CurY - FKeepCurY + kBandHeight > PageHeight - FooterHeight - HeaderHeight) then
|
|
begin
|
|
FKeeping := False;
|
|
FAggregates.EndKeep;
|
|
if FKeepBand is TfrxGroupHeader then
|
|
begin
|
|
RemoveFromHeaderList(FKeepBand);
|
|
AddToHeaderList(FKeepBand);
|
|
end;
|
|
end;
|
|
RepeatedHeader := nil;
|
|
|
|
if FKeeping then
|
|
begin
|
|
{ The band is not printed yet when FKeepHeader is active #549537 }
|
|
if FKeepFooter and not FKeepHeader then
|
|
FAggregates.DeleteValue(FKeepBand);
|
|
PreviewPages.CutObjects(FKeepPosition);
|
|
LastY := PreviewPages.GetLastY;
|
|
if (ABS(LastY - CurY) >= 1e-4) and (FCurBand is TfrxDataband) and (TfrxDataband(FCurBand).KeepHeader) then
|
|
RepeatedHeader := TfrxDataband(FCurBand).FHeader;
|
|
RemoveFromHeaderList(RepeatedHeader);
|
|
end;
|
|
FLastBandOnPage := FCurBand;
|
|
EndPage;
|
|
{ fix for report with several pages and EndlessHeight }
|
|
if FPage.EndlessHeight then
|
|
TfrxPreviewPages(PreviewPages).UpdatePageDimension(PreviewPages.Count - 1,
|
|
PageWidth / fr01cm + FPage.LeftMargin + FPage.RightMargin,
|
|
(CurY {+ FooterHeight}) / fr01cm + FPage.TopMargin + FPage.BottomMargin,
|
|
FPage.Orientation);
|
|
AddPage;
|
|
FLastBandOnPage := nil;
|
|
if FKeeping then
|
|
begin
|
|
FAggregates.EndKeep;
|
|
FSubSaveCurY := CurY;
|
|
PreviewPages.PasteObjects(0, CurY);
|
|
FSubSavePageN := PreviewPages.CurPage;
|
|
PreviewPages.Outline.ShiftItems(FKeepOutline, Round(CurY));
|
|
PreviewPages.ShiftAnchors(FKeepAnchor, Round(CurY));
|
|
// if (FKeepBand is TfrxDataBand) and (TfrxDataBand(FKeepBand).CurColumn = 1) then
|
|
// FSaveCurY := CurY;
|
|
CurY := PreviewPages.GetLastY;
|
|
{ The band is not printed yet when FKeepHeader is active #549537 }
|
|
if FKeepFooter and not FKeepHeader then
|
|
FAggregates.AddValue(FKeepBand);
|
|
AddToHeaderList(RepeatedHeader);
|
|
end;
|
|
FKeeping := False;
|
|
FKeepHeader := False;
|
|
FKeepCurY := 0;
|
|
AddPageOutline;
|
|
end;
|
|
|
|
procedure TfrxEngine.NewColumn;
|
|
begin
|
|
if CurColumn >= FPage.Columns then
|
|
NewPage
|
|
else
|
|
begin
|
|
{ keeping for columns }
|
|
if FKeeping then
|
|
begin
|
|
if FKeepFooter then
|
|
FAggregates.DeleteValue(FKeepBand);
|
|
PreviewPages.CutObjects(FKeepPosition);
|
|
CurY := PreviewPages.GetLastY;
|
|
end;
|
|
FLastBandOnPage := FCurBand;
|
|
EndColumn;
|
|
AddColumn;
|
|
FLastBandOnPage := nil;
|
|
if FKeeping then
|
|
begin
|
|
FAggregates.EndKeep;
|
|
PreviewPages.PasteObjects(CurX, CurY);
|
|
PreviewPages.Outline.ShiftItems(FKeepOutline, Round(CurY));
|
|
PreviewPages.ShiftAnchors(FKeepAnchor, Round(CurY));
|
|
{ new version of GetLastY has one parameter by default = 0,}
|
|
{ it determinate column position for correct Y out result. }
|
|
{ if parameter = 0 ,then function return last position on the page, }
|
|
{ in other cases it return last Y position on X coordinate .}
|
|
CurY := PreviewPages.GetLastY(CurX);
|
|
if FKeepFooter then
|
|
FAggregates.AddValue(FKeepBand);
|
|
end;
|
|
FKeeping := False;
|
|
FKeepHeader := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.DrawSplit(Band: TfrxBand);
|
|
var
|
|
i: Integer;
|
|
List, SaveObjects: TList;
|
|
CurHeight: Extended;
|
|
|
|
procedure DrawPart;
|
|
begin
|
|
{ draw current objects }
|
|
Band.Left := CurX;
|
|
Band.Top := CurY;
|
|
PreviewPages.AddObject(Band);
|
|
{ add new column/page }
|
|
CurY := CurY + Band.Height;
|
|
FCurBand := Band;
|
|
if List.Count > 0 then NewColumn;
|
|
end;
|
|
|
|
begin
|
|
List := TList.Create;
|
|
SaveObjects := TList.Create;
|
|
|
|
if FinalPass then
|
|
Band.DisposeFillMemo;
|
|
{ initializing lists }
|
|
InitializeSplit(Band, SaveObjects, List);
|
|
|
|
while List.Count > 0 do
|
|
begin
|
|
if not Report.EngineOptions.EnableThreadSafe then
|
|
Application.ProcessMessages;
|
|
if Report.Terminated then Break;
|
|
CurHeight := FreeSpace;
|
|
SplitObjects(Band, SaveObjects, List, CurHeight);
|
|
if FinalPass then
|
|
Band.CreateFillMemo;
|
|
{ draw the visible part }
|
|
DrawPart;
|
|
FinalizeSplit(Band, SaveObjects, List, CurHeight);
|
|
end;
|
|
|
|
{ get objects back to the band }
|
|
Band.Objects.Clear;
|
|
for i := 0 to SaveObjects.Count - 1 do
|
|
Band.Objects.Add(SaveObjects[i]);
|
|
if FinalPass then
|
|
Band.CreateFillMemo;
|
|
List.Free;
|
|
SaveObjects.Free;
|
|
end;
|
|
|
|
procedure TfrxEngine.CheckSuppress(Band: TfrxBand);
|
|
var
|
|
i: Integer;
|
|
c: TfrxComponent;
|
|
hasSuppress: Boolean;
|
|
begin
|
|
hasSuppress := False;
|
|
for i := 0 to Band.Objects.Count - 1 do
|
|
begin
|
|
c := Band.Objects[i];
|
|
if (c is TfrxCustomMemoView) and TfrxCustomMemoView(c).SuppressRepeated then
|
|
begin
|
|
hasSuppress := True;
|
|
TfrxCustomMemoView(c).ResetSuppress;
|
|
end;
|
|
end;
|
|
|
|
if hasSuppress and not Band.FHasVBands then
|
|
begin
|
|
UnStretch(Band);
|
|
CurLine := Band.FLineN;
|
|
CurLineThrough := Band.FLineThrough;
|
|
SecondScriptCall := True;
|
|
try
|
|
Stretch(Band);
|
|
finally
|
|
SecondScriptCall := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.DoProcessState(aBand: TfrxBand; aState: TfrxProcessAtMode);
|
|
var
|
|
SaveBand: TfrxBand;
|
|
begin
|
|
if FLockResetAggregates then
|
|
Exit;
|
|
if aState = paDefault then
|
|
begin
|
|
if aBand is TfrxGroupFooter then
|
|
aState := paGroupFinished
|
|
else if aBand is TfrxFooter then
|
|
aState := paDataFinished
|
|
else if aBand is TfrxPageFooter then
|
|
aState := paPageFinished
|
|
else if aBand is TfrxReportSummary then
|
|
aState := paReportPageFinished;
|
|
end;
|
|
if aState <> paDefault then
|
|
begin
|
|
// need for correct expression calculation when band was moved to next page
|
|
SaveBand := FCurBand;
|
|
FCurBand := aBand;
|
|
try
|
|
PreviewPages.PostProcessor.ProcessExpressions(Report, aBand, aState);
|
|
finally
|
|
FCurBand := SaveBand;
|
|
end;
|
|
end;
|
|
if aBand <> nil then
|
|
FAggregates.Reset(aBand);
|
|
end;
|
|
|
|
procedure TfrxEngine.DoShow(Band: TfrxBand);
|
|
var
|
|
IsMultiColumnBand, IsSplit: Boolean;
|
|
TempBand: TfrxBand;
|
|
SaveCurX: Extended;
|
|
SavePageList: TList;
|
|
SaveVMasterBand: TfrxBand;
|
|
i: Integer;
|
|
|
|
procedure RenderVBand;
|
|
var
|
|
i, j, SavePageN: Integer;
|
|
SaveCurY: Extended;
|
|
c: TfrxComponent;
|
|
SaveObjects: TList;
|
|
begin
|
|
SaveObjects := TList.Create;
|
|
SavePageN := PreviewPages.CurPage;
|
|
SaveCurY := CurY;
|
|
{ the next NewPage call shouldn't form a new page }
|
|
PreviewPages.AddPageAction := apWriteOver;
|
|
|
|
{ save hband objects }
|
|
for i := 0 to FVMasterBand.Objects.Count - 1 do
|
|
SaveObjects.Add(FVMasterBand.Objects[i]);
|
|
|
|
for i := 0 to FVPageList.Count - 2 do
|
|
begin
|
|
FVMasterBand.Objects.Clear;
|
|
for j := Integer(FVPageList[i]) to Integer(FVPageList[i + 1]) - 1 do
|
|
begin
|
|
c := SaveObjects[j];
|
|
FVMasterBand.Objects.Add(c);
|
|
end;
|
|
PreviewPages.AddObject(FVMasterBand);
|
|
|
|
if i <> FVPageList.Count - 2 then
|
|
begin
|
|
FDontShowHeaders := True;
|
|
NewPage;
|
|
FDontShowHeaders := False;
|
|
end
|
|
else
|
|
EndPage;
|
|
end;
|
|
|
|
{ restore hband objects }
|
|
FVMasterBand.Objects.Clear;
|
|
for i := 0 to SaveObjects.Count - 1 do
|
|
FVMasterBand.Objects.Add(SaveObjects[i]);
|
|
SaveObjects.Free;
|
|
|
|
PreviewPages.CurPage := SavePageN;
|
|
CurY := SaveCurY;
|
|
CurX := SaveCurX;
|
|
{ the next NewPage call should form a new page }
|
|
PreviewPages.AddPageAction := apAdd;
|
|
end;
|
|
|
|
procedure AddVBand;
|
|
var
|
|
i: Integer;
|
|
c, c1: TfrxReportComponent;
|
|
SavePaperWidth: Extended;
|
|
bStartNPage: Boolean;
|
|
begin
|
|
if Band is TfrxDataBand then
|
|
CurVColumn := CurVColumn + 1;
|
|
if (Band is TfrxFooter) or (Band is TfrxGroupFooter) then
|
|
FCurBand := Band
|
|
else
|
|
FCurBand := FVMasterBand;
|
|
|
|
{ fire beforeprint }
|
|
Report.CurObject := Band.Name;
|
|
Band.BeforePrint;
|
|
Report.DoBeforePrint(Band);
|
|
|
|
if Band.Visible then
|
|
begin
|
|
bStartNPage := (Band.StartNewPage and (Band.FLineThrough > 1));
|
|
if (CurX + Band.Width > PageWidth) or bStartNPage then
|
|
if FPage.EndlessWidth and not bStartNPage then
|
|
PageWidth := PageWidth + Band.Width
|
|
else
|
|
begin
|
|
{need for correct Page calculation when move VBand to the next page}
|
|
with PreviewPages do
|
|
begin
|
|
if FPage.EndlessWidth then
|
|
begin
|
|
SavePaperWidth := FPage.PaperWidth * fr01cm;
|
|
TfrxPreviewPages(PreviewPages).UpdatePageDimension
|
|
(TfrxPreviewPages(PreviewPages).Count - 1,
|
|
PageWidth / fr01cm + FPage.LeftMargin + FPage.RightMargin,
|
|
PageHeight / fr01cm + FPage.TopMargin + FPage.BottomMargin,
|
|
FPage.Orientation);
|
|
PageWidth := SavePaperWidth;
|
|
end;
|
|
if Count - 1 <= CurPage then
|
|
AddPage(FPage)
|
|
else
|
|
CurPage := CurPage + 1;
|
|
end;
|
|
CurX := 0;
|
|
FVPageList.Add(Pointer(FVMasterBand.Objects.Count));
|
|
{ reprint headers }
|
|
for i := 0 to FVHeaderList.Count - 1 do
|
|
ShowBand(TfrxBand(FVHeaderList[i]));
|
|
end;
|
|
|
|
{ find objects that intersect with vertical Band }
|
|
for i := 0 to Band.Objects.Count - 1 do
|
|
begin
|
|
c := Band.Objects[i];
|
|
if THackComponent(c).FOriginalBand = FVMasterBand then
|
|
begin
|
|
{ fire beforeprint and getdata }
|
|
Report.CurObject := c.Name;
|
|
c.BeforePrint;
|
|
Report.DoBeforePrint(c);
|
|
c.GetData;
|
|
Report.DoNotifyEvent(c, c.OnAfterData);
|
|
|
|
{ copy the object }
|
|
c1 := TfrxReportComponent(c.NewInstance);
|
|
c1.Create(FVMasterBand);
|
|
with THackComponent(c1) do
|
|
begin
|
|
FAliasName := THackComponent(c).FAliasName;
|
|
FOriginalComponent := THackComponent(c).FOriginalComponent;
|
|
end;
|
|
if csObjectsContainer in c.frComponentStyle then
|
|
c1.AssignAllWithOriginals(c, True)
|
|
else
|
|
c1.Assign(c);
|
|
c1.Left := c1.Left + CurX;
|
|
|
|
{ restore the object's state }
|
|
c.AfterPrint;
|
|
end;
|
|
end;
|
|
|
|
CurX := CurX + Band.Width;
|
|
end;
|
|
|
|
{ fire afterprint }
|
|
Report.CurObject := Band.Name;
|
|
Report.DoAfterPrint(Band);
|
|
Band.AfterPrint;
|
|
|
|
if Band is TfrxDataBand then
|
|
FAggregates.AddValue(FVMasterBand, CurVColumn);
|
|
|
|
{ reset aggregates }
|
|
if (Band is TfrxFooter) or (Band is TfrxGroupFooter) then
|
|
DoProcessState(Band, paDefault);
|
|
end;
|
|
|
|
begin
|
|
SavePageList := nil;
|
|
SaveVMasterBand := nil;
|
|
|
|
{ make cross-bands }
|
|
if Band.FHasVBands and not (Band is TfrxPageHeader) then
|
|
begin
|
|
SaveCurX := CurX;
|
|
{ fire onbeforeprint }
|
|
Report.CurObject := Band.Name;
|
|
Band.BeforePrint;
|
|
Report.DoBeforePrint(Band);
|
|
{ show vertical bands }
|
|
ShowVBands(Band);
|
|
CurX := 0;
|
|
{ restore Line variables}
|
|
CurLine := Band.FLineN;
|
|
CurLineThrough := Band.FLineThrough;
|
|
{ the next NewPage call should form a new page }
|
|
PreviewPages.AddPageAction := apAdd;
|
|
|
|
{ save global variables - FVPageList and FVMasterBand }
|
|
{ they may be changed in the NewPage call, if cross has a h-header }
|
|
{ with ReprintOnNewPage option }
|
|
SavePageList := TList.Create;
|
|
for i := 0 to FVPageList.Count - 1 do
|
|
SavePageList.Add(FVPageList[i]);
|
|
SaveVMasterBand := FVMasterBand;
|
|
{ new objects can be added for VBand }
|
|
{ we need to clear shift tree }
|
|
{ temporary disabled may cause performance drop }
|
|
//FShiftEngine.ClearContainer(Band);
|
|
end;
|
|
|
|
{ show one vertical band }
|
|
if Band.Vertical then
|
|
begin
|
|
AddVBand;
|
|
Exit;
|
|
end;
|
|
|
|
IsMultiColumnBand := (Band is TfrxDataBand) and (TfrxDataBand(Band).Columns > 1);
|
|
IsSplit := False;
|
|
|
|
{ check for StartNewPage flag }
|
|
if not FCallFromAddPage then
|
|
if Band.Visible then { don't process invisible bands }
|
|
if Band.StartNewPage then
|
|
if FOutputTo = nil then
|
|
if not (((Band is TfrxDataBand) or (Band is TfrxGroupHeader)) and (Band.FLineN = 1)) then
|
|
begin
|
|
FStartNewPageBand := Band;
|
|
if (Band is TfrxGroupHeader) and (TfrxGroupHeader(Band).ResetPageNumbers) then
|
|
PreviewPages.ResetLogicalPageNumber;
|
|
NewPage;
|
|
FStartNewPageBand := nil;
|
|
end;
|
|
|
|
Stretch(Band);
|
|
Band.FStretchedHeight := Band.Height;
|
|
try
|
|
if Band.Visible then
|
|
begin
|
|
{ if band has columns, print all columns in one page. Page feed will be
|
|
performed after the last column }
|
|
if not IsMultiColumnBand and not (Band is TfrxOverlay) and not (Band is TfrxNullBand) and
|
|
(Band.Height - FreeSpace > 1E-4) then
|
|
if (Band is TfrxChild) and (TfrxChild(Band).ToNRowsMode = rmTillPageEnds) then
|
|
begin
|
|
FBreakShowBandTree := True;
|
|
Exit;
|
|
end
|
|
else if FOutputTo = nil then
|
|
{ TID#441608 workaround }
|
|
if (Band.AllowSplit and (not FKeeping or (FCurY - FKeepCurY + Band.Height > PageHeight - HeaderHeight - FooterHeight)) ) or
|
|
((Band.Height > PageHeight - FooterHeight) and not band.FHasVBands) then
|
|
begin
|
|
if (not Band.AllowSplit) and (Band.FLineThrough > 1) and (not Band.StartNewPage) then
|
|
begin
|
|
FCurBand := Band;
|
|
NewColumn;
|
|
end;
|
|
if FKeeping then
|
|
EndKeep(Band);
|
|
DrawSplit(Band);
|
|
IsSplit := True;
|
|
end
|
|
else
|
|
begin
|
|
if not FKeeping then
|
|
CheckSuppress(Band);
|
|
if not((Band is TfrxChild) and FCallFromPHeader) then {endless loop fix}
|
|
NewColumn;
|
|
end;
|
|
|
|
if not IsSplit then
|
|
begin
|
|
if not (Band is TfrxNullBand) then
|
|
begin
|
|
{ multicolumn band manages its Left property itself }
|
|
if IsMultiColumnBand then
|
|
begin
|
|
Band.Left := Band.Left + CurX;
|
|
// if (TfrxDataBand(Band).CurColumn = 1) and (Band.Height > FreeSpace) then
|
|
// begin
|
|
//
|
|
// FCurBand := Band;
|
|
// NewColumn;
|
|
// // FSaveCurY := CurY;
|
|
// end;
|
|
end
|
|
else
|
|
Band.Left := CurX;
|
|
Band.Top := CurY;
|
|
end;
|
|
|
|
{ output the band }
|
|
if FOutputTo = nil then
|
|
begin
|
|
if FinalPass then
|
|
Band.CreateFillMemo;
|
|
{ handle PrintAtBottom property : TODO move to Show method }
|
|
if (Band is TfrxReportSummary) and TfrxReportSummary(Band).PrintAtBottom then
|
|
begin
|
|
CurY := CurY + (FreeSpace - Band.FStretchedHeight);
|
|
Band.Top := CurY;
|
|
end;
|
|
|
|
if (Band.FHasVBands) and not (Band is TfrxPageHeader) then
|
|
begin
|
|
{ restore global variables - FVPageList and FVMasterBand }
|
|
{ they may be changed in the NewPage call, if cross has a h-header }
|
|
{ with ReprintOnNewPage option }
|
|
FVPageList.Clear;
|
|
for i := 0 to SavePageList.Count - 1 do
|
|
FVPageList.Add(SavePageList[i]);
|
|
SavePageList.Free;
|
|
FVMasterBand := SaveVMasterBand;
|
|
Band.Left := 0;
|
|
RenderVBand;
|
|
end
|
|
else if (not FCallFromAddPage) or (not PreviewPages.BandExists(Band)) then
|
|
PreviewPages.AddObject(Band)
|
|
end
|
|
else
|
|
begin
|
|
TempBand := TfrxBand.Create(FOutputTo);
|
|
{ temporary solution for Duplicates on subreport with PrintOnParent }
|
|
{ it assigns source objects of report template for proper link by Name }
|
|
TempBand.AssignAllWithOriginals(Band, True);
|
|
TempBand.Name := Band.Name;
|
|
if FinalPass then
|
|
TempBand.CreateFillMemo;
|
|
end;
|
|
|
|
{ move the current position }
|
|
CurY := CurY + Band.Height;
|
|
end;
|
|
end;
|
|
finally
|
|
UnStretch(Band);
|
|
end;
|
|
|
|
{ reset aggregate values }
|
|
// if (Band is TfrxFooter) or (Band is TfrxGroupFooter) or
|
|
// (Band is TfrxPageFooter) or (Band is TfrxReportSummary) then
|
|
// if Band is TfrxGroupFooter then
|
|
// PreviewPages.PostProcessor.ProcessExpressions(Report, paGroupFinished);
|
|
// else if Band is TfrxGroupFooter then
|
|
// PreviewPages.PostProcessor.ProcessExpressions(Report, paGroupFinished)
|
|
|
|
//FAggregates.Reset(Band);
|
|
DoProcessState(Band, paDefault);
|
|
|
|
{ print subreports contained in this band }
|
|
if Band.Visible then
|
|
CheckSubReports(Band);
|
|
end;
|
|
|
|
procedure TfrxEngine.CheckSubReports(Band: TfrxBand);
|
|
var
|
|
i, SavePageN, SaveColumnN: Integer;
|
|
SaveCurX, SaveCurY, SavePageCurX: Extended;
|
|
HaveSub: Boolean;
|
|
Sub: TfrxSubreport;
|
|
MaxPageN, MaxColumnN: Integer;
|
|
MaxCurY, SaveSubCurY: Extended;
|
|
begin
|
|
{ save the current position }
|
|
HaveSub := False;
|
|
SavePageN := PreviewPages.CurPage;
|
|
SaveColumnN := CurColumn;
|
|
SaveCurX := CurX;
|
|
SaveCurY := CurY;
|
|
SavePageCurX := FPageCurX;
|
|
|
|
{ init max position }
|
|
MaxPageN := SavePageN; //0
|
|
MaxColumnN := SaveColumnN; //0
|
|
MaxCurY := SaveCurY; //0
|
|
SaveSubCurY := 0;
|
|
{ looking for subreport objects }
|
|
for i := 0 to Band.Objects.Count - 1 do
|
|
if TObject(Band.Objects[i]) is TfrxSubreport then
|
|
begin
|
|
Sub := TfrxSubreport(Band.Objects[i]);
|
|
if not Sub.Visible or Sub.PrintOnParent then continue;
|
|
HaveSub := True;
|
|
FSubSavePageN := -1;
|
|
FSubSaveCurY := -1;
|
|
if FKeeping then
|
|
SaveSubCurY := SaveCurY - FKeepCurY;
|
|
{ set up all properties... }
|
|
PreviewPages.CurPage := SavePageN;
|
|
FPageCurX := SavePageCurX + Sub.Left;
|
|
CurColumn := SaveColumnN;
|
|
CurX := SaveCurX + Sub.Left;
|
|
CurY := SaveCurY - Band.FStretchedHeight + Sub.Top; //SaveCurY - Sub.Height;
|
|
{ ...and run the subreport }
|
|
RunPage(Sub.Page);
|
|
// set from newpage. It broke keep and move part of data
|
|
// we need to correct page coords for another subreports
|
|
{ #446438 keepchild and multicolumn band on subreport }
|
|
if (FSubSavePageN <> -1) and (FSubSaveCurY >= 0) then
|
|
begin
|
|
SavePageN := FSubSavePageN;
|
|
SaveCurY := FSubSaveCurY + SaveSubCurY;
|
|
end;
|
|
{ calc max position }
|
|
if PreviewPages.CurPage > MaxPageN then
|
|
begin
|
|
MaxPageN := PreviewPages.CurPage;
|
|
MaxColumnN := CurColumn;
|
|
MaxCurY := CurY;
|
|
end
|
|
else if PreviewPages.CurPage = MaxPageN then
|
|
if CurColumn > MaxColumnN then
|
|
begin
|
|
MaxColumnN := CurColumn;
|
|
MaxCurY := CurY;
|
|
end
|
|
else if CurColumn = MaxColumnN then
|
|
if CurY > MaxCurY then
|
|
MaxCurY := CurY;
|
|
end;
|
|
|
|
{ move the current position to the last generated page }
|
|
if HaveSub then
|
|
begin
|
|
PreviewPages.CurPage := MaxPageN;
|
|
CurColumn := MaxColumnN;
|
|
CurX := SavePageCurX;
|
|
if CurColumn > 1 then
|
|
CurX := CurX + frxStrToFloat(FPage.ColumnPositions[CurColumn - 1]) * fr01cm;
|
|
CurY := MaxCurY;
|
|
FPageCurX := SavePageCurX;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.StartKeep(Band: TfrxBand; Position: Integer = 0);
|
|
begin
|
|
{ forces Keeping for child band sequences from report title check #393031 issue }
|
|
{ do not reset FIsFirstBand flag for that case otherwise it will cause issues like #581052 (task #4619)}
|
|
if FKeeping or (FIsFirstBand and not (Band is TfrxChild)) then Exit;
|
|
|
|
FKeepCurY := CurY;
|
|
FKeeping := True;
|
|
FKeepBand := Band;
|
|
if Position = 0 then
|
|
Position := PreviewPages.GetCurPosition;
|
|
FKeepPosition := Position;
|
|
FKeepOutline := PreviewPages.Outline.GetCurPosition;
|
|
FKeepAnchor := PreviewPages.GetAnchorCurPosition;
|
|
FAggregates.StartKeep;
|
|
end;
|
|
|
|
procedure TfrxEngine.EndKeep(Band: TfrxBand);
|
|
begin
|
|
if FKeepBand = Band then
|
|
begin
|
|
FKeepCurY := 0;
|
|
FKeeping := False;
|
|
FKeepBand := nil;
|
|
FAggregates.EndKeep;
|
|
FKeepHeader := False;
|
|
end;
|
|
end;
|
|
|
|
function TfrxEngine.GetAggregates: TfrxCustomAggregateList;
|
|
begin
|
|
Result := FAggregates;
|
|
end;
|
|
|
|
function TfrxEngine.GetAggregateValue(const Name, Expression: String;
|
|
DataRow: TfrxComponent; Flags: Integer): Variant;
|
|
begin
|
|
Result := FAggregates.GetValue(FCurBand, CurVColumn, Name, Expression, DataRow, Flags);
|
|
end;
|
|
|
|
procedure TfrxEngine.AddBandOutline(Band: TfrxBand);
|
|
var
|
|
pos: Integer;
|
|
begin
|
|
if Band.OutlineText <> '' then
|
|
begin
|
|
Report.CurObject := Band.Name;
|
|
if Band.Stretched then
|
|
pos := Round(CurY - Band.FStretchedHeight)
|
|
else
|
|
pos := Round(CurY - Band.Height);
|
|
if Band.Visible then
|
|
PreviewPages.Outline.AddItem(VarToStr(Report.Calc(Band.OutlineText)), pos);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.AddPageOutline;
|
|
begin
|
|
if FPage.OutlineText <> '' then
|
|
begin
|
|
OutlineRoot;
|
|
Report.CurObject := FPage.Name;
|
|
PreviewPages.Outline.AddItem(VarToStr(Report.Calc(FPage.OutlineText)), 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxEngine.OutlineRoot;
|
|
begin
|
|
PreviewPages.Outline.LevelRoot;
|
|
end;
|
|
|
|
procedure TfrxEngine.OutlineUp(Band: TfrxBand);
|
|
begin
|
|
if Band.OutlineText <> '' then
|
|
PreviewPages.Outline.LevelUp;
|
|
end;
|
|
|
|
|
|
function TfrxEngine.GetPageHeight: Extended;
|
|
begin
|
|
if (FPage <> nil) and FPage.EndlessHeight then
|
|
Result := CurY + FooterHeight
|
|
else
|
|
Result := inherited GetPageHeight;
|
|
end;
|
|
|
|
{ TfrxShiftItem }
|
|
|
|
function TfrxShiftItem.Add(AReportObject: TfrxReportComponent): TfrxShiftItem;
|
|
begin
|
|
Result := TfrxShiftItem.Create(Self);
|
|
Result.FReportObject := AReportObject;
|
|
end;
|
|
|
|
constructor TfrxShiftItem.Create(AParent: TfrxShiftItem);
|
|
begin
|
|
FShiftChildren := TList.Create;
|
|
FShiftAmount := 0;
|
|
FShiftedTo := 0;
|
|
FMinDist := 0;
|
|
FRefCount := 0;
|
|
if Assigned(AParent) then
|
|
Inc(FRefCount);
|
|
end;
|
|
|
|
procedure TfrxShiftItem.AddExist(Item: TfrxShiftItem);
|
|
begin
|
|
FShiftChildren.Add(Item);
|
|
Inc(Item.FRefCount);
|
|
end;
|
|
|
|
function TfrxShiftItem.Count: Integer;
|
|
begin
|
|
Result := FShiftChildren.Count;
|
|
end;
|
|
|
|
constructor TfrxShiftItem.Create(AParent: TfrxShiftItem;
|
|
AReportObject: TfrxReportComponent);
|
|
begin
|
|
Create(AParent);
|
|
FReportObject := AReportObject;
|
|
AReportObject.FShiftObject := Self;
|
|
end;
|
|
|
|
procedure TfrxShiftItem.DefaultHandler(var Message);
|
|
begin
|
|
inherited;
|
|
if TfrxDispatchMessage(Message).MsgID = FRX_OWNER_DESTROY_MESSAGE then
|
|
begin
|
|
FReportObject := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxShiftItem.DeleteClildren(Item: TfrxShiftItem);
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := FShiftChildren.IndexOf(Item);
|
|
if idx <> -1 then
|
|
TObject(FShiftChildren[idx]).Free;
|
|
end;
|
|
|
|
destructor TfrxShiftItem.Destroy;
|
|
var
|
|
i: Integer;
|
|
Item: TfrxShiftItem;
|
|
begin
|
|
if Assigned(FReportObject) then
|
|
FReportObject.FShiftObject := nil;
|
|
for i := 0 to FShiftChildren.Count - 1 do
|
|
begin
|
|
Item := TfrxShiftItem(FShiftChildren[i]);
|
|
if Item <> nil then
|
|
begin
|
|
if Item.FRefCount > 1 then
|
|
Dec(Item.FRefCount)
|
|
else
|
|
Item.Free;
|
|
end;
|
|
FShiftChildren[i] := nil;
|
|
end;
|
|
FReportObject := nil;
|
|
FreeAndNil(FShiftChildren);
|
|
inherited;
|
|
end;
|
|
|
|
function TfrxShiftItem.GetHeight: Extended;
|
|
begin
|
|
Result := FReportObject.Height;
|
|
end;
|
|
|
|
function TfrxShiftItem.GetItems(Index: Integer): TfrxShiftItem;
|
|
begin
|
|
Result := TfrxShiftItem(FShiftChildren[Index]);
|
|
end;
|
|
|
|
function TfrxShiftItem.GetLeft: Extended;
|
|
begin
|
|
Result := FReportObject.Left;
|
|
end;
|
|
|
|
function TfrxShiftItem.GetTop: Extended;
|
|
begin
|
|
Result := FReportObject.Top;
|
|
end;
|
|
|
|
function TfrxShiftItem.GetWidth: Extended;
|
|
begin
|
|
Result := FReportObject.Width;
|
|
end;
|
|
|
|
{ TfrxShifEngine }
|
|
|
|
procedure TfrxShiftEngine.Clear;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to FContainers.Count - 1 do
|
|
if Assigned(FContainers[i]) then
|
|
TObject(FContainers[i]).Free;
|
|
FContainers.Clear;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.ClearContainer(Container: TfrxReportComponent);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if Assigned(Container) and Assigned(Container.FShiftObject) then
|
|
begin
|
|
i := FContainers.IndexOf(Container.FShiftObject);
|
|
if i > -1 then
|
|
begin
|
|
Container.FShiftObject.Free;
|
|
Container.FShiftObject := nil;
|
|
FContainers.Delete(i);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.ClearDestroyQueue;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to FDestroyQueue.Count - 1 do
|
|
TObject(FDestroyQueue[i]).Free;
|
|
FDestroyQueue.Clear;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.ContainerToDestroyQueue(AContainer: TObject);
|
|
var
|
|
Index: Integer;
|
|
begin
|
|
Index := FContainers.IndexOf(AContainer);
|
|
if Index <> -1 then
|
|
begin
|
|
FDestroyQueue.Add(AContainer);
|
|
FContainers[Index] := nil;
|
|
end;
|
|
end;
|
|
|
|
constructor TfrxShiftEngine.Create;
|
|
begin
|
|
FContainers := TList.Create;
|
|
FDestroyQueue := TList.Create;
|
|
end;
|
|
|
|
destructor TfrxShiftEngine.Destroy;
|
|
begin
|
|
ClearDestroyQueue;
|
|
Clear;
|
|
FreeAndNil(FContainers);
|
|
FreeAndNil(FDestroyQueue);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.InitShiftAmount(AObject: TfrxReportComponent; ShiftAmount: Extended);
|
|
var
|
|
sItem: TfrxShiftItem;
|
|
begin
|
|
if Assigned(AObject.FShiftObject) then
|
|
begin
|
|
sItem := TfrxShiftItem(AObject.FShiftObject);
|
|
sItem.FShiftAmount := ShiftAmount;
|
|
sItem.FShiftedTo := 0;
|
|
THackComponent(AObject).UnlockAnchorsUpdate;
|
|
sItem.FMinDist := 0;
|
|
sItem.FShifted := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.PrepareShiftTree(Container: TfrxReportComponent);
|
|
var
|
|
i, j, k: Integer;
|
|
c0, c1, c2, top: TfrxReportComponent;
|
|
allObjectsSorted: TfrxShiftedObjectList;
|
|
Found: Boolean;
|
|
area0, area1, area2, area01: TfrxRectArea;
|
|
cItem, cItem0, cItem1: TfrxShiftItem;
|
|
begin
|
|
ClearDestroyQueue;
|
|
//if Container.FShiftChildren.Count <> 0 then
|
|
if Assigned(Container.FShiftObject) or
|
|
((Container is TfrxBand) and (TfrxBand(Container).ShiftEngine = seDontShift))
|
|
then
|
|
Exit;
|
|
|
|
allObjectsSorted := TfrxShiftedObjectList.Create(Self);
|
|
|
|
{ sort objects }
|
|
for i := 0 to Container.Objects.Count - 1 do
|
|
begin
|
|
c0 := Container.Objects[i];
|
|
{ check if coors are inside a container }
|
|
if (Container.Width > 0) and (((c0.Left >= 0) and (c0.Left <= Container.Width)) or
|
|
((c0.Left + c0.Width >= 0) and (c0.Left + c0.Width <= Container.Width))) then
|
|
allObjectsSorted.AppendObject(c0.Top, TfrxShiftItem.Create(nil, c0));
|
|
end;
|
|
allObjectsSorted.SortList;
|
|
|
|
{ for linear mode we use sorted list by Top coordinate }
|
|
{ FShiftObject of container contains pointer to sorted list in this case }
|
|
if (Container Is TfrxBand) and (TfrxBand(Container).ShiftEngine = seLinear) then
|
|
begin
|
|
Container.FShiftObject := allObjectsSorted;
|
|
FContainers.Add(allObjectsSorted);
|
|
allObjectsSorted.FParentContainer := Container;
|
|
allObjectsSorted.FreeObjects := True;
|
|
Exit;
|
|
end;
|
|
|
|
{ temporary top object }
|
|
{ FShiftObject of container contains pointer to Root node of the tree }
|
|
top := TfrxMemoView.Create(nil);
|
|
top.SetBounds(Container.AbsLeft, 0, Container.Width + 1, 1);
|
|
cItem := TfrxShiftRootItem.Create(nil, top, Self);
|
|
allObjectsSorted.InsertObject(0, top.Top, cItem);
|
|
|
|
{ for tree mode we build tree structure }
|
|
for i := 0 to allObjectsSorted.Count - 1 do
|
|
begin
|
|
cItem0 := TfrxShiftItem(allObjectsSorted.Objects[i]);
|
|
c0 := cItem0.FReportObject;
|
|
|
|
area0 := TfrxRectArea.Create(c0);
|
|
|
|
{ find an object under c0 }
|
|
for j := i + 1 to allObjectsSorted.Count - 1 do
|
|
begin
|
|
cItem1 := TfrxShiftItem(allObjectsSorted.Objects[j]);
|
|
c1 := cItem1.FReportObject;
|
|
area1 := TfrxRectArea.Create(c1);
|
|
{ The correct behaviour for lines }
|
|
{ vertivcal line should shift when has same }
|
|
{ coordinates as an object above and width = 0 }
|
|
{ commented bacause we don't want to break old reports }
|
|
{ need to handle it somehow
|
|
{ if c1.width = 0 then
|
|
begin
|
|
area1.X := area1.X - 1E-2;
|
|
area1.X1 := area1.X1 + 1E-2;
|
|
end;}
|
|
|
|
if not (area0.InterceptsY(area1)) and (area0.Y < area1.Y) and
|
|
area0.InterceptsX(area1) then
|
|
begin
|
|
area01 := area0.InterceptX(area1);
|
|
Found := False;
|
|
|
|
{ check if there is no other objects between c1 and c0 }
|
|
for k := j - 1 downto i + 1 do
|
|
begin
|
|
c2 := TfrxShiftItem(allObjectsSorted.Objects[k]).FReportObject;
|
|
area2 := TfrxRectArea.Create(c2);
|
|
{ special case for height = 0 }
|
|
if ((c2.Height > 0) and (c1.Height > 0) or (area1.Y - area2.Y > 1E-4)) and
|
|
not(area0.InterceptsY(area2)) and not(area1.InterceptsY(area2)) and
|
|
area01.InterceptsX(area2) then
|
|
Found := True;
|
|
|
|
area2.Free;
|
|
if Found then
|
|
break;
|
|
end;
|
|
|
|
if not Found and not ((i > 0) and (cItem0.FRefCount = 0)) then
|
|
cItem0.AddExist(cItem1);
|
|
|
|
area01.Free;
|
|
end;
|
|
area1.Free;
|
|
end;
|
|
|
|
{ clear all items that out of container bound }
|
|
{ normally should never happens }
|
|
{ the cause described above }
|
|
if (i > 0) and (cItem0.FRefCount = 0) then
|
|
begin
|
|
if Assigned(cItem0.FReportObject) then
|
|
cItem0.FReportObject.FShiftObject := nil;
|
|
cItem0.Free;
|
|
allObjectsSorted.Objects[i] := nil;
|
|
end;
|
|
area0.Free;
|
|
end;
|
|
|
|
{ copy children from the top object to the band }
|
|
if Assigned(Container.FShiftObject) then
|
|
begin
|
|
FContainers.Remove(Container.FShiftObject);
|
|
Container.FShiftObject.Free;
|
|
end;
|
|
cItem.FReportObject := Container;
|
|
Container.FShiftObject := cItem;
|
|
FContainers.Add(cItem);
|
|
allObjectsSorted.Free;
|
|
top.FShiftObject := nil;
|
|
top.Free;
|
|
end;
|
|
|
|
procedure TfrxShiftEngine.ShiftObjects(Container: TfrxReportComponent);
|
|
|
|
{ TODO : Remove recursion }
|
|
{ in some cases it consumes a lot of stack resources }
|
|
{ linear cycle algoritmh shold make it better and faster }
|
|
procedure InternalShiftTree(Parent: TfrxReportComponent; Amount: Extended);
|
|
var
|
|
i: Integer;
|
|
v: TfrxView;
|
|
diff, lShiftAmount: Extended;
|
|
lItem: TfrxShiftItem;
|
|
bIsNotParentContainer: Boolean;
|
|
begin
|
|
lItem := TfrxShiftItem(Parent.FShiftObject);
|
|
{ mark node as processed }
|
|
lItem.FShifted := True;
|
|
{ save amout of shift }
|
|
lItem.FShiftedTo := Amount;
|
|
if Amount <> 0 then
|
|
THackComponent(lItem.FReportObject).LockAnchorsUpdate;
|
|
|
|
bIsNotParentContainer := (lItem.FRefCount > 0);
|
|
|
|
for i := 0 to lItem.Count - 1 do
|
|
begin
|
|
if not Assigned(lItem[i].FReportObject) then continue;
|
|
v := TfrxView(lItem[i].FReportObject);
|
|
lShiftAmount := lItem[i].FShiftedTo;
|
|
{ we check distance to the closest Top object }
|
|
{ don't move object up when some Top object from tree }
|
|
{ has a height grater than sift distance }
|
|
if (lItem[i].FMinDist > 0) and bIsNotParentContainer and
|
|
((lItem[i].FMinDist > lItem.Top + lItem.Height) or
|
|
(Abs(lItem[i].FMinDist - (lItem.Top + lItem.Height)) < 1e-4)) then
|
|
continue;
|
|
if (lItem[i].FMinDist > 0) and bIsNotParentContainer and
|
|
(lItem[i].FMinDist < lItem.Top + lItem.Height) and (lShiftAmount > 0) then
|
|
v.Top := v.Top - (lShiftAmount - lItem[i].FShiftAmount)
|
|
else if (lItem[i].FShiftedTo <> 0) and (Amount <> 0) then
|
|
v.Top := v.Top - lShiftAmount;
|
|
{ calculate shift offset }
|
|
if (v.ShiftMode = smAlways) or (Amount < 0) then
|
|
begin
|
|
v.Top := v.Top + Amount;
|
|
lShiftAmount := Amount + lItem[i].FShiftAmount;
|
|
end
|
|
else if v.ShiftMode = smWhenOverlapped then
|
|
begin
|
|
if not (Parent is TfrxBand) and (v.Top < Parent.Top + Parent.Height) then
|
|
begin
|
|
diff := Parent.Top + Parent.Height - v.Top;
|
|
v.Top := Parent.Top + Parent.Height;
|
|
lShiftAmount := diff + lItem[i].FShiftAmount;
|
|
end;
|
|
end
|
|
else
|
|
lShiftAmount := lItem[i].FShiftAmount;
|
|
{ Top lower position for object }
|
|
{ we can't move upper than it }
|
|
if bIsNotParentContainer then
|
|
lItem[i].FMinDist := Max(lItem[i].FMinDist, lItem.Top + lItem.Height);
|
|
{ check if Engine need to shift object's tree again }
|
|
if not (lItem[i].FShifted and (Abs(lItem[i].FShiftedTo - lShiftAmount) < 1e-4)) then
|
|
InternalShiftTree(v, lShiftAmount);
|
|
end;
|
|
end;
|
|
|
|
procedure InternalShiftLine(AList: TfrxExtendedObjectList);
|
|
var
|
|
i, j: Integer;
|
|
ParentView, ChildView: TfrxView;
|
|
Shift, childShift, parentShift, MinDist: Extended;
|
|
lItem: TfrxShiftItem;
|
|
begin
|
|
for i := 0 to AList.Count - 1 do
|
|
begin
|
|
lItem := TfrxShiftItem(AList.Objects[i]);
|
|
//if lItem.FShiftAmount = 0 then
|
|
// continue;
|
|
if not Assigned(lItem.FReportObject) then continue;
|
|
ParentView := TfrxView(lItem.FReportObject);
|
|
Shift := lItem.FShiftAmount;
|
|
parentShift := lItem.FShiftedTo;
|
|
for j := i + 1 to AList.Count - 1 do
|
|
begin
|
|
lItem := TfrxShiftItem(AList.Objects[j]);
|
|
if not Assigned(lItem.FReportObject) then continue;
|
|
ChildView := TfrxView(lItem.FReportObject);
|
|
if ChildView.ShiftMode = smDontShift then
|
|
continue;
|
|
if ChildView.Top >= ParentView.Top + ParentView.Height - Shift - 1e-4 then
|
|
begin
|
|
if (ChildView.ShiftMode = smWhenOverlapped) and
|
|
((ChildView.Left > ParentView.Left + ParentView.Width - 1E-4) or
|
|
(ParentView.Left > ChildView.Left + ChildView.Width - 1E-4)) then
|
|
continue;
|
|
|
|
childShift := lItem.FShiftedTo;
|
|
MinDist := ParentView.Top + ParentView.Height + parentShift;
|
|
if (shift > 0) or (lItem.FShifted) and
|
|
(ChildView.Top + childShift < MinDist) then
|
|
childShift := Max(shift + parentShift, childShift)
|
|
else
|
|
childShift := Min(shift + parentShift, childShift);
|
|
if (lItem.FShifted) and (ChildView.Top + childShift < lItem.FMinDist) then
|
|
break;
|
|
lItem.FMinDist := Max(lItem.FMinDist, MinDist);
|
|
lItem.FShiftedTo := childShift;
|
|
lItem.FShifted := True;
|
|
end;
|
|
end;
|
|
end;
|
|
for i := 0 to AList.Count - 1 do
|
|
begin
|
|
lItem := TfrxShiftItem(AList.Objects[i]);
|
|
if not Assigned(lItem.FReportObject) then continue;
|
|
ParentView := TfrxView(lItem.FReportObject);
|
|
ParentView.Top := ParentView.Top + lItem.FShiftedTo;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
ClearDestroyQueue;
|
|
if Container.FShiftObject is TfrxShiftItem then
|
|
InternalShiftTree(Container, 0)
|
|
else if Container.FShiftObject is TfrxExtendedObjectList then
|
|
InternalShiftLine(TfrxExtendedObjectList(Container.FShiftObject));
|
|
end;
|
|
|
|
{ TfrxShiftRootItem }
|
|
|
|
constructor TfrxShiftRootItem.Create(AParent: TfrxShiftItem;
|
|
AReportObject: TfrxReportComponent; AShiftEngine: TfrxShiftEngine);
|
|
begin
|
|
Inherited Create(AParent, AReportObject);
|
|
FShiftEngine := AShiftEngine;
|
|
end;
|
|
|
|
procedure TfrxShiftRootItem.DefaultHandler(var Message);
|
|
begin
|
|
inherited;
|
|
if (TfrxDispatchMessage(Message).MsgID = FRX_OWNER_DESTROY_MESSAGE) and Assigned(FShiftEngine) then
|
|
begin
|
|
FShiftEngine.ContainerToDestroyQueue(Self);
|
|
end;
|
|
end;
|
|
|
|
{ TfrxShiftedObjectList }
|
|
|
|
constructor TfrxShiftedObjectList.Create(AShiftEngine: TfrxShiftEngine);
|
|
begin
|
|
FShiftEngine := AShiftEngine;
|
|
end;
|
|
|
|
procedure TfrxShiftedObjectList.DefaultHandler(var Message);
|
|
begin
|
|
inherited;
|
|
if (TfrxDispatchMessage(Message).MsgID = FRX_OWNER_DESTROY_MESSAGE) and Assigned(FShiftEngine) then
|
|
begin
|
|
FShiftEngine.ContainerToDestroyQueue(Self);
|
|
end;
|
|
end;
|
|
|
|
destructor TfrxShiftedObjectList.Destroy;
|
|
begin
|
|
if Assigned(FParentContainer) then
|
|
FParentContainer.FShiftObject := nil;
|
|
inherited;
|
|
end;
|
|
|
|
end.
|