Browse Source

* Patch from Pascal Riekenberg to implement nested groups

git-svn-id: trunk@37273 -
michael 8 years ago
parent
commit
b793b2dd28

+ 2 - 0
.gitattributes

@@ -2730,6 +2730,7 @@ packages/fcl-report/demos/company-logo.png -text svneol=unset#image/png
 packages/fcl-report/demos/compiling.txt svneol=native#text/plain
 packages/fcl-report/demos/countries.inc svneol=native#text/plain
 packages/fcl-report/demos/countries.json svneol=native#text/plain
+packages/fcl-report/demos/countries2.inc svneol=native#text/plain
 packages/fcl-report/demos/demos.inc svneol=native#text/plain
 packages/fcl-report/demos/fcldemo.lpi svneol=native#text/plain
 packages/fcl-report/demos/fcldemo.pp svneol=native#text/plain
@@ -2769,6 +2770,7 @@ packages/fcl-report/demos/rptimages.pp svneol=native#text/plain
 packages/fcl-report/demos/rptjson.pp svneol=native#text/plain
 packages/fcl-report/demos/rptmasterdetail.pp svneol=native#text/plain
 packages/fcl-report/demos/rptmasterdetaildataset.pp svneol=native#text/plain
+packages/fcl-report/demos/rptnestedgroups.pp svneol=native#text/plain
 packages/fcl-report/demos/rptshapes.pp svneol=native#text/plain
 packages/fcl-report/demos/rptsimplelist.pp svneol=native#text/plain
 packages/fcl-report/demos/rptttf.pp svneol=native#text/plain

+ 217 - 0
packages/fcl-report/demos/countries2.inc

