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