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