@@ -0,0 +1,217 @@
+sl.Add('Africa;Eastern Africa;Burundi;BDI;10524117');
+sl.Add('Africa;Eastern Africa;Comoros;COM;795601');
+sl.Add('Africa;Eastern Africa;Djibouti;DJI;942333');
+sl.Add('Africa;Eastern Africa;Eritrea;ERI;5395998');
+sl.Add('Africa;Eastern Africa;Ethiopia;ETH;102403196');
+sl.Add('Africa;Eastern Africa;Kenya;KEN;48461567');
+sl.Add('Africa;Eastern Africa;Madagascar;MDG;24894551');
+sl.Add('Africa;Eastern Africa;Malawi;MWI;18091575');
+sl.Add('Africa;Eastern Africa;Mauritius;MUS;1263473');
+sl.Add('Africa;Eastern Africa;Mozambique;MOZ;28829476');
+sl.Add('Africa;Eastern Africa;Rwanda;RWA;11917508');
+sl.Add('Africa;Eastern Africa;Seychelles;SYC;94677');
+sl.Add('Africa;Eastern Africa;Somalia;SOM;14317996');
+sl.Add('Africa;Eastern Africa;Tanzania;TZA;55572201');
+sl.Add('Africa;Eastern Africa;Uganda;UGA;41487965');
+sl.Add('Africa;Eastern Africa;Zambia;ZMB;16591390');
+sl.Add('Africa;Eastern Africa;Zimbabwe;ZWE;16150362');
+sl.Add('Africa;Middle Africa;Angola;AGO;28813463');
+sl.Add('Africa;Middle Africa;Cameroon;CMR;23439189');
+sl.Add('Africa;Middle Africa;Central African Republic;CAF;4594621');
+sl.Add('Africa;Middle Africa;Chad;TCD;14452543');
+sl.Add('Africa;Middle Africa;Congo, Dem. Rep.;COD;78736153');
+sl.Add('Africa;Middle Africa;Congo, Rep.;COG;5125821');
+sl.Add('Africa;Middle Africa;Equatorial Guinea;GNQ;1221490');
+sl.Add('Africa;Middle Africa;Gabon;GAB;1979786');
+sl.Add('Africa;Middle Africa;Sao Tome and Principe;STP;199910');
+sl.Add('Africa;Middle Africa;South Sudan;SSD;12230730');
+sl.Add('Africa;Northern Africa;Algeria;DZA;40606052');
+sl.Add('Africa;Northern Africa;Egypt, Arab Rep.;EGY;95688681');
+sl.Add('Africa;Northern Africa;Libya;LBY;6293253');
+sl.Add('Africa;Northern Africa;Morocco;MAR;35276786');
+sl.Add('Africa;Northern Africa;Sudan;SDN;39578828');
+sl.Add('Africa;Northern Africa;Tunisia;TUN;11403248');
+sl.Add('Africa;Southern Africa;Botswana;BWA;2250260');
+sl.Add('Africa;Southern Africa;Lesotho;LSO;2203821');
+sl.Add('Africa;Southern Africa;Namibia;NAM;2479713');
+sl.Add('Africa;Southern Africa;South Africa;ZAF;55908865');
+sl.Add('Africa;Southern Africa;Swaziland;SWZ;1343098');
+sl.Add('Africa;Western Africa;Benin;BEN;10872298');
+sl.Add('Africa;Western Africa;Burkina Faso;BFA;18646433');
+sl.Add('Africa;Western Africa;Cabo Verde;CPV;539560');
+sl.Add('Africa;Western Africa;Cote d''Ivoire;CIV;23695919');
+sl.Add('Africa;Western Africa;Gambia, The;GMB;2038501');
+sl.Add('Africa;Western Africa;Ghana;GHA;28206728');
+sl.Add('Africa;Western Africa;Guinea;GIN;12395924');
+sl.Add('Africa;Western Africa;Guinea-Bissau;GNB;1815698');
+sl.Add('Africa;Western Africa;Liberia;LBR;4613823');
+sl.Add('Africa;Western Africa;Mali;MLI;17994837');
+sl.Add('Africa;Western Africa;Mauritania;MRT;4301018');
+sl.Add('Africa;Western Africa;Niger;NER;20672987');
+sl.Add('Africa;Western Africa;Nigeria;NGA;185989640');
+sl.Add('Africa;Western Africa;Senegal;SEN;15411614');
+sl.Add('Africa;Western Africa;Sierra Leone;SLE;7396190');
+sl.Add('Africa;Western Africa;Togo;TGO;7606374');
+sl.Add('Americas;Caribbean;Antigua and Barbuda;ATG;100963');
+sl.Add('Americas;Caribbean;Aruba;ABW;104822');
+sl.Add('Americas;Caribbean;Bahamas, The;BHS;391232');
+sl.Add('Americas;Caribbean;Barbados;BRB;284996');
+sl.Add('Americas;Caribbean;British Virgin Islands;VGB;30661');
+sl.Add('Americas;Caribbean;Cayman Islands;CYM;60765');
+sl.Add('Americas;Caribbean;Cuba;CUB;11475982');
+sl.Add('Americas;Caribbean;Curacao;CUW;159999');
+sl.Add('Americas;Caribbean;Dominica;DMA;73543');
+sl.Add('Americas;Caribbean;Dominican Republic;DOM;10648791');
+sl.Add('Americas;Caribbean;Grenada;GRD;107317');
+sl.Add('Americas;Caribbean;Haiti;HTI;10847334');
+sl.Add('Americas;Caribbean;Jamaica;JAM;2881355');
+sl.Add('Americas;Caribbean;Puerto Rico;PRI;3411307');
+sl.Add('Americas;Caribbean;Sint Maarten (Dutch part);SXM;40005');
+sl.Add('Americas;Caribbean;St. Kitts and Nevis;KNA;54821');
+sl.Add('Americas;Caribbean;St. Lucia;LCA;178015');
+sl.Add('Americas;Caribbean;St. Martin (French part);MAF;31949');
+sl.Add('Americas;Caribbean;St. Vincent and the Grenadines;VCT;109643');
+sl.Add('Americas;Caribbean;Trinidad and Tobago;TTO;1364962');
+sl.Add('Americas;Caribbean;Turks and Caicos Islands;TCA;34900');
+sl.Add('Americas;Caribbean;Virgin Islands (U.S.);VIR;102951');
+sl.Add('Americas;Central America;Belize;BLZ;366954');
+sl.Add('Americas;Central America;Costa Rica;CRI;4857274');
+sl.Add('Americas;Central America;El Salvador;SLV;6344722');
+sl.Add('Americas;Central America;Guatemala;GTM;16582469');
+sl.Add('Americas;Central America;Honduras;HND;9112867');
+sl.Add('Americas;Central America;Nicaragua;NIC;6149928');
+sl.Add('Americas;Central America;Panama;PAN;4034119');
+sl.Add('Americas;Northern America;Bermuda;BMU;65331');
+sl.Add('Americas;Northern America;Canada;CAN;36286425');
+sl.Add('Americas;Northern America;Greenland;GRL;56186');
+sl.Add('Americas;Northern America;Mexico;MEX;127540423');
+sl.Add('Americas;Northern America;United States;USA;323127513');
+sl.Add('Americas;South America;Argentina;ARG;43847430');
+sl.Add('Americas;South America;Bolivia;BOL;10887882');
+sl.Add('Americas;South America;Brazil;BRA;207652865');
+sl.Add('Americas;South America;Chile;CHL;17909754');
+sl.Add('Americas;South America;Colombia;COL;48653419');
+sl.Add('Americas;South America;Ecuador;ECU;16385068');
+sl.Add('Americas;South America;Guyana;GUY;773303');
+sl.Add('Americas;South America;Paraguay;PRY;6725308');
+sl.Add('Americas;South America;Peru;PER;31773839');
+sl.Add('Americas;South America;Suriname;SUR;558368');
+sl.Add('Americas;South America;Uruguay;URY;3444006');
+sl.Add('Americas;South America;Venezuela, RB;VEN;31568179');
+sl.Add('Asia;Central Asia;Kazakhstan;KAZ;17797032');
+sl.Add('Asia;Central Asia;Kyrgyz Republic;KGZ;6082700');
+sl.Add('Asia;Central Asia;Tajikistan;TJK;8734951');
+sl.Add('Asia;Central Asia;Turkmenistan;TKM;5662544');
+sl.Add('Asia;Central Asia;Uzbekistan;UZB;31848200');
+sl.Add('Asia;Eastern Asia;China;CHN;1378665000');
+sl.Add('Asia;Eastern Asia;Hong Kong SAR, China;HKG;7346700');
+sl.Add('Asia;Eastern Asia;Japan;JPN;126994511');
+sl.Add('Asia;Eastern Asia;Korea, Dem. People''s Rep.;PRK;25368620');
+sl.Add('Asia;Eastern Asia;Korea, Rep.;KOR;51245707');
+sl.Add('Asia;Eastern Asia;Macao SAR, China;MAC;612167');
+sl.Add('Asia;Eastern Asia;Mongolia;MNG;3027398');
+sl.Add('Asia;South-Eastern Asia;Brunei Darussalam;BRN;423196');
+sl.Add('Asia;South-Eastern Asia;Cambodia;KHM;15762370');
+sl.Add('Asia;South-Eastern Asia;Indonesia;IDN;261115456');
+sl.Add('Asia;South-Eastern Asia;Lao PDR;LAO;6758353');
+sl.Add('Asia;South-Eastern Asia;Malaysia;MYS;31187265');
+sl.Add('Asia;South-Eastern Asia;Myanmar;MMR;52885223');
+sl.Add('Asia;South-Eastern Asia;Philippines;PHL;103320222');
+sl.Add('Asia;South-Eastern Asia;Singapore;SGP;5607283');
+sl.Add('Asia;South-Eastern Asia;Thailand;THA;68863514');
+sl.Add('Asia;South-Eastern Asia;Timor-Leste;TLS;1268671');
+sl.Add('Asia;South-Eastern Asia;Vietnam;VNM;92701100');
+sl.Add('Asia;Southern Asia;Afghanistan;AFG;34656032');
+sl.Add('Asia;Southern Asia;Bangladesh;BGD;162951560');
+sl.Add('Asia;Southern Asia;Bhutan;BTN;797765');
+sl.Add('Asia;Southern Asia;India;IND;1324171354');
+sl.Add('Asia;Southern Asia;Iran, Islamic Rep.;IRN;80277428');
+sl.Add('Asia;Southern Asia;Maldives;MDV;417492');
+sl.Add('Asia;Southern Asia;Nepal;NPL;28982771');
+sl.Add('Asia;Southern Asia;Pakistan;PAK;193203476');
+sl.Add('Asia;Southern Asia;Sri Lanka;LKA;21203000');
+sl.Add('Asia;Western Asia;Armenia;ARM;2924816');
+sl.Add('Asia;Western Asia;Azerbaijan;AZE;9762274');
+sl.Add('Asia;Western Asia;Bahrain;BHR;1425171');
+sl.Add('Asia;Western Asia;Georgia;GEO;3719300');
+sl.Add('Asia;Western Asia;Iraq;IRQ;37202572');
+sl.Add('Asia;Western Asia;Israel;ISR;8547100');
+sl.Add('Asia;Western Asia;Jordan;JOR;9455802');
+sl.Add('Asia;Western Asia;Kuwait;KWT;4052584');
+sl.Add('Asia;Western Asia;Lebanon;LBN;6006668');
+sl.Add('Asia;Western Asia;Oman;OMN;4424762');
+sl.Add('Asia;Western Asia;Qatar;QAT;2569804');
+sl.Add('Asia;Western Asia;Saudi Arabia;SAU;32275687');
+sl.Add('Asia;Western Asia;Syrian Arab Republic;SYR;18430453');
+sl.Add('Asia;Western Asia;Turkey;TUR;79512426');
+sl.Add('Asia;Western Asia;United Arab Emirates;ARE;9269612');
+sl.Add('Asia;Western Asia;West Bank and Gaza;PSE;4551566');
+sl.Add('Asia;Western Asia;Yemen, Rep.;YEM;27584213');
+sl.Add('Europe;Central Europe;Slovak Republic;SVK;5428704');
+sl.Add('Europe;Eastern Europe;Belarus;BLR;9507120');
+sl.Add('Europe;Eastern Europe;Bulgaria;BGR;7127822');
+sl.Add('Europe;Eastern Europe;Cyprus;CYP;1170125');
+sl.Add('Europe;Eastern Europe;Czech Republic;CZE;10561633');
+sl.Add('Europe;Eastern Europe;Hungary;HUN;9817958');
+sl.Add('Europe;Eastern Europe;Kosovo;UNK;1816200');
+sl.Add('Europe;Eastern Europe;Moldova;MDA;3552000');
+sl.Add('Europe;Eastern Europe;Poland;POL;37948016');
+sl.Add('Europe;Eastern Europe;Romania;ROU;19705301');
+sl.Add('Europe;Eastern Europe;Russian Federation;RUS;144342396');
+sl.Add('Europe;Eastern Europe;Ukraine;UKR;45004645');
+sl.Add('Europe;Northern Europe;Channel Islands;GGY;164541');
+sl.Add('Europe;Northern Europe;Denmark;DNK;5731118');
+sl.Add('Europe;Northern Europe;Estonia;EST;1316481');
+sl.Add('Europe;Northern Europe;Faroe Islands;FRO;49117');
+sl.Add('Europe;Northern Europe;Finland;FIN;5495096');
+sl.Add('Europe;Northern Europe;Iceland;ISL;334252');
+sl.Add('Europe;Northern Europe;Ireland;IRL;4773095');
+sl.Add('Europe;Northern Europe;Isle of Man;IMN;83737');
+sl.Add('Europe;Northern Europe;Latvia;LVA;1960424');
+sl.Add('Europe;Northern Europe;Lithuania;LTU;2872298');
+sl.Add('Europe;Northern Europe;Norway;NOR;5232929');
+sl.Add('Europe;Northern Europe;Sweden;SWE;9903122');
+sl.Add('Europe;Northern Europe;United Kingdom;GBR;65637239');
+sl.Add('Europe;Southern Europe;Albania;ALB;2876101');
+sl.Add('Europe;Southern Europe;Andorra;AND;77281');
+sl.Add('Europe;Southern Europe;Bosnia and Herzegovina;BIH;3516816');
+sl.Add('Europe;Southern Europe;Croatia;HRV;4170600');
+sl.Add('Europe;Southern Europe;Gibraltar;GIB;34408');
+sl.Add('Europe;Southern Europe;Greece;GRC;10746740');
+sl.Add('Europe;Southern Europe;Italy;ITA;60600590');
+sl.Add('Europe;Southern Europe;Macedonia, FYR;MKD;2081206');
+sl.Add('Europe;Southern Europe;Malta;MLT;436947');
+sl.Add('Europe;Southern Europe;Montenegro;MNE;622781');
+sl.Add('Europe;Southern Europe;Portugal;PRT;10324611');
+sl.Add('Europe;Southern Europe;San Marino;SMR;33203');
+sl.Add('Europe;Southern Europe;Serbia;SRB;7057412');
+sl.Add('Europe;Southern Europe;Slovenia;SVN;2064845');
+sl.Add('Europe;Southern Europe;Spain;ESP;46443959');
+sl.Add('Europe;Western Europe;Austria;AUT;8747358');
+sl.Add('Europe;Western Europe;Belgium;BEL;11348159');
+sl.Add('Europe;Western Europe;France;FRA;66896109');
+sl.Add('Europe;Western Europe;Germany;DEU;82667685');
+sl.Add('Europe;Western Europe;Liechtenstein;LIE;37666');
+sl.Add('Europe;Western Europe;Luxembourg;LUX;582972');
+sl.Add('Europe;Western Europe;Monaco;MCO;38499');
+sl.Add('Europe;Western Europe;Netherlands;NLD;17018408');
+sl.Add('Europe;Western Europe;Switzerland;CHE;8372098');
+sl.Add('Oceania;Australia and New Zealand;Australia;AUS;24127159');
+sl.Add('Oceania;Australia and New Zealand;New Zealand;NZL;4692700');
+sl.Add('Oceania;Melanesia;Fiji;FJI;898760');
+sl.Add('Oceania;Melanesia;New Caledonia;NCL;278000');
+sl.Add('Oceania;Melanesia;Papua New Guinea;PNG;8084991');
+sl.Add('Oceania;Melanesia;Solomon Islands;SLB;599419');
+sl.Add('Oceania;Melanesia;Vanuatu;VUT;270402');
+sl.Add('Oceania;Micronesia;Guam;GUM;162896');
+sl.Add('Oceania;Micronesia;Kiribati;KIR;114395');
+sl.Add('Oceania;Micronesia;Marshall Islands;MHL;53066');
+sl.Add('Oceania;Micronesia;Micronesia, Fed. Sts.;FSM;104937');
+sl.Add('Oceania;Micronesia;Nauru;NRU;13049');
+sl.Add('Oceania;Micronesia;Northern Mariana Islands;MNP;55023');
+sl.Add('Oceania;Micronesia;Palau;PLW;21503');
+sl.Add('Oceania;Polynesia;American Samoa;ASM;55599');
+sl.Add('Oceania;Polynesia;French Polynesia;PYF;280208');
+sl.Add('Oceania;Polynesia;Samoa;WSM;195125');
+sl.Add('Oceania;Polynesia;Tonga;TON;107122');
+sl.Add('Oceania;Polynesia;Tuvalu;TUV;11097');

