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