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