+ 5 - 1
packages/fcl-report/demos/fcldemo.lpi

@@ -35,7 +35,7 @@
         <CommandLineParams Value="-d simplelist -f fpimage"/>
       </local>
     </RunParams>
-    <Units Count="15">
+    <Units Count="16">
       <Unit0>
         <Filename Value="fcldemo.pp"/>
         <IsPartOfProject Value="True"/>
@@ -96,6 +96,10 @@
         <Filename Value="rptcontnr.pp"/>
         <IsPartOfProject Value="True"/>
       </Unit14>
+      <Unit15>
+        <Filename Value="rptnestedgroups.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit15>
     </Units>
   </ProjectOptions>
   <CompilerOptions>

+ 2 - 0
packages/fcl-report/demos/regreports.pp

@@ -22,6 +22,7 @@ uses
 {$ENDIF}
   rptjson,
   rptcontnr,
+  rptnestedgroups,
   udapp
   ;
 
@@ -55,6 +56,7 @@ begin
   R('jsondata',TJSONDemo);
   R('collectiondata',TCollectionDemo);
   R('objectlistdata',TObjectListDemo);
+  R('nestedgroups',TNestedGroupsDemo);
 end;
 
 initialization

+ 872 - 0
packages/fcl-report/demos/rptnestedgroups.pp

