compiler/libec; ecere; ide: Safer default virtual method calls
[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, scrollable = true;
1326
1327       bool OnKeyDown(Key key, unichar ch)
1328       {
1329          if(key == insert)
1330          {
1331             ((BuildTab)parent).createConfig.NotifyClicked(parent, ((BuildTab)parent).createConfig, 0, 0, 0);
1332             return false;
1333          }
1334          else if(key == del)
1335          {
1336             ((BuildTab)parent).deleteConfig.NotifyClicked(parent, ((BuildTab)parent).deleteConfig, 0, 0, 0);
1337             return false;
1338          }
1339          return SelectorBar::OnKeyDown(key, ch);
1340       }
1341
1342       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1343       {
1344          ((BuildTab)master).labelConfigurations.Update(null);
1345          return true;
1346       }
1347    };
1348
1349    Button createConfig
1350    {
1351       parent = this, bevelOver = true, inactive = true;
1352       size = { 22, 22 };
1353       anchor = { top = 10, right = 31 };
1354       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/docNew.png", alphaBlend = true };
1355
1356       bool NotifyClicked(Button b, int x, int y, Modifiers mods)
1357       {
1358          char tmp[MAX_F_STRING];
1359          ProjectConfig config;
1360          EditableSelectorButton button;
1361
1362          FindUniqueConfigName("NewConfig", false, tmp);
1363
1364          config =
1365          {
1366             makingModified = true;
1367             compilingModified = true;
1368             linkingModified = true;
1369             name = CopyString(tmp);
1370             options =
1371             {
1372                // objectsDirectory = /*CopyString(*/defaultObjDirExpression/*)*/;
1373             };
1374          };
1375          if(!project.topNode.configurations) project.topNode.configurations = { };
1376          project.topNode.configurations.Add(config);
1377          /*
1378          targetType = project.config.options.targetType;
1379          config.options.
1380          config.options.targetFileName = project.moduleName;
1381          config.options.targetDir.dir = "";
1382          config.options.objectsDirectory = defaultObjDirExpression);
1383          config.options.debug = true;
1384          config.options.optimization = none;
1385          config.options.warnings = all;
1386          */
1387
1388          button =
1389          {
1390             configSelector, renameable = true, master = this, text = config.name, id = (int64)(intptr)config;
1391             NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1392          };
1393
1394          configSelector.Select(button);
1395          modifiedDocument = true;
1396          return true;
1397       }
1398    };
1399    /*Button duplicateConfig
1400    {
1401       parent = this, bevelOver = true, inactive = true;
1402       size = { 22, 22 };
1403       anchor = { top = 10, right = 31 };
1404       hotKey = altU, bitmap = BitmapResource { fileName = ":actions/editCopy.png", alphaBlend = true };
1405    };*/
1406    Button deleteConfig
1407    {
1408       parent = this, bevelOver = true, inactive = true;
1409       size = { 22, 22 };
1410       anchor = { top = 10, right = 8 };
1411       hotKey = altD, bitmap = BitmapResource { fileName = ":actions/delete2.png", alphaBlend = true };
1412
1413       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1414       {
1415          if(config)
1416          {
1417             String title = PrintString($"Delete ", config.name, $" Configuration");
1418             String msg = PrintString($"Are you sure you wish to delete the ", config.name, $" configuration?");
1419             if(MessageBox { type = okCancel, text = title, contents = msg }.Modal() == ok)
1420             {
1421                //Iterator<Window> it { configSelector.controls };
1422                ProjectConfig configToDelete = config;
1423                /*
1424                while(it.Next())
1425                {
1426                   SelectorButton button = (SelectorButton)it.data;
1427                   if((ProjectConfig)button.id == config)
1428                   {
1429                      button.visible = false;
1430                      button.Destroy(0);
1431
1432                      if(it.Prev())
1433                      {
1434                         button = (SelectorButton)it.data;
1435                         config = (ProjectConfig)button.id;
1436                         configSelector.Select(button);
1437                      }
1438                      break;
1439                   }
1440                }
1441                */
1442                SelectorButton button = configSelector.FindButtonByID((int64)(intptr)configToDelete);
1443                if(button)
1444                   configSelector.RemoveButton(button);
1445
1446                project.topNode.DeleteConfig(configToDelete);
1447
1448                modifiedDocument = true;
1449             }
1450             delete title;
1451             delete msg;
1452          }
1453          return true;
1454       }
1455    };
1456
1457    Label labelPlatforms
1458    {
1459       this, anchor = { left = 8, top = 44 }, labeledWindow = platformSelector;
1460
1461       void OnRedraw(Surface surface)
1462       {
1463          Label::OnRedraw(surface);
1464          if(labeledWindow.active) DrawStipple(surface, clientSize);
1465       }
1466    };
1467    SelectorBar platformSelector
1468    {
1469       this, text = $"Platforms: ", anchor = { left = 64, top = 38, right = 54 }; size = { 0, 26 };
1470       opacity = 0;
1471       direction = horizontal, scrollable = true;
1472
1473       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1474       {
1475          ((BuildTab)master).labelPlatforms.Update(null);
1476          return true;
1477       }
1478    };
1479
1480    TabControl buildTabControl
1481    {
1482       this, background = formColor, anchor = { left = 8, top = 64, right = 8, bottom = 8 };
1483       curTab = compilerTab;
1484    };
1485    CompilerTab compilerTab { this, tabControl = buildTabControl };
1486    LinkerTab linkerTab { this, tabControl = buildTabControl };
1487    BuilderTab builderTab { this, tabControl = buildTabControl };
1488    Label rightClick
1489    {
1490       this, font = { font.faceName, font.size, italic = true }, stayOnTop = true,
1491       text = $"(Right click or press Ctrl-Del to revert an option to inherited value)", anchor = { top = 72, right = 16 }
1492    };
1493
1494    void FindUniqueConfigName(const char * baseName, bool startWithNumber, char * output)
1495    {
1496       int num = 0;
1497       char tmp[MAX_F_STRING];
1498       if(startWithNumber)
1499          sprintf(tmp, "%s%d", baseName, num);
1500       else
1501          strcpy(tmp, baseName);
1502       while(true)
1503       {
1504          ProjectConfig config = null;
1505          for(c : project.topNode.configurations)
1506          {     // TOFIX: Error when omitting these brackets, c not found
1507             if(c.name && !strcmp(c.name, tmp))
1508             {
1509                config = c;
1510                break;
1511             }
1512          }
1513          if(config)
1514          {
1515             num++;
1516             sprintf(tmp, "%s%d", baseName, num);
1517          }
1518          else
1519             break;
1520       }
1521       strcpy(output, tmp);
1522    }
1523
1524    bool PlatformClicked(Button clickedButton, int x, int y, Modifiers mods)
1525    {
1526       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1527       {
1528          platform = (Platform)clickedButton.id;
1529
1530          // Load Settings Into Dialog
1531          compilerTab.LoadSettings();
1532          linkerTab.LoadSettings();
1533          builderTab.LoadSettings();
1534
1535          if(!mods)
1536             buildTabControl.Activate();
1537
1538          if(compilerTab.rightPaneHeader.visible)
1539             compilerTab.rightPaneHeader.Update(null);
1540          ((ProjectSettings)master).UpdateDialogTitle();
1541       }
1542       return true;
1543    }
1544
1545    ~BuildTab()
1546    {
1547       platformSelector.DestroyChildren();
1548       configSelector.DestroyChildren();
1549
1550       delete activeConfigName;
1551    }
1552
1553    bool ConfigOnRename(EditableSelectorButton button, char * * oldName, char * * newName)
1554    {
1555       int c, d = 0;
1556       char ch;
1557
1558       for(c = 0; (ch = (*newName)[c]); c++)
1559       {
1560          if(ch == '_' || isalpha(ch) || (isdigit(ch) && d))
1561             (*newName)[d++] = ch;
1562       }
1563       (*newName)[d] = 0;
1564
1565       {
1566          bool found = false;
1567          for(c : project.topNode.configurations; c != config)
1568          {
1569             if(!strcmpi(c.name, *newName))
1570             {
1571                found = true;
1572                break;
1573             }
1574          }
1575          if(found || !(*newName)[0])
1576          {
1577             char tmp[MAX_F_STRING];
1578             char * tmpName = config.name;
1579             config.name = null;
1580             FindUniqueConfigName("NewConfig", false, tmp);
1581             config.name = tmpName;
1582             delete *newName;
1583             *newName = CopyString(tmp);
1584          }
1585       }
1586
1587       if(activeConfigName && !strcmp(activeConfigName, *oldName))
1588       {
1589          delete activeConfigName;
1590          activeConfigName = CopyString(*newName);
1591       }
1592
1593       project.topNode.RenameConfig(config.name, *newName);
1594
1595       modifiedDocument = true;
1596       Update(null);
1597       return true;
1598    }
1599
1600    bool ConfigClicked(Button clickedButton, int x, int y, Modifiers mods)
1601    {
1602       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1603       {
1604          config = (ProjectConfig)(intptr)clickedButton.id;
1605
1606          // Load Settings Into Dialog
1607          compilerTab.LoadSettings();
1608          linkerTab.LoadSettings();
1609          builderTab.LoadSettings();
1610
1611          deleteConfig.disabled = (clickedButton._class == class(SelectorButton));
1612
1613          if(!mods)
1614             buildTabControl.Activate();
1615
1616          compilerTab.fileList.Update(null);
1617          if(compilerTab.rightPaneHeader.visible)
1618             compilerTab.rightPaneHeader.Update(null);
1619          ((ProjectSettings)master).UpdateDialogTitle();
1620       }
1621       return true;
1622    }
1623
1624    void SelectNode(ProjectNode node, bool ignoreAsLastSelection)
1625    {
1626       if(node != currentNode)
1627       {
1628          Window ac = compilerTab.rightPane.activeChild;
1629          bool prevNodeRes = currentNode ? currentNode.isInResources : false;
1630          bool newNodeRes;
1631
1632          if(!node) node = project.topNode;
1633
1634          newNodeRes = node.isInResources;
1635
1636          currentNode = node;
1637          if(!ignoreAsLastSelection)
1638             lastSelectedNode = node;
1639
1640          ((ProjectSettings)master).UpdateDialogTitle();
1641          if(node.type == project)
1642          {
1643             compilerTab.rightPaneHeader.visible = false;
1644          }
1645          else
1646          {
1647             compilerTab.rightPaneHeader.id = (int64)(intptr)node;
1648             compilerTab.rightPaneHeader.Update(null);
1649             compilerTab.rightPaneHeader.visible = true;
1650          }
1651
1652          {
1653             DataRow row = compilerTab.fileList.FindSubRow((int64)(intptr)currentNode);
1654             if(row)
1655             {
1656                compilerTab.fileList.currentRow = row;
1657                while((row = row.parent))
1658                   row.collapsed = false;
1659             }
1660          }
1661
1662          if(prevNodeRes != newNodeRes)
1663          {
1664             compilerTab.labelObjDir.visible = !newNodeRes;
1665             compilerTab.objDir.visible = !newNodeRes;
1666             compilerTab.excludeFromBuild.visible = !newNodeRes;
1667             compilerTab.labelPreprocessorDefs.visible = !newNodeRes;
1668             compilerTab.preprocessorDefs.visible = !newNodeRes;
1669             compilerTab.labelDefaultNameSpace.visible = !newNodeRes;
1670             compilerTab.defaultNameSpace.visible = !newNodeRes;
1671             compilerTab.strictNameSpaces.visible = !newNodeRes;
1672             compilerTab.memoryGuard.visible = !newNodeRes;
1673             compilerTab.noLineNumbers.visible = !newNodeRes;
1674             compilerTab.debug.visible = !newNodeRes;
1675             compilerTab.labelWarnings.visible = !newNodeRes;
1676             compilerTab.warnings.visible = !newNodeRes;
1677             compilerTab.profiling.visible = !newNodeRes;
1678             compilerTab.labelOptimization.visible = !newNodeRes;
1679             compilerTab.optimization.visible = !newNodeRes;
1680             compilerTab.fastMath.visible = !newNodeRes;
1681             compilerTab.labelIncludeDirs.visible = !newNodeRes;
1682             compilerTab.includeDirs.visible = !newNodeRes;
1683          }
1684
1685          if(node == project.topNode)
1686          {
1687             compilerTab.objDir.visible = true;
1688             compilerTab.labelObjDir.visible = true;
1689
1690             compilerTab.excludeFromBuild.visible = false;
1691          }
1692          else
1693          {
1694             compilerTab.objDir.visible = false;
1695             compilerTab.labelObjDir.visible = false;
1696
1697             compilerTab.excludeFromBuild.visible = (node != project.resNode);
1698          }
1699
1700          // Load Settings Into Dialog
1701          compilerTab.LoadSettings();
1702          linkerTab.LoadSettings();
1703          builderTab.LoadSettings();
1704
1705          if(ac)
1706          {
1707             if(!ac.visible)
1708             {
1709                if(ac == compilerTab.excludeFromBuild.editor)
1710                   ac = compilerTab.objDir.editor;
1711                else if(compilerTab.excludeFromBuild.editor.visible)
1712                   ac = compilerTab.excludeFromBuild.editor;
1713             }
1714             ac.MakeActive();
1715          }
1716       }
1717    }
1718
1719    void CreateConfigButtons()
1720    {
1721       // Create Config Buttons
1722       SelectorButton
1723       {
1724          configSelector, master = this, text = $"Common", id = 0; font = { font.faceName, font.size, true };
1725          checked = true;
1726          NotifyClicked = ConfigClicked;
1727       };
1728
1729       config = null;
1730
1731       if(project.topNode.configurations)
1732       {
1733          for(c : project.topNode.configurations)
1734          {
1735             EditableSelectorButton button
1736             {
1737                configSelector, master = this, renameable = true, text = c.name, id = (int64)(intptr)c;
1738                NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1739             };
1740          }
1741       }
1742    }
1743
1744    void Init()
1745    {
1746       Platform p;
1747       SelectorButton button;
1748
1749       activeConfigName = project.config ? CopyString(project.config.name) : null;
1750
1751       compilerTab.AddNode(project.topNode, null);
1752
1753       CreateConfigButtons();
1754
1755       platformButton = button =
1756       {
1757          platformSelector, master = this, text = $"Common", id = 0;  font = { font.faceName, font.size, true };
1758          NotifyClicked = PlatformClicked; checked = true;
1759       };
1760
1761       platform = 0;
1762
1763       for(p = (Platform)1; p < Platform::enumSize; p++)
1764       {
1765          SelectorButton button
1766          {
1767             platformSelector, master = this, text = p.OnGetString(0,0,0), id = (int64)p;
1768             NotifyClicked = PlatformClicked;
1769          };
1770       }
1771    }
1772    SelectorButton platformButton;
1773
1774    bool OnPostCreate()
1775    {
1776       // Backup Current Settings
1777       backupNode = project.topNode.Backup();
1778
1779       buildTabControl.Activate();
1780
1781       {
1782          Iterator<Window> it { configSelector.controls };
1783          while(it.Next())
1784          {
1785             SelectorButton configButton = (SelectorButton)it.data;
1786             ProjectConfig buttonConfig = (ProjectConfig)(intptr)configButton.id;
1787             if(buttonConfig == project.config)
1788             {
1789                configButton.Activate();
1790                configButton.checked = true;
1791                ConfigClicked(configButton, 0, 0, 0);
1792                break;
1793             }
1794          }
1795       }
1796       if(platformButton)
1797       {
1798          platformButton.MakeActive();
1799          platformButton = null;
1800       }
1801       return true;
1802    }
1803
1804    void OnDestroy()
1805    {
1806       delete backupNode;
1807
1808       lastSelectedNode = null;
1809
1810       project.config = null;
1811
1812       /* // THIS IS NOW AUTOMATED WITH A project CHECK IN ProjectNode
1813       project.configurations = project.topNode.configurations;
1814       project.platforms = project.topNode.platforms;
1815       project.options = project.topNode.options;
1816       */
1817
1818       if(project.topNode.configurations)
1819       {
1820          for(c : project.topNode.configurations)
1821          {
1822             if(!strcmpi(c.name, activeConfigName))
1823             {
1824                project.config = c;
1825                break;
1826             }
1827          }
1828       }
1829       if(!project.config)
1830       {
1831          List<ProjectConfig> configs = project.topNode.configurations;
1832          if(configs && configs.count)
1833             project.config = configs[0];
1834       }
1835
1836       ide.UpdateToolBarActiveConfigs(false);
1837    }
1838
1839    void RevertChanges()
1840    {
1841       String configName = config ? CopyString(config.name) : null;
1842
1843       // Revert to saved project options
1844       project.topNode.Revert(backupNode);
1845
1846       configSelector.DestroyChildren();
1847       CreateConfigButtons();
1848
1849       // Reselect Configuration
1850       if(configName)
1851       {
1852          Iterator<Window> it { configSelector.controls };
1853          while(it.Next())
1854          {
1855             Button button = (Button)it.data;
1856             ProjectConfig c = (ProjectConfig)(intptr)button.id;
1857             if(c && !strcmp(c.name, configName))
1858             {
1859                config = c;
1860                button.Activate();
1861                button.checked = true;
1862                ConfigClicked(button, 0,0, 0);
1863                break;
1864             }
1865          }
1866       }
1867
1868       SelectNode(project.topNode, false);
1869
1870       delete configName;
1871    }
1872
1873    bool OnClose(bool parentClosing)
1874    {
1875       if(modifiedDocument)
1876       {
1877          DialogResult diagRes = MessageBox
1878          {
1879             type = yesNoCancel, master = ide,
1880             text = $"Save changes to project settings?",
1881             contents = $"Would you like to save changes made to the build options?"
1882          }.Modal();
1883          if(diagRes == no)
1884             RevertChanges();
1885          if(diagRes == cancel)
1886             return false;
1887          if(diagRes == yes)
1888          {
1889             project.MarkChanges(backupNode);
1890             project.topNode.modified = true;
1891             ide.projectView.modifiedDocument = true;
1892             ide.UpdateToolBarActiveConfigs(false);
1893             ide.projectView.Update(null);
1894          }
1895          modifiedDocument = false;
1896       }
1897       return true;
1898    }
1899 }
1900
1901 class CompilerTab : Tab
1902 {
1903    background = formColor;
1904    text = $"Compiler";
1905
1906    Window leftPane { this, size = { 180 }, anchor = { left = 0, top = 0, bottom = 0 }, background = formColor };
1907
1908    Label labelFileList { leftPane, this, position = { 8, 8 }, labeledWindow = fileList };
1909    ListBox fileList
1910    {
1911       leftPane, this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true;
1912       // THIS WOULD BE EVEN MORE FUN: multiSelect = true,
1913       fullRowSelect = false, collapseControl = true, treeBranches = true;
1914       alwaysHighLight = true;
1915       selectionColor = unfocusedSelectorColor;
1916       size = { 180 };
1917       anchor = Anchor { left = 8, top = 24, right = 4, bottom = 8 };
1918       text = $"Files";
1919
1920       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1921       {
1922          BuildTab buildTab = (BuildTab)master;
1923          ProjectNode node = (ProjectNode)(intptr)row.tag;
1924          buildTab.SelectNode(node, false);
1925          return true;
1926       }
1927
1928       void OnRedraw(Surface surface)
1929       {
1930          ide.projectView.drawingInProjectSettingsDialog = true;
1931          ListBox::OnRedraw(surface);
1932          ide.projectView.drawingInProjectSettingsDialog = false;
1933       }
1934
1935       bool NotifyActivate(Window window, bool active, Window previous)
1936       {
1937          if(active)
1938          {
1939             //subclass(Skin) skinClass = (subclass(Skin))eSystem_FindClass(app, app.skin);
1940             fileList.selectionColor = Color { 10, 36, 106 }; //skinClass.selectionColor; // darkBlue;
1941          }
1942          else if(fileList.currentRow)
1943          {
1944             DataRow currentRow = fileList.currentRow;
1945             //int headerSize = ((fileList.style.header) ? fileList.rowHeight : 0);
1946             int height = fileList.clientSize.h + 1;// - fileList.headerSize;
1947             fileList.selectionColor = unfocusedSelectorColor;
1948             if(currentRow && currentRow.index * fileList.rowHeight > fileList.scroll.y + height - fileList.rowHeight)
1949                fileList.SetScrollPosition(fileList.scroll.x, currentRow.index * fileList.rowHeight - height + fileList.rowHeight);
1950             else if(!currentRow || currentRow.index * fileList.rowHeight < fileList.scroll.y)
1951                fileList.SetScrollPosition(fileList.scroll.x, currentRow ? currentRow.index * fileList.rowHeight : 0);
1952
1953          }
1954
1955          return true;
1956       }
1957    };
1958
1959    Window rightPane
1960    {
1961       this, anchor = { left = 196, top = 0, right = 0, bottom = 0 }, background = formColor, tabCycle = true;
1962    };
1963
1964    Window rightPaneHeader
1965    {
1966       rightPane, this, size = { h = 21 }, anchor = { left = 0, top = 0, right = 0 }, background = Color { 70, 96, 166 };//0x0F3F66;
1967       foreground = white; visible = false;
1968
1969       void OnRedraw(Surface surface)
1970       {
1971          if(id)
1972          {
1973             void (* onDisplay)(void *, void *, void *, int, int, int, void *, uint, uint) = (void *)class(ProjectNode)._vTbl[__ecereVMethodID_class_OnDisplay];
1974
1975             ide.projectView.drawingInProjectSettingsDialogHeader = true;
1976             if(onDisplay)
1977                onDisplay(class(ProjectNode), (void *)(intptr)id, surface, 8, 2, clientSize.w, ide.projectView, Alignment::left, DataDisplayFlags { selected = true });
1978             ide.projectView.drawingInProjectSettingsDialogHeader = false;
1979          }
1980       }
1981    };
1982
1983    PaneSplitter splitter
1984    {
1985       this, leftPane = leftPane, rightPane = rightPane, split = 188
1986    };
1987
1988    Label labelObjDir { rightPane, this, position = { 8, 8 }, labeledWindow = objDir };
1989    PathOptionBox objDir
1990    {
1991       rightPane, this, size = { 250, 22 }, anchor = { left = 8, top = 24, right = 8 };
1992       text = $"Intermediate Objects Directory", hotKey = altJ, option = OPTION(objectsDirectory);
1993    };
1994
1995    BoolOptionBox excludeFromBuild
1996    {
1997       rightPane, this, position = { 8, 28 },
1998       text = $"Exclude from Build", visible = false, option = OPTION(excludeFromBuild);
1999    };
2000
2001    Label labelPreprocessorDefs { rightPane, this, position = { 8, 50 }, labeledWindow = preprocessorDefs };
2002    StringArrayOptionBox preprocessorDefs
2003    {
2004       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2005       text = $"Preprocessor Definitions", hotKey = altD, option = OPTION(preprocessorDefinitions);
2006    };
2007
2008    Label labelDefaultNameSpace { rightPane, this, position = { 8, 92 }, labeledWindow = defaultNameSpace };
2009    StringOptionBox defaultNameSpace
2010    {
2011       rightPane, this, size = { 160, 22 }, position = { 8, 108 };
2012       text = $"Default Name Space", option = OPTION(defaultNameSpace);
2013    };
2014    BoolOptionBox strictNameSpaces
2015    {
2016       rightPane, this, position = { 172, 112 },
2017       text = $"Strict Name Spaces", option = OPTION(strictNameSpaces);
2018    };
2019
2020    BoolOptionBox fastMath
2021    {
2022       rightPane, this, position = { 316, 112 },
2023       text = $"Fast Math", option = OPTION(fastMath);
2024    };
2025
2026    BoolOptionBox memoryGuard
2027    {
2028       rightPane, this, position = { 8, 154 };
2029       text = $"MemoryGuard", hotKey = altM, option = OPTION(memoryGuard);
2030    };
2031
2032    Label labelWarnings { rightPane, position = { 116, 138 }, labeledWindow = warnings };
2033    WarningsDB warnings
2034    {
2035       rightPane, this, position = { 116, 154 };
2036       text = $"Warnings", hotKey = altW, option = OPTION(warnings);
2037    };
2038
2039    Label labelOptimization { rightPane, position = { 220, 138 }, labeledWindow = optimization };
2040    OptimizationDB optimization
2041    {
2042       rightPane, this, position = { 220, 154 }, size = { 120, 22 };
2043       text = $"Optimization", hotKey = altO, option = OPTION(optimization);
2044    };
2045
2046    BoolOptionBox debug
2047    {
2048       rightPane, this, position = { 8, 188 };
2049       text = $"Debuggable", hotKey = altG, option = OPTION(debug);
2050    };
2051
2052    BoolOptionBox profiling
2053    {
2054       rightPane, this, position = { 116, 188 };
2055       text = $"Profiling Data", hotKey = altP, option = OPTION(profile);
2056    };
2057
2058    BoolOptionBox noLineNumbers
2059    {
2060       rightPane, this, position = { 220, 188 };
2061       text = $"No Line Numbers", hotKey = altN, option = OPTION(noLineNumbers);
2062    };
2063
2064    Label labelCompilerOptions { rightPane, this, position = { 8, 208 }, labeledWindow = compilerOptions };
2065    StringArrayOptionBox compilerOptions
2066    {
2067       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 224, right = 8 };
2068       text = $"Compiler Options", hotKey = altO, option = OPTION(compilerOptions);
2069       configReplaces = true;
2070    };
2071
2072    Label labelIncludeDirs { includeDirs.editor, labeledWindow = includeDirs, position = { 0, 6 }; };
2073    DirsArrayOptionBox includeDirs
2074    {
2075       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 250, right = 8, bottom = 8 };
2076       text = $"Additional Include Directories", hotKey = altI, option = OPTION(includeDirs), switchToKeep = "I";
2077    };
2078
2079    CompilerTab()
2080    {
2081       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false,
2082          userData = null /* Now set in the ProjectNode directly to know we're in ProjectSettings Dialog -- ide.projectView*/ });
2083    }
2084
2085    bool OnCreate()
2086    {
2087       BuildTab buildTab = (BuildTab)master;
2088       buildTab.SelectNode(buildTab.lastSelectedNode, true);
2089       return true;
2090    }
2091
2092    void AddNode(ProjectNode node, DataRow addTo)
2093    {
2094       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
2095
2096       row.tag = (int64)(intptr)node;
2097
2098       row.SetData(null, node);
2099
2100       if(node.files && node.files.first && node.parent &&
2101             !(!node.parent.parent &&
2102                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") ||
2103                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
2104          row.collapsed = true;
2105       else if(node.type == folder)
2106          node.icon = openFolder;
2107
2108       if(node.files)
2109       {
2110          for(child : node.files)
2111             AddNode(child, row);
2112       }
2113    }
2114
2115    void LoadSettings()
2116    {
2117       OptionBox ob;
2118       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2119          if(eClass_IsDerived(ob._class, class(OptionBox)))
2120             ob.Load();
2121
2122       if(activeChild && activeChild.active)
2123       {
2124          Window control = activeChild;
2125          control.Deactivate();
2126          control.Activate();
2127       }
2128    }
2129
2130    bool OnPostCreate()
2131    {
2132       objDir.editor.Activate();
2133       return true;
2134    }
2135 }
2136
2137 class LinkerTab : Tab
2138 {
2139    background = formColor;
2140    text = $"Linker";
2141
2142    Label labelTargetName { this, position = { 8, 8 }, labeledWindow = targetName };
2143    StringOptionBox targetName
2144    {
2145       this, position = { 8, 24 }, size = { 200, 22 };
2146       text = $"Target Name", hotKey = altN, option = OPTION(targetFileName);
2147    };
2148
2149    Label labelTargetType { this, position = { 216, 8 }, labeledWindow = targetType };
2150    TargetTypeDB targetType
2151    {
2152       this, position = { 216, 24 }, size = { 120, 22 };
2153       text = $"Target Type", hotKey = altT, option = OPTION(targetType);
2154    };
2155
2156    Label labelTargetDirectory { this, position = { 344, 8 }, labeledWindow = targetDirectory };
2157    PathOptionBox targetDirectory
2158    {
2159       this, size = { 270, 22 }, anchor = { left = 344, top = 24, right = 8 };
2160       hotKey = altR, text = $"Target Directory", option = OPTION(targetDirectory);
2161    };
2162
2163    Label labelLibraries { this, position = { 8, 50 }, labeledWindow = libraries };
2164    StringArrayOptionBox libraries
2165    {
2166       this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2167       text = $"Additional Libraries", hotKey = altL, option = OPTION(libraries), switchToKeep = "l";
2168       configReplaces = true;
2169    };
2170
2171    Label labelLinkerOptions { this, position = { 8, 92 }, labeledWindow = linkerOptions };
2172    StringArrayOptionBox linkerOptions
2173    {
2174       this, size = { 290, 22 }, anchor = { left = 8, top = 108, right = 8 };
2175       text = $"Linker Options", hotKey = altO, option = OPTION(linkerOptions);
2176       configReplaces = true;
2177    };
2178
2179    BoolOptionBox console
2180    {
2181       this, position = { 8, 138 };
2182       text = $"Console Application", hotKey = altC, option = OPTION(console);
2183    };
2184
2185    BoolOptionBox compress
2186    {
2187       this, position = { 8, 162 };
2188       text = $"Compress", hotKey = altW, option = OPTION(compress);
2189    };
2190
2191    Label labelLibraryDirs { libraryDirs.editor, labeledWindow = libraryDirs, position = { 0, 6 }; };
2192    DirsArrayOptionBox libraryDirs
2193    {
2194       this, size = { 290, 22 }, anchor = { left = 8, top = 182, right = 8, bottom = 8 };
2195       text = $"Additional Library Directories", hotKey = altY, option = OPTION(libraryDirs), switchToKeep = "L";
2196    };
2197
2198    bool OnCreate()
2199    {
2200       ((BuildTab)master).SelectNode(project.topNode, true);
2201       return true;
2202    }
2203
2204    void LoadSettings()
2205    {
2206       OptionBox ob;
2207       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2208          if(eClass_IsDerived(ob._class, class(OptionBox)))
2209             ob.Load();
2210       compress.disabled = (config && config.options && config.options.debug == true) || project.topNode.options.debug == true;
2211
2212       if(activeChild && activeChild.active)
2213       {
2214          Window control = activeChild;
2215          control.Deactivate();
2216          control.Activate();
2217       }
2218    }
2219 }
2220
2221 class BuilderTab : Tab
2222 {
2223    background = formColor;
2224    text = $"Builder";
2225
2226    Label labelPrebuildCommands { prebuildCommands.editor, labeledWindow = prebuildCommands, position = { 0, 6 }; };
2227    StringsArrayOptionBox prebuildCommands
2228    {
2229       this, size = { 290, 92 }, anchor = { left = 8, top = 8, right = 8, bottom = 200 };
2230       text = $"Pre-build Commands", hotKey = altE, option = OPTION(prebuildCommands);
2231    };
2232
2233    Label labelPostbuildCommands { postbuildCommands.editor, labeledWindow = postbuildCommands, position = { 0, 6 }; };
2234    StringsArrayOptionBox postbuildCommands
2235    {
2236       this, size = { 290, 92 }, anchor = { left = 8, top = 100, right = 8, bottom = 100 };
2237       text = $"Post-build Commands", hotKey = altT, option = OPTION(postbuildCommands);
2238    };
2239
2240    Label labelInstallCommands { installCommands.editor, labeledWindow = installCommands, position = { 0, 6 }; };
2241    StringsArrayOptionBox installCommands
2242    {
2243       this, size = { 290, 92 }, anchor = { left = 8, top = 200, right = 8, bottom = 8 };
2244       text = $"Install Commands", hotKey = altT, option = OPTION(installCommands);
2245    };
2246
2247    void LoadSettings()
2248    {
2249       OptionBox ob;
2250       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2251          if(eClass_IsDerived(ob._class, class(OptionBox)))
2252             ob.Load();
2253
2254       if(activeChild && activeChild.active)
2255       {
2256          Window control = activeChild;
2257          control.Deactivate();
2258          control.Activate();
2259       }
2260    }
2261
2262    void OnResize(int width, int height)
2263    {
2264       int h = (height - 8 * 4) / 3;
2265       prebuildCommands.anchor = { left = 8, top = 8, right = 8, bottom = h * 2 + 8 * 3 };
2266       postbuildCommands.anchor = { left = 8, top = h + 8 * 2, right = 8, bottom = h + 8 * 2 };
2267       installCommands.anchor = { left = 8, top = h * 2 + 8 * 3, right = 8, bottom = 8 };
2268    }
2269 }