ide/Project: Fixed crash on 'unset' trying to delete the ProjectNode::options propert...
[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       {
509          // delete currentNode.options;
510          // Property will free:
511          currentNode.options = null;
512       }
513
514       Load();
515    }
516
517    void FigureOutInherited()
518    {
519       ProjectNode node;
520       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
521       bool skipped = false;
522       for(node = currentNode; node; node = node.parent)
523       {
524          if(config && node.configurations)
525          {
526             for(c : node.configurations; !strcmpi(c.name, config.name))
527             {
528                if(platform && c.platforms)
529                {
530                   for(p : c.platforms; !strcmpi(p.name, platformName))
531                   {
532                      if(skipped && p.options && OptionSet(p.options))
533                         LoadOption(p.options);
534                      skipped = true;
535                      break;
536                   }
537                }               
538
539                if(skipped && c.options && OptionSet(c.options))
540                {
541                   LoadOption(c.options);
542                   if(configReplaces) return;
543                }
544                skipped = true;
545                break;
546             }
547          }
548          if(platform && node.platforms)
549          {
550             for(p : node.platforms; !strcmpi(p.name, platformName))
551             {
552                if(skipped && p.options && OptionSet(p.options))
553                   LoadOption(p.options);
554                skipped = true;
555                break;
556             }
557          }
558          if(skipped && node.options && OptionSet(node.options))
559             LoadOption(node.options);
560          else if(skipped && !node.parent)
561             LoadOption(null);
562          skipped = true;
563       }
564    }
565
566    void Retrieve()
567    {
568       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
569       MarkBuildTabModified();
570       if(config)
571       {
572          ProjectConfig c = null;
573          if(!currentNode.configurations) currentNode.configurations = { };
574          for(i : currentNode.configurations; !strcmpi(i.name, config.name)) { c = i; break; }
575          if(!c)
576             currentNode.configurations.Add(c = ProjectConfig { name = CopyString(config.name) });
577          if(platform)
578          {
579             PlatformOptions p = null;
580             if(!c.platforms) c.platforms = { };
581
582             for(i : c.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
583             if(!p)
584                c.platforms.Add(p = PlatformOptions { CopyString(platformName) });
585
586             if(!p.options) p.options = { };
587             RetrieveOption(p.options, true);
588             if(!mergeValues) SetAttribs(1);
589             return;
590          }
591          if(!c.options) c.options = { };
592          RetrieveOption(c.options, true);
593          if(!mergeValues) SetAttribs(1);
594          return;
595       }
596       if(platform)
597       {
598          PlatformOptions p = null;
599          if(!currentNode.platforms) currentNode.platforms = { };
600          for(i : currentNode.platforms; !strcmpi(i.name, platformName)) { p = i; break; }
601          if(!p)
602             currentNode.platforms.Add(p = PlatformOptions { CopyString(platformName) });
603
604          if(!p.options) p.options = { };
605          RetrieveOption(p.options, true);
606          if(!mergeValues) SetAttribs(1);
607          return;
608       }
609
610       if(!currentNode.options) currentNode.options = { };
611       RetrieveOption(currentNode.options, false);
612       if(!mergeValues) SetAttribs((currentNode.parent || OptionCheck(currentNode.options)) ? 1 : 0);
613    }
614
615    void Load()
616    {
617       ProjectNode node;
618       char * platformName = platform ? platform.OnGetString(0,0,0) : null;
619       bool setAttribs = false;
620       for(node = currentNode; node; node = node.parent)
621       {
622          ProjectConfig nodeConfig = null;
623          if(config && node.configurations)
624          {
625             for(c : node.configurations; !strcmpi(c.name, config.name))
626             {
627                if(platform && c.platforms)
628                {
629                   for(p : c.platforms; !strcmpi(p.name, platformName))
630                   {
631                      if(p.options && (mergeValues ? OptionCheck(p.options) : OptionSet(p.options)))
632                      {
633                         LoadOption(p.options);
634                         if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode) ? 1 : 2); }
635                         if(!mergeValues) { FinalizeLoading(); return; }
636                      }
637                      break;
638                   }
639                }               
640
641                nodeConfig = c;
642                break;
643             }
644          }
645          if(platform && node.platforms)
646          {
647             for(p : node.platforms; !strcmpi(p.name, platformName))
648             {
649                if(p.options && (mergeValues ? OptionCheck(p.options) : OptionSet(p.options)))
650                {
651                   LoadOption(p.options);
652                   if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !config) ? 1 : 2); }
653                   if(!mergeValues) { FinalizeLoading(); return; }
654                }
655                break;
656             }
657          }
658
659          if(nodeConfig && nodeConfig.options && ((mergeValues && !configReplaces) ? OptionCheck(nodeConfig.options) : OptionSet(nodeConfig.options)))
660          {
661             LoadOption(nodeConfig.options);
662             if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !platform) ? 1 : 2); }
663             if(!mergeValues || configReplaces) { FinalizeLoading(); return; }
664          }
665
666          if(node.options && (mergeValues ? OptionCheck(node.options) : OptionSet(node.options)))
667          {
668             LoadOption(node.options);
669             if(!node.parent && !OptionCheck(node.options))
670             {
671                if(!setAttribs) { setAttribs = true; SetAttribs(0); }
672             }
673             else
674             {
675                if(!setAttribs) { setAttribs = true; SetAttribs((node == currentNode && !config && !platform) ? 1 : 2); }
676             }
677             if(!mergeValues) { FinalizeLoading(); return; }
678          }
679          else if(!node.parent)
680          {
681             LoadOption(null);
682             if(!setAttribs) { setAttribs = true; SetAttribs(0); }
683             if(!mergeValues) { FinalizeLoading(); return; }
684          }
685       }
686       FinalizeLoading();
687    }
688 }
689
690 class StringOptionBox : OptionBox<String>
691 {
692    editor = EditBox
693    {
694       bool NotifyModified(EditBox editBox)
695       {
696          ((OptionBox)editBox.id).Retrieve();
697          return true;
698       }
699
700       textHorzScroll = true;
701    };
702
703    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
704    {
705       String * string = (String*)((byte *)options + option);
706       if(*string) delete *string;
707       *string = CopyString(((EditBox)editor).contents);
708    }
709
710    void LoadOption(ProjectOptions options)
711    {
712       ((EditBox)editor).contents = options ? *(String*)((byte *)options + option) : "";
713       ((EditBox)editor).Deselect();
714    }
715
716    bool OptionCheck(ProjectOptions options)
717    {
718       String string = *(String*)((byte *)options + option);
719       return string && string[0];
720    }
721
722    void UnsetOption(ProjectOptions options)
723    {
724       delete *(String*)((byte *)options + option);
725    }
726 }
727
728 class PathOptionBox : OptionBox<String>
729 {
730    bool Window::EditBoxORB(int x, int y, Modifiers mods)
731    {
732       Window parent = this.parent;
733       x += clientStart.x + position.x;
734       y += clientStart.y + position.y;
735       return ((OptionBox)this).OptionBox_OnRightButtonDown(parent, x, y, mods);
736    }
737
738    editor = PathBox
739    {
740       typeExpected = directory, browseDialog = { };
741       editBox.OnRightButtonDown = (void *)EditBoxORB;
742
743       bool NotifyModified(PathBox pathBox)
744       {
745          ((OptionBox)pathBox.id).Retrieve();
746          return true;
747       }
748    };
749
750    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
751    {
752       String * string = (String*)((byte *)options + option);
753       String slashPath = ((PathBox)editor).slashPath;
754       if(*string) delete *string;
755       *string = CopyString(slashPath);//(slashPath && slashPath[0]) ? CopyString(slashPath) : null;
756    }
757
758    void LoadOption(ProjectOptions options)
759    {
760       ((PathBox)editor).path = options ? *(String*)((byte *)options + option) : "";
761       ((PathBox)editor).Deselect();
762    }
763
764    bool OptionCheck(ProjectOptions options)
765    {
766       String string = *(String*)((byte *)options + option);
767       return string && string[0];
768    }
769
770    void UnsetOption(ProjectOptions options)
771    {
772       delete *(String*)((byte *)options + option);
773    }
774 }
775
776 class MultiStringOptionBox : OptionBox<Array<String>>
777 {
778    bool caseSensitive;
779
780    mergeValues = true;
781    caseSensitive = true;
782
783    virtual Array<String> GetStrings();
784    virtual void SetStrings(Array<String> value);
785
786    Array<String> tempStrings;
787
788    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
789    {
790       Array<String> newStrings = GetStrings();
791       Array<String> * strings = (Array<String>*)((byte *)options + option);
792       if(*strings) { strings->Free(); delete *strings; }
793
794       if(mergeValues)
795       {
796          Iterator<String> it { newStrings };
797
798          FigureOutInherited();
799
800          if(tempStrings)
801          {
802             Array<String> ts = tempStrings;
803             while(it.Next())
804             {
805                String s = it.data;
806                bool found = false;
807                for(i : tempStrings; !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
808                if(found && (!configReplaces || platform))   // ADDED || platform here...
809                {
810                   delete s;
811                   it.Remove();
812                }
813             }
814          }
815          delete tempStrings;
816       }
817
818       *strings = newStrings;
819
820       Load();
821    }
822
823    void LoadOption(ProjectOptions options)
824    {
825       if(mergeValues)
826       {
827          Array<String> strings = options ? *((Array<String>*)((byte *)options + option) : null;
828          if(strings)
829          {
830             if(!tempStrings)
831                tempStrings = { };
832             for(s : strings)
833             {
834                bool found = false;
835                for(i : tempStrings; !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
836                if(!found) tempStrings.Add(s);
837             }
838          }
839       }         
840       else
841       {
842          SetStrings(options ? *(Array<String>*)((byte *)options + option) : null);
843       }
844    }
845
846    void FinalizeLoading()
847    {
848       if(mergeValues)
849       {
850          SetStrings(tempStrings);
851          delete tempStrings;
852       }
853    }
854
855    bool OptionSet(ProjectOptions options)
856    {
857       Array<String> strings = *(Array<String>*)((byte *)options + option);
858       if(mergeValues && !configReplaces)
859       {
860          return strings && strings.count;
861       }
862       else
863          return strings != null;
864    }
865
866    bool OptionCheck(ProjectOptions options)
867    {
868       Array<String> strings = *(Array<String>*)((byte *)options + option);
869       return strings && strings.count;
870    }
871
872    void UnsetOption(ProjectOptions options)
873    {
874       Array<String> * strings = (Array<String>*)((byte *)options + option);
875       if(*strings) { strings->Free(); delete *strings; }
876    }
877 }
878
879 class StringArrayOptionBox : MultiStringOptionBox
880 {
881    editor = StringListBox
882    {
883       bool NotifyModified(EditBox editBox)
884       {
885          ((OptionBox)editBox.id).Retrieve();
886          return true;
887       }
888    };
889
890    // NO VIRTUAL PROPERTIES YET...
891    Array<String> GetStrings() { return ((StringListBox)editor).strings; }
892    void SetStrings(Array<String> value) { ((StringListBox)editor).strings = value; }
893 }
894
895 class StringsArrayOptionBox : MultiStringOptionBox
896 {
897    editor = StringsBox
898    {
899       bool OnCreate()
900       {
901          project = ::project;
902          return true;
903       }
904
905       bool NotifyModified(StringsBox stringsBox)
906       {
907          ((OptionBox)stringsBox.id).Retrieve();
908          return true;
909       }
910    };
911
912    Array<String> GetStrings() { return ((StringsBox)editor).strings; }
913    void SetStrings(Array<String> value) { ((StringsBox)editor).strings = value; }
914 }
915
916 class DirsArrayOptionBox : MultiStringOptionBox
917 {
918    editor = DirectoriesBox
919    {
920       bool NotifyModified(DirectoriesBox dirsBox)
921       {
922          ((OptionBox)dirsBox.id).Retrieve();
923          return true;
924       }
925
926       bool OnChangedDir(char * * directory)
927       {
928          char fixedDirectory[MAX_LOCATION] = "";
929          if(PathCat(fixedDirectory, *directory))
930          {
931             char cwdBackup[MAX_LOCATION];
932             if(project)
933             {
934                GetWorkingDir(cwdBackup, sizeof(cwdBackup));
935                ChangeWorkingDir(project.topNode.path);
936             }
937             FileFixCase(fixedDirectory);
938             if(project)
939                ChangeWorkingDir(cwdBackup);
940             delete *directory;
941             *directory = CopyString(fixedDirectory);
942             return true;
943          }
944          return false;
945       }
946
947       bool OnPrepareBrowseDir(char * * directory)
948       {
949          char dir[MAX_LOCATION];
950          if(project)
951          {
952             GetSystemPathBuffer(dir, project.topNode.path);
953             if(*directory)
954                PathCat(dir, *directory);
955          }
956          else if(*directory)
957             strcpy(dir, *directory);
958          else
959             dir[0] = '\0';
960          
961          delete *directory;
962          *directory = CopyString(dir);
963
964             // GCC 4.4 bug:  -----  path becomes *directory
965             //strcpy(dir, path ? path : "");
966          return true;
967       }
968
969       bool OnBrowsedDir(char * * directory)
970       {
971          if(project)
972          {
973             char path[MAX_LOCATION];
974             MakePathRelative(*directory, project.topNode.path, path);
975             delete *directory;
976             *directory = CopyString(path);
977          }
978          return true;
979       }
980    };
981
982    Array<String> GetStrings() { return ((DirectoriesBox)editor).strings; }
983    void SetStrings(Array<String> value) { ((DirectoriesBox)editor).strings = value; }
984 }
985
986 class BoolOptionBox : OptionBox<SetBool>
987 {
988    editor = Button
989    {
990       isCheckbox = true;
991
992       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
993       {
994          ((OptionBox)button.id).Retrieve();
995          return true;
996       }
997    };
998
999    bool OptionCheck(ProjectOptions options)
1000    {
1001       return *(SetBool*)((byte *)options + option) == true;
1002    }
1003
1004    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1005    {
1006       bool checked = ((Button)editor).checked;
1007       *(SetBool*)((byte *)options + option) = checked ? true : 
1008          ((currentNode.parent || isCfgOrPlt) ? false : unset);
1009    }
1010
1011    void LoadOption(ProjectOptions options)
1012    {
1013       ((Button)editor).checked = options && (*(SetBool*)((byte *)options + option) == true);
1014    }
1015 }
1016
1017 class DropOptionBox : OptionBox
1018 {
1019    editor = DropBox
1020    {
1021       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
1022       {
1023          ((OptionBox)dropBox.id).Retrieve();
1024          return true;
1025       }
1026    };   
1027
1028    void LoadOption(ProjectOptions options)
1029    {
1030       DropBox dropBox = (DropBox)editor;
1031       Z value = options ? *(Z*)((byte *)options + option) : (Z)0;
1032       dropBox.currentRow = value ? dropBox.FindRow((int)value) : dropBox.firstRow;
1033    }
1034
1035    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1036    {
1037       DropBox dropBox = (DropBox)editor;
1038       DataRow row = dropBox.currentRow;
1039       Z value = (Z)(row ? row.tag : 0);
1040       *(Z*)((byte *)options + option) = value;
1041    }
1042 }
1043
1044 class TargetTypeDB : DropOptionBox<TargetTypes>
1045 {
1046    TargetTypeDB()
1047    {
1048       DataRow row;
1049
1050       row = ((DropBox)editor).AddRow();
1051       row.tag = TargetTypes::executable;
1052       row.SetData(null, $"Executable");
1053
1054       row = ((DropBox)editor).AddRow();
1055       row.tag = TargetTypes::sharedLibrary;
1056       row.SetData(null, $"Shared Library");
1057
1058       row = ((DropBox)editor).AddRow();
1059       row.tag = TargetTypes::staticLibrary;
1060       row.SetData(null, $"Static Library");
1061    }
1062
1063    bool OptionCheck(ProjectOptions options)
1064    {
1065       TargetTypes value = *(TargetTypes*)((byte *)options + option);
1066       return value && value != executable;
1067    }
1068 }
1069
1070 class OptimizationDB : DropOptionBox<OptimizationStrategy>
1071 {
1072    OptimizationDB()
1073    {
1074       DataRow row;
1075       row = ((DropBox)editor).AddRow();
1076       row.tag = OptimizationStrategy::none;
1077       row.SetData(null, $"None");
1078
1079       row = ((DropBox)editor).AddRow();
1080       row.tag = OptimizationStrategy::speed;
1081       row.SetData(null, $"For Speed (-O2)");
1082
1083       row = ((DropBox)editor).AddRow();
1084       row.tag = OptimizationStrategy::size;
1085       row.SetData(null, $"For Size (-Os)");
1086    }
1087
1088    bool OptionCheck(ProjectOptions options)
1089    {
1090       OptimizationStrategy value = *(OptimizationStrategy*)((byte *)options + option);
1091       return value && value != none;
1092    }
1093 }
1094
1095 class WarningsDB : DropOptionBox<WarningsOption>
1096 {
1097    WarningsDB()
1098    {
1099       DataRow row;
1100       row = ((DropBox)editor).AddRow();
1101       row.tag = WarningsOption::normal;
1102       row.SetData(null, $"Normal");
1103
1104       row = ((DropBox)editor).AddRow();
1105       row.tag = WarningsOption::none;
1106       row.SetData(null, $"None");
1107
1108       row = ((DropBox)editor).AddRow();
1109       row.tag = WarningsOption::all;
1110       row.SetData(null, $"All");
1111    }
1112
1113    bool OptionCheck(ProjectOptions options)
1114    {
1115       WarningsOption value = *(WarningsOption*)((byte *)options + option);
1116       return value && value != none;
1117    }
1118 }
1119
1120 void DrawStipple(Surface surface, Size clientSize)
1121 {
1122    int x1 = 0;
1123    int y1 = 0;
1124    int x2 = clientSize.w - 1;
1125    int y2 = clientSize.h - 1;
1126    if((x2 - x1) & 1) x2--;
1127    if((y2 - y1) & 1) y2--;
1128
1129    surface.LineStipple(0x5555);
1130    surface.Rectangle(x1, y1, x2, y2);
1131    surface.LineStipple(0);            
1132 }
1133
1134 class BuildTab : Tab
1135 {
1136    text = $"Build";
1137    background = activeBorder;
1138    tabCycle = true;
1139
1140    ProjectNode backupNode;
1141    String activeConfigName;
1142
1143    ProjectNode lastSelectedNode;
1144
1145    property char * selectedConfigName
1146    {
1147       get
1148       {
1149          if(created)
1150          {
1151             SelectorButton button = (SelectorButton)configSelector.selectedButton;
1152             if(button && button.id)
1153             {
1154                ProjectConfig config = (ProjectConfig)button.id;
1155                return config.name;
1156             }
1157          }
1158          return "";
1159       }
1160    }
1161
1162    property char * selectedPlatformName
1163    {
1164       get
1165       {
1166          if(created)
1167          {
1168             SelectorButton button = (SelectorButton)platformSelector.selectedButton;
1169             if(button && button.id)
1170             {
1171                Platform platform = (Platform)button.id;
1172                char * platformName = platform ? platform.OnGetString(0,0,0) : null; // all these platformName are leaking, no? 
1173                return platformName;
1174             }
1175          }
1176          return "";
1177       }
1178    }
1179
1180    Label labelConfigurations
1181    {
1182       this, anchor = { left = 8, top = 14 }, labeledWindow = configSelector;
1183
1184       void OnRedraw(Surface surface)
1185       {
1186          Label::OnRedraw(surface);
1187          if(labeledWindow.active) DrawStipple(surface, clientSize);
1188       }
1189    };
1190    SelectorBar configSelector
1191    {
1192       this, text = $"Configurations: ", anchor = { left = 98, top = 8, right = 54 }; size = { 0, 26 };
1193       opacity = 0;
1194       direction = horizontal, scrollable = true;
1195
1196       bool OnKeyDown(Key key, unichar ch)
1197       {
1198          if(key == insert)
1199          {
1200             ((BuildTab)parent).createConfig.NotifyClicked(parent, ((BuildTab)parent).createConfig, 0, 0, 0);
1201             return false;
1202          }
1203          else if(key == del)
1204          {
1205             ((BuildTab)parent).deleteConfig.NotifyClicked(parent, ((BuildTab)parent).deleteConfig, 0, 0, 0);
1206             return false;
1207          }
1208          return SelectorBar::OnKeyDown(key, ch);
1209       }
1210       
1211       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1212       {
1213          ((BuildTab)master).labelConfigurations.Update(null);
1214          return true;
1215       }
1216    };
1217
1218    Button createConfig
1219    {
1220       parent = this, bevelOver = true, inactive = true;
1221       size = { 22, 22 };
1222       anchor = { top = 10, right = 31 };
1223       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/docNew.png", alphaBlend = true };
1224
1225       bool NotifyClicked(Button b, int x, int y, Modifiers mods)
1226       {
1227          char tmp[MAX_F_STRING];
1228          ProjectConfig config;
1229          EditableSelectorButton button;
1230
1231          FindUniqueConfigName("NewConfig", false, tmp);
1232
1233          config =
1234          {
1235             makingModified = true;
1236             compilingModified = true;
1237             linkingModified = true;
1238             name = CopyString(tmp);
1239             options =
1240             {
1241                // objectsDirectory = /*CopyString(*/defaultObjDirExpression/*)*/;
1242             };
1243          };
1244          if(!project.topNode.configurations) project.topNode.configurations = { };
1245          project.topNode.configurations.Add(config);
1246          /*
1247          targetType = project.config.options.targetType;
1248          config.options.
1249          config.options.targetFileName = project.moduleName;
1250          config.options.targetDir.dir = "";
1251          config.options.objectsDirectory = defaultObjDirExpression);
1252          config.options.debug = true;
1253          config.options.optimization = none;
1254          config.options.warnings = all;
1255          */         
1256
1257          button =
1258          {
1259             configSelector, renameable = true, master = this, text = config.name, id = (int)config;
1260             NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1261          };
1262
1263          configSelector.Select(button);
1264          modifiedDocument = true;
1265          return true;
1266       }
1267    };
1268    /*Button duplicateConfig
1269    {
1270       parent = this, bevelOver = true, inactive = true;
1271       size = { 22, 22 };
1272       anchor = { top = 10, right = 31 };
1273       hotKey = altU, bitmap = BitmapResource { fileName = ":actions/editCopy.png", alphaBlend = true };
1274    };*/
1275    Button deleteConfig
1276    {
1277       parent = this, bevelOver = true, inactive = true;
1278       size = { 22, 22 };
1279       anchor = { top = 10, right = 8 };
1280       hotKey = altD, bitmap = BitmapResource { fileName = ":actions/delete2.png", alphaBlend = true };
1281
1282       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1283       {
1284          if(config)
1285          {
1286             String title = PrintString($"Delete ", config.name, $" Configuration");
1287             String msg = PrintString($"Are you sure you wish to delete the ", config.name, $" configuration?");
1288             if(MessageBox { type = okCancel, text = title, contents = msg }.Modal() == ok)
1289             {
1290                Iterator<Window> it { configSelector.controls };
1291                ProjectConfig configToDelete = config;
1292                /*
1293                while(it.Next())
1294                {
1295                   SelectorButton button = (SelectorButton)it.data;
1296                   if((ProjectConfig)button.id == config)
1297                   {
1298                      button.visible = false;
1299                      button.Destroy(0);
1300
1301                      if(it.Prev())
1302                      {
1303                         button = (SelectorButton)it.data;
1304                         config = (ProjectConfig)button.id;
1305                         configSelector.Select(button);
1306                      }
1307                      break;
1308                   }
1309                }
1310                */
1311                SelectorButton button = configSelector.FindButtonByID((int)configToDelete);
1312                if(button)
1313                   configSelector.RemoveButton(button);
1314
1315                project.topNode.DeleteConfig(configToDelete);
1316
1317                modifiedDocument = true;
1318             }
1319             delete title;
1320             delete msg;
1321          }
1322          return true;
1323       }
1324    };
1325    
1326    Label labelPlatforms
1327    {
1328       this, anchor = { left = 8, top = 44 }, labeledWindow = platformSelector;
1329
1330       void OnRedraw(Surface surface)
1331       {
1332          Label::OnRedraw(surface);
1333          if(labeledWindow.active) DrawStipple(surface, clientSize);
1334       }
1335    };
1336    SelectorBar platformSelector
1337    {
1338       this, text = $"Platforms: ", anchor = { left = 64, top = 38, right = 54 }; size = { 0, 26 };
1339       opacity = 0;
1340       direction = horizontal, scrollable = true;
1341
1342       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1343       {
1344          ((BuildTab)master).labelPlatforms.Update(null);
1345          return true;
1346       }
1347    };
1348
1349    TabControl buildTabControl
1350    {
1351       this, background = activeBorder, anchor = { left = 8, top = 64, right = 8, bottom = 8 };
1352       curTab = compilerTab;
1353    };
1354    CompilerTab compilerTab { this, tabControl = buildTabControl };
1355    LinkerTab linkerTab { this, tabControl = buildTabControl };
1356    BuilderTab builderTab { this, tabControl = buildTabControl };
1357    Label rightClick
1358    {
1359       this, font = { font.faceName, font.size, italic = true }, stayOnTop = true,
1360       text = $"(Right click or press Ctrl-Del to revert an option to inherited value)", anchor = { top = 72, right = 16 }
1361    };
1362
1363    void FindUniqueConfigName(char * baseName, bool startWithNumber, char * output)
1364    {
1365       int num = 0;
1366       char tmp[MAX_F_STRING];
1367       if(startWithNumber)
1368          sprintf(tmp, "%s%d", baseName, num);
1369       else
1370          strcpy(tmp, baseName);
1371       while(true)
1372       {
1373          ProjectConfig config = null;
1374          for(c : project.topNode.configurations)
1375          {     // TOFIX: Error when omitting these brackets, c not found
1376             if(c.name && !strcmp(c.name, tmp))
1377             {
1378                config = c;
1379                break;
1380             }
1381          }
1382          if(config)
1383          {
1384             num++;
1385             sprintf(tmp, "%s%d", baseName, num);
1386          }
1387          else
1388             break;
1389       }
1390       strcpy(output, tmp);
1391    }
1392
1393    bool PlatformClicked(Button clickedButton, int x, int y, Modifiers mods)
1394    {
1395       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1396       {
1397          platform = (Platform)clickedButton.id;
1398
1399          // Load Settings Into Dialog
1400          compilerTab.LoadSettings();
1401          linkerTab.LoadSettings();
1402          builderTab.LoadSettings();
1403
1404          if(!mods)
1405             buildTabControl.Activate();
1406
1407          if(compilerTab.rightPaneHeader.visible)
1408             compilerTab.rightPaneHeader.Update(null);
1409          ((ProjectSettings)master).UpdateDialogTitle();
1410       }
1411       return true;
1412    }
1413
1414    ~BuildTab()
1415    {
1416       platformSelector.DestroyChildren();
1417       configSelector.DestroyChildren();
1418
1419       delete activeConfigName;
1420    }
1421
1422    bool ConfigOnRename(EditableSelectorButton button, char * * oldName, char * * newName)
1423    {
1424       int c, d = 0;
1425       char ch;
1426
1427       for(c = 0; (ch = (*newName)[c]); c++)
1428       {
1429          if(ch == '_' || isalpha(ch) || (isdigit(ch) && d))
1430             (*newName)[d++] = ch;
1431       }
1432       (*newName)[d] = 0;
1433
1434       {
1435          bool found = false;
1436          for(c : project.topNode.configurations; c != config)
1437          {
1438             if(!strcmpi(c.name, *newName))
1439             {
1440                found = true;
1441                break;
1442             }
1443          }
1444          if(found || !(*newName)[0])
1445          {
1446             char tmp[MAX_F_STRING];
1447             char * tmpName = config.name;
1448             config.name = null;
1449             FindUniqueConfigName("NewConfig", false, tmp);
1450             config.name = tmpName;
1451             delete *newName;
1452             *newName = CopyString(tmp);
1453          }
1454       }
1455
1456       if(activeConfigName && !strcmp(activeConfigName, *oldName))
1457       {
1458          delete activeConfigName;
1459          activeConfigName = CopyString(*newName);
1460       }
1461
1462       project.topNode.RenameConfig(config.name, *newName);
1463       
1464       modifiedDocument = true;
1465       return true;
1466    }
1467
1468    bool ConfigClicked(Button clickedButton, int x, int y, Modifiers mods)
1469    {
1470       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1471       {
1472          config = (ProjectConfig)clickedButton.id;
1473
1474          // Load Settings Into Dialog
1475          compilerTab.LoadSettings();
1476          linkerTab.LoadSettings();
1477          builderTab.LoadSettings();
1478
1479          deleteConfig.disabled = (clickedButton._class == class(SelectorButton));
1480
1481          if(!mods)
1482             buildTabControl.Activate();
1483
1484          if(compilerTab.rightPaneHeader.visible)
1485             compilerTab.rightPaneHeader.Update(null);
1486          ((ProjectSettings)master).UpdateDialogTitle();
1487       }
1488       return true;
1489    }
1490
1491    void SelectNode(ProjectNode node, bool ignoreAsLastSelection)
1492    {
1493       if(node != currentNode)
1494       {
1495          Window ac = compilerTab.rightPane.activeChild;
1496          bool prevNodeRes = currentNode ? currentNode.isInResources : false;
1497          bool newNodeRes;
1498
1499          if(!node) node = project.topNode;
1500
1501          newNodeRes = node.isInResources;
1502          
1503          currentNode = node;
1504          if(!ignoreAsLastSelection)
1505             lastSelectedNode = node;
1506
1507          ((ProjectSettings)master).UpdateDialogTitle();
1508          if(node.type == project)
1509          {
1510             compilerTab.rightPaneHeader.visible = false;
1511          }
1512          else
1513          {
1514             compilerTab.rightPaneHeader.id = (int)node;
1515             compilerTab.rightPaneHeader.Update(null);
1516             compilerTab.rightPaneHeader.visible = true;
1517          }
1518
1519          {
1520             DataRow row = compilerTab.fileList.FindSubRow((int)currentNode);
1521             if(row)
1522             {
1523                compilerTab.fileList.currentRow = row;
1524                while((row = row.parent))
1525                   row.collapsed = false;
1526             }
1527          }
1528
1529          if(prevNodeRes != newNodeRes)
1530          {
1531             compilerTab.labelObjDir.visible = !newNodeRes;
1532             compilerTab.objDir.visible = !newNodeRes;
1533             compilerTab.excludeFromBuild.visible = !newNodeRes;
1534             compilerTab.labelPreprocessorDefs.visible = !newNodeRes;
1535             compilerTab.preprocessorDefs.visible = !newNodeRes;
1536             compilerTab.labelDefaultNameSpace.visible = !newNodeRes;
1537             compilerTab.defaultNameSpace.visible = !newNodeRes;
1538             compilerTab.strictNameSpaces.visible = !newNodeRes;
1539             compilerTab.memoryGuard.visible = !newNodeRes;
1540             compilerTab.noLineNumbers.visible = !newNodeRes;
1541             compilerTab.debug.visible = !newNodeRes;
1542             compilerTab.labelWarnings.visible = !newNodeRes;
1543             compilerTab.warnings.visible = !newNodeRes;
1544             compilerTab.profiling.visible = !newNodeRes;
1545             compilerTab.labelOptimization.visible = !newNodeRes;
1546             compilerTab.optimization.visible = !newNodeRes;
1547             compilerTab.labelIncludeDirs.visible = !newNodeRes;
1548             compilerTab.includeDirs.visible = !newNodeRes;
1549          }
1550          
1551          if(node == project.topNode)
1552          {
1553             compilerTab.objDir.visible = true;
1554             compilerTab.labelObjDir.visible = true;
1555
1556             compilerTab.excludeFromBuild.visible = false;
1557          }
1558          else
1559          {
1560             compilerTab.objDir.visible = false;
1561             compilerTab.labelObjDir.visible = false;
1562
1563             compilerTab.excludeFromBuild.visible = (node != project.resNode);
1564          }
1565
1566          // Load Settings Into Dialog
1567          compilerTab.LoadSettings();
1568          linkerTab.LoadSettings();
1569          builderTab.LoadSettings();
1570
1571          if(ac)
1572          {
1573             if(!ac.visible)
1574             {
1575                if(ac == compilerTab.excludeFromBuild.editor)
1576                   ac = compilerTab.objDir.editor;
1577                else if(compilerTab.excludeFromBuild.editor.visible)
1578                   ac = compilerTab.excludeFromBuild.editor;
1579             }
1580             ac.MakeActive();
1581          }
1582       }
1583    }
1584
1585    void CreateConfigButtons()
1586    {
1587       SelectorButton commonButton;
1588
1589       // Create Config Buttons
1590       commonButton = SelectorButton
1591       {
1592          configSelector, master = this, text = $"Common", id = (int)null; font = { font.faceName, font.size, true };
1593          checked = true;
1594          NotifyClicked = ConfigClicked;
1595       };
1596       
1597       config = null;
1598
1599       if(project.topNode.configurations)
1600       {
1601          for(c : project.topNode.configurations)
1602          {
1603             EditableSelectorButton button
1604             {
1605                configSelector, master = this, renameable = true, text = c.name, id = (int)c;
1606                NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1607             };
1608          }
1609       }
1610    }
1611    
1612    void Init()
1613    {
1614       Platform p;
1615       SelectorButton button;
1616
1617       activeConfigName = project.config ? CopyString(project.config.name) : null;
1618       
1619       compilerTab.AddNode(project.topNode, null);
1620
1621       CreateConfigButtons();
1622
1623       platformButton = button =
1624       {
1625          platformSelector, master = this, text = $"Common", id = 0;  font = { font.faceName, font.size, true };
1626          NotifyClicked = PlatformClicked; checked = true;
1627       };
1628
1629       platform = 0;
1630
1631       for(p = (Platform)1; p < Platform::enumSize; p++)
1632       {
1633          SelectorButton button
1634          {
1635             platformSelector, master = this, text = p.OnGetString(0,0,0), id = (int)p; 
1636             NotifyClicked = PlatformClicked;
1637          };
1638       }
1639    }
1640    SelectorButton platformButton;
1641
1642    bool OnPostCreate()
1643    {
1644       // Backup Current Settings
1645       backupNode = project.topNode.Backup();
1646
1647       buildTabControl.Activate();
1648
1649       {
1650          Iterator<Window> it { configSelector.controls };
1651          while(it.Next())
1652          {
1653             SelectorButton configButton = (SelectorButton)it.data;
1654             ProjectConfig buttonConfig = (ProjectConfig)configButton.id;
1655             if(buttonConfig == project.config)
1656             {
1657                configButton.Activate();
1658                configButton.checked = true;
1659                ConfigClicked(configButton, 0, 0, (Modifiers)null);
1660                break;
1661             }
1662          }
1663       }
1664       if(platformButton)
1665       {
1666          platformButton.MakeActive();
1667          platformButton = null;
1668       }
1669       return true;
1670    }
1671
1672    void OnDestroy()
1673    {
1674       delete backupNode;
1675
1676       lastSelectedNode = null;
1677
1678       project.config = null;
1679
1680       /* // THIS IS NOW AUTOMATED WITH A project CHECK IN ProjectNode
1681       project.configurations = project.topNode.configurations;
1682       project.platforms = project.topNode.platforms;
1683       project.options = project.topNode.options;
1684       */
1685
1686       if(project.topNode.configurations)
1687       {
1688          for(c : project.topNode.configurations)
1689          {
1690             if(!strcmpi(c.name, activeConfigName))
1691             {
1692                project.config = c;
1693                break;
1694             }
1695          }
1696       }
1697    }
1698
1699    void RevertChanges()
1700    {
1701       String configName = config ? CopyString(config.name) : null;
1702
1703       // Revert to saved project options
1704       project.topNode.Revert(backupNode);
1705
1706       configSelector.DestroyChildren();
1707       CreateConfigButtons();
1708
1709       // Reselect Configuration
1710       if(configName)
1711       {
1712          Iterator<Window> it { configSelector.controls };
1713          while(it.Next())
1714          {
1715             Button button = (Button)it.data;
1716             ProjectConfig c = (ProjectConfig)button.id;
1717             if(c && !strcmp(c.name, configName))
1718             {
1719                config = c;
1720                button.Activate();
1721                button.checked = true;
1722                ConfigClicked(button, 0,0, 0);
1723                break;
1724             }
1725          }
1726       }
1727
1728       SelectNode(project.topNode, false);
1729
1730       delete configName;
1731    }
1732
1733    bool OnClose(bool parentClosing)
1734    {
1735       if(modifiedDocument)
1736       {
1737          DialogResult diagRes = MessageBox
1738          {
1739             type = yesNoCancel, master = ide,
1740             text = $"Save changes to project settings?",
1741             contents = $"Would you like to save changes made to the build options?"
1742          }.Modal();
1743          if(diagRes == no)
1744             RevertChanges();
1745          if(diagRes == cancel)
1746             return false;
1747          if(diagRes == yes)
1748          {
1749             project.MarkChanges(backupNode);
1750             project.topNode.modified = true;
1751             ide.projectView.modifiedDocument = true;
1752             ide.projectView.Update(null);
1753          }
1754          modifiedDocument = false;
1755       }
1756       return true;
1757    }
1758 }
1759
1760 class CompilerTab : Tab
1761 {
1762    background = activeBorder;
1763    text = $"Compiler";
1764
1765    Window leftPane { this, size = { 180 }, anchor = { left = 0, top = 0, bottom = 0 }, background = activeBorder };
1766
1767    Label labelFileList { leftPane, this, position = { 8, 8 }, labeledWindow = fileList };
1768    ListBox fileList
1769    {
1770       leftPane, this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true;
1771       // THIS WOULD BE EVEN MORE FUN: multiSelect = true,
1772       fullRowSelect = false, collapseControl = true, treeBranches = true;
1773       alwaysHighLight = true;
1774       selectionColor = unfocusedSelectorColor;
1775       size = { 180 };
1776       anchor = Anchor { left = 8, top = 24, right = 4, bottom = 8 };
1777       text = $"Files";
1778
1779       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1780       {
1781          BuildTab buildTab = (BuildTab)master;
1782          ProjectNode node = (ProjectNode)row.tag;
1783          buildTab.SelectNode(node, false);
1784          return true;
1785       }
1786
1787       void OnRedraw(Surface surface)
1788       {
1789          ide.projectView.drawingInProjectSettingsDialog = true;
1790          ListBox::OnRedraw(surface);
1791          ide.projectView.drawingInProjectSettingsDialog = false;
1792       }
1793
1794       bool NotifyActivate(Window window, bool active, Window previous)
1795       {
1796          if(active)
1797          {
1798             //subclass(Skin) skinClass = (subclass(Skin))eSystem_FindClass(app, app.skin);
1799             fileList.selectionColor = Color { 10, 36, 106 }; //skinClass.selectionColor; // darkBlue;
1800          }
1801          else if(fileList.currentRow)
1802          {
1803             DataRow currentRow = fileList.currentRow;
1804             //int headerSize = ((fileList.style.header) ? fileList.rowHeight : 0);
1805             int height = fileList.clientSize.h + 1;// - fileList.headerSize;
1806             fileList.selectionColor = unfocusedSelectorColor;
1807             if(currentRow && currentRow.index * fileList.rowHeight > fileList.scroll.y + height - fileList.rowHeight)
1808                fileList.SetScrollPosition(fileList.scroll.x, currentRow.index * fileList.rowHeight - height + fileList.rowHeight);
1809             else if(!currentRow || currentRow.index * fileList.rowHeight < fileList.scroll.y)
1810                fileList.SetScrollPosition(fileList.scroll.x, currentRow ? currentRow.index * fileList.rowHeight : 0);
1811
1812          }
1813
1814          return true;
1815       }
1816    };
1817
1818    Window rightPane 
1819    {
1820       this, anchor = { left = 196, top = 0, right = 0, bottom = 0 }, background = activeBorder, tabCycle = true;
1821    };
1822
1823    Window rightPaneHeader
1824    {
1825       rightPane, this, size = { h = 21 }, anchor = { left = 0, top = 0, right = 0 }, background = Color { 70, 96, 166 };//0x0F3F66;
1826       foreground = white; visible = false;
1827
1828       void OnRedraw(Surface surface)
1829       {
1830          if(id)
1831          {
1832             ide.projectView.drawingInProjectSettingsDialogHeader = true;
1833             class(ProjectNode)._vTbl[__ecereVMethodID_class_OnDisplay](class(ProjectNode),
1834                id, surface, 8, 2, clientSize.w, ide.projectView, Alignment::left, DataDisplayFlags { selected = true });
1835             ide.projectView.drawingInProjectSettingsDialogHeader = false;
1836          }
1837       }
1838    };
1839
1840    PaneSplitter splitter
1841    {
1842       this, leftPane = leftPane, rightPane = rightPane, split = 188
1843    };
1844
1845    Label labelObjDir { rightPane, this, position = { 8, 8 }, labeledWindow = objDir };
1846    PathOptionBox objDir
1847    {
1848       rightPane, this, size = { 250, 22 }, anchor = { left = 8, top = 24, right = 8 };
1849       text = $"Intermediate Objects Directory", hotKey = altJ, option = OPTION(objectsDirectory);
1850    };
1851
1852    BoolOptionBox excludeFromBuild
1853    {
1854       rightPane, this, position = { 8, 28 },
1855       text = $"Exclude from Build", visible = false, option = OPTION(excludeFromBuild);
1856    };
1857
1858    Label labelPreprocessorDefs { rightPane, this, position = { 8, 50 }, labeledWindow = preprocessorDefs };
1859    StringArrayOptionBox preprocessorDefs
1860    {
1861       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
1862       text = $"Preprocessor Definitions", hotKey = altD, option = OPTION(preprocessorDefinitions);
1863    };
1864
1865    Label labelDefaultNameSpace { rightPane, this, position = { 8, 92 }, labeledWindow = defaultNameSpace };
1866    StringOptionBox defaultNameSpace
1867    {
1868       rightPane, this, size = { 160, 22 }, position = { 8, 108 };
1869       text = $"Default Name Space", option = OPTION(defaultNameSpace);
1870    };
1871    BoolOptionBox strictNameSpaces
1872    {
1873       rightPane, this, position = { 172, 112 }, 
1874       text = $"Strict Name Spaces", option = OPTION(strictNameSpaces);
1875    };
1876
1877    BoolOptionBox memoryGuard
1878    {
1879       rightPane, this, position = { 8, 154 };
1880       text = $"MemoryGuard", hotKey = altM, option = OPTION(memoryGuard);
1881    };
1882
1883    Label labelWarnings { rightPane, position = { 116, 138 }, labeledWindow = warnings };
1884    WarningsDB warnings
1885    {
1886       rightPane, this, position = { 116, 154 };
1887       text = $"Warnings", hotKey = altW, option = OPTION(warnings);
1888    };
1889
1890    Label labelOptimization { rightPane, position = { 244, 138 }, labeledWindow = optimization };
1891    OptimizationDB optimization
1892    {
1893       rightPane, this, position = { 244, 154 }, size = { 120, 22 };
1894       text = $"Optimization", hotKey = altO, option = OPTION(optimization);
1895    };
1896
1897    BoolOptionBox debug
1898    {
1899       rightPane, this, position = { 8, 188 };
1900       text = $"Debuggable", hotKey = altG, option = OPTION(debug);
1901    };
1902
1903    BoolOptionBox profiling
1904    {
1905       rightPane, this, position = { 116, 188 };
1906       text = $"Profiling Data", hotKey = altP, option = OPTION(profile);
1907    };
1908
1909    BoolOptionBox noLineNumbers
1910    {
1911       rightPane, this, position = { 244, 188 };
1912       text = $"No Line Numbers", hotKey = altN, option = OPTION(noLineNumbers);
1913    };
1914
1915    Label labelIncludeDirs { includeDirs.editor, labeledWindow = includeDirs, position = { 0, 6 }; };
1916    DirsArrayOptionBox includeDirs
1917    {
1918       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 208, right = 8, bottom = 8 };
1919       text = $"Additional Include Directories", hotKey = altI, option = OPTION(includeDirs);
1920    };
1921
1922    CompilerTab()
1923    {
1924       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false,
1925          userData = null /* Now set in the ProjectNode directly to know we're in ProjectSettings Dialog -- ide.projectView*/ });
1926    }
1927
1928    bool OnCreate()
1929    {
1930       BuildTab buildTab = (BuildTab)master;
1931       buildTab.SelectNode(buildTab.lastSelectedNode, true);
1932       return true;
1933    }
1934
1935    void AddNode(ProjectNode node, DataRow addTo)
1936    {
1937       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1938
1939       row.tag = (int)node;
1940
1941       row.SetData(null, node);
1942
1943       if(node.files && node.files.first && node.parent && 
1944             !(!node.parent.parent && 
1945                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") || 
1946                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1947          row.collapsed = true;
1948       else if(node.type == folder)
1949          node.icon = openFolder;
1950
1951       if(node.files)
1952       {
1953          for(child : node.files)
1954             AddNode(child, row);
1955       }
1956    }
1957
1958    void LoadSettings()
1959    {
1960       OptionBox ob;
1961       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
1962          if(eClass_IsDerived(ob._class, class(OptionBox)))
1963             ob.Load();
1964
1965       if(activeChild && activeChild.active)
1966       {
1967          Window control = activeChild;
1968          control.Deactivate();         
1969          control.Activate();
1970       }
1971    }
1972
1973    bool OnPostCreate()
1974    {
1975       objDir.editor.Activate();
1976       return true;
1977    }
1978 }
1979
1980 class LinkerTab : Tab
1981 {
1982    background = activeBorder;
1983    text = $"Linker";
1984
1985    Label labelTargetName { this, position = { 8, 8 }, labeledWindow = targetName };
1986    StringOptionBox targetName
1987    {
1988       this, position = { 8, 24 }, size = { 200, 22 };
1989       text = $"Target Name", hotKey = altN, option = OPTION(targetFileName);
1990    };
1991    
1992    Label labelTargetType { this, position = { 216, 8 }, labeledWindow = targetType };
1993    TargetTypeDB targetType
1994    {
1995       this, position = { 216, 24 }, size = { 120, 22 };
1996       text = $"Target Type", hotKey = altT, option = OPTION(targetType);
1997    };
1998    
1999    Label labelTargetDirectory { this, position = { 344, 8 }, labeledWindow = targetDirectory };
2000    PathOptionBox targetDirectory
2001    {
2002       this, size = { 270, 22 }, anchor = { left = 344, top = 24, right = 8 };
2003       hotKey = altR, text = $"Target Directory", option = OPTION(targetDirectory);
2004    };
2005
2006    Label labelLibraries { this, position = { 8, 50 }, labeledWindow = libraries };
2007    StringArrayOptionBox libraries
2008    {
2009       this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2010       text = $"Additional Libraries", hotKey = altL, option = OPTION(libraries);
2011       configReplaces = true;
2012    };
2013
2014    Label labelLinkerOptions { this, position = { 8, 92 }, labeledWindow = linkerOptions };
2015    StringArrayOptionBox linkerOptions
2016    {
2017       this, size = { 290, 22 }, anchor = { left = 8, top = 108, right = 8 };
2018       text = $"Linker Options", hotKey = altO, option = OPTION(linkerOptions);
2019       configReplaces = true;
2020    };
2021
2022    BoolOptionBox console
2023    {
2024       this, position = { 8, 138 };
2025       text = $"Console Application", hotKey = altC, option = OPTION(console);
2026    };
2027
2028    BoolOptionBox compress
2029    {
2030       this, position = { 8, 162 };
2031       text = $"Compress", hotKey = altW, option = OPTION(compress);
2032    };
2033
2034    Label labelLibraryDirs { libraryDirs.editor, labeledWindow = libraryDirs, position = { 0, 6 }; };
2035    DirsArrayOptionBox libraryDirs
2036    {
2037       this, size = { 290, 22 }, anchor = { left = 8, top = 182, right = 8, bottom = 8 };
2038       text = $"Additional Library Directories", hotKey = altY, option = OPTION(libraryDirs);
2039    };
2040
2041    bool OnCreate()
2042    {
2043       ((BuildTab)master).SelectNode(project.topNode, true);
2044       return true;
2045    }
2046
2047    void LoadSettings()
2048    {
2049       OptionBox ob;
2050       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2051          if(eClass_IsDerived(ob._class, class(OptionBox)))
2052             ob.Load();
2053       compress.disabled = (config && config.options && config.options.debug == true) || project.topNode.options.debug == true;
2054
2055       if(activeChild && activeChild.active)
2056       {
2057          Window control = activeChild;
2058          control.Deactivate();         
2059          control.Activate();
2060       }
2061    }
2062 }
2063
2064 class BuilderTab : Tab
2065 {
2066    background = activeBorder;
2067    text = $"Builder";
2068
2069    Label labelPrebuildCommands { prebuildCommands.editor, labeledWindow = prebuildCommands, position = { 0, 6 }; };
2070    StringsArrayOptionBox prebuildCommands
2071    {
2072       this, size = { 290, 100 }, anchor = { left = 8, top = 52, right = 8 };
2073       text = $"Pre-build Commands", hotKey = altE, option = OPTION(prebuildCommands);
2074    };
2075
2076    Label labelPostbuildCommands { postbuildCommands.editor, labeledWindow = postbuildCommands, position = { 0, 6 }; };
2077    StringsArrayOptionBox postbuildCommands
2078    {
2079       this, size = { 290, 100 }, anchor = { left = 8, top = 160, right = 8 };
2080       text = $"Post-build Commands", hotKey = altT, option = OPTION(postbuildCommands);
2081    };
2082
2083    void LoadSettings()
2084    {
2085       bool disabled = strlen(((BuildTab)master).selectedPlatformName) > 0;
2086       OptionBox ob;
2087       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2088          if(eClass_IsDerived(ob._class, class(OptionBox)))
2089             ob.Load();
2090
2091       if(activeChild && activeChild.active)
2092       {
2093          Window control = activeChild;
2094          control.Deactivate();         
2095          control.Activate();
2096       }
2097    }
2098 }