@@ -0,0 +1,872 @@
+unit rptnestedgroups;
+
+{$mode objfpc}{$H+}
+{$I demos.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  fpreport,
+  udapp;
+
+type
+
+  { TNestedGroupsDemo }
+
+  TNestedGroupsDemo = class(TReportDemoApp)
+  private
+    FReportData: TFPReportUserData;
+    sl: TStringList;
+    rec: TStringList;
+    procedure   GetReportDataFirst(Sender: TObject);
+    procedure   GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+    procedure   GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+    procedure   GetReportFieldNames(Sender: TObject; List: TStrings);
+    procedure   ReportDataNext(Sender: TObject);
+    procedure   PrepareRecord;
+  Protected
+    procedure   InitialiseData; override;
+    procedure   CreateReportDesign;override;
+    procedure   LoadDesignFromFile(const AFilename: string);
+    procedure   HookupData(const AComponentName: string; const AData: TFPReportData);
+  public
+    constructor Create(AOWner :TComponent); override;
+    destructor  Destroy; override;
+    Class function Description : string; override;
+  end;
+
+
+implementation
+
+uses
+  fpReportStreamer,
+  fpTTF,
+  fpJSON,
+  jsonparser,
+  fpexprpars;
+
+const
+  clGroupHeaderFooter2   = TFPReportColor($EFE1C7);
+  clGroupHeaderFooter3   = TFPReportColor($DFD1B7);
+
+{ TNestedGroupsDemo }
+
+procedure TNestedGroupsDemo.GetReportDataFirst(Sender: TObject);
+begin
+  {$IFDEF gdebug}
+  writeln('GetReportDataFirst');
+  {$ENDIF}
+  PrepareRecord;
+end;
+
+procedure TNestedGroupsDemo.GetReportDataValue(Sender: TObject; const AValueName: String; var AValue: Variant);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataValue - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  case AValueName of
+    'region': AValue := rec[0];
+    'subregion': AValue := rec[1];
+    'country': AValue := rec[2];
+    'code': AValue := rec[3];
+    'population': AValue := rec[4];
+  end;
+end;
+
+procedure TNestedGroupsDemo.GetReportDataEOF(Sender: TObject; var IsEOF: Boolean);
+begin
+  {$IFDEF gdebug}
+  writeln(Format('GetReportDataEOF - %d', [lReportData.RecNo]));
+  {$ENDIF}
+  if FReportData.RecNo > sl.Count then
+    IsEOF := True
+  else
+    IsEOF := False;
+end;
+
+procedure TNestedGroupsDemo.GetReportFieldNames(Sender: TObject; List: TStrings);
+begin
+  {$IFDEF gdebug}
+  writeln('********** GetReportFieldNames');
+  {$ENDIF}
+  List.Add('region');
+  List.Add('subregion');
+  List.Add('country');
+  List.Add('code');
+  List.Add('population');
+end;
+
+procedure TNestedGroupsDemo.ReportDataNext(Sender: TObject);
+begin
+  PrepareRecord;
+end;
+
+procedure TNestedGroupsDemo.PrepareRecord;
+begin
+  if FReportData.RecNo > sl.Count then
+    exit;
+  rec.DelimitedText := sl[FReportData.RecNo-1];
+end;
+
+procedure TNestedGroupsDemo.InitialiseData;
+begin
+  sl := TStringList.Create;
+  {$I countries2.inc}
+  rec := TStringList.Create;
+  rec.Delimiter := ';';
+  rec.StrictDelimiter := true;
+end;
+
+procedure TNestedGroupsDemo.CreateReportDesign;
+var
+  p: TFPReportPage;
+  TitleBand: TFPReportTitleBand;
+  DataBand: TFPReportDataBand;
+  GroupHeader, GroupHeader1Region,
+    GroupHeader2Subregion, GroupHeader3Initial: TFPReportGroupHeaderBand;
+  Memo: TFPReportMemo;
+  PageFooter: TFPReportPageFooterBand;
+  GroupFooter, GroupFooter3Initial,
+    GroupFooter2SubRegion, GroupFooter1Region: TFPReportGroupFooterBand;
+  ChildBand: TFPReportChildBand;
+  Shape: TFPReportShape;
+begin
+  Inherited;
+  rpt.Author := 'Pascal Riekenberg';
+  rpt.Title := 'FPReport Demo 13 - Nested Grouping';
+
+  {****************}
+  {***   page   ***}
+  {****************}
+
+  p :=  TFPReportPage.Create(rpt);
+  p.Orientation := poPortrait;
+  p.PageSize.PaperName := 'A4';
+  { page margins }
+  p.Margins.Left := 25;
+  p.Margins.Top := 20;
+  p.Margins.Right := 10;
+  p.Margins.Bottom := 20;
+  p.Data := FReportData;
+  p.Font.Name := 'LiberationSans';
+
+
+  {*****************}
+  {***   title   ***}
+  {*****************}
+
+  TitleBand := TFPReportTitleBand.Create(p);
+  TitleBand.Layout.Height := 40;
+  TitleBand.Frame.Shape := fsRectangle;
+  TitleBand.Frame.BackgroundColor := clReportTitleSummary;
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 10;
+  Memo.Layout.Width := p.PageSize.Width - p.Margins.Left - p.Margins.Right;
+  Memo.Layout.Height := 16;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Text := 'COUNTRY AND POPULATION AS OF 2016';
+  Memo.Font.Size := 16;
+
+  Memo := TFPReportMemo.Create(TitleBand);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 18;
+  Memo.Layout.Width := p.PageSize.Width - p.Margins.Left - p.Margins.Right;
+  Memo.Layout.Height := 10;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.UseParentFont := False;
+  Memo.Text := '(Total [formatfloat(''#,##0.0'',total_sum_population_in_M / 1000)] B)';
+  Memo.Font.Size := 10;
+
+
+  {**********************}
+  {***  group header  ***}
+  {**********************}
+
+  {*** group header 1 region ***}
+
+  GroupHeader1Region := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader1Region.Layout.Height := 15;
+  GroupHeader1Region.GroupCondition := 'region';
+  GroupHeader1Region.Frame.Shape := fsRectangle;
+  GroupHeader1Region.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Memo := TFPReportMemo.Create(GroupHeader1Region);
+  Memo.Layout.Left := 3;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.Text := 'Region: [region] ([formatfloat(''#,##0.0'', grp1region_sum_population_in_M)] M)';
+
+  Memo := TFPReportMemo.Create(GroupHeader1Region);
+  Memo.Layout.Left := 25;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 145;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 10;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#0.0'', grp1region_sum_population / total_sum_population * 100)] % in world';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupHeader1Region.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  {*** group header 2 subregion ***}
+
+  GroupHeader2Subregion := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader2Subregion.Layout.Height := 15;
+  GroupHeader2Subregion.GroupCondition := 'subregion';
+  GroupHeader2Subregion.Frame.Shape := fsRectangle;
+  GroupHeader2Subregion.Frame.BackgroundColor := clGroupHeaderFooter2;
+  GroupHeader2Subregion.GroupHeader := GroupHeader1Region;
+
+  Shape := TFPReportShape.Create(GroupHeader2Subregion);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(GroupHeader2Subregion);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Memo := TFPReportMemo.Create(GroupHeader2Subregion);
+  Memo.Layout.Left := 7;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.Text := 'Subregion: [subregion] ([formatfloat(''#,##0.0'', grp2subregion_sum_population_in_M)] M)';
+
+  Memo := TFPReportMemo.Create(GroupHeader2Subregion);
+  Memo.Layout.Left := 25;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 145;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 10;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#0.0'', grp2subregion_sum_population / grp1region_sum_population * 100)] % in [region] - [formatfloat(''#0.0'', grp2subregion_sum_population / total_sum_population * 100)] % in world';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupHeader2Subregion.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  {*** group header 3 initial ***}
+
+  GroupHeader3Initial := TFPReportGroupHeaderBand.Create(p);
+  GroupHeader3Initial.Layout.Height := 15;
+  GroupHeader3Initial.GroupCondition := 'copy(country,1,1)';
+  GroupHeader3Initial.Frame.Shape := fsRectangle;
+  GroupHeader3Initial.Frame.BackgroundColor := clGroupHeaderFooter3;
+  GroupHeader3Initial.GroupHeader := GroupHeader2Subregion;
+
+  Shape := TFPReportShape.Create(GroupHeader3Initial);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(GroupHeader3Initial);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Shape := TFPReportShape.Create(GroupHeader3Initial);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(GroupHeader3Initial);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 8;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 12;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.Text := '[copy(country,1,1)]  ([formatfloat(''#,##0.0'', grp3initial_sum_population_in_M)] M)';
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 25;
+  Memo.Layout.Top := 1;
+  Memo.Layout.Width := 145;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 10;
+  Memo.TextAlignment.Vertical := tlBottom;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#0.0'', grp3initial_sum_population / grp2subregion_sum_population * 100)] % in [subregion] - [formatfloat(''#0.0'', grp3initial_sum_population / grp1region_sum_population * 100)] % in [region] - [formatfloat(''#0.0'', grp3initial_sum_population / total_sum_population * 100)] % in world';
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 90;
+  Memo.Layout.Top := 10.5;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 4;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := 'Initial %';
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 110;
+  Memo.Layout.Top := 10.5;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 4;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := 'Subreg. %';
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 130;
+  Memo.Layout.Top := 10.5;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 4;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := 'Region %';
+
+  Memo := TFPReportMemo.Create(GroupHeader3Initial);
+  Memo.Layout.Left := 150;
+  Memo.Layout.Top := 10.5;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 4;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := 'Total %';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupHeader3Initial.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter3;
+  Shape.Layout.Left := 10;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter3;
+
+  {*** variables ***}
+
+  rpt.Variables.AddExprVariable('population_in_M', 'StrToFloat(population) / 1000000', rtFloat);
+  rpt.Variables.AddExprVariable('grp1region_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, rtGroup, GroupHeader1Region);
+  rpt.Variables.AddExprVariable('grp1region_sum_population', 'sum(StrToFloat(population))', rtFloat, rtGroup, GroupHeader1Region);
+  rpt.Variables.AddExprVariable('grp2subregion_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, rtGroup, GroupHeader2Subregion);
+  rpt.Variables.AddExprVariable('grp2subregion_sum_population', 'sum(StrToFloat(population))', rtFloat, rtGroup, GroupHeader2Subregion);
+  rpt.Variables.AddExprVariable('grp3initial_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat, rtGroup, GroupHeader3Initial);
+  rpt.Variables.AddExprVariable('grp3initial_sum_population', 'sum(StrToFloat(population))', rtFloat, rtGroup, GroupHeader3Initial);
+  rpt.Variables.AddExprVariable('total_sum_population_in_M', 'sum(StrToFloat(population) / 1000000)', rtFloat);
+  rpt.Variables.AddExprVariable('total_sum_population', 'sum(StrToFloat(population))', rtFloat);
+
+
+  {****************}
+  {***  detail  ***}
+  {****************}
+
+  DataBand := TFPReportDataBand.Create(p);
+  DataBand.Layout.Height := 8;
+  DataBand.Frame.Shape := fsRectangle;
+  DataBand.Frame.BackgroundColor := clDataBand;
+  //DataBand.VisibleExpr := 'StrToFloat(''[population]'') > 50000000';
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 8;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clGroupHeaderFooter3;
+  Shape.Layout.Left := 10;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter3;
+
+  Shape := TFPReportShape.Create(DataBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 13;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 8;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 17;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 45;
+  Memo.Layout.Height := 5;
+  Memo.Text := '[country]';
+  Memo.Options := memo.Options + [moDisableWordWrap];
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 55;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 25;
+  Memo.Layout.Height := 5;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#,##0'', StrToFloat(population))]';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 80;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := '> DEU';
+  Memo.UseParentFont := false;
+  Memo.Font.Color := clGreen;
+  Memo.VisibleExpr := 'StrToFloat(population) > 82667685';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 80;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 20;
+  Memo.Layout.Height := 5;
+  Memo.Text := '< DEU';
+  Memo.UseParentFont := false;
+  Memo.Font.Color := clRed;
+  Memo.VisibleExpr := 'StrToFloat(population) < 82667685';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 95;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp3initial_sum_population*100)] %';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 115;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp2subregion_sum_population*100)] %';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 135;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/grp1region_sum_population*100)] %';
+
+  Memo := TFPReportMemo.Create(DataBand);
+  Memo.Layout.Left := 155;
+  Memo.Layout.Top := 2;
+  Memo.Layout.Width := 15;
+  Memo.Layout.Height := 5;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+  Memo.Text := '[formatfloat(''#,##0.0'',StrToFloat(population)/total_sum_population*100)] %';
+
+
+  {**********************}
+  {***  group footer  ***}
+  {**********************}
+
+  {*** group footer 3 initial ***}
+
+  GroupFooter3Initial := TFPReportGroupFooterBand.Create(p);
+  GroupFooter3Initial.Layout.Height := 2;
+  GroupFooter3Initial.GroupHeader := GroupHeader3Initial;
+
+  Shape := TFPReportShape.Create(GroupFooter3Initial);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(GroupFooter3Initial);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(GroupFooter3Initial);
+  Shape.Color := clGroupHeaderFooter3;
+  Shape.Layout.Left := 10;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter3;
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 15;
+  ChildBand.Frame.Shape := fsRectangle;
+  ChildBand.Frame.BackgroundColor := clGroupHeaderFooter3;
+  GroupFooter3Initial.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 8;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Memo := TFPReportMemo.Create(ChildBand);
+  Memo.Layout.Left := 12;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  //Memo.Text := 'Population [copy(country,1,1)]: [formatfloat(''#,##0'', grp3_sum_population)]';
+  Memo.Text := 'Population: [formatfloat(''#,##0'', grp3initial_sum_population)]';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupFooter3Initial.ChildBand.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter2;
+  Shape.Layout.Left := 5;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 8;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  {*** group footer 2 subregion ***}
+
+  GroupFooter2SubRegion := TFPReportGroupFooterBand.Create(p);
+  GroupFooter2SubRegion.Layout.Height := 15;
+  GroupFooter2SubRegion.GroupHeader := GroupHeader2Subregion;
+  GroupFooter2SubRegion.Frame.Shape := fsRectangle;
+  GroupFooter2SubRegion.Frame.BackgroundColor := clGroupHeaderFooter2;
+
+  Shape := TFPReportShape.Create(GroupFooter2SubRegion);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Shape := TFPReportShape.Create(GroupFooter2SubRegion);
+  Shape.Color := clWhite;
+  Shape.Layout.Left := 3;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 2;
+  Shape.Layout.Height := 15;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clWhite;
+
+  Memo := TFPReportMemo.Create(GroupFooter2SubRegion);
+  Memo.Layout.Left := 7;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  //Memo.Text := 'Population [subregion]: [formatfloat(''#,##0'', grp2_sum_population)]';
+  Memo.Text := 'Population: [formatfloat(''#,##0'', grp2subregion_sum_population)]';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupFooter2SubRegion.ChildBand := ChildBand;
+
+  Shape := TFPReportShape.Create(ChildBand);
+  Shape.Color := clGroupHeaderFooter;
+  Shape.Layout.Left := 0;
+  Shape.Layout.Top := 0;
+  Shape.Layout.Width := 3;
+  Shape.Layout.Height := 2;
+  Shape.Frame.Shape := fsRectangle;
+  Shape.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  {*** group footer 1 region ***}
+
+  GroupFooter1Region := TFPReportGroupFooterBand.Create(p);
+  GroupFooter1Region.Layout.Height := 15;
+  GroupFooter1Region.GroupHeader := GroupHeader1Region;
+  GroupFooter1Region.Frame.Shape := fsRectangle;
+  GroupFooter1Region.Frame.BackgroundColor := clGroupHeaderFooter;
+
+  Memo := TFPReportMemo.Create(GroupFooter1Region);
+  Memo.Layout.Left := 3;
+  Memo.Layout.Top := 3;
+  Memo.Layout.Width := 170;
+  Memo.Layout.Height := 6;
+  Memo.UseParentFont := False;
+  Memo.Font.Size := 16;
+  Memo.TextAlignment.Vertical := tlBottom;
+  //Memo.Text := 'Population [region]: [formatfloat(''#,##0'', grp_sum_population)]';
+  Memo.Text := 'Population: [formatfloat(''#,##0'', grp1region_sum_population)]';
+
+  ChildBand := TFPReportChildBand.Create(p);
+  ChildBand.Layout.Height := 2;
+  GroupFooter1Region.ChildBand := ChildBand;
+
+
+  {*******************}
+  {*** page footer ***}
+  {*******************}
+
+  PageFooter := TFPReportPageFooterBand.Create(p);
+  PageFooter.Layout.Height := 20;
+  PageFooter.Frame.Shape := fsRectangle;
+  PageFooter.Frame.BackgroundColor := clPageHeaderFooter;
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 123;
+  Memo.Layout.Top := 13;
+  Memo.Layout.Width := 50;
+  Memo.Layout.Height := 5;
+  Memo.Text := 'Page [PageNo] of [PageCount]';
+  Memo.TextAlignment.Vertical := tlCenter;
+  Memo.TextAlignment.Horizontal := taRightJustified;
+
+  Memo := TFPReportMemo.Create(PageFooter);
+  Memo.Layout.Left := 0;
+  Memo.Layout.Top := 5;
+  Memo.Layout.Width := p.PageSize.Width - p.Margins.Left - p.Margins.Right;
+  Memo.Layout.Height := 8;
+  Memo.UseParentFont := False;
+  Memo.TextAlignment.Horizontal := taCentered;
+  Memo.Text := 'world population: [formatfloat(''#,##0'', total_sum_population)]';
+  Memo.Font.Size := 16;
+end;
+
+procedure TNestedGroupsDemo.LoadDesignFromFile(const AFilename: string);
+var
+  rs: TFPReportJSONStreamer;
+  fs: TFileStream;
+  lJSON: TJSONObject;
+begin
+  if AFilename = '' then
+    Exit;
+  if not FileExists(AFilename) then
+    raise Exception.CreateFmt('The file "%s" can not be found', [AFilename]);
+
+  fs := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
+  try
+    lJSON := TJSONObject(GetJSON(fs));
+  finally
+    fs.Free;
+  end;
+
+  rs := TFPReportJSONStreamer.Create(nil);
+  rs.JSON := lJSON; // rs takes ownership of lJSON
+  try
+    rpt.ReadElement(rs);
+  finally
+    rs.Free;
+  end;
+end;
+
+procedure TNestedGroupsDemo.HookupData(const AComponentName: string; const AData: TFPReportData);
+var
+  b: TFPReportCustomBandWithData;
+begin
+  b := TFPReportCustomBandWithData(rpt.FindRecursive(AComponentName));
+  if Assigned(b) then
+    b.Data := AData;
+end;
+
+constructor TNestedGroupsDemo.Create(AOWner: TComponent);
+begin
+  inherited;
+  FReportData := TFPReportUserData.Create(nil);
+  FReportData.OnGetValue := @GetReportDataValue;
+  FReportData.OnGetEOF := @GetReportDataEOF;
+  FReportData.OnFirst := @GetReportDataFirst;
+  FReportData.OnGetNames := @GetReportFieldNames;
+  FReportData.OnNext := @ReportDataNext;
+end;
+
+destructor TNestedGroupsDemo.Destroy;
+begin
+  FreeAndNil(FReportData);
+  rec.DelimitedText := '';
+  FreeAndNil(rec);
+  FreeAndNil(sl);
+  inherited Destroy;
+end;
+
+class function TNestedGroupsDemo.Description: string;
+begin
+  Result:='Demo showing grouping';
+end;
+
+end.
+

+ 247 - 133
packages/fcl-report/src/fpreport.pp

@@ -1022,15 +1022,17 @@ type
 
   TFPReportCustomGroupHeaderBand = class(TFPReportCustomBandWithData)
   private
-    FConditionValue: string;
+    FTotalGroupConditionValue,
+    FLastTotalGroupConditionValue,
+    FGroupConditionValue: string;
     FGroupHeader: TFPReportCustomGroupHeaderBand;
     FChildGroupHeader: TFPReportCustomGroupHeaderBand;
     FGroupFooter: TFPReportCustomGroupFooterBand;
-    FCondition: string;
+    FGroupCondition: string;
     procedure   SetGroupHeader(AValue: TFPReportCustomGroupHeaderBand);
+    function    InternalEvaluateGroupCondition: string;
   protected
     function    GetReportBandName: string; override;
-    Procedure   CalcGroupConditionValue;
     procedure   DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement = nil); override;
     procedure   Notification(AComponent: TComponent; Operation: TOperation); override;
     { This property defines the hierarchy of nested groups. For the top most group, this property will be nil. }
