3 define pgs = 10; // print pgs size
6 public enum Orientation { portrait, landscape };
8 public enum RenderAction { addPage, closePage, levelStart, levelFinish, groupStart, groupFinish, actualRows };
10 static class PleaseWait : Window
15 text = $"Please wait while the report is being generated...";
16 clientSize = { 400, 30 };
17 ProgressBar progress { this, anchor = { 0,0,0,0 } };
20 static PleaseWait pleaseWait { };
22 public class ReportTitle : Window
25 borderStyle = contour;
28 font = { $"Arial", 14, bold = true };
30 Label { this, foreground = black, anchor = { top = 4 }, labeledWindow = this };
33 class PreviewPage : Window
36 //size = { 850 + shadowS + pgs * 2, 1100 + shadowS + pgs * 2 };
38 public property Orientation orientation
43 size = { 850 + shadowS + pgs * 2, 1100 + shadowS + pgs * 2 };
44 else if(value == landscape)
45 size = { 1100 + shadowS + pgs * 2, 850 + shadowS + pgs * 2 };
49 Orientation orientation;
53 void OnRedraw(Surface surface)
55 int x = clientSize.w - pgs - 1, y = clientSize.h - pgs - 1;
57 surface.SetBackground(black);
59 surface.Rectangle(pgs, pgs, x - shadowS, y - shadowS);
60 surface.Area(pgs + shadowS / 2, y - shadowS + 1, x, y);
61 surface.Area(x - shadowS + 1, pgs + shadowS / 2, x, y);
63 surface.SetBackground(white);
65 surface.Area(pgs + 1, pgs + 1, x - shadowS - 1, y - shadowS - 1);
69 public class Page : Window
74 property Orientation orientation
81 inside.size = { 850, 1100 };
83 else if(value == landscape)
86 inside.size = { 1100, 850 };
92 Window inside { this };
97 Orientation orientation;
100 public class ReportRender
103 virtual void Render(ReportDestination destination, Report report);
104 virtual int GetPageNumber();
107 static ReportRenderNormal ersCurrentReport;
108 static int ersNumRows;
110 public void ERSProgressAdvanceLevelCheck()
112 if(!ersCurrentReport.level)
115 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
116 pleaseWait.progress.progress = ersNumRows;
117 ((GuiApplication)__thisModule.application).ProcessInput(true);
118 pleaseWait.UpdateDisplay();
122 public void ERSProgressAdvance()
124 if(!ersNumRows) ersNumRows++;
126 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
127 pleaseWait.progress.progress = ersNumRows;
128 if(ersNumRows == pleaseWait.progress.range || !(ersNumRows%100))
130 ((GuiApplication)__thisModule.application).ProcessInput(true);
131 pleaseWait.UpdateDisplay();
135 public class ReportRenderNormal : ReportRender
140 void Render(ReportDestination destination, Report report)
142 bool dontAdvance = false;
146 renderAction = levelStart;
147 ersCurrentReport = this;
149 if(!report.groupings[0].filtered)
151 pleaseWait.master = destination.master;
154 pleaseWait.progress.range = report.groupings[0].rowsCount ? report.groupings[0].rowsCount : report.groupings[0].row.rowsCount;
155 pleaseWait.progress.progress = 0;
156 ((GuiApplication)__thisModule.application).ProcessInput(true);
157 pleaseWait.UpdateDisplay();
159 for(pageNumber = 1; true; pageNumber++)
161 Detail lastDetail = null;
162 page = Page { orientation = report.orientation };
163 destination.AddPage(page);
164 inside = page.inside;
165 inside.anchor = report.insideMarginAnchor;
166 insideSize = inside.size.h;
171 if(report.reportHeader)
173 reportHeader = eInstance_New(report.reportHeader);
174 reportHeader.anchor = Anchor { left = 0, top = 0, right = 0 };
175 reportHeader.master = destination;
176 reportHeader.parent = inside;
178 pageTop += reportHeader.size.h;
179 reportHeader.Create();
182 /*if(report.reportFooter)
184 reportFooter = eInstance_New(report.reportFooter);
185 reportFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
186 reportFooter.master = destination;
187 reportFooter.parent = inside;
188 reportFooter.Create();
192 if(report.pageHeader)
194 pageHeader = eInstance_New(report.pageHeader);
195 pageHeader.anchor = Anchor { left = 0, top = pageTop, right = 0 };
196 pageHeader.master = destination;
197 pageHeader.parent = inside;
199 pageTop += pageHeader.size.h;
204 if(report.pageFooter)
206 pageFooter = eInstance_New(report.pageFooter);
207 pageFooter.master = destination;
208 pageFooter.parent = inside;
209 footerHeight = pageFooter.size.h;
221 if((renderAction != levelStart && renderAction != groupStart)|| level > 0)
224 for(c = 0; c < ((renderAction == groupStart) ? level : (level + 1)); c++)
226 if(report.groupings[c].continuation)
228 Detail continuation = eInstance_New(report.groupings[c].continuation);
229 continuation.level = c;
230 AddDetailToPage(destination, continuation);
241 if(level == report.groupings.size - 1)
242 renderAction = actualRows;
244 renderAction = groupStart;
249 // end of rows, end of last group, end of report
250 // TESTING THIS HERE... (UNCOMMENTED AND ADDED CHECK FOR size == 1)
251 if(report.groupings.size == 1 && report.groupings[level].footer)
253 Detail groupFooter = eInstance_New(report.groupings[level].footer);
254 groupFooter.level = level;
255 if(AddDetailToPage(destination, groupFooter))
257 //dontAdvance = true;
268 renderAction = groupFinish;
273 if(report.Advance(report.groupings[level], level ? report.groupings[level - 1].groupId : 0, &dontAdvance))
275 report.ExecuteRowData(level);
276 if(report.groupings[level].header)
278 Detail groupStart = eInstance_New(report.groupings[level].header);
279 groupStart.level = level;
281 if(AddDetailToPage(destination, groupStart))
287 else if(overlap - groupStart.size.h /** 2*/ < 0)
290 groupStart.Destroy(0);
297 renderAction = levelStart;
301 renderAction = levelFinish;
306 lastDetail.isLast = true;
307 if(report.groupings[level].footer)
309 Detail groupEnd = eInstance_New(report.groupings[level].footer);
310 int hh = (insideSize - pageTop - footerHeight) - groupEnd.size.h;
311 groupEnd.level = level;
312 if(AddDetailToPage(destination, groupEnd))
314 //dontAdvance = true;
318 else if(hh < 0) // Use the value before calling AddDetailToPage()
326 renderAction = groupStart;
329 if(report.Advance(report.groupings[level], level ? report.groupings[level - 1].groupId : 0, &dontAdvance))
332 if(AddDetailToPage(destination, (detail = eInstance_New(report.rowDetail))))
340 report.ExecuteRowData(level);
346 renderAction = levelFinish;
359 // Cancel group headers if we didn't have space to display any row
363 for(detail = (Detail)inside.lastChild; detail; detail = prev)
365 prev = (Detail)detail.previous;
366 if(level > 0 && detail._class == report.groupings[level-1].header)
370 renderAction = groupStart;
374 if(detail._class == report.groupings[level].footer);
382 lastDetail.isLast = true;
384 if(nil && report.reportFooter)
386 reportFooter = eInstance_New(report.reportFooter);
387 reportFooter.master = destination;
388 reportFooter.parent = inside;
389 inside.anchor = { left = inside.anchor.left, top = inside.anchor.top };
390 inside.size = { inside.size.w, inside.size.h + reportFooter.size.h };
391 reportFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
394 if(report.pageFooter)
396 if(nil && report.reportFooter)
397 pageFooter.anchor = Anchor { left = 0, bottom = (int)reportFooter.size.h, right = 0 };
399 pageFooter.anchor = Anchor { left = 0, bottom = 0, right = 0 };
403 if(nil && report.reportFooter)
405 reportFooter.Create();
408 destination.EndPage(page);
413 // still have to bump report footer if it does not fit...
415 pleaseWait.Destroy(0);
426 int overlap, pageTop, insideSize, footerHeight;
427 RenderAction renderAction;
437 // This function returns true if it there's no room and we should display on next page
438 bool AddDetailToPage(ReportDestination destination, Detail detail)
442 detail.anchor = Anchor { left = 0, right = 0 };
443 detail.master = destination;
444 detail.parent = inside;
446 detailSize = detail.size.h;
447 overlap = (insideSize - pageTop - footerHeight) - detailSize;
449 if(overlap < 0 && detail.keepTogether)
457 detail.anchor = Anchor { left = 0, top = pageTop, right = 0 };
458 pageTop += detailSize;
461 // This will probably never go here... (Only report/page headers have keepTogether set to false)
472 public class ReportDestination : Window
477 virtual void EndPage(Page page)
479 SetScrollArea(page.master.size.w, page.master.position.y + page.master.size.h, false);
480 SetScrollPosition((page.master.size.w - clientSize.w) / 2, 0);
483 virtual void AddPage(Page page);
484 virtual Report GetReport() { return null; }
488 List<PreviewPage> pages { };
491 public class PrintedReport : ReportDestination
493 displayDriver = "Win32Printer";
501 SetPrintingDocumentName(report.title);
502 return ReportDestination::OnCreate();
505 void AddPage(Page page)
507 if(pageCount && display)
510 page.anchor = { left = 0, top = 0, right = 0, bottom = 0};
517 void EndPage(Page page)
520 ((GuiApplication)__thisModule.application).ProcessInput(true);
521 ((GuiApplication)__thisModule.application).UpdateDisplay();
531 public class ReportPreviewArea : ReportDestination
533 hasHorzScroll = true;
534 hasVertScroll = true;
535 dontHideScroll = true;
536 background = dimGray;
538 void AddPage(Page page)
540 PreviewPage previewPage { this, this, page = page, orientation = page.orientation,
541 anchor = { top = pageCount * ((int)page.size.h + shadowS + pgs) } };
542 previewPage.Create();
543 page.anchor = { left = pgs, top = pgs, right = shadowS + pgs, bottom = shadowS + pgs};
544 page.master = previewPage;
545 page.parent = previewPage;
555 void OnResize(int width, int height)
557 scroll = { (scrollArea.w - width) / 2, scroll.y };
561 Array<FileFilter> csvFilters
564 $"Comma Separated Values Spreadsheet (*.csv)",
567 { $"All files", null }
570 public class CSVReport : ReportDestination
572 hasHorzScroll = true;
573 hasVertScroll = true;
574 dontHideScroll = true;
575 background = dimGray;
579 void AddPage(Page page)
582 if(pageCount && display)
587 page.size = { MAXINT - 10, MAXINT - 10 };
593 void PutString(File f, char * text)
600 while((ch = text[s++]) && d < sizeof(output) - 1)
605 int keyCode = UTF8GetChar(text + s - 1, &nb);
612 if(ch == '\"') output[d++] = '\\';
620 FileDialog saveTo { type = save, text = $"Export as Spreadsheet (CSV)", filters = csvFilters.array, sizeFilters = csvFilters.count * sizeof(FileFilter) };
622 void EndPage(Page page)
624 char filePath[MAX_LOCATION];
625 strcpy(filePath, report.title);
626 strcat(filePath, ".csv");
627 saveTo.master = master;
628 saveTo.filePath = filePath;
631 File f = FileOpen(saveTo.filePath, write);
634 Detail detail, first = null;
635 for(detail = (Detail)page.inside.firstChild; detail && detail != first; detail = (Detail)detail.next)
637 if(!first) first = detail;
638 if(eClass_IsDerived(detail._class, class(Detail)))
640 Label label, first = null;
642 if(detail._class == report.pageFooter) continue;
643 if(detail._class == report.groupings[0].header)
645 for(label = (Label)detail.firstChild; label && label != first; label = (Label)label.next)
647 if(!first) first = label;
648 if(label._class == class(ReportTitle) || eClass_IsDerived(label._class, class(Label)))
650 char * text = label.text;
651 if(label != first)f.Puts(",");
656 if(detail._class == report.groupings[0].header)
673 public class IdFilter : struct
679 bool RowMatch(Row row)
684 if(!row.GetData(field, value))
690 public class Grouping
695 Field field, fieldLink;
697 Array<IdFilter> filters { };
699 // Contractors have a list of trades they're in
702 // Portfolios have the list of contacts for them, Portfolio id goes in fieldLink
703 Field reverseListFieldLink;
704 Grouping reverseLink;
710 subclass(Detail) header;
711 subclass(Detail) continuation;
712 subclass(Detail) footer;
720 virtual bool ShouldSkip()
725 virtual bool Advance(Id linkId, bool *dontAdvance)
728 IdList reverseIdList = null;
730 if(dontAdvance && *dontAdvance)
732 *dontAdvance = false;
735 if(!reverseLink && fieldLink && row.nil)
736 result = row.Find(fieldLink, middle, nil, linkId);
743 reverseLink.row.GetData(reverseListFieldLink, reverseIdList);
745 if(activeOnly && !activeField)
746 activeField = row.tbl.FindField("Active");
750 if(!ersCurrentReport.level)
753 ersNumRows = Min(ersNumRows, pleaseWait.progress.range);
754 pleaseWait.progress.progress = ersNumRows;
755 ((GuiApplication)__thisModule.application).ProcessInput(true);
756 pleaseWait.UpdateDisplay();
762 row.GetData(fieldLink, id);
763 if(!reverseIdList || !reverseIdList.Includes(id))
766 else if(listFieldLink)
769 row.GetData(listFieldLink, list);
770 if(!list || !list.Includes(linkId))
777 row.GetData(fieldLink, id);
788 row.GetData(activeField, active);
792 if(result && filtered && filters.size)
795 for(f = 0; f < filters.size && result && filters[f].field; f++)
798 row.GetData(filters[f].field, id);
799 if(id != filters[f].id)
806 if(result && ShouldSkip())
814 row.GetData(field, groupId);
816 delete reverseIdList;
821 delete reverseIdList;
829 Orientation orientation;
830 Anchor insideMarginAnchor;
832 Array<Grouping> groupings { };
834 property String title
840 title = CopyString(value);
847 subclass(Detail) reportHeader;
848 subclass(Detail) reportFooter;
850 subclass(Detail) pageHeader;
851 subclass(Detail) pageFooter;
853 subclass(Detail) rowDetail;
855 virtual bool Advance(Grouping grouping, Id linkId, bool *dontAdvance)
857 return grouping.Advance(linkId, dontAdvance);
860 virtual bool ExecuteData(Database db)
865 virtual void ExecuteRowData(int group);
866 virtual void OnReset();
872 if(groupings && groupings.size && groupings[0].row)
873 return groupings[0].row.nil;
886 public class Detail : Window
893 subclass(Detail) rowDetail;