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