@@ -1038,14 +1040,17 @@ type
     { Indicates related GroupFooter band. This will automatically be set by the GroupFooter. }
     property    GroupFooter: TFPReportCustomGroupFooterBand read FGroupFooter;
     { can be a field name or an expression }
-    property    GroupCondition: string read FCondition write FCondition;
+    property    GroupCondition: string read FGroupCondition write FGroupCondition;
     { Run-time calculated value }
-    property    GroupConditionValue : string read FConditionValue write FConditionValue;
+    property    GroupConditionValue : string read FGroupConditionValue write FGroupConditionValue;
   public
     constructor Create(AOwner: TComponent); override;
     procedure   Assign(Source: TPersistent); override;
     procedure   ReadElement(AReader: TFPReportStreamer); override;
-    function    Evaluate: string;
+    procedure   EvaluateGroupCondition;
+    function    GroupChanged: Boolean;
+    function    IsInitialGroupChange: Boolean;
+    procedure   ResetGroupConditionValues;
     Class Function ReportBandType : TFPReportBandType; override;
     property    ChildGroupHeader: TFPReportCustomGroupHeaderBand read FChildGroupHeader;
   end;
@@ -1055,6 +1060,7 @@ type
   public
     property    GroupFooter;
   published
+    property    ChildBand;
     property    Font;
     property    GroupCondition;
     property    GroupHeader;
