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