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