@@ -1122,6 +1128,7 @@ type
 
   TFPReportGroupFooterBand = class(TFPReportCustomGroupFooterBand)
   published
+    property    ChildBand;
     property    Font;
     property    GroupHeader;
     property    UseParentFont;
@@ -1423,7 +1430,6 @@ type
     FPageIdx: integer;
     FNewPage: boolean;  // indicates if a new ReportPage needs to be created - used if DataBand spans multiple pages for example
     FNewColumn: boolean;
-    FNewGroupHeader: boolean;
     FLastDsgnDataBand: TFPReportCustomDataBand;
     FSpaceLeft: TFPReportUnits;
     FLastYPos: TFPReportUnits;
@@ -1432,7 +1438,7 @@ type
     FOverflowed: boolean;
     FLastGroupCondition: string;
     FFoundDataBand: boolean;
-    FHasGroupBand: boolean;
+    FHasGroups: boolean;
     FHasGroupFooter: boolean;
     FHasReportSummaryBand: boolean;
     FDataHeaderPrinted: boolean;
@@ -1440,6 +1446,7 @@ type
     FMultiColumn: boolean;
     FHeaderList: TBandList;
     FFooterList: TBandList;
+    FGroupList: TBandList;
     FRTPage: TFPReportCustomPage;
     FRTBand: TFPReportCustomBand;
     FCurrentRTColumnFooterBand: TFPReportCustomColumnFooterBand;
@@ -1470,6 +1477,7 @@ type
     procedure RecalcBandLayout(ABand: TFPReportCustomBand); virtual;
     procedure PopulateFooterList(APage: TFPReportCustomPage); virtual;
     procedure PopulateHeaderList(APage: TFPReportCustomPage);virtual;
+    procedure PopulateGroupList(APage: TFPReportCustomPage);virtual;
     procedure RemoveTitleBandFromHeaderList;virtual;
     procedure RemoveColumnFooterFromFooterList;virtual;
     procedure UpdateSpaceRemaining(const ABand: TFPReportCustomBand; const AUpdateYPos: boolean = True);virtual;
@@ -1486,10 +1494,11 @@ type
     Procedure HandleHeaderBands; virtual;
     Procedure HandleFooterBands; virtual;
     procedure HandleDatabands; virtual;
-    procedure HandleGroupBands; virtual;
-    procedure HandleGroupFooters; virtual;
+    procedure HandleGroups; virtual;
+    procedure HandleLastGroupFooters; virtual;
     procedure HandleReportSummaries; virtual;
-    procedure PrepareGroupHeaderBand(aBand: TFPReportCustomGroupHeaderBand); virtual;
+    procedure HandleGroupHeader(aBand: TFPReportCustomGroupHeaderBand); virtual;
+    procedure HandleGroupFooter(aBand: TFPReportCustomGroupFooterBand); virtual;
     function NoSpaceRemaining: boolean;virtual;
     procedure StartNewPage; virtual;
     procedure StartNewColumn;virtual;
@@ -2732,6 +2741,7 @@ begin
   end;
   FAggregateValues.Free;
   FExpressionNode.Free;
+  FResetValueExpressionNode.Free;
   inherited Destroy;
 end;
 
@@ -4991,15 +5001,10 @@ begin
   Result := 'GroupHeaderBand';
 end;
 
-procedure TFPReportCustomGroupHeaderBand.CalcGroupConditionValue;
-begin
-  GroupConditionValue:=Evaluate;
-end;
-
 procedure TFPReportCustomGroupHeaderBand.DoWriteLocalProperties(AWriter: TFPReportStreamer; AOriginal: TFPReportElement);
 begin
   inherited DoWriteLocalProperties(AWriter, AOriginal);
-  AWriter.WriteString('GroupCondition', FCondition);
+  AWriter.WriteString('GroupCondition', FGroupCondition);
 end;
 
 procedure TFPReportCustomGroupHeaderBand.Notification(AComponent: TComponent; Operation: TOperation);
@@ -5020,18 +5025,49 @@ end;
 procedure TFPReportCustomGroupHeaderBand.Assign(Source: TPersistent);
 begin
   inherited Assign(Source);
-  FCondition := TFPReportCustomGroupHeaderBand(Source).GroupCondition;
+  FGroupCondition := TFPReportCustomGroupHeaderBand(Source).GroupCondition;
 end;
 
 procedure TFPReportCustomGroupHeaderBand.ReadElement(AReader: TFPReportStreamer);
 begin
   inherited ReadElement(AReader);
-  FCondition := AReader.ReadString('GroupCondition', '');
+  FGroupCondition := AReader.ReadString('GroupCondition', '');
+end;
+
+procedure TFPReportCustomGroupHeaderBand.EvaluateGroupCondition;
+begin
+  if Assigned(FChildGroupHeader) then
+    FChildGroupHeader.EvaluateGroupCondition
+  else
+    InternalEvaluateGroupCondition;
+end;
+
+function TFPReportCustomGroupHeaderBand.GroupChanged: Boolean;
+begin
+  Result := FLastTotalGroupConditionValue <> FTotalGroupConditionValue;
+end;
+
+function TFPReportCustomGroupHeaderBand.IsInitialGroupChange: Boolean;
+begin
+  Result := GroupChanged and (FLastTotalGroupConditionValue = '');
+end;
+
+procedure TFPReportCustomGroupHeaderBand.ResetGroupConditionValues;
+begin
+  FLastTotalGroupConditionValue := '';
+  FTotalGroupConditionValue := '';
+  FGroupConditionValue := '';
 end;
 
-function TFPReportCustomGroupHeaderBand.Evaluate: string;
+function TFPReportCustomGroupHeaderBand.InternalEvaluateGroupCondition: string;
 begin
-  Result := EvaluateExpressionAsText(GroupCondition);
+  FGroupConditionValue := EvaluateExpressionAsText(GroupCondition);
+  FLastTotalGroupConditionValue := FTotalGroupConditionValue;
+  if Assigned(FGroupHeader) then
+    FTotalGroupConditionValue := FGroupHeader.InternalEvaluateGroupCondition + FGroupConditionValue
+  else
+    FTotalGroupConditionValue := FGroupConditionValue;
+  Result := FGroupConditionValue;
 end;
 
 class function TFPReportCustomGroupHeaderBand.ReportBandType: TFPReportBandType;
