ide/ProjectSettings: Opted for simpler code using SelectorBar::RemoveButton() which...
[sdk] / ide / src / ProjectSettings.ec
1 import "ide"
2 import "WorkspaceSettings"
3 import "ProjectTabSettings"
4 import "StringsBox"
5 // import "SelectorBar"
6
7 static ProjectConfig config;
8 static Platform platform;
9 static ProjectNode currentNode;
10 static Project project;
11
12 static String MakeString(char * s, int len)
13 {
14    String string = new char[len+1];
15    memcpy(string, s, len);
16    string[len] = 0;
17    return string;
18 }
19
20 class StringListBox : EditBox
21 {
22    textHorzScroll = true;
23
24    property Array<String> strings
25    {
26       set
27       {
28          contents = "";
29          if(value)
30          {
31             bool first = true;
32             for(item : value)
33             {
34                bool quoted = strchr(item, ' ') != null;
35                if(!first)
36                   AddS(" ");
37                if(quoted)
38                   AddS("\"");
39                AddS(item);
40                if(quoted)
41                   AddS("\"");
42                first = false;
43             }
44          }
45       }
46       get
47       {
48          Array<String> array { };
49          int c, start = 0;
50          char * contents = property::contents;
51          char ch;
52          bool quoted = false;
53
54          for(c = 0; (ch = contents[c]); c++)
55          {
56             if(ch == ' ' && !quoted)
57             {
58                if(c - start)
59                   array.Add(MakeString(contents + start, c - start));
60                start = c + 1;
61             }
62             else if(ch == '\"')
63             {
64                if(quoted)
65                {
66                   if(c - start)
67                      array.Add(MakeString(contents + start, c - start));
68                   quoted = false;
69                   start = c + 1;
70                }
71                else
72                {
73                   quoted = true;
74                   start = c + 1;
75                }
76             }
77          }
78          if(c - start)
79             array.Add(MakeString(contents + start, c - start));
80          return array;
81       }
82    }
83 }
84
85 define dialogTitle = $"Project Settings";
86 static Color unfocusedSelectorColor { 70, 96, 166 };
87 class ProjectSettings : Window
88 {
89    text = dialogTitle;
90    background = activeBorder;
91    borderStyle = sizable;
92    minClientSize = { 650, 490 };
93    hasClose = true;
94    tabCycle = true;
95    size = { 650, 490 };
96
97    property Project project
98    {
99       set
100       {
101          ::project = value;
102          projectTab.project = value;
103          buildTab.Init();
104
105          buildTab.SelectNode(project.topNode, false);
106
107          if(project && project.topNode && project.topNode.name && project.topNode.name[0])
108             UpdateDialogTitle();
109       }
110       get { return ::project; }
111    };
112
113    property ProjectNode projectNode
114    {
115       set { buildTab.SelectNode(value, false); }
116       get { return currentNode; }
117    }
118
119    void UpdateDialogTitle()
120    {
121       //char * s = PrintString("Project Settings - ", project.topNode.fileName);
122       //text = s;
123       char * projectName = new char[strlen(project.topNode.name) + 1];
124       char * nodeName = currentNode && currentNode != project.topNode ? currentNode.name : "";
125       char * config = buildTab.selectedConfigName;
126       char * platform = buildTab.selectedPlatformName;
127       char * label = new char[strlen(dialogTitle) + 3 + strlen(project.topNode.name) + 3 + 
128                               strlen(nodeName) + 2 + strlen(config) + 1 + strlen(platform) + 1 + 1];
129       strcpy(label, dialogTitle);
130       strcat(label, " - ");
131       strcpy(projectName, project.topNode.name);
132       StripExtension(projectName);
133       strcat(label, projectName);
134       if(currentNode && currentNode.type != project)
135       {
136          strcat(label, " - ");
137          strcat(label, nodeName);
138       }
139       if(strlen(config) || strlen(platform))
140       {
141          strcat(label, " (");
142          if(strlen(config))
143             strcat(label, config);
144          if(strlen(config) && strlen(platform))
145             strcat(label, "/");
146          if(strlen(platform))
147             strcat(label, platform);
148          strcat(label, ")");
149       }
150       text = label;
151       delete projectName;
152       delete label;
153    }
154
155    ~ProjectSettings()
156    {
157       currentNode = null;
158    }
159
160    TabControl prjTabControl
161    {
162       this, background = activeBorder, anchor = { left = 8, top = 4, right = 8, bottom = 38 };
163    };
164    ProjectTab projectTab { this, tabControl = prjTabControl };
165    BuildTab buildTab { this, tabControl = prjTabControl };
166    WorkspaceTab workspaceTab { this, tabControl = prjTabControl };
167
168    Button cancel
169    {
170       this, size = { 80, 22 };
171       anchor = { right = 8, bottom = 8 };
172       text = $"Cancel", hotKey = escape, id = DialogResult::cancel;
173
174       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
175       {
176          if(prjTabControl.curTab.modifiedDocument)
177          {
178             DialogResult diagRes = MessageBox
179             {
180                type = okCancel, master = ide,
181                text = $"Lose Changes?",
182                contents = $"Are you sure you wish to discard changes made to the build options?"
183             }.Modal();
184             if(diagRes == ok)
185             {
186                if(prjTabControl.curTab == buildTab)
187                {
188                   buildTab.RevertChanges();
189                   buildTab.modifiedDocument = false;
190                }
191                if(prjTabControl.curTab == workspaceTab)
192                {
193                   workspaceTab.modifiedDocument = false;
194                }
195                if(prjTabControl.curTab == projectTab)
196                {
197                   projectTab.modifiedDocument = false;
198                }
199                Destroy(DialogResult::cancel);
200             }
201          }
202          else
203             Destroy(DialogResult::cancel);
204          return true;
205       }
206    };
207    Button ok
208    {
209       this, size = { 80, 22 };
210       anchor = { right = 96, bottom = 8 };
211       text = $"OK", isDefault = true;
212
213       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
214       {
215          if(prjTabControl.curTab == buildTab && buildTab.modifiedDocument)
216          {
217             buildTab.modifiedDocument = false;
218
219             project.topNode.modified = true;
220             project.MarkChanges(buildTab.backupNode);
221             ide.projectView.modifiedDocument = true;
222             ide.projectView.Update(null);
223          }
224          if(prjTabControl.curTab == workspaceTab && workspaceTab.modifiedDocument)
225          {
226             workspaceTab.SaveChanges();
227             workspaceTab.modifiedDocument = false;
228          }
229          if(prjTabControl.curTab == projectTab && projectTab.modifiedDocument)
230          {
231             projectTab.SaveChanges();
232             projectTab.modifiedDocument = false;
233          }
234          Destroy(DialogResult::ok);
235          return true;
236       }
237    };
238
239    bool OnPostCreate()
240    {
241       UpdateDialogTitle();
242       prjTabControl.curTab = buildTab;
243       return true;
244    }
245 }
246
247 #define OPTION(x) ((uint)(&((ProjectOptions)0).x))
248
249 // TOFIX: USING T INSTEAD OF Z HERE CAUSED US SOME CONFLICTS WITH T IN Array TEMPLATES
250
251 class OptionBox<class Z> : CommonControl
252 {
253    bool mergeValues, configReplaces;
254    void * chainKeyDown;
255
256    autoCreate = false;
257
258    ~OptionBox()
259    {
260       delete editor;
261    }
262    property Window editor
263    {
264       set
265       {
266          editor = value;
267          incref editor;
268          editor.OnRightButtonDown = OptionBox_OnRightButtonDown;
269          chainKeyDown = (void *)editor.OnKeyDown;
270          editor.OnKeyDown = OptionBox_OnKeyDown;
271       }
272    }
273
274    property bool visible { set { editor.visible = value; } get { return editor.visible; } }
275    property Window parent { set { editor.parent = value; Window::parent = value; master = value; editor.id = (int)this; } }
276    property Point position { set { editor.position = value; } }
277    property Size size { set { editor.size = value; } }
278    property Anchor anchor { set { editor.anchor = value; } }
279    property Key hotKey { set { editor.hotKey = value; } }
280    property char * text { set { editor.text = value; Window::text = value; } }
281
282    uint option;
283
284    Window editor;
285    Menu clearMenu { };
286    MenuItem clearItem
287    {
288       clearMenu, $"Clear";
289
290       bool NotifySelect(MenuItem selection, Modifiers mods)
291       {
292          OptionBox ob = (OptionBox)id;
293          ob.Unset();
294          return true;
295       }
296    };
297    
298    bool Window::OptionBox_OnRightButtonDown(int x, int y, Modifiers mods)
299    {
300       OptionBox ob = (OptionBox)id;
301       GuiApplication app = ((GuiApplication)__thisModule.application);
302       Activate();
303       PopupMenu { null, this, menu = ob.clearMenu,
304          position = { absPosition.x + clientStart.x + x - app.desktop.position.x, absPosition.y + clientStart.y + y - app.desktop.position.y } }.Create();
305       return true;
306    }
307
308    bool Window::OptionBox_OnKeyDown(Key key, unichar ch)
309    {
310       OptionBox ob = (OptionBox)id;
311       if(key == Key { del, ctrl = true } || key == Key { keyPadDelete, ctrl = true })
312       {
313          ob.Unset();
314          return false;
315       }
316       return (((bool(*)(Window, Key, unichar)) ob.chainKeyDown)(this, key, ch);
317    }
318
319    void SetAttribs(int code)
320    {
321       Window c;
322       Label label = null;
323
324       for(c = Window::parent.firstChild; c; c = c.next)
325       {
326          if(eClass_IsDerived(c._class, class(Label)))
327          {
328             label = (Label)c;
329             if(label.labeledWindow == this)
330                break;
331          }
332       }
333       
334       if(!c)
335       {
336          label = null;
337          for(c = editor.firstChild; c; c = c.next)
338          {
339             if(eClass_IsDerived(c._class, class(Label)))
340             {
341                label = (Label)c;
342                break;
343             }
344          }
345       }
346       // control.foreground = foreground;
347
348       if(code == 0 || code == 1)
349       {
350          editor.font = { editor.font.faceName, editor.font.size, bold = (code == 1) };
351          editor.background = white;
352       }
353       else if(code == 2)
354       {
355          Color foreground = 0x0F3F66;
356          int r = foreground.r, g = foreground.g, b = foreground.b;
357          Color src = white;
358          double alpha = 0.1;
359
360          editor.font = { editor.font.faceName, editor.font.size };
361
362          r = (int)(alpha * r + src.r * (1 - alpha));
363          g = (int)(alpha * g + src.g * (1 - alpha));
364          b = (int)(alpha * b + src.b * (1 - alpha));
365
366          r = Max(0,Min(255,r));
367          g = Max(0,Min(255,g));
368          b = Max(0,Min(255,b));
369
370          editor.background = Color { (byte) r, (byte) g, (byte) b };
371
372       }
373       if(label)
374       {
375          label.font = { editor.font.faceName, editor.font.size, bold = (code == 1) };
376          //label.foreground = foreground;
377       }
378    }
379
380    virtual void FinalizeLoading();
381
382    virtual void LoadOption(ProjectOptions options);
383    virtual void RetrieveOption(ProjectOptions options, bool isCfgOrPlt);
384    virtual void UnsetOption(ProjectOptions options)
385    {
386       Z value = (Z)0;
387       *(Z*)((byte *)options + option) = value;
388    }
389
390    virtual bool OptionSet(ProjectOptions options)
391    {
392       // TOFIX: If you get a crash here, it might be because JSON.ec must be after ProjectConfig.ec in the project files
393       //        JSON.ec must also be before ProjectSettings.ec in the project files
394       if(*(Z*)((byte *)options + option))
395          return true;
396       return false;
397    }
398    // BUG: OptionCheck = OptionSet; // Overrides derived classes OptionCheck ?
399
400    virtual bool OptionCheck(ProjectOptions options)
401    {
402       return OptionSet(options);
403    }
404    
405    void MarkBuildTabModified()
406    {
407       BuildTab buildTab = (BuildTab)master;
408       while(buildTab && buildTab._class != class(BuildTab))
409          buildTab = (BuildTab)buildTab.master;
410       if(buildTab) buildTab.modifiedDocument = true;
411    }
412    
413    void Unset()
414    {
415       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
416       MarkBuildTabModified();
417
418       if(config)
419       {
420          ProjectConfig c = null;
421          if(currentNode.configurations)
422          {
423             for(i : currentNode.configurations; !strcmpi(i.name, config.name)) { c = i; break; }
424             if(c)
425             {
426                if(platform)
427                {
428                   PlatformOptions p = null;
429                   if(c.platforms)
430                   {
431                      for(i : c.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
432                      if(p)
433                      {
434                         if(p.options && OptionSet(p.options))
435                            UnsetOption(p.options);
436                         if(p.options && p.options.isEmpty)
437                            delete p.options;
438                         if(!p.options)
439                         {
440                            Iterator<PlatformOptions> it { c.platforms };
441                            if(it.Find(p))
442                            {
443                               it.Remove();
444                               delete p;
445                            }
446                         }
447                      }
448                      if(!c.platforms.count)
449                         c.platforms = null;
450                   }
451                   Load();
452                   return;
453                }
454                if(c.options && OptionSet(c.options))
455                   UnsetOption(c.options);
456                if(c.options && c.options.isEmpty)
457                   delete c.options;
458
459                // DON'T DELETE PROJECT CONFIGS HERE!
460                if(!c.options && currentNode != project.topNode)
461                {
462                   Iterator<ProjectConfig> it { currentNode.configurations };
463                   if(it.Find(c))
464                   {
465                      it.Remove();
466                      delete c;
467                   }
468                }
469             }
470             if(!currentNode.configurations.count)
471                currentNode.configurations = null;
472          }
473          Load();
474          return;
475       }
476       if(platform)
477       {
478          PlatformOptions p = null;
479          if(currentNode.platforms)
480          {
481             for(i : currentNode.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
482             if(p)
483             {
484                if(p.options && OptionSet(p.options))
485                   UnsetOption(p.options);
486                if(p.options && p.options.isEmpty)
487                   delete p.options;
488                if(!p.options)
489                {
490                   Iterator<PlatformOptions> it { currentNode.platforms };
491                   if(it.Find(p))
492                   {
493                      it.Remove(p);
494                      delete p;
495                   }
496                }
497             }
498             if(!currentNode.platforms.count)
499                currentNode.platforms = null;
500          }
501          Load();
502          return;
503       }
504
505       if(currentNode.options && OptionSet(currentNode.options))
506          UnsetOption(currentNode.options);
507       if(currentNode.options && currentNode.options.isEmpty)
508          delete currentNode.options;
509
510       Load();
511    }
512
513    void FigureOutInherited()
514    {
515       ProjectNode node;
516       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
517       bool skipped = false;
518       for(node = currentNode; node; node = node.parent)
519       {
520          if(config && node.configurations)
521          {
522             for(c : node.configurations; !strcmpi(c.name, config.name))
523             {
524                if(platform && c.platforms)
525                {
526                   for(p : c.platforms; !strcmpi(p.name, platformName))
527                   {
528                      if(skipped && p.options && OptionSet(p.options))
529                         LoadOption(p.options);
530                      skipped = true;
531                      break;
532                   }
533                }               
534
535                if(skipped && c.options && OptionSet(c.options))
536                {
537                   LoadOption(c.options);
538                   if(configReplaces) return;
539                }
540                skipped = true;
541                break;
542             }
543          }
544          if(platform && node.platforms)
545          {
546             for(p : node.platforms; !strcmpi(p.name, platformName))
547             {
548                if(skipped && p.options && OptionSet(p.options))
549                   LoadOption(p.options);
550                skipped = true;
551                break;
552             }
553          }
554          if(skipped && node.options && OptionSet(node.options))
555             LoadOption(node.options);
556          else if(skipped && !node.parent)
557             LoadOption(null);
558          skipped = true;
559       }
560    }
561
562    void Retrieve()
563    {
564       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
565       MarkBuildTabModified();
566       if(config)
567       {
568          ProjectConfig c = null;
569          if(!currentNode.configurations) currentNode.configurations = { };
570          for(i : currentNode.configurations; !strcmpi(i.name, config.name)) { c = i; break; }
571          if(!c)
572             currentNode.configurations.Add(c = ProjectConfig { name = CopyString(config.name) });
573          if(platform)
574          {
575             PlatformOptions p = null;
576             if(!c.platforms) c.platforms = { };
577
578             for(i : c.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
579             if(!p)
580                c.platforms.Add(p = PlatformOptions { CopyString(platformName) });
581
582             if(!p.options) p.options = { };
583             RetrieveOption(p.options, true);
584             if(!mergeValues) SetAttribs(1);
585             return;
586          }
587          if(!c.options) c.options = { };
588          RetrieveOption(c.options, true);
589          if(!mergeValues) SetAttribs(1);
590          return;
591       }
592       if(platform)
593       {
594          PlatformOptions p = null;
595          if(!currentNode.platforms) currentNode.platforms = { };
596          for(i : currentNode.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
597          if(!p)
598             currentNode.platforms.Add(p = PlatformOptions { CopyString(platformName) });
599
600          if(!p.options) p.options = { };
601          RetrieveOption(p.options, true);
602          if(!mergeValues) SetAttribs(1);
603          return;
604       }
605
606       if(!currentNode.options) currentNode.options = { };
607       RetrieveOption(currentNode.options, false);
608       if(!mergeValues) SetAttribs((currentNode.parent || OptionCheck(currentNode.options)) ? 1 : 0);
609    }
610
611    void Load()
612    {
613       ProjectNode node;
614       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
615       bool setAttribs = false;
616       for(node = currentNode; node; node = node.parent)
617       {
618          ProjectConfig nodeConfig = null;
619          if(config && node.configurations)
620          {
621             for(c : node.configurations; !strcmpi(c.name, config.name))
622             {
623                if(platform && c.platforms)
624                {
625                   for(p : c.platforms; !strcmpi(p.name, platformName))
626                   {
627                      if(p.options && (mergeValues ? OptionCheck(p.options) : OptionSet(p.options)))
628                      {
629                         LoadOption(p.options);
630                         if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode) ? 1 : 2); }
631                         if(!mergeValues) { FinalizeLoading(); return; }
632                      }
633                      break;
634                   }
635                }               
636
637                nodeConfig = c;
638                break;
639             }
640          }
641          if(platform && node.platforms)
642          {
643             for(p : node.platforms; !strcmpi(p.name, platformName))
644             {
645                if(p.options && (mergeValues ? OptionCheck(p.options) : OptionSet(p.options)))
646                {
647                   LoadOption(p.options);
648                   if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !config) ? 1 : 2); }
649                   if(!mergeValues) { FinalizeLoading(); return; }
650                }
651                break;
652             }
653          }
654
655          if(nodeConfig && nodeConfig.options && ((mergeValues && !configReplaces) ? OptionCheck(nodeConfig.options) : OptionSet(nodeConfig.options)))
656          {
657             LoadOption(nodeConfig.options);
658             if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !platform) ? 1 : 2); }
659             if(!mergeValues || configReplaces) { FinalizeLoading(); return; }
660          }
661
662          if(node.options && (mergeValues ? OptionCheck(node.options) : OptionSet(node.options)))
663          {
664             LoadOption(node.options);
665             if(!node.parent && !OptionCheck(node.options))
666             {
667                if(!setAttribs) { setAttribs = true; SetAttribs(0); }
668             }
669             else
670             {
671                if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !config && !platform) ? 1 : 2); }
672             }
673             if(!mergeValues) { FinalizeLoading(); return; }
674          }
675          else if(!node.parent)
676          {
677             LoadOption(null);
678             if(!setAttribs) { setAttribs = true; SetAttribs(0); }
679             if(!mergeValues) { FinalizeLoading(); return; }
680          }
681       }
682       FinalizeLoading();
683    }
684 }
685
686 class StringOptionBox : OptionBox<String>
687 {
688    editor = EditBox
689    {
690       bool NotifyModified(EditBox editBox)
691       {
692          ((OptionBox)editBox.id).Retrieve();
693          return true;
694       }
695
696       textHorzScroll = true;
697    };
698
699    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
700    {
701       String * string = (String*)((byte *)options + option);
702       if(*string) delete *string;
703       *string = CopyString(((EditBox)editor).contents);
704    }
705
706    void LoadOption(ProjectOptions options)
707    {
708       ((EditBox)editor).contents = options ? *(String*)((byte *)options + option) : "";
709       ((EditBox)editor).Deselect();
710    }
711
712    bool OptionCheck(ProjectOptions options)
713    {
714       String string = *(String*)((byte *)options + option);
715       return string && string[0];
716    }
717
718    void UnsetOption(ProjectOptions options)
719    {
720       delete *(String*)((byte *)options + option);
721    }
722 }
723
724 class PathOptionBox : OptionBox<String>
725 {
726    bool Window::EditBoxORB(int x, int y, Modifiers mods)
727    {
728       Window parent = this.parent;
729       x += clientStart.x + position.x;
730       y += clientStart.y + position.y;
731       return ((OptionBox)this).OptionBox_OnRightButtonDown(parent, x, y, mods);
732    }
733
734    editor = PathBox
735    {
736       typeExpected = directory, browseDialog = { };
737       editBox.OnRightButtonDown = (void *)EditBoxORB;
738
739       bool NotifyModified(PathBox pathBox)
740       {
741          ((OptionBox)pathBox.id).Retrieve();
742          return true;
743       }
744    };
745
746    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
747    {
748       String * string = (String*)((byte *)options + option);
749       String slashPath = ((PathBox)editor).slashPath;
750       if(*string) delete *string;
751       *string = CopyString(slashPath);//(slashPath && slashPath[0]) ? CopyString(slashPath) : null;
752    }
753
754    void LoadOption(ProjectOptions options)
755    {
756       ((PathBox)editor).path = options ? *(String*)((byte *)options + option) : "";
757       ((PathBox)editor).Deselect();
758    }
759
760    bool OptionCheck(ProjectOptions options)
761    {
762       String string = *(String*)((byte *)options + option);
763       return string && string[0];
764    }
765
766    void UnsetOption(ProjectOptions options)
767    {
768       delete *(String*)((byte *)options + option);
769    }
770 }
771
772 class MultiStringOptionBox : OptionBox<Array<String>>
773 {
774    bool caseSensitive;
775
776    mergeValues = true;
777    caseSensitive = true;
778
779    virtual Array<String> GetStrings();
780    virtual void SetStrings(Array<String> value);
781
782    Array<String> tempStrings;
783
784    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
785    {
786       Array<String> newStrings = GetStrings();
787       Array<String> * strings = (Array<String>*)((byte *)options + option);
788       if(*strings) { strings->Free(); delete *strings; }
789
790       if(mergeValues)
791       {
792          Iterator<String> it { newStrings };
793
794          FigureOutInherited();
795
796          if(tempStrings)
797          {
798             Array<String> ts = tempStrings;
799             while(it.Next())
800             {
801                String s = it.data;
802                bool found = false;
803                for(i : tempStrings; !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
804                if(found && (!configReplaces || platform))   // ADDED || platform here...
805                {
806                   delete s;
807                   it.Remove();
808                }
809             }
810          }
811          delete tempStrings;
812       }
813
814       *strings = newStrings;
815
816       Load();
817    }
818
819    void LoadOption(ProjectOptions options)
820    {
821       if(mergeValues)
822       {
823          Array<String> strings = options ? *((Array<String>*)((byte *)options + option) : null;
824          if(strings)
825          {
826             if(!tempStrings)
827                tempStrings = { };
828             for(s : strings)
829             {
830                bool found = false;
831                for(i : tempStrings; !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
832                if(!found) tempStrings.Add(s);
833             }
834          }
835       }         
836       else
837       {
838          SetStrings(options ? *(Array<String>*)((byte *)options + option) : null);
839       }
840    }
841
842    void FinalizeLoading()
843    {
844       if(mergeValues)
845       {
846          SetStrings(tempStrings);
847          delete tempStrings;
848       }
849    }
850
851    bool OptionSet(ProjectOptions options)
852    {
853       Array<String> strings = *(Array<String>*)((byte *)options + option);
854       if(mergeValues && !configReplaces)
855       {
856          return strings && strings.count;
857       }
858       else
859          return strings != null;
860    }
861
862    bool OptionCheck(ProjectOptions options)
863    {
864       Array<String> strings = *(Array<String>*)((byte *)options + option);
865       return strings && strings.count;
866    }
867
868    void UnsetOption(ProjectOptions options)
869    {
870       Array<String> * strings = (Array<String>*)((byte *)options + option);
871       if(*strings) { strings->Free(); delete *strings; }
872    }
873 }
874
875 class StringArrayOptionBox : MultiStringOptionBox
876 {
877    editor = StringListBox
878    {
879       bool NotifyModified(EditBox editBox)
880       {
881          ((OptionBox)editBox.id).Retrieve();
882          return true;
883       }
884    };
885
886    // NO VIRTUAL PROPERTIES YET...
887    Array<String> GetStrings() { return ((StringListBox)editor).strings; }
888    void SetStrings(Array<String> value) { ((StringListBox)editor).strings = value; }
889 }
890
891 class StringsArrayOptionBox : MultiStringOptionBox
892 {
893    editor = StringsBox
894    {
895       bool OnCreate()
896       {
897          project = ::project;
898          return true;
899       }
900
901       bool NotifyModified(StringsBox stringsBox)
902       {
903          ((OptionBox)stringsBox.id).Retrieve();
904          return true;
905       }
906    };
907
908    Array<String> GetStrings() { return ((StringsBox)editor).strings; }
909    void SetStrings(Array<String> value) { ((StringsBox)editor).strings = value; }
910 }
911
912 class DirsArrayOptionBox : MultiStringOptionBox
913 {
914    editor = DirectoriesBox
915    {
916       bool NotifyModified(DirectoriesBox dirsBox)
917       {
918          ((OptionBox)dirsBox.id).Retrieve();
919          return true;
920       }
921
922       bool OnChangedDir(char * * directory)
923       {
924          char fixedDirectory[MAX_LOCATION] = "";
925          if(PathCat(fixedDirectory, *directory))
926          {
927             char cwdBackup[MAX_LOCATION];
928             if(project)
929             {
930                GetWorkingDir(cwdBackup, sizeof(cwdBackup));
931                ChangeWorkingDir(project.topNode.path);
932             }
933             FileFixCase(fixedDirectory);
934             if(project)
935                ChangeWorkingDir(cwdBackup);
936             delete *directory;
937             *directory = CopyString(fixedDirectory);
938             return true;
939          }
940          return false;
941       }
942
943       bool OnPrepareBrowseDir(char * * directory)
944       {
945          char dir[MAX_LOCATION];
946          if(project)
947          {
948             GetSystemPathBuffer(dir, project.topNode.path);
949             if(*directory)
950                PathCat(dir, *directory);
951          }
952          else if(*directory)
953             strcpy(dir, *directory);
954          else
955             dir[0] = '\0';
956          
957          delete *directory;
958          *directory = CopyString(dir);
959
960             // GCC 4.4 bug:  -----  path becomes *directory
961             //strcpy(dir, path ? path : "");
962          return true;
963       }
964
965       bool OnBrowsedDir(char * * directory)
966       {
967          if(project)
968          {
969             char path[MAX_LOCATION];
970             MakePathRelative(*directory, project.topNode.path, path);
971             delete *directory;
972             *directory = CopyString(path);
973          }
974          return true;
975       }
976    };
977
978    Array<String> GetStrings() { return ((DirectoriesBox)editor).strings; }
979    void SetStrings(Array<String> value) { ((DirectoriesBox)editor).strings = value; }
980 }
981
982 class BoolOptionBox : OptionBox<SetBool>
983 {
984    editor = Button
985    {
986       isCheckbox = true;
987
988       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
989       {
990          ((OptionBox)button.id).Retrieve();
991          return true;
992       }
993    };
994
995    bool OptionCheck(ProjectOptions options)
996    {
997       return *(SetBool*)((byte *)options + option) == true;
998    }
999
1000    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1001    {
1002       bool checked = ((Button)editor).checked;
1003       *(SetBool*)((byte *)options + option) = checked ? true : 
1004          ((currentNode.parent || isCfgOrPlt) ? false : unset);
1005    }
1006
1007    void LoadOption(ProjectOptions options)
1008    {
1009       ((Button)editor).checked = options && (*(SetBool*)((byte *)options + option) == true);
1010    }
1011 }
1012
1013 class DropOptionBox : OptionBox
1014 {
1015    editor = DropBox
1016    {
1017       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
1018       {
1019          ((OptionBox)dropBox.id).Retrieve();
1020          return true;
1021       }
1022    };   
1023
1024    void LoadOption(ProjectOptions options)
1025    {
1026       DropBox dropBox = (DropBox)editor;
1027       Z value = options ? *(Z*)((byte *)options + option) : (Z)0;
1028       dropBox.currentRow = value ? dropBox.FindRow((int)value) : dropBox.firstRow;
1029    }
1030
1031    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1032    {
1033       DropBox dropBox = (DropBox)editor;
1034       DataRow row = dropBox.currentRow;
1035       Z value = (Z)(row ? row.tag : 0);
1036       *(Z*)((byte *)options + option) = value;
1037    }
1038 }
1039
1040 class TargetTypeDB : DropOptionBox<TargetTypes>
1041 {
1042    TargetTypeDB()
1043    {
1044       DataRow row;
1045
1046       row = ((DropBox)editor).AddRow();
1047       row.tag = TargetTypes::executable;
1048       row.SetData(null, $"Executable");
1049
1050       row = ((DropBox)editor).AddRow();
1051       row.tag = TargetTypes::sharedLibrary;
1052       row.SetData(null, $"Shared Library");
1053
1054       row = ((DropBox)editor).AddRow();
1055       row.tag = TargetTypes::staticLibrary;
1056       row.SetData(null, $"Static Library");
1057    }
1058
1059    bool OptionCheck(ProjectOptions options)
1060    {
1061       TargetTypes value = *(TargetTypes*)((byte *)options + option);
1062       return value && value != executable;
1063    }
1064 }
1065
1066 class OptimizationDB : DropOptionBox<OptimizationStrategy>
1067 {
1068    OptimizationDB()
1069    {
1070       DataRow row;
1071       row = ((DropBox)editor).AddRow();
1072       row.tag = OptimizationStrategy::none;
1073       row.SetData(null, $"None");
1074
1075       row = ((DropBox)editor).AddRow();
1076       row.tag = OptimizationStrategy::speed;
1077       row.SetData(null, $"For Speed (-O2)");
1078
1079       row = ((DropBox)editor).AddRow();
1080       row.tag = OptimizationStrategy::size;
1081       row.SetData(null, $"For Size (-Os)");
1082    }
1083
1084    bool OptionCheck(ProjectOptions options)
1085    {
1086       OptimizationStrategy value = *(OptimizationStrategy*)((byte *)options + option);
1087       return value && value != none;
1088    }
1089 }
1090
1091 class WarningsDB : DropOptionBox<WarningsOption>
1092 {
1093    WarningsDB()
1094    {
1095       DataRow row;
1096       row = ((DropBox)editor).AddRow();
1097       row.tag = WarningsOption::normal;
1098       row.SetData(null, $"Normal");
1099
1100       row = ((DropBox)editor).AddRow();
1101       row.tag = WarningsOption::none;
1102       row.SetData(null, $"None");
1103
1104       row = ((DropBox)editor).AddRow();
1105       row.tag = WarningsOption::all;
1106       row.SetData(null, $"All");
1107    }
1108
1109    bool OptionCheck(ProjectOptions options)
1110    {
1111       WarningsOption value = *(WarningsOption*)((byte *)options + option);
1112       return value && value != none;
1113    }
1114 }
1115
1116 void DrawStipple(Surface surface, Size clientSize)
1117 {
1118    int x1 = 0;
1119    int y1 = 0;
1120    int x2 = clientSize.w - 1;
1121    int y2 = clientSize.h - 1;
1122    if((x2 - x1) & 1) x2--;
1123    if((y2 - y1) & 1) y2--;
1124
1125    surface.LineStipple(0x5555);
1126    surface.Rectangle(x1, y1, x2, y2);
1127    surface.LineStipple(0);            
1128 }
1129
1130 class BuildTab : Tab
1131 {
1132    text = $"Build";
1133    background = activeBorder;
1134    tabCycle = true;
1135
1136    ProjectNode backupNode;
1137    String activeConfigName;
1138
1139    ProjectNode lastSelectedNode;
1140
1141    property char * selectedConfigName
1142    {
1143       get
1144       {
1145          if(created)
1146          {
1147             SelectorButton button = (SelectorButton)configSelector.selectedButton;
1148             if(button && button.id)
1149             {
1150                ProjectConfig config = (ProjectConfig)button.id;
1151                return config.name;
1152             }
1153          }
1154          return "";
1155       }
1156    }
1157
1158    property char * selectedPlatformName
1159    {
1160       get
1161       {
1162          if(created)
1163          {
1164             SelectorButton button = (SelectorButton)platformSelector.selectedButton;
1165             if(button && button.id)
1166             {
1167                Platform platform = (Platform)button.id;
1168                char * platformName = platform ? platform.OnGetString(0,0,0) : null; // all these platformName are leaking, no? 
1169                return platformName;
1170             }
1171          }
1172          return "";
1173       }
1174    }
1175
1176    Label labelConfigurations
1177    {
1178       this, anchor = { left = 8, top = 14 }, labeledWindow = configSelector;
1179
1180       void OnRedraw(Surface surface)
1181       {
1182          Label::OnRedraw(surface);
1183          if(labeledWindow.active) DrawStipple(surface, clientSize);
1184       }
1185    };
1186    SelectorBar configSelector
1187    {
1188       this, text = $"Configurations: ", anchor = { left = 98, top = 8, right = 54 }; size = { 0, 26 };
1189       opacity = 0;
1190       direction = horizontal, scrollable = true;
1191
1192       bool OnKeyDown(Key key, unichar ch)
1193       {
1194          if(key == insert)
1195          {
1196             ((BuildTab)parent).createConfig.NotifyClicked(parent, ((BuildTab)parent).createConfig, 0, 0, 0);
1197             return false;
1198          }
1199          else if(key == del)
1200          {
1201             ((BuildTab)parent).deleteConfig.NotifyClicked(parent, ((BuildTab)parent).deleteConfig, 0, 0, 0);
1202             return false;
1203          }
1204          return SelectorBar::OnKeyDown(key, ch);
1205       }
1206       
1207       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1208       {
1209          ((BuildTab)master).labelConfigurations.Update(null);
1210          return true;
1211       }
1212    };
1213
1214    Button createConfig
1215    {
1216       parent = this, bevelOver = true, inactive = true;
1217       size = { 22, 22 };
1218       anchor = { top = 10, right = 31 };
1219       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/docNew.png", alphaBlend = true };
1220
1221       bool NotifyClicked(Button b, int x, int y, Modifiers mods)
1222       {
1223          char tmp[MAX_F_STRING];
1224          ProjectConfig config;
1225          EditableSelectorButton button;
1226
1227          FindUniqueConfigName("NewConfig", false, tmp);
1228
1229          config =
1230          {
1231             makingModified = true;
1232             compilingModified = true;
1233             linkingModified = true;
1234             name = CopyString(tmp);
1235             options =
1236             {
1237                // objectsDirectory = /*CopyString(*/defaultObjDirExpression/*)*/;
1238             };
1239          };
1240          if(!project.topNode.configurations) project.topNode.configurations = { };
1241          project.topNode.configurations.Add(config);
1242          /*
1243          targetType = project.config.options.targetType;
1244          config.options.
1245          config.options.targetFileName = project.moduleName;
1246          config.options.targetDir.dir = "";
1247          config.options.objectsDirectory = defaultObjDirExpression);
1248          config.options.debug = true;
1249          config.options.optimization = none;
1250          config.options.warnings = all;
1251          */         
1252
1253          button =
1254          {
1255             configSelector, renameable = true, master = this, text = config.name, id = (int)config;
1256             NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1257          };
1258
1259          configSelector.Select(button);
1260          modifiedDocument = true;
1261          return true;
1262       }
1263    };
1264    /*Button duplicateConfig
1265    {
1266       parent = this, bevelOver = true, inactive = true;
1267       size = { 22, 22 };
1268       anchor = { top = 10, right = 31 };
1269       hotKey = altU, bitmap = BitmapResource { fileName = ":actions/editCopy.png", alphaBlend = true };
1270    };*/
1271    Button deleteConfig
1272    {
1273       parent = this, bevelOver = true, inactive = true;
1274       size = { 22, 22 };
1275       anchor = { top = 10, right = 8 };
1276       hotKey = altD, bitmap = BitmapResource { fileName = ":actions/delete2.png", alphaBlend = true };
1277
1278       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1279       {
1280          if(config)
1281          {
1282             String title = PrintString($"Delete ", config.name, $" Configuration");
1283             String msg = PrintString($"Are you sure you wish to delete the ", config.name, $" configuration?");
1284             if(MessageBox { type = okCancel, text = title, contents = msg }.Modal() == ok)
1285             {
1286                Iterator<Window> it { configSelector.controls };
1287                ProjectConfig configToDelete = config;
1288                /*
1289                while(it.Next())
1290                {
1291                   SelectorButton button = (SelectorButton)it.data;
1292                   if((ProjectConfig)button.id == config)
1293                   {
1294                      button.visible = false;
1295                      button.Destroy(0);
1296
1297                      if(it.Prev())
1298                      {
1299                         button = (SelectorButton)it.data;
1300                         config = (ProjectConfig)button.id;
1301                         configSelector.Select(button);
1302                      }
1303                      break;
1304                   }
1305                }
1306                */
1307                SelectorButton button = configSelector.FindButtonByID((int)configToDelete);
1308                if(button)
1309                   configSelector.RemoveButton(button);
1310
1311                project.topNode.DeleteConfig(configToDelete);
1312
1313                modifiedDocument = true;
1314             }
1315             delete title;
1316             delete msg;
1317          }
1318          return true;
1319       }
1320    };
1321    
1322    Label labelPlatforms
1323    {
1324       this, anchor = { left = 8, top = 44 }, labeledWindow = platformSelector;
1325
1326       void OnRedraw(Surface surface)
1327       {
1328          Label::OnRedraw(surface);
1329          if(labeledWindow.active) DrawStipple(surface, clientSize);
1330       }
1331    };
1332    SelectorBar platformSelector
1333    {
1334       this, text = $"Platforms: ", anchor = { left = 64, top = 38, right = 54 }; size = { 0, 26 };
1335       opacity = 0;
1336       direction = horizontal, scrollable = true;
1337
1338       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1339       {
1340          ((BuildTab)master).labelPlatforms.Update(null);
1341          return true;
1342       }
1343    };
1344
1345    TabControl buildTabControl
1346    {
1347       this, background = activeBorder, anchor = { left = 8, top = 64, right = 8, bottom = 8 };
1348       curTab = compilerTab;
1349    };
1350    CompilerTab compilerTab { this, tabControl = buildTabControl };
1351    LinkerTab linkerTab { this, tabControl = buildTabControl };
1352    BuilderTab builderTab { this, tabControl = buildTabControl };
1353    Label rightClick
1354    {
1355       this, font = { font.faceName, font.size, italic = true }, stayOnTop = true,
1356       text = $"(Right click or press Ctrl-Del to revert an option to inherited value)", anchor = { top = 72, right = 16 }
1357    };
1358
1359    void FindUniqueConfigName(char * baseName, bool startWithNumber, char * output)
1360    {
1361       int num = 0;
1362       char tmp[MAX_F_STRING];
1363       if(startWithNumber)
1364          sprintf(tmp, "%s%d", baseName, num);
1365       else
1366          strcpy(tmp, baseName);
1367       while(true)
1368       {
1369          ProjectConfig config = null;
1370          for(c : project.topNode.configurations)
1371          {     // TOFIX: Error when omitting these brackets, c not found
1372             if(c.name && !strcmp(c.name, tmp))
1373             {
1374                config = c;
1375                break;
1376             }
1377          }
1378          if(config)
1379          {
1380             num++;
1381             sprintf(tmp, "%s%d", baseName, num);
1382          }
1383          else
1384             break;
1385       }
1386       strcpy(output, tmp);
1387    }
1388
1389    bool PlatformClicked(Button clickedButton, int x, int y, Modifiers mods)
1390    {
1391       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1392       {
1393          platform = (Platform)clickedButton.id;
1394
1395          // Load Settings Into Dialog
1396          compilerTab.LoadSettings();
1397          linkerTab.LoadSettings();
1398          builderTab.LoadSettings();
1399
1400          if(!mods)
1401             buildTabControl.Activate();
1402
1403          if(compilerTab.rightPaneHeader.visible)
1404             compilerTab.rightPaneHeader.Update(null);
1405          ((ProjectSettings)master).UpdateDialogTitle();
1406       }
1407       return true;
1408    }
1409
1410    ~BuildTab()
1411    {
1412       platformSelector.DestroyChildren();
1413       configSelector.DestroyChildren();
1414
1415       delete activeConfigName;
1416    }
1417
1418    bool ConfigOnRename(EditableSelectorButton button, char * * oldName, char * * newName)
1419    {
1420       int c, d = 0;
1421       char ch;
1422
1423       for(c = 0; (ch = (*newName)[c]); c++)
1424       {
1425          if(ch == '_' || isalpha(ch) || (isdigit(ch) && d))
1426             (*newName)[d++] = ch;
1427       }
1428       (*newName)[d] = 0;
1429
1430       {
1431          bool found = false;
1432          for(c : project.topNode.configurations; c != config)
1433          {
1434             if(!strcmpi(c.name, *newName))
1435             {
1436                found = true;
1437                break;
1438             }
1439          }
1440          if(found || !(*newName)[0])
1441          {
1442             char tmp[MAX_F_STRING];
1443             char * tmpName = config.name;
1444             config.name = null;
1445             FindUniqueConfigName("NewConfig", false, tmp);
1446             config.name = tmpName;
1447             delete *newName;
1448             *newName = CopyString(tmp);
1449          }
1450       }
1451
1452       if(activeConfigName && !strcmp(activeConfigName, *oldName))
1453       {
1454          delete activeConfigName;
1455          activeConfigName = CopyString(*newName);
1456       }
1457
1458       project.topNode.RenameConfig(config.name, *newName);
1459       
1460       modifiedDocument = true;
1461       return true;
1462    }
1463
1464    bool ConfigClicked(Button clickedButton, int x, int y, Modifiers mods)
1465    {
1466       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1467       {
1468          config = (ProjectConfig)clickedButton.id;
1469
1470          // Load Settings Into Dialog
1471          compilerTab.LoadSettings();
1472          linkerTab.LoadSettings();
1473          builderTab.LoadSettings();
1474
1475          deleteConfig.disabled = (clickedButton._class == class(SelectorButton));
1476
1477          if(!mods)
1478             buildTabControl.Activate();
1479
1480          if(compilerTab.rightPaneHeader.visible)
1481             compilerTab.rightPaneHeader.Update(null);
1482          ((ProjectSettings)master).UpdateDialogTitle();
1483       }
1484       return true;
1485    }
1486
1487    void SelectNode(ProjectNode node, bool ignoreAsLastSelection)
1488    {
1489       if(node != currentNode)
1490       {
1491          Window ac = compilerTab.rightPane.activeChild;
1492          bool prevNodeRes = currentNode ? currentNode.isInResources : false;
1493          bool newNodeRes;
1494
1495          if(!node) node = project.topNode;
1496
1497          newNodeRes = node.isInResources;
1498          
1499          currentNode = node;
1500          if(!ignoreAsLastSelection)
1501             lastSelectedNode = node;
1502
1503          ((ProjectSettings)master).UpdateDialogTitle();
1504          if(node.type == project)
1505          {
1506             compilerTab.rightPaneHeader.visible = false;
1507          }
1508          else
1509          {
1510             compilerTab.rightPaneHeader.id = (int)node;
1511             compilerTab.rightPaneHeader.Update(null);
1512             compilerTab.rightPaneHeader.visible = true;
1513          }
1514
1515          {
1516             DataRow row = compilerTab.fileList.FindSubRow((int)currentNode);
1517             if(row)
1518             {
1519                compilerTab.fileList.currentRow = row;
1520                while((row = row.parent))
1521                   row.collapsed = false;
1522             }
1523          }
1524
1525          if(prevNodeRes != newNodeRes)
1526          {
1527             compilerTab.labelObjDir.visible = !newNodeRes;
1528             compilerTab.objDir.visible = !newNodeRes;
1529             compilerTab.excludeFromBuild.visible = !newNodeRes;
1530             compilerTab.labelPreprocessorDefs.visible = !newNodeRes;
1531             compilerTab.preprocessorDefs.visible = !newNodeRes;
1532             compilerTab.labelDefaultNameSpace.visible = !newNodeRes;
1533             compilerTab.defaultNameSpace.visible = !newNodeRes;
1534             compilerTab.strictNameSpaces.visible = !newNodeRes;
1535             compilerTab.memoryGuard.visible = !newNodeRes;
1536             compilerTab.noLineNumbers.visible = !newNodeRes;
1537             compilerTab.debug.visible = !newNodeRes;
1538             compilerTab.labelWarnings.visible = !newNodeRes;
1539             compilerTab.warnings.visible = !newNodeRes;
1540             compilerTab.profiling.visible = !newNodeRes;
1541             compilerTab.labelOptimization.visible = !newNodeRes;
1542             compilerTab.optimization.visible = !newNodeRes;
1543             compilerTab.labelIncludeDirs.visible = !newNodeRes;
1544             compilerTab.includeDirs.visible = !newNodeRes;
1545          }
1546          
1547          if(node == project.topNode)
1548          {
1549             compilerTab.objDir.visible = true;
1550             compilerTab.labelObjDir.visible = true;
1551
1552             compilerTab.excludeFromBuild.visible = false;
1553          }
1554          else
1555          {
1556             compilerTab.objDir.visible = false;
1557             compilerTab.labelObjDir.visible = false;
1558
1559             compilerTab.excludeFromBuild.visible = (node != project.resNode);
1560          }
1561
1562          // Load Settings Into Dialog
1563          compilerTab.LoadSettings();
1564          linkerTab.LoadSettings();
1565          builderTab.LoadSettings();
1566
1567          if(ac)
1568          {
1569             if(!ac.visible)
1570             {
1571                if(ac == compilerTab.excludeFromBuild.editor)
1572                   ac = compilerTab.objDir.editor;
1573                else if(compilerTab.excludeFromBuild.editor.visible)
1574                   ac = compilerTab.excludeFromBuild.editor;
1575             }
1576             ac.MakeActive();
1577          }
1578       }
1579    }
1580
1581    void CreateConfigButtons()
1582    {
1583       SelectorButton commonButton;
1584
1585       // Create Config Buttons
1586       commonButton = SelectorButton
1587       {
1588          configSelector, master = this, text = $"Common", id = (int)null; font = { font.faceName, font.size, true };
1589          checked = true;
1590          NotifyClicked = ConfigClicked;
1591       };
1592       
1593       config = null;
1594
1595       if(project.topNode.configurations)
1596       {
1597          for(c : project.topNode.configurations)
1598          {
1599             EditableSelectorButton button
1600             {
1601                configSelector, master = this, renameable = true, text = c.name, id = (int)c;
1602                NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1603             };
1604          }
1605       }
1606    }
1607    
1608    void Init()
1609    {
1610       Platform p;
1611       SelectorButton button;
1612
1613       activeConfigName = project.config ? CopyString(project.config.name) : null;
1614       
1615       compilerTab.AddNode(project.topNode, null);
1616
1617       CreateConfigButtons();
1618
1619       platformButton = button =
1620       {
1621          platformSelector, master = this, text = $"Common", id = 0;  font = { font.faceName, font.size, true };
1622          NotifyClicked = PlatformClicked; checked = true;
1623       };
1624
1625       platform = 0;
1626
1627       for(p = (Platform)1; p < Platform::enumSize; p++)
1628       {
1629          SelectorButton button
1630          {
1631             platformSelector, master = this, text = p.OnGetString(0,0,0), id = (int)p; 
1632             NotifyClicked = PlatformClicked;
1633          };
1634       }
1635    }
1636    SelectorButton platformButton;
1637
1638    bool OnPostCreate()
1639    {
1640       // Backup Current Settings
1641       backupNode = project.topNode.Backup();
1642
1643       buildTabControl.Activate();
1644
1645       {
1646          Iterator<Window> it { configSelector.controls };
1647          while(it.Next())
1648          {
1649             SelectorButton configButton = (SelectorButton)it.data;
1650             ProjectConfig buttonConfig = (ProjectConfig)configButton.id;
1651             if(buttonConfig == project.config)
1652             {
1653                configButton.Activate();
1654                configButton.checked = true;
1655                ConfigClicked(configButton, 0, 0, (Modifiers)null);
1656                break;
1657             }
1658          }
1659       }
1660       if(platformButton)
1661       {
1662          platformButton.MakeActive();
1663          platformButton = null;
1664       }
1665       return true;
1666    }
1667
1668    void OnDestroy()
1669    {
1670       delete backupNode;
1671
1672       lastSelectedNode = null;
1673
1674       project.config = null;
1675
1676       /* // THIS IS NOW AUTOMATED WITH A project CHECK IN ProjectNode
1677       project.configurations = project.topNode.configurations;
1678       project.platforms = project.topNode.platforms;
1679       project.options = project.topNode.options;
1680       */
1681
1682       if(project.topNode.configurations)
1683       {
1684          for(c : project.topNode.configurations)
1685          {
1686             if(!strcmpi(c.name, activeConfigName))
1687             {
1688                project.config = c;
1689                break;
1690             }
1691          }
1692       }
1693    }
1694
1695    void RevertChanges()
1696    {
1697       String configName = config ? CopyString(config.name) : null;
1698
1699       // Revert to saved project options
1700       project.topNode.Revert(backupNode);
1701
1702       configSelector.DestroyChildren();
1703       CreateConfigButtons();
1704
1705       // Reselect Configuration
1706       if(configName)
1707       {
1708          Iterator<Window> it { configSelector.controls };
1709          while(it.Next())
1710          {
1711             Button button = (Button)it.data;
1712             ProjectConfig c = (ProjectConfig)button.id;
1713             if(c && !strcmp(c.name, configName))
1714             {
1715                config = c;
1716                button.Activate();
1717                button.checked = true;
1718                ConfigClicked(button, 0,0, 0);
1719                break;
1720             }
1721          }
1722       }
1723
1724       SelectNode(project.topNode, false);
1725
1726       delete configName;
1727    }
1728
1729    bool OnClose(bool parentClosing)
1730    {
1731       if(modifiedDocument)
1732       {
1733          DialogResult diagRes = MessageBox
1734          {
1735             type = yesNoCancel, master = ide,
1736             text = $"Save changes to project settings?",
1737             contents = $"Would you like to save changes made to the build options?"
1738          }.Modal();
1739          if(diagRes == no)
1740             RevertChanges();
1741          if(diagRes == cancel)
1742             return false;
1743          if(diagRes == yes)
1744          {
1745             project.MarkChanges(backupNode);
1746             project.topNode.modified = true;
1747             ide.projectView.modifiedDocument = true;
1748             ide.projectView.Update(null);
1749          }
1750          modifiedDocument = false;
1751       }
1752       return true;
1753    }
1754 }
1755
1756 class CompilerTab : Tab
1757 {
1758    background = activeBorder;
1759    text = $"Compiler";
1760
1761    Window leftPane { this, size = { 180 }, anchor = { left = 0, top = 0, bottom = 0 }, background = activeBorder };
1762
1763    Label labelFileList { leftPane, this, position = { 8, 8 }, labeledWindow = fileList };
1764    ListBox fileList
1765    {
1766       leftPane, this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true;
1767       // THIS WOULD BE EVEN MORE FUN: multiSelect = true,
1768       fullRowSelect = false, collapseControl = true, treeBranches = true;
1769       alwaysHighLight = true;
1770       selectionColor = unfocusedSelectorColor;
1771       size = { 180 };
1772       anchor = Anchor { left = 8, top = 24, right = 4, bottom = 8 };
1773       text = $"Files";
1774
1775       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1776       {
1777          BuildTab buildTab = (BuildTab)master;
1778          ProjectNode node = (ProjectNode)row.tag;
1779          buildTab.SelectNode(node, false);
1780          return true;
1781       }
1782
1783       void OnRedraw(Surface surface)
1784       {
1785          ide.projectView.drawingInProjectSettingsDialog = true;
1786          ListBox::OnRedraw(surface);
1787          ide.projectView.drawingInProjectSettingsDialog = false;
1788       }
1789
1790       bool NotifyActivate(Window window, bool active, Window previous)
1791       {
1792          if(active)
1793          {
1794             //subclass(Skin) skinClass = (subclass(Skin))eSystem_FindClass(app, app.skin);
1795             fileList.selectionColor = Color { 10, 36, 106 }; //skinClass.selectionColor; // darkBlue;
1796          }
1797          else if(fileList.currentRow)
1798          {
1799             DataRow currentRow = fileList.currentRow;
1800             //int headerSize = ((fileList.style.header) ? fileList.rowHeight : 0);
1801             int height = fileList.clientSize.h + 1;// - fileList.headerSize;
1802             fileList.selectionColor = unfocusedSelectorColor;
1803             if(currentRow && currentRow.index * fileList.rowHeight > fileList.scroll.y + height - fileList.rowHeight)
1804                fileList.SetScrollPosition(fileList.scroll.x, currentRow.index * fileList.rowHeight - height + fileList.rowHeight);
1805             else if(!currentRow || currentRow.index * fileList.rowHeight < fileList.scroll.y)
1806                fileList.SetScrollPosition(fileList.scroll.x, currentRow ? currentRow.index * fileList.rowHeight : 0);
1807
1808          }
1809
1810          return true;
1811       }
1812    };
1813
1814    Window rightPane 
1815    {
1816       this, anchor = { left = 196, top = 0, right = 0, bottom = 0 }, background = activeBorder, tabCycle = true;
1817    };
1818
1819    Window rightPaneHeader
1820    {
1821       rightPane, this, size = { h = 21 }, anchor = { left = 0, top = 0, right = 0 }, background = Color { 70, 96, 166 };//0x0F3F66;
1822       foreground = white; visible = false;
1823
1824       void OnRedraw(Surface surface)
1825       {
1826          if(id)
1827          {
1828             ide.projectView.drawingInProjectSettingsDialogHeader = true;
1829             class(ProjectNode)._vTbl[__ecereVMethodID_class_OnDisplay](class(ProjectNode),
1830                id, surface, 8, 2, clientSize.w, ide.projectView, Alignment::left, DataDisplayFlags { selected = true });
1831             ide.projectView.drawingInProjectSettingsDialogHeader = false;
1832          }
1833       }
1834    };
1835
1836    PaneSplitter splitter
1837    {
1838       this, leftPane = leftPane, rightPane = rightPane, split = 188
1839    };
1840
1841    Label labelObjDir { rightPane, this, position = { 8, 8 }, labeledWindow = objDir };
1842    PathOptionBox objDir
1843    {
1844       rightPane, this, size = { 250, 22 }, anchor = { left = 8, top = 24, right = 8 };
1845       text = $"Intermediate Objects Directory", hotKey = altJ, option = OPTION(objectsDirectory);
1846    };
1847
1848    BoolOptionBox excludeFromBuild
1849    {
1850       rightPane, this, position = { 8, 28 },
1851       text = $"Exclude from Build", visible = false, option = OPTION(excludeFromBuild);
1852    };
1853
1854    Label labelPreprocessorDefs { rightPane, this, position = { 8, 50 }, labeledWindow = preprocessorDefs };
1855    StringArrayOptionBox preprocessorDefs
1856    {
1857       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
1858       text = $"Preprocessor Definitions", hotKey = altD, option = OPTION(preprocessorDefinitions);
1859    };
1860
1861    Label labelDefaultNameSpace { rightPane, this, position = { 8, 92 }, labeledWindow = defaultNameSpace };
1862    StringOptionBox defaultNameSpace
1863    {
1864       rightPane, this, size = { 160, 22 }, position = { 8, 108 };
1865       text = $"Default Name Space", option = OPTION(defaultNameSpace);
1866    };
1867    BoolOptionBox strictNameSpaces
1868    {
1869       rightPane, this, position = { 172, 112 }, 
1870       text = $"Strict Name Spaces", option = OPTION(strictNameSpaces);
1871    };
1872
1873    BoolOptionBox memoryGuard
1874    {
1875       rightPane, this, position = { 8, 154 };
1876       text = $"MemoryGuard", hotKey = altM, option = OPTION(memoryGuard);
1877    };
1878
1879    Label labelWarnings { rightPane, position = { 116, 138 }, labeledWindow = warnings };
1880    WarningsDB warnings
1881    {
1882       rightPane, this, position = { 116, 154 };
1883       text = $"Warnings", hotKey = altW, option = OPTION(warnings);
1884    };
1885
1886    Label labelOptimization { rightPane, position = { 244, 138 }, labeledWindow = optimization };
1887    OptimizationDB optimization
1888    {
1889       rightPane, this, position = { 244, 154 }, size = { 120, 22 };
1890       text = $"Optimization", hotKey = altO, option = OPTION(optimization);
1891    };
1892
1893    BoolOptionBox debug
1894    {
1895       rightPane, this, position = { 8, 188 };
1896       text = $"Debuggable", hotKey = altG, option = OPTION(debug);
1897    };
1898
1899    BoolOptionBox profiling
1900    {
1901       rightPane, this, position = { 116, 188 };
1902       text = $"Profiling Data", hotKey = altP, option = OPTION(profile);
1903    };
1904
1905    BoolOptionBox noLineNumbers
1906    {
1907       rightPane, this, position = { 244, 188 };
1908       text = $"No Line Numbers", hotKey = altN, option = OPTION(noLineNumbers);
1909    };
1910
1911    Label labelIncludeDirs { includeDirs.editor, labeledWindow = includeDirs, position = { 0, 6 }; };
1912    DirsArrayOptionBox includeDirs
1913    {
1914       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 208, right = 8, bottom = 8 };
1915       text = $"Additional Include Directories", hotKey = altI, option = OPTION(includeDirs);
1916    };
1917
1918    CompilerTab()
1919    {
1920       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false,
1921          userData = null /* Now set in the ProjectNode directly to know we're in ProjectSettings Dialog -- ide.projectView*/ });
1922    }
1923
1924    bool OnCreate()
1925    {
1926       BuildTab buildTab = (BuildTab)master;
1927       buildTab.SelectNode(buildTab.lastSelectedNode, true);
1928       return true;
1929    }
1930
1931    void AddNode(ProjectNode node, DataRow addTo)
1932    {
1933       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1934
1935       row.tag = (int)node;
1936
1937       row.SetData(null, node);
1938
1939       if(node.files && node.files.first && node.parent && 
1940             !(!node.parent.parent && 
1941                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") || 
1942                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1943          row.collapsed = true;
1944       else if(node.type == folder)
1945          node.icon = openFolder;
1946
1947       if(node.files)
1948       {
1949          for(child : node.files)
1950             AddNode(child, row);
1951       }
1952    }
1953
1954    void LoadSettings()
1955    {
1956       OptionBox ob;
1957       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
1958          if(eClass_IsDerived(ob._class, class(OptionBox)))
1959             ob.Load();
1960
1961       if(activeChild && activeChild.active)
1962       {
1963          Window control = activeChild;
1964          control.Deactivate();         
1965          control.Activate();
1966       }
1967    }
1968
1969    bool OnPostCreate()
1970    {
1971       objDir.editor.Activate();
1972       return true;
1973    }
1974 }
1975
1976 class LinkerTab : Tab
1977 {
1978    background = activeBorder;
1979    text = $"Linker";
1980
1981    Label labelTargetName { this, position = { 8, 8 }, labeledWindow = targetName };
1982    StringOptionBox targetName
1983    {
1984       this, position = { 8, 24 }, size = { 200, 22 };
1985       text = $"Target Name", hotKey = altN, option = OPTION(targetFileName);
1986    };
1987    
1988    Label labelTargetType { this, position = { 216, 8 }, labeledWindow = targetType };
1989    TargetTypeDB targetType
1990    {
1991       this, position = { 216, 24 }, size = { 120, 22 };
1992       text = $"Target Type", hotKey = altT, option = OPTION(targetType);
1993    };
1994    
1995    Label labelTargetDirectory { this, position = { 344, 8 }, labeledWindow = targetDirectory };
1996    PathOptionBox targetDirectory
1997    {
1998       this, size = { 270, 22 }, anchor = { left = 344, top = 24, right = 8 };
1999       hotKey = altR, text = $"Target Directory", option = OPTION(targetDirectory);
2000    };
2001
2002    Label labelLibraries { this, position = { 8, 50 }, labeledWindow = libraries };
2003    StringArrayOptionBox libraries
2004    {
2005       this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2006       text = $"Additional Libraries", hotKey = altL, option = OPTION(libraries);
2007       configReplaces = true;
2008    };
2009
2010    Label labelLinkerOptions { this, position = { 8, 92 }, labeledWindow = linkerOptions };
2011    StringArrayOptionBox linkerOptions
2012    {
2013       this, size = { 290, 22 }, anchor = { left = 8, top = 108, right = 8 };
2014       text = $"Linker Options", hotKey = altO, option = OPTION(linkerOptions);
2015       configReplaces = true;
2016    };
2017
2018    BoolOptionBox console
2019    {
2020       this, position = { 8, 138 };
2021       text = $"Console Application", hotKey = altC, option = OPTION(console);
2022    };
2023
2024    BoolOptionBox compress
2025    {
2026       this, position = { 8, 162 };
2027       text = $"Compress", hotKey = altW, option = OPTION(compress);
2028    };
2029
2030    Label labelLibraryDirs { libraryDirs.editor, labeledWindow = libraryDirs, position = { 0, 6 }; };
2031    DirsArrayOptionBox libraryDirs
2032    {
2033       this, size = { 290, 22 }, anchor = { left = 8, top = 182, right = 8, bottom = 8 };
2034       text = $"Additional Library Directories", hotKey = altY, option = OPTION(libraryDirs);
2035    };
2036
2037    bool OnCreate()
2038    {
2039       ((BuildTab)master).SelectNode(project.topNode, true);
2040       return true;
2041    }
2042
2043    void LoadSettings()
2044    {
2045       OptionBox ob;
2046       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2047          if(eClass_IsDerived(ob._class, class(OptionBox)))
2048             ob.Load();
2049       compress.disabled = (config && config.options && config.options.debug == true) || project.topNode.options.debug == true;
2050
2051       if(activeChild && activeChild.active)
2052       {
2053          Window control = activeChild;
2054          control.Deactivate();         
2055          control.Activate();
2056       }
2057    }
2058 }
2059
2060 class BuilderTab : Tab
2061 {
2062    background = activeBorder;
2063    text = $"Builder";
2064
2065    Label labelPrebuildCommands { prebuildCommands.editor, labeledWindow = prebuildCommands, position = { 0, 6 }; };
2066    StringsArrayOptionBox prebuildCommands
2067    {
2068       this, size = { 290, 100 }, anchor = { left = 8, top = 52, right = 8 };
2069       text = $"Pre-build Commands", hotKey = altE, option = OPTION(prebuildCommands);
2070    };
2071
2072    Label labelPostbuildCommands { postbuildCommands.editor, labeledWindow = postbuildCommands, position = { 0, 6 }; };
2073    StringsArrayOptionBox postbuildCommands
2074    {
2075       this, size = { 290, 100 }, anchor = { left = 8, top = 160, right = 8 };
2076       text = $"Post-build Commands", hotKey = altT, option = OPTION(postbuildCommands);
2077    };
2078
2079    void LoadSettings()
2080    {
2081       bool disabled = strlen(((BuildTab)master).selectedPlatformName) > 0;
2082       OptionBox ob;
2083       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2084          if(eClass_IsDerived(ob._class, class(OptionBox)))
2085             ob.Load();
2086
2087       if(activeChild && activeChild.active)
2088       {
2089          Window control = activeChild;
2090          control.Deactivate();         
2091          control.Activate();
2092       }
2093    }
2094 }