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