@@ -7521,8 +7557,9 @@ begin
   end;
   inherited PrepareObjects;
 
-  if self is TFPReportGroupHeaderBand then
-    TFPReportGroupHeaderBand(Page.Report.FRTCurBand).CalcGroupConditionValue;
+  // at this time we already have evaluated if a group change occured
+  //if self is TFPReportGroupHeaderBand then
+  //  TFPReportGroupHeaderBand(Page.Report.FRTCurBand).CalcGroupConditionValue;
 
   if Assigned(FChildren) then
   begin
@@ -9388,6 +9425,35 @@ begin
     FHeaderList.Add(Pages[PageIdx].FindBand(TFPReportColumnHeaderBand));
 end;
 
+procedure TFPReportLayouter.PopulateGroupList(APage: TFPReportCustomPage);
+var
+  I: Integer;
+  lGroup: TFPReportCustomGroupHeaderBand;
+begin
+  FGroupList.Clear;
+  lGroup := nil;
+  // search for lowest group (without child group)
+  for I:=0 to APage.BandCount-1 do
+    if APage.Bands[I] is TFPReportCustomGroupHeaderBand
+    and not Assigned(TFPReportCustomGroupHeaderBand(APage.Bands[I]).ChildGroupHeader) then
+      begin
+      lGroup := TFPReportCustomGroupHeaderBand(APage.Bands[I]);
+      break;
+      end;
+  if not Assigned(lGroup) then
+    exit;
+  FHasGroups := true;
+  // populate list from lowest to highest group level
+  while Assigned(lGroup) do
+    begin
+    lGroup.ResetGroupConditionValues;
+    FGroupList.Add(lGroup);
+    if Assigned(lGroup.GroupFooter) then
+      FHasGroupFooter := true;
+    lGroup := lGroup.GroupHeader;
+    end;
+end;
+
 function TFPReportLayouter.GetPerDesignerPageCount(Index : Cardinal): Cardinal;
 begin
   Result:=Report.FPerDesignerPageCount[Index];
@@ -9452,7 +9518,6 @@ begin
 end;
 
 procedure TFPReportLayouter.IncPageNumber;
-
 begin
   Inc(Report.FPageNumber);
 end;
@@ -9629,75 +9694,113 @@ begin
   end;
 end;
 
-Procedure TFPReportLayouter.PrepareGroupHeaderBand(aBand : TFPReportCustomGroupHeaderBand);
+procedure TFPReportLayouter.HandleGroupFooter(
+  aBand: TFPReportCustomGroupFooterBand);
 
-Var
-  CurrGroup : String;
-  lBand : TFPReportCustomBand;
+var
+  lBand: TFPReportCustomBand;
 
 begin
-  // Writeln('Found group with expr: ',TFPReportCustomGroupHeaderBand(lDsgnBand).GroupCondition);
-  CurrGroup := aBand.Evaluate;
-  // Writeln('Group new ? "',lLastGroupCondition,'" <> "', CurrGroup,'"');
-  if (FLastGroupCondition = CurrGroup) then
-    Exit;
-  FNewGroupHeader := True;
-  { process group footer }
-  if Assigned(aBand.GroupFooter) then
-    begin
-    Report.FRTUseLastValues:=true;
+  lBand := aBand;
+  while Assigned(lBand) do
+  begin
     try
-      lBand := aBand.GroupFooter;
-      CommonRuntimeBandProcessing(lBand);
+      Report.FRTUseLastValues:=true;
+      try
+        CommonRuntimeBandProcessing(lBand);
+        if not lBand.EvaluateVisibility then
+        begin
+          FRTPage.RemoveChild(FRTBand);
+          FRTBand.Free;
+          Continue;
+        end;
+      finally
+        Report.FRTUseLastValues:=false;
+      end;
+      UpdateSpaceRemaining(FRTBand);
+      if NoSpaceRemaining then
+        CheckNewOrOverFlow;
     finally
-      Report.FRTUseLastValues:=false;
-    end;
-    UpdateSpaceRemaining(FRTBand);
-    if NoSpaceRemaining then
-      CheckNewOrOverFlow;
+      lBand := lBand.ChildBand;
     end;
+  end;
 end;
 
-Procedure TFPReportLayouter.HandleGroupBands;
+procedure TFPReportLayouter.HandleGroups;
 
 Var
-  I : Integer;
-  lBand :  TFPReportCustomBand;
+  I, lHighestGroupWithChange: Integer;
+  lGroup: TFPReportCustomGroupHeaderBand;
+  lGroupChanged: Boolean;
 
 begin
-  if FLastGroupCondition = '' then
-    FNewGroupHeader := True
-  else
-    for I := 0 to Report.FBands.Count-1 do
-      begin
-      lBand := TFPReportCustomBand(Report.FBands[i]);
-      if lBand is TFPReportCustomGroupHeaderBand then
-        PrepareGroupHeaderBand(lBand as TFPReportCustomGroupHeaderBand);
-      end;
-  if not FNewGroupHeader then
-    Exit;
-  for i := 0 to Report.FBands.Count-1 do
+  lGroupChanged := false;
+  lHighestGroupWithChange := 0;
+  // process footers
+  For I := 0 to FGroupList.Count - 1 do
+  begin
+    lGroup := TFPReportCustomGroupHeaderBand(FGroupList[I]);
+    if I = 0 then
+      lGroup.EvaluateGroupCondition; // evaluates all groups
+    if lGroup.GroupChanged then
     begin
-    lBand := TFPReportCustomBand(Report.FBands[i]);
-    if lBand is TFPReportCustomGroupHeaderBand then
-      begin
-      if Assigned(FLastDsgnDataBand) then
-        Report.ClearDataBandLastTextValues(FLastDsgnDataBand);
-      CommonRuntimeBandProcessing(lBand);
-      FLastGroupCondition := TFPReportGroupHeaderBand(FRTBand).GroupConditionValue;
-      if Not lBand.EvaluateVisibility  then
-        begin
-        FRTPage.RemoveChild(FRTBand);
-        FRTBand.Free;
-        Continue; { process next band }
-        end;
-      UpdateSpaceRemaining(FRTBand);
-      if NoSpaceRemaining then
-        Break;  { break out of FOR loop }
-      end;
-    end;
-  FNewGroupHeader := False;
-  FDataHeaderPrinted := False;
+      lGroupChanged := true;
+      lHighestGroupWithChange := I;
+      if Assigned(lGroup.GroupFooter) and
+      not lGroup.IsInitialGroupChange then
+        HandleGroupFooter(lGroup.GroupFooter);
+    end
+    else
+      break;
+  end;
+
+  if not lGroupChanged then
+    exit;
+
+  if Assigned(FLastDsgnDataBand) then
+    Report.ClearDataBandLastTextValues(FLastDsgnDataBand);
+
+  // process headers
+  For I := lHighestGroupWithChange downto 0 do
+  begin
+    lGroup := TFPReportCustomGroupHeaderBand(FGroupList[I]);
+    HandleGroupHeader(lGroup);
+  end;
+
+
+  //if FLastGroupCondition = '' then
+  //  FNewGroupHeader := True
+  //else
+  //  for I := 0 to Report.FBands.Count-1 do
+  //    begin
+  //    lBand := TFPReportCustomBand(Report.FBands[i]);
+  //    if lBand is TFPReportCustomGroupHeaderBand then
+  //      PrepareGroupFooterBand(lBand as TFPReportCustomGroupHeaderBand);
+  //    end;
+  //if not FNewGroupHeader then
+  //  Exit;
+  //for i := 0 to Report.FBands.Count-1 do
+  //  begin
+  //  lBand := TFPReportCustomBand(Report.FBands[i]);
+  //  if lBand is TFPReportCustomGroupHeaderBand then
+  //    begin
+  //    if Assigned(FLastDsgnDataBand) then
+  //      Report.ClearDataBandLastTextValues(FLastDsgnDataBand);
+  //    CommonRuntimeBandProcessing(lBand);
+  //    FLastGroupCondition := TFPReportGroupHeaderBand(FRTBand).GroupConditionValue;
+  //    if Not lBand.EvaluateVisibility  then
+  //      begin
+  //      FRTPage.RemoveChild(FRTBand);
+  //      FRTBand.Free;
+  //      Continue; { process next band }
+  //      end;
+  //    UpdateSpaceRemaining(FRTBand);
+  //    if NoSpaceRemaining then
+  //      Break;  { break out of FOR loop }
+  //    end;
+  //  end;
+  //FNewGroupHeader := False;
+  //FDataHeaderPrinted := False;
 end;
 
 Procedure TFPReportLayouter.HandleDatabands;
