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