FastReport_2022_VCL/Source/frxEngine.pas
2024-01-01 16:13:08 +01:00

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.