@@ -9724,29 +9827,19 @@ begin
     end;
 end;
 
-procedure TFPReportLayouter.HandleGroupFooters;
+procedure TFPReportLayouter.HandleLastGroupFooters;
 
 Var
-  I : Integer;
-  lBand : TFPReportCustomBand;
+  I: Integer;
+  lBand: TFPReportCustomGroupFooterBand;
 
 begin
-  for I := 0 to Report.FBands.Count-1 do
-    begin
-    lBand := TFPReportCustomBand(Report.FBands[I]);
-    if lBand is TFPReportCustomGroupHeaderBand then
-      begin
-      lBand:=(lBand as TFPReportCustomGroupHeaderBand).GroupFooter;
-      { We are allowed to use design Layout.Height instead of RTLayout.Height
-        because this band appears outside the data loop, thus memos will not
-        grow. Height of the band is as it was at design time. }
-      if lBand.Layout.Height > FSpaceLeft then
-        StartNewPage;
-      CommonRuntimeBandProcessing(lBand);
-      UpdateSpaceRemaining(FRTBand);
-      Break;
-      end;
-    end;
+  for I := 0 to FGroupList.Count-1 do
+  begin
+    lBand := TFPReportCustomGroupHeaderBand(FGroupList[I]).GroupFooter;
+    if Assigned(lBand) then
+      HandleGroupFooter(lBand);
+  end;
 end;
 
 Procedure TFPReportLayouter.ClearBandList;
@@ -9762,10 +9855,12 @@ Var
   lBand : TFPReportCustomBand;
 
 begin
-  // Create a list of band that need to be printed as page headers
+  // Create a list of bands that need to be printed as page headers
   PopulateHeaderList(aPage);
   // Create a list of bands that need to be printed as page footers
   PopulateFooterList(aPage);
+  // Create a list of group headers
+  PopulateGroupList(aPage);
   // find Bands of interest
   ClearBandList;
   for I := 0 to aPage.BandCount-1 do
@@ -9793,13 +9888,7 @@ begin
       Report.FBands.Add(aPage.Bands[I]);  { all non-data bands are of interest }
       end;
 
-    if lBand is TFPReportCustomGroupHeaderBand then
-      begin
-      FHasGroupBand := True;
-      if Assigned(TFPReportCustomGroupHeaderBand(lBand).GroupFooter) then
-        FHasGroupFooter := True;
-      end
-    else if lBand is TFPReportCustomSummaryBand then
+    if lBand is TFPReportCustomSummaryBand then
       FHasReportSummaryBand := True;
     end;
 end;
@@ -9839,40 +9928,37 @@ begin
     if Report.TwoPass and IsFirstPass then
       Report.Variables.BuildAggregates;
     CheckNewOrOverFlow(True);
-    if FHasGroupBand then
-      HandleGroupBands;
+    if FHasGroups then
+      HandleGroups;
     { handle overflow possibly caused by Group Band just processed. }
     if FOverflowed then
       Continue;
     HandleDataBands;
     lPageData.Next;
     end;
-  if not (Report.TwoPass and IsFirstPass) then
+  CheckNewOrOverFlow(True);
+  // only print if we actually had data
+  if (lPageData.RecNo > 1) then
     begin
-    CheckNewOrOverFlow(True);
-    // only print if we actually had data
-    if (lPageData.RecNo > 1) then
-      begin
-      for I := 0 to Report.FBands.Count-1 do
-        begin
-        lBand := TFPReportCustomBand(Report.FBands[I]);
-        if lBand is TFPReportCustomDataBand then
-          if TFPReportCustomDataBand(lBand).FooterBand <> nil then
-            ShowDataFooterBand(TFPReportCustomDataBand(lBand).FooterBand);
-        end;
-      end;
-    { Process ColumnFooterBand as needed }
-    if FMultiColumn then
+    for I := 0 to Report.FBands.Count-1 do
       begin
-      FCurrentRTColumnFooterBand:= TFPReportCustomColumnFooterBand(FFooterList.Find(TFPReportCustomColumnFooterBand));
-      if Assigned(FCurrentRTColumnFooterBand) then
-        ShowColumnFooterBand(FRTPage, FCurrentRTColumnFooterBand);
+      lBand := TFPReportCustomBand(Report.FBands[I]);
+      if lBand is TFPReportCustomDataBand then
+        if TFPReportCustomDataBand(lBand).FooterBand <> nil then
+          ShowDataFooterBand(TFPReportCustomDataBand(lBand).FooterBand);
       end;
-    { ColumnFooter could have caused a new column or page }
-    CheckNewOrOverFlow(True);
-    if FHasGroupFooter then
-      HandleGroupFooters;
     end;
+  { Process ColumnFooterBand as needed }
+  if FMultiColumn then
+    begin
+    FCurrentRTColumnFooterBand:= TFPReportCustomColumnFooterBand(FFooterList.Find(TFPReportCustomColumnFooterBand));
+    if Assigned(FCurrentRTColumnFooterBand) then
+      ShowColumnFooterBand(FRTPage, FCurrentRTColumnFooterBand);
+    end;
+  { ColumnFooter could have caused a new column or page }
+  CheckNewOrOverFlow(True);
+  if FHasGroupFooter then
+    HandleLastGroupFooters;
   lPageData.Close;
 end;
 
@@ -9883,10 +9969,11 @@ begin
   Report.EmptyRTObjects;
   FHeaderList.Clear;
   FFooterList.Clear;
+  FGroupList.Clear;
   ClearBandList;
   InitRTCurPageIdx;
   FOverflowed := False;
-  FHasGroupBand := False;
+  FHasGroups := False;
   FHasGroupFooter := False;
   FHasReportSummaryBand := False;
   FDataHeaderPrinted := False;
@@ -9900,7 +9987,6 @@ Procedure TFPReportLayouter.InitDesignPage(aPageIdx : integer);
 begin
   FPageIdx:=aPageIdx;
   FNewPage := True;
-  FNewGroupHeader := True;
   FCurrentColumn := 1;
   FMultiColumn := Pages[aPageIdx].ColumnCount > 1;
   FNewColumn := False;
@@ -9936,6 +10022,31 @@ begin
     end;
 end;
 
+procedure TFPReportLayouter.HandleGroupHeader(
+  aBand: TFPReportCustomGroupHeaderBand);
+var
+  lBand: TFPReportCustomBand;
+begin
+  lBand := aBand;
+  while Assigned(lBand) do
+  begin
+    try
+      CommonRuntimeBandProcessing(lBand);
+      if not lBand.EvaluateVisibility then
+      begin
+        FRTPage.RemoveChild(FRTBand);
+        FRTBand.Free;
+        Continue;
+      end;
+      UpdateSpaceRemaining(FRTBand);
+      if NoSpaceRemaining then
+        CheckNewOrOverFlow;
+    finally
+      lBand := lBand.ChildBand;
+    end;
+  end;
+end;
+
 procedure TFPReportLayouter.RecalcBandLayout(ABand: TFPReportCustomBand);
 
 var
@@ -9995,12 +10106,15 @@ procedure TFPReportLayouter.Execute(aReport: TFPCustomReport);
 begin
   FHeaderList := Nil;
   FFooterList := Nil;
+  FGroupList := Nil;
   FmyReport:=AReport;
   try
     FHeaderList := TBandList.Create;
     FFooterList := TBandList.Create;
+    FGroupList := TBandList.Create;
     DoExecute;
   finally
+    FreeAndNil(FGroupList);
     FreeAndNil(FHeaderList);
     FreeAndNil(FFooterList);
     FMyReport:=Nil; // Don't free :)