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