3 define pgs = 10; // print pgs size
6 public enum Orientation { portrait, landscape };
8 public enum PageFormat { custom, letter, legal, ledger };
10 public enum RenderAction { addPage, closePage, levelStart, levelFinish, groupStart, groupFinish, actualRows };
12 static class PleaseWait : Window
17 text = $"Please wait while the report is being generated...";
18 clientSize = { 400, 30 };
19 ProgressBar progress { this, anchor = { 0,0,0,0 } };
22 static PleaseWait pleaseWait { };
24 public class ReportTitle : Window
27 borderStyle = contour;
30 font = { $"Arial", 14, bold = true };
32 Label { this, foreground = black, anchor = { top = 4 }, labeledWindow = this };
35 static define dpi = 100;
36 class PreviewPage : Window
40 public property Page page
45 if(page && page.report)
47 switch(page.report.pageFormat)
50 if(page.report.orientation == landscape)
51 size = { 11*dpi + shadowS + pgs * 2, 8.5*dpi + shadowS + pgs * 2 };
53 size = { 8.5*dpi + shadowS + pgs * 2, 11*dpi + shadowS + pgs * 2 };
56 if(page.report.orientation == landscape)
57 size = { 14*dpi + shadowS + pgs * 2, 8.5*dpi + shadowS + pgs * 2 };
59 size = { 8.5*dpi + shadowS + pgs * 2, 14*dpi + shadowS + pgs * 2 };
62 if(page.report.orientation == landscape)
63 size = { 17*dpi + shadowS + pgs * 2, 11*dpi + shadowS + pgs * 2 };
65 size = { 11*dpi + shadowS + pgs * 2, 17*dpi + shadowS + pgs * 2 };
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 };
71 size = { page.report.pageSize.h + shadowS + pgs * 2, page.report.pageSize.w + shadowS + pgs * 2 };
79 void OnRedraw(Surface surface)
81 int x = clientSize.w - pgs - 1, y = clientSize.h - pgs - 1;
83 surface.SetBackground(black);
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);
89 surface.SetBackground(white);
91 surface.Area(pgs + 1, pgs + 1, x - shadowS - 1, y - shadowS - 1);
95 public class Page : Window
100 property Report report
107 size = report.pageSize;
108 inside.size = report.pageSize;
111 get { return report; }
114 Window inside { this };
122 public class ReportRender
125 virtual void Render(ReportDestination destination, Report report);
126 virtual int GetPageNumber();
129 static ReportRenderNormal ersCurrentReport;
130 static int ersNumRows;
132 public void ERSProgressAdvanceLevelCheck()
134 if(!ersCurrentReport.level)
137 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
138 pleaseWait.progress.progress = ersNumRows;
139 ((GuiApplication)__thisModule.application).ProcessInput(true);
140 pleaseWait.UpdateDisplay();
144 public void ERSProgressAdvance()
146 if(!ersNumRows) ersNumRows++;
148 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
149 pleaseWait.progress.progress = ersNumRows;
150 if(ersNumRows == pleaseWait.progress.range || !(ersNumRows%100))
152 ((GuiApplication)__thisModule.application).ProcessInput(true);
153 pleaseWait.UpdateDisplay();
157 public class ReportRenderNormal : ReportRender
162 void Render(ReportDestination destination, Report report)
164 bool dontAdvance = false;
168 renderAction = levelStart;
169 ersCurrentReport = this;
171 if(!report.groupings[0].filtered)
173 pleaseWait.master = destination.master;
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();
181 for(pageNumber = 1; ; pageNumber++)
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;
194 if(report.reportHeader)
196 reportHeader = eInstance_New(report.reportHeader);
197 reportHeader.anchor = Anchor { left = 0, top = 0, right = 0 };
198 reportHeader.master = destination;
199 reportHeader.parent = inside;
201 pageTop += reportHeader.size.h;
202 reportHeader.Create();
205 /*if(report.reportFooter)
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();
215 if(report.pageHeader)
217 pageHeader = eInstance_New(report.pageHeader);
218 pageHeader.anchor = Anchor { left = 0, top = pageTop, right = 0 };
219 pageHeader.master = destination;
220 pageHeader.parent = inside;
222 pageTop += pageHeader.size.h;
227 if(report.pageFooter)
229 pageFooter = eInstance_New(report.pageFooter);
230 pageFooter.master = destination;
231 pageFooter.parent = inside;
232 footerHeight = pageFooter.size.h;
237 if(report.rowDetail || (renderAction == levelFinish && level > 0) || renderAction == groupFinish)
244 if((renderAction != levelStart && renderAction != groupStart)|| level > 0)
247 for(c = 0; c < ((renderAction == groupStart) ? level : (level + 1)); c++)
249 if(report.groupings[c].continuation)
251 Detail continuation = eInstance_New(report.groupings[c].continuation);
252 continuation.level = c;
253 AddDetailToPage(destination, continuation);
264 if(level == report.groupings.size - 1)
265 renderAction = actualRows;
267 renderAction = groupStart;
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)
276 Detail groupFooter = eInstance_New(report.groupings[level].footer);
277 groupFooter.level = level;
278 if(AddDetailToPage(destination, groupFooter))
280 //dontAdvance = true;
291 renderAction = groupFinish;
296 if(report.Advance(report.groupings[level], level ? report.groupings[level - 1].groupId : 0, &dontAdvance))
298 report.ExecuteRowData(level);
299 if(report.groupings[level].header)
301 Detail groupStart = eInstance_New(report.groupings[level].header);
302 groupStart.level = level;
304 if(AddDetailToPage(destination, groupStart))
310 else if(overlap - groupStart.size.h /** 2*/ < 0)
313 groupStart.Destroy(0);
320 renderAction = levelStart;
324 renderAction = levelFinish;
329 lastDetail.isLast = true;
330 if(report.groupings[level].footer)
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))
337 //dontAdvance = true;
341 else if(hh < 0) // Use the value before calling AddDetailToPage()
349 renderAction = groupStart;
352 if(report.Advance(report.groupings[level], level ? report.groupings[level - 1].groupId : 0, &dontAdvance))
355 if(AddDetailToPage(destination, (detail = eInstance_New(report.rowDetail))))
363 report.ExecuteRowData(level);
369 renderAction = levelFinish;
375 nil = report.nil && renderAction != groupFinish && (renderAction != levelFinish || level == 0);
382 // Cancel group headers if we didn't have space to display any row
386 for(detail = (Detail)inside.lastChild; detail; detail = prev)
388 prev = (Detail)detail.previous;
389 if(level > 0 && detail._class == report.groupings[level-1].header)
393 renderAction = groupStart;
397 if(detail._class == report.groupings[level].footer);
405 lastDetail.isLast = true;
407 if(nil && report.reportFooter)
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 };
417 if(report.pageFooter)
419 if(nil && report.reportFooter)
420 pageFooter.anchor = Anchor { left = 0, bottom = (int)reportFooter.size.h, right = 0 };
422 pageFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
426 if(nil && report.reportFooter)
428 reportFooter.Create();
431 destination.EndPage(page);
436 // still have to bump report footer if it does not fit...
438 pleaseWait.Destroy(0);
449 int overlap, pageTop, insideSize, footerHeight;
450 RenderAction renderAction;
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)
465 detail.anchor = Anchor { left = 0, right = 0 };
466 detail.master = destination;
467 detail.parent = inside;
469 detailSize = detail.size.h;
470 overlap = (insideSize - pageTop - footerHeight) - detailSize;
472 if(overlap < 0 && detail.keepTogether)
480 detail.anchor = Anchor { left = 0, top = pageTop, right = 0 };
481 pageTop += detailSize;
484 // This will probably never go here... (Only report/page headers have keepTogether set to false)
495 public class ReportDestination : Window
498 public property Report report { watchable set { report = value; } get { return report; } }
500 virtual void EndPage(Page page)
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);
506 virtual void AddPage(Page page);
507 virtual Report GetReport() { return null; }
512 List<PreviewPage> pages { };
515 public class PrintedReport : ReportDestination
517 displayDriver = "Win32Printer";
525 SetPrintingDocumentName(report.title);
526 return ReportDestination::OnCreate();
531 size = report.pageSize;
534 void AddPage(Page page)
536 if(pageCount && display)
539 page.anchor = { left = 0, top = 0, right = 0, bottom = 0};
546 void EndPage(Page page)
549 ((GuiApplication)__thisModule.application).ProcessInput(true);
550 ((GuiApplication)__thisModule.application).UpdateDisplay();
560 public class ReportPreviewArea : ReportDestination
562 hasHorzScroll = true;
563 hasVertScroll = true;
564 dontHideScroll = true;
565 background = dimGray;
567 void AddPage(Page page)
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;
583 void OnResize(int width, int height)
585 scroll = { (scrollArea.w - width) / 2, scroll.y };
589 Array<FileFilter> csvFilters
592 $"Comma Separated Values Spreadsheet (*.csv)",
595 { $"All files", null }
598 public class CSVReport : ReportDestination
600 hasHorzScroll = true;
601 hasVertScroll = true;
602 dontHideScroll = true;
603 background = dimGray;
607 void AddPage(Page page)
609 if(pageCount && display)
614 page.size = { MAXINT - 10, MAXINT - 10 };
619 void PutString(File f, const char * text)
626 while((ch = text[s++]) && d < sizeof(output) - 1)
631 int keyCode = UTF8GetChar(text + s - 1, &nb);
638 if(ch == '\"') output[d++] = '\\';
646 FileDialog saveTo { type = save, text = $"Export as Spreadsheet (CSV)", filters = csvFilters.array, sizeFilters = csvFilters.count * sizeof(FileFilter) };
648 void EndPage(Page page)
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;
658 File f = FileOpen(saveTo.filePath, write);
661 Detail detail, first = null;
662 for(detail = (Detail)page.inside.firstChild; detail && detail != first; detail = (Detail)detail.next)
664 if(!first) first = detail;
665 if(eClass_IsDerived(detail._class, class(Detail)))
667 Label label, first = null;
669 if(detail._class == report.pageFooter) continue;
670 if(detail._class == report.groupings[0].header)
672 for(label = (Label)detail.firstChild; label && label != first; label = (Label)label.next)
674 if(!first) first = label;
675 if(label._class == class(ReportTitle) || eClass_IsDerived(label._class, class(Label)))
677 const char * text = label.text;
678 if(label != first)f.Puts(",");
683 if(detail._class == report.groupings[0].header)
700 public class IdFilter : struct
706 bool RowMatch(Row row)
711 if(!row.GetData(field, value))
717 public class Grouping
722 Field field, fieldLink;
724 Array<IdFilter> filters { };
726 // Contractors have a list of trades they're in
729 // Portfolios have the list of contacts for them, Portfolio id goes in fieldLink
730 Field reverseListFieldLink;
731 Grouping reverseLink;
737 subclass(Detail) header;
738 subclass(Detail) continuation;
739 subclass(Detail) footer;
747 virtual bool ShouldSkip()
752 virtual bool Advance(Id linkId, bool *dontAdvance)
755 IdList reverseIdList = null;
757 if(dontAdvance && *dontAdvance)
759 *dontAdvance = false;
762 if(!reverseLink && fieldLink && row.nil)
763 result = row.Find(fieldLink, middle, nil, linkId);
770 reverseLink.row.GetData(reverseListFieldLink, reverseIdList);
772 if(activeOnly && !activeField)
773 activeField = row.tbl.FindField("Active");
777 if(!ersCurrentReport.level)
780 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
781 pleaseWait.progress.progress = ersNumRows;
782 ((GuiApplication)__thisModule.application).ProcessInput(true);
783 pleaseWait.UpdateDisplay();
789 row.GetData(fieldLink, id);
790 if(!reverseIdList || !reverseIdList.Includes(id))
793 else if(listFieldLink)
796 row.GetData(listFieldLink, list);
797 if(!list || !list.Includes(linkId))
804 row.GetData(fieldLink, id);
815 row.GetData(activeField, active);
819 if(result && filtered && filters.size)
822 for(f = 0; f < filters.size && result && filters[f].field; f++)
825 row.GetData(filters[f].field, id);
826 if(id != filters[f].id)
833 if(result && ShouldSkip())
841 row.GetData(field, groupId);
843 delete reverseIdList;
848 delete reverseIdList;
856 public property Orientation orientation { set { orientation = value; UpdateSize(); } get { return orientation; } }
857 public property PageFormat pageFormat { set { pageFormat = value; UpdateSize(); } get { return pageFormat; } }
860 Anchor insideMarginAnchor;
862 Array<Grouping> groupings { };
864 property const String title
870 title = CopyString(value);
877 subclass(Detail) reportHeader;
878 subclass(Detail) reportFooter;
880 subclass(Detail) pageHeader;
881 subclass(Detail) pageFooter;
883 subclass(Detail) rowDetail;
885 virtual bool Advance(Grouping grouping, Id linkId, bool *dontAdvance)
887 return grouping.Advance(linkId, dontAdvance);
890 virtual bool ExecuteData(Database db)
895 virtual void ExecuteRowData(int group);
896 virtual void OnReset();
902 if(groupings && groupings.size && groupings[0].row)
903 return groupings[0].row.nil;
909 Orientation orientation;
910 PageFormat pageFormat;
917 if(orientation == landscape)
918 pageSize = { 11*dpi, 8.5*dpi };
920 pageSize = { 8.5*dpi, 11*dpi };
923 if(orientation == landscape)
924 pageSize = { 14*dpi, 8.5*dpi };
926 pageSize = { 8.5*dpi, 14*dpi };
929 if(orientation == landscape)
930 pageSize = { 17*dpi, 11*dpi };
932 pageSize = { 11*dpi, 17*dpi };
939 property::pageFormat = letter;
950 public class Detail : Window
957 subclass(Detail) rowDetail;