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