ecere/gui/Window: (#789) Fixed snapshot extent using native decorations
[sdk] / eda / libeda / src / ers.ec
1 import "idList"
2
3 define pgs = 10; // print pgs size
4 define margin = 36;
5
6 public enum Orientation { portrait, landscape };
7
8 public enum PageFormat { custom, letter, legal, ledger };
9
10 public enum RenderAction { addPage, closePage, levelStart, levelFinish, groupStart, groupFinish, actualRows };
11
12 static class PleaseWait : Window
13 {
14    isModal = true;
15    autoCreate = false;
16    borderStyle = fixed;
17    text = $"Please wait while the report is being generated...";
18    clientSize = { 400, 30 };
19    ProgressBar progress { this, anchor = { 0,0,0,0 } };
20 }
21
22 static PleaseWait pleaseWait { };
23
24 public class ReportTitle : Window
25 {
26    size = { 500, 32 };
27    borderStyle = contour;
28    background = white;
29
30    font = { $"Arial", 14, bold = true };
31
32    Label { this, foreground = black, anchor = { top = 4 }, labeledWindow = this };
33 }
34
35 static define dpi = 100;
36 class PreviewPage : Window
37 {
38    background = dimGray;
39    noConsequential = true;
40    public property Page page
41    {
42       set
43       {
44          page = value;
45          if(page && page.report)
46          {
47             switch(page.report.pageFormat)
48             {
49                case letter:
50                   if(page.report.orientation == landscape)
51                      size = { 11*dpi + shadowS + pgs * 2, 8.5*dpi + shadowS + pgs * 2 };
52                   else
53                      size = { 8.5*dpi + shadowS + pgs * 2, 11*dpi + shadowS + pgs * 2 };
54                   break;
55                case legal:
56                   if(page.report.orientation == landscape)
57                      size = { 14*dpi + shadowS + pgs * 2, 8.5*dpi + shadowS + pgs * 2 };
58                   else
59                      size = { 8.5*dpi + shadowS + pgs * 2, 14*dpi + shadowS + pgs * 2 };
60                   break;
61                case ledger:
62                   if(page.report.orientation == landscape)
63                      size = { 17*dpi + shadowS + pgs * 2, 11*dpi + shadowS + pgs * 2 };
64                   else
65                      size = { 11*dpi + shadowS + pgs * 2, 17*dpi + shadowS + pgs * 2 };
66                   break;
67                case custom:
68                   if(page.report.orientation == landscape && page.report.pageSize.w > page.report.pageSize.h)
69                      size = { page.report.pageSize.w + shadowS + pgs * 2, page.report.pageSize.h + shadowS + pgs * 2 };
70                   else
71                      size = { page.report.pageSize.h + shadowS + pgs * 2, page.report.pageSize.w + shadowS + pgs * 2 };
72                   break;
73             }
74          }
75       }
76    }
77    Page page;
78
79    void OnRedraw(Surface surface)
80    {
81       int x = clientSize.w - pgs - 1, y = clientSize.h - pgs - 1;
82
83       surface.SetBackground(black);
84
85       surface.Rectangle(pgs, pgs, x - shadowS, y - shadowS);
86       surface.Area(pgs + shadowS / 2, y - shadowS + 1, x, y);
87       surface.Area(x - shadowS + 1, pgs + shadowS / 2, x, y);
88
89       surface.SetBackground(white);
90
91       surface.Area(pgs + 1, pgs + 1, x - shadowS - 1, y - shadowS - 1);
92    }
93 }
94
95 public class Page : Window
96 {
97    background = white;
98
99 public:
100    property Report report
101    {
102       set
103       {
104          report = value;
105          if(report)
106          {
107             size = report.pageSize;
108             inside.size = report.pageSize;
109          }
110       }
111       get { return report; }
112    }
113
114    Window inside { this, noConsequential = true };
115
116    int headerHeight;
117
118 private:
119    Report report;
120 }
121
122 public class ReportRender
123 {
124 public:
125    virtual void Render(ReportDestination destination, Report report);
126    virtual int GetPageNumber();
127 }
128
129 static ReportRenderNormal ersCurrentReport;
130 static int ersNumRows;
131
132 public void ERSProgressAdvanceLevelCheck()
133 {
134    if(!ersCurrentReport.level)
135    {
136       ersNumRows++;
137       ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
138       pleaseWait.progress.progress = ersNumRows;
139       ((GuiApplication)__thisModule.application).ProcessInput(true);
140       pleaseWait.UpdateDisplay();
141    }
142 }
143
144 public void ERSProgressAdvance()
145 {
146    if(!ersNumRows) ersNumRows++;
147    ersNumRows++;
148    ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
149    pleaseWait.progress.progress = ersNumRows;
150    if(ersNumRows == pleaseWait.progress.range || !(ersNumRows%100))
151    {
152       ((GuiApplication)__thisModule.application).ProcessInput(true);
153       pleaseWait.UpdateDisplay();
154    }
155 }
156
157 public class ReportRenderNormal : ReportRender
158 {
159 public:
160    int pageNumber;
161
162    void Render(ReportDestination destination, Report report)
163    {
164       bool dontAdvance = false;
165       bool nil;
166       level = 0;
167       overlap = 0;
168       renderAction = levelStart;
169       ersCurrentReport = this;
170       ersNumRows = 0;
171       if(!report.groupings[0].filtered)
172       {
173          pleaseWait.master = destination.master;
174          pleaseWait.Create();
175
176          pleaseWait.progress.range = report.groupings[0].rowsCount ? report.groupings[0].rowsCount : report.groupings[0].row.rowsCount;
177          pleaseWait.progress.progress = 0;
178          ((GuiApplication)__thisModule.application).ProcessInput(true);
179          pleaseWait.UpdateDisplay();
180       }
181       for(pageNumber = 1; ; pageNumber++)
182       {
183          Detail lastDetail = null;
184          page = Page { report = report };
185          destination.AddPage(page);
186          inside = page.inside;
187          inside.anchor = Anchor { left = report.insideMarginAnchor.left.distance, top = report.insideMarginAnchor.top.distance,
188                                   right = report.insideMarginAnchor.right.distance, bottom = report.insideMarginAnchor.bottom.distance };
189          insideSize = inside.size.h;
190
191          pageTop = 0;
192          if(pageNumber == 1)
193          {
194             if(report.reportHeader)
195             {
196                reportHeader = eInstance_New(report.reportHeader);
197                reportHeader.anchor = Anchor { left = 0, top = 0, right = 0 };
198                reportHeader.master = destination;
199                reportHeader.parent = inside;
200
201                pageTop += reportHeader.size.h;
202                reportHeader.Create();
203
204             }
205             /*if(report.reportFooter)
206             {
207                reportFooter = eInstance_New(report.reportFooter);
208                reportFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
209                reportFooter.master = destination;
210                reportFooter.parent = inside;
211                reportFooter.Create();
212             }*/
213          }
214
215          if(report.pageHeader)
216          {
217             pageHeader = eInstance_New(report.pageHeader);
218             pageHeader.anchor = Anchor { left = 0, top = pageTop, right = 0 };
219             pageHeader.master = destination;
220             pageHeader.parent = inside;
221
222             pageTop += pageHeader.size.h;
223
224             pageHeader.Create();
225          }
226
227          if(report.pageFooter)
228          {
229             pageFooter = eInstance_New(report.pageFooter);
230             pageFooter.master = destination;
231             pageFooter.parent = inside;
232             footerHeight = pageFooter.size.h;
233          }
234          else
235             footerHeight = 0;
236
237          if(report.rowDetail || (renderAction == levelFinish && level > 0) || renderAction == groupFinish)
238          {
239             bool loop;
240             //int levelHead = 0;
241             //int levelFoot = 0;
242             pageTop += overlap;
243
244             if((renderAction != levelStart && renderAction != groupStart)|| level > 0)
245             {
246                int c;
247                for(c = 0; c < ((renderAction == groupStart) ? level : (level + 1)); c++)
248                {
249                   if(report.groupings[c].continuation)
250                   {
251                      Detail continuation = eInstance_New(report.groupings[c].continuation);
252                      continuation.level = c;
253                      AddDetailToPage(destination, continuation);
254                   }
255                }
256             }
257
258             loop = true;
259             for( ; loop; )
260             {
261                switch(renderAction)
262                {
263                   case levelStart:
264                      if(level == report.groupings.size - 1)
265                         renderAction = actualRows;
266                      else
267                         renderAction = groupStart;
268                      break;
269                   case levelFinish:
270                      if(level == 0)
271                      {
272                         // end of rows, end of last group, end of report
273                         // TESTING THIS HERE... (UNCOMMENTED AND ADDED CHECK FOR size == 1)
274                         if(report.groupings.size == 1 && report.groupings[level].footer)
275                         {
276                            Detail groupFooter = eInstance_New(report.groupings[level].footer);
277                            groupFooter.level = level;
278                            if(AddDetailToPage(destination, groupFooter))
279                            {
280                               //dontAdvance = true;
281                               loop = false;
282                               break;
283                            }
284                         }
285                         loop = false;
286                         break;
287                      }
288                      else
289                      {
290                         level--;
291                         renderAction = groupFinish;
292                      }
293                      break;
294                   case groupStart:
295                      lastDetail = null;
296                      if(report.Advance(level, &dontAdvance))
297                      {
298                         report.ExecuteRowData(level);
299                         if(report.groupings[level].header)
300                         {
301                            Detail groupStart = eInstance_New(report.groupings[level].header);
302                            groupStart.level = level;
303
304                            if(AddDetailToPage(destination, groupStart))
305                            {
306                               dontAdvance = true;
307                               loop = false;
308                               break;
309                            }
310                            else if(overlap - groupStart.size.h /** 2*/ < 0)
311                            {
312                               overlap = 0;
313                               groupStart.Destroy(0);
314                               dontAdvance = true;
315                               loop = false;
316                               break;
317                            }
318                         }
319                         level++;
320                         renderAction = levelStart;
321                      }
322                      else
323                      {
324                         renderAction = levelFinish;
325                      }
326                      break;
327                   case groupFinish:
328                      if(lastDetail)
329                         lastDetail.isLast = true;
330                      if(report.groupings[level].footer)
331                      {
332                         Detail groupEnd = eInstance_New(report.groupings[level].footer);
333                         int hh = (insideSize - pageTop - footerHeight) - groupEnd.size.h;
334                         groupEnd.level = level;
335                         if(AddDetailToPage(destination, groupEnd))
336                         {
337                            //dontAdvance = true;
338                            loop = false;
339                            break;
340                         }
341                         else if(hh < 0)   // Use the value before calling AddDetailToPage()
342                         {
343                            overlap = 0;
344                            groupEnd.Destroy(0);
345                            loop = false;
346                            break;
347                         }
348                      }
349                      renderAction = groupStart;
350                      break;
351                   case actualRows:
352                      if(report.Advance(level, &dontAdvance))
353                      {
354                         Detail detail;
355                         if(AddDetailToPage(destination, (detail = eInstance_New(report.rowDetail))))
356                         {
357                            dontAdvance = true;
358                            loop = false;
359                            break;
360                         }
361                         else
362                         {
363                            report.ExecuteRowData(level);
364                            lastDetail = detail;
365                         }
366                      }
367                      else
368                      {
369                         renderAction = levelFinish;
370                      }
371                      break;
372
373                }
374             }
375             nil = report.nil && renderAction != groupFinish && (renderAction != levelFinish || level == 0);
376          }
377          else
378          {
379             nil = true;
380          }
381
382          // Cancel group headers if we didn't have space to display any row
383          if(!lastDetail)
384          {
385             Detail detail, prev;
386             for(detail = (Detail)inside.lastChild; detail; detail = prev)
387             {
388                prev = (Detail)detail.previous;
389                if(level > 0 && detail._class == report.groupings[level-1].header)
390                {
391                   detail.Destroy(0);
392                   level--;
393                   renderAction = groupStart;
394                }
395                else
396                {
397                   if(detail._class == report.groupings[level].footer);
398                   else
399                      lastDetail = detail;
400                   break;
401                }
402             }
403          }
404          if(lastDetail)
405             lastDetail.isLast = true;
406
407          if(nil && report.reportFooter)
408          {
409             reportFooter = eInstance_New(report.reportFooter);
410             reportFooter.master = destination;
411             reportFooter.parent = inside;
412             inside.anchor = { left = inside.anchor.left, top = inside.anchor.top };
413             inside.size = { inside.size.w, inside.size.h + reportFooter.size.h };
414             reportFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
415          }
416
417          if(report.pageFooter)
418          {
419             if(nil && report.reportFooter)
420                pageFooter.anchor = Anchor { left = 0, bottom = (int)reportFooter.size.h, right = 0 };
421             else
422                pageFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
423
424             pageFooter.Create();
425          }
426          if(nil && report.reportFooter)
427          {
428             reportFooter.Create();
429          }
430
431          destination.EndPage(page);
432
433          if(nil)
434             break;
435
436          // still have to bump report footer if it does not fit...
437       }
438       pleaseWait.Destroy(0);
439    }
440
441    int GetPageNumber()
442    {
443       return pageNumber;
444    }
445
446    int level;
447
448 private:
449    int overlap, pageTop, insideSize, footerHeight;
450    RenderAction renderAction;
451
452    Page page;
453    Window inside;
454
455    Detail reportHeader;
456    Detail reportFooter;
457    Detail pageHeader;
458    Detail pageFooter;
459
460    // This function returns true if it there's no room and we should display on next page
461    bool AddDetailToPage(ReportDestination destination, Detail detail)
462    {
463       int detailSize;
464
465       detail.anchor = Anchor { left = 0, right = 0 };
466       detail.master = destination;
467       detail.parent = inside;
468
469       detailSize = detail.size.h;
470       overlap = (insideSize - pageTop - footerHeight) - detailSize;
471
472       if(overlap < 0 && detail.keepTogether)
473       {
474          delete detail;
475          overlap = 0;
476          return true;
477       }
478       else
479       {
480          detail.anchor = Anchor { left = 0, top = pageTop, right = 0 };
481          pageTop += detailSize;
482
483          detail.Create();
484          // This will probably never go here... (Only report/page headers have keepTogether set to false)
485          /*if(overlap < 0)
486          {
487             printf("bug");
488          }*/
489
490       }
491       return false;
492    }
493 }
494
495 public class ReportDestination : Window
496 {
497 public:
498    public property Report report { watchable set { report = value; } get { return report; } }
499
500    virtual void EndPage(Page page)
501    {
502       SetScrollArea(page.master.size.w, page.master.position.y + page.master.size.h, false);
503       SetScrollPosition((page.master.size.w - clientSize.w) / 2, 0);
504    }
505
506    virtual void AddPage(Page page);
507    virtual Report GetReport() { return null; }
508 private:
509    Report report;
510    int pageCount;
511
512    List<PreviewPage> pages { };
513 }
514
515 public class PrintedReport : ReportDestination
516 {
517    displayDriver = "Win32Printer";
518    fullRender = true;
519
520    Page lastPage;
521
522    bool OnCreate()
523    {
524       if(report)
525          SetPrintingDocumentName(report.title);
526       return ReportDestination::OnCreate();
527    }
528
529    watch(report)
530    {
531       size = report.pageSize;
532    };
533
534    void AddPage(Page page)
535    {
536       if(pageCount && display)
537          display.NextPage();
538       lastPage = page;
539       page.anchor = { left = 0, top = 0, right = 0, bottom = 0 };
540       page.master = this;
541       page.parent = this;
542       pageCount++;
543       page.Create();
544    }
545
546    void EndPage(Page page)
547    {
548       Update(null);
549       ((GuiApplication)__thisModule.application).ProcessInput(true);
550       ((GuiApplication)__thisModule.application).UpdateDisplay();
551       lastPage.Destroy(0);
552    }
553
554    Report GetReport()
555    {
556       return report;
557    }
558 }
559
560 public class ReportPreviewArea : ReportDestination
561 {
562    hasHorzScroll = true;
563    hasVertScroll = true;
564    dontHideScroll = true;
565    background = dimGray;
566
567    void AddPage(Page page)
568    {
569       PreviewPage previewPage { this, this, page = page, anchor = { top = pageCount * ((int)page.size.h + shadowS + pgs) } };
570       previewPage.Create();
571       page.anchor = { left = pgs, top = pgs, right = shadowS + pgs, bottom = shadowS + pgs};
572       page.master = previewPage;
573       page.parent = previewPage;
574       page.Create();
575       pageCount++;
576    }
577
578    Report GetReport()
579    {
580       return report;
581    }
582
583    void OnResize(int width, int height)
584    {
585       scroll = { (scrollArea.w - width) / 2, scroll.y };
586    }
587 }
588
589 Array<FileFilter> csvFilters
590 { [
591    {
592       $"Comma Separated Values Spreadsheet (*.csv)",
593       "csv"
594    },
595    { $"All files", null }
596 ] };
597
598 public class CSVReport : ReportDestination
599 {
600    hasHorzScroll = true;
601    hasVertScroll = true;
602    dontHideScroll = true;
603    background = dimGray;
604
605    Page lastPage;
606
607    void AddPage(Page page)
608    {
609       if(pageCount && display)
610          display.NextPage();
611       lastPage = page;
612       page.master = this;
613       page.parent = this;
614       page.size = { MAXINT - 10, MAXINT - 10 };
615       pageCount++;
616       page.Create();
617    }
618
619    void PutString(File f, const char * text)
620    {
621       char output[4096];
622       int s = 0, d = 0;
623       byte ch;
624
625       output[d++] = '"';
626       while((ch = text[s++]) && d < sizeof(output) - 1)
627       {
628          if(ch > 127)
629          {
630             int nb;
631             int keyCode = UTF8GetChar(text + s - 1, &nb);
632             s += nb - 1;
633             if(keyCode < 256)
634                ch = (byte)keyCode;
635             else
636                ch = '?';
637          }
638          if(ch == '\"') output[d++] = '\\';
639          output[d++] = ch;
640       }
641       output[d++] = '"';
642       output[d] = 0;
643       f.Puts(output);
644    }
645
646    FileDialog saveTo { type = save, text = $"Export as Spreadsheet (CSV)", filters = csvFilters.array, sizeFilters = csvFilters.count * sizeof(FileFilter) };
647
648    void EndPage(Page page)
649    {
650       char filePath[MAX_LOCATION];
651       strcpy(filePath, report.title);
652       ChangeChars(filePath, "/\\:*?\"|<>", '_');
653       strcat(filePath, ".csv");
654       saveTo.master = master;
655       saveTo.filePath = filePath;
656       if(saveTo.Modal())
657       {
658          File f = FileOpen(saveTo.filePath, write);
659          if(f)
660          {
661             Detail detail, first = null;
662             for(detail = (Detail)page.inside.firstChild; detail && detail != first; detail = (Detail)detail.next)
663             {
664                if(!first) first = detail;
665                if(eClass_IsDerived(detail._class, class(Detail)))
666                {
667                   Label label, first = null;
668
669                   if(detail._class == report.pageFooter) continue;
670                   if(detail._class == report.groupings[0].header)
671                      f.Puts("\n");
672                   for(label = (Label)detail.firstChild; label && label != first; label = (Label)label.next)
673                   {
674                      if(!first) first = label;
675                      if(label._class == class(ReportTitle) || eClass_IsDerived(label._class, class(Label)))
676                      {
677                         const char * text = label.text;
678                         if(label != first)f.Puts(",");
679                         if(text)
680                            PutString(f, text);
681                      }
682                   }
683                   if(detail._class == report.groupings[0].header)
684                      f.Puts("\n");
685                   f.Puts("\n");
686                }
687             }
688          }
689          delete f;
690       }
691       lastPage.Destroy(0);
692    }
693
694    Report GetReport()
695    {
696       return report;
697    }
698 }
699
700 public class IdFilter : struct
701 {
702 public:
703    Id id;
704    Field field;
705
706    bool RowMatch(Row row)
707    {
708       Id value;
709       if(!field)
710          return false;
711       if(!row.GetData(field, value))
712          return false;
713       return value == id;
714    }
715 }
716
717 public class Grouping
718 {
719 public:
720    Id groupId;
721    Row row;
722    Field field, fieldLink;
723    bool filtered;
724    Array<IdFilter> filters { };
725
726    // Contractors have a list of trades they're in
727    Field listFieldLink;
728
729    // Portfolios have the list of contacts for them, Portfolio id goes in fieldLink
730    Field reverseListFieldLink;
731    Grouping reverseLink;
732
733    bool activeOnly;
734    Field activeField;
735    uint rowsCount;
736
737    subclass(Detail) header;
738    subclass(Detail) continuation;
739    subclass(Detail) footer;
740
741    ~Grouping()
742    {
743       filters.Free();
744       delete row;
745    }
746
747    virtual bool ShouldSkip()
748    {
749       return false;
750    }
751
752    virtual bool Advance(Array<Grouping> groupings, int level, bool *dontAdvance)
753    {
754       bool result;
755       Id linkId = level ? groupings[level - 1].groupId : 0;
756       IdList reverseIdList = null;
757
758       if(dontAdvance && *dontAdvance)
759       {
760          *dontAdvance = false;
761          return !row.nil;
762       }
763       if(!reverseLink && fieldLink && row.nil)
764          result = row.Find(fieldLink, middle, nil, linkId);
765       else
766          result = row.Next();
767       if(!result)
768          return false;
769
770       if(reverseLink)
771          reverseLink.row.GetData(reverseListFieldLink, reverseIdList);
772
773       if(activeOnly && !activeField)
774          activeField = row.tbl.FindField("Active");
775
776       while(result)
777       {
778          if(!ersCurrentReport.level)
779          {
780             ersNumRows++;
781             ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
782             pleaseWait.progress.progress = ersNumRows;
783             ((GuiApplication)__thisModule.application).ProcessInput(true);
784             pleaseWait.UpdateDisplay();
785          }
786
787          if(reverseLink)
788          {
789             Id id = 0;
790             row.GetData(fieldLink, id);
791             if(!reverseIdList || !reverseIdList.Includes(id))
792                result = false;
793          }
794          else if(listFieldLink)
795          {
796             IdList list = null;
797             row.GetData(listFieldLink, list);
798             if(!list || !list.Includes(linkId))
799                result = false;
800             delete list;
801          }
802          else if(fieldLink)
803          {
804             // WARNING: This implementation may not work properly with more than 1 level of grouping
805             Id id = 0;
806             row.GetData(fieldLink, id);
807             if(id != linkId)
808             {
809                row.Last();
810                row.Next();
811                return false;
812             }
813          }
814          if(activeOnly)
815          {
816             bool active = true;
817             row.GetData(activeField, active);
818             if(!active)
819                result = false;
820          }
821          if(result && filtered && filters.size)
822          {
823             int f;
824             for(f = 0; f < filters.size && result && filters[f].field; f++)
825             {
826                Id id = 0;
827                row.GetData(filters[f].field, id);
828                if(id != filters[f].id)
829                {
830                   result = false;
831                   break;
832                }
833             }
834          }
835          if(result && ShouldSkip())
836             result = false;
837
838          if(result)
839          {
840             if(field)
841             {
842                groupId = 0;
843                row.GetData(field, groupId);
844             }
845             delete reverseIdList;
846             return true;
847          }
848          result = row.Next();
849       }
850       delete reverseIdList;
851       return result;
852    }
853 }
854
855 public class Report
856 {
857 public:
858    public property Orientation orientation { set { orientation = value; UpdateSize(); } get { return orientation; } }
859    public property PageFormat pageFormat { set { pageFormat = value; UpdateSize(); } get { return pageFormat; } }
860
861    Size pageSize;
862    Anchor insideMarginAnchor;
863
864    Array<Grouping> groupings { };
865
866    property const String title
867    {
868       set
869       {
870          delete title;
871          if(value)
872             title = CopyString(value);
873       }
874    }
875    String title;
876
877    ReportRender render;
878
879    subclass(Detail) reportHeader;
880    subclass(Detail) reportFooter;
881
882    subclass(Detail) pageHeader;
883    subclass(Detail) pageFooter;
884
885    subclass(Detail) rowDetail;
886
887    virtual bool Advance(int level, bool *dontAdvance)
888    {
889       return groupings[level].Advance(groupings, level, dontAdvance);
890    }
891
892    virtual bool ExecuteData(Database db)
893    {
894       return false;
895    }
896
897    virtual void ExecuteRowData(int group);
898    virtual void OnReset();
899
900    property bool nil
901    {
902       get
903       {
904          if(groupings && groupings.size && groupings[0].row)
905             return groupings[0].row.nil;
906          return true;
907       }
908    }
909
910 private:
911    Orientation orientation;
912    PageFormat pageFormat;
913
914    void UpdateSize()
915    {
916       switch(pageFormat)
917       {
918          case letter:
919             if(orientation == landscape)
920                pageSize = { 11*dpi, 8.5*dpi };
921             else
922                pageSize = { 8.5*dpi, 11*dpi };
923             break;
924          case legal:
925             if(orientation == landscape)
926                pageSize = { 14*dpi, 8.5*dpi };
927             else
928                pageSize = { 8.5*dpi, 14*dpi };
929             break;
930          case ledger:
931             if(orientation == landscape)
932                pageSize = { 17*dpi, 11*dpi };
933             else
934                pageSize = { 11*dpi, 17*dpi };
935             break;
936       }
937    }
938
939    Report()
940    {
941       property::pageFormat = letter;
942    }
943
944    ~Report()
945    {
946       groupings.Free();
947       delete title;
948       delete render;
949    }
950 }
951
952 public class Detail : Window
953 {
954 public:
955    bool keepTogether;
956    bool isLast;
957    int level;
958
959    creationActivation = doNothing;
960    noConsequential = true;
961
962    subclass(Detail) rowDetail;
963 }