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