sdk: Fixed many warnings and related problems
[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)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)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)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)id;
342       if(key == Key { del, ctrl = true } || key == Key { keyPadDelete, ctrl = true })
343       {
344          ob.Unset();
345          return false;
346       }
347       return ((bool(*)(Window, Key, unichar)) ob.chainKeyDown)(this, key, ch);
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)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)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)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)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)dirsBox.id).switchToKeep;
1027          if(switchToKeep && switchToKeep[0])
1028          {
1029             bool change = false;
1030             int lenSwitchToKeep = ((DirsArrayOptionBox)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)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)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)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)button.id &&
1123                      ((OptionBox)slave).option == ((OptionBox)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)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)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)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)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       return true;
1597    }
1598
1599    bool ConfigClicked(Button clickedButton, int x, int y, Modifiers mods)
1600    {
1601       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1602       {
1603          config = (ProjectConfig)clickedButton.id;
1604
1605          // Load Settings Into Dialog
1606          compilerTab.LoadSettings();
1607          linkerTab.LoadSettings();
1608          builderTab.LoadSettings();
1609
1610          deleteConfig.disabled = (clickedButton._class == class(SelectorButton));
1611
1612          if(!mods)
1613             buildTabControl.Activate();
1614
1615          compilerTab.fileList.Update(null);
1616          if(compilerTab.rightPaneHeader.visible)
1617             compilerTab.rightPaneHeader.Update(null);
1618          ((ProjectSettings)master).UpdateDialogTitle();
1619       }
1620       return true;
1621    }
1622
1623    void SelectNode(ProjectNode node, bool ignoreAsLastSelection)
1624    {
1625       if(node != currentNode)
1626       {
1627          Window ac = compilerTab.rightPane.activeChild;
1628          bool prevNodeRes = currentNode ? currentNode.isInResources : false;
1629          bool newNodeRes;
1630
1631          if(!node) node = project.topNode;
1632
1633          newNodeRes = node.isInResources;
1634
1635          currentNode = node;
1636          if(!ignoreAsLastSelection)
1637             lastSelectedNode = node;
1638
1639          ((ProjectSettings)master).UpdateDialogTitle();
1640          if(node.type == project)
1641          {
1642             compilerTab.rightPaneHeader.visible = false;
1643          }
1644          else
1645          {
1646             compilerTab.rightPaneHeader.id = (int64)node;
1647             compilerTab.rightPaneHeader.Update(null);
1648             compilerTab.rightPaneHeader.visible = true;
1649          }
1650
1651          {
1652             DataRow row = compilerTab.fileList.FindSubRow((int64)currentNode);
1653             if(row)
1654             {
1655                compilerTab.fileList.currentRow = row;
1656                while((row = row.parent))
1657                   row.collapsed = false;
1658             }
1659          }
1660
1661          if(prevNodeRes != newNodeRes)
1662          {
1663             compilerTab.labelObjDir.visible = !newNodeRes;
1664             compilerTab.objDir.visible = !newNodeRes;
1665             compilerTab.excludeFromBuild.visible = !newNodeRes;
1666             compilerTab.labelPreprocessorDefs.visible = !newNodeRes;
1667             compilerTab.preprocessorDefs.visible = !newNodeRes;
1668             compilerTab.labelDefaultNameSpace.visible = !newNodeRes;
1669             compilerTab.defaultNameSpace.visible = !newNodeRes;
1670             compilerTab.strictNameSpaces.visible = !newNodeRes;
1671             compilerTab.memoryGuard.visible = !newNodeRes;
1672             compilerTab.noLineNumbers.visible = !newNodeRes;
1673             compilerTab.debug.visible = !newNodeRes;
1674             compilerTab.labelWarnings.visible = !newNodeRes;
1675             compilerTab.warnings.visible = !newNodeRes;
1676             compilerTab.profiling.visible = !newNodeRes;
1677             compilerTab.labelOptimization.visible = !newNodeRes;
1678             compilerTab.optimization.visible = !newNodeRes;
1679             compilerTab.fastMath.visible = !newNodeRes;
1680             compilerTab.labelIncludeDirs.visible = !newNodeRes;
1681             compilerTab.includeDirs.visible = !newNodeRes;
1682          }
1683
1684          if(node == project.topNode)
1685          {
1686             compilerTab.objDir.visible = true;
1687             compilerTab.labelObjDir.visible = true;
1688
1689             compilerTab.excludeFromBuild.visible = false;
1690          }
1691          else
1692          {
1693             compilerTab.objDir.visible = false;
1694             compilerTab.labelObjDir.visible = false;
1695
1696             compilerTab.excludeFromBuild.visible = (node != project.resNode);
1697          }
1698
1699          // Load Settings Into Dialog
1700          compilerTab.LoadSettings();
1701          linkerTab.LoadSettings();
1702          builderTab.LoadSettings();
1703
1704          if(ac)
1705          {
1706             if(!ac.visible)
1707             {
1708                if(ac == compilerTab.excludeFromBuild.editor)
1709                   ac = compilerTab.objDir.editor;
1710                else if(compilerTab.excludeFromBuild.editor.visible)
1711                   ac = compilerTab.excludeFromBuild.editor;
1712             }
1713             ac.MakeActive();
1714          }
1715       }
1716    }
1717
1718    void CreateConfigButtons()
1719    {
1720       // Create Config Buttons
1721       SelectorButton
1722       {
1723          configSelector, master = this, text = $"Common", id = (int64)null; font = { font.faceName, font.size, true };
1724          checked = true;
1725          NotifyClicked = ConfigClicked;
1726       };
1727
1728       config = null;
1729
1730       if(project.topNode.configurations)
1731       {
1732          for(c : project.topNode.configurations)
1733          {
1734             EditableSelectorButton button
1735             {
1736                configSelector, master = this, renameable = true, text = c.name, id = (int64)c;
1737                NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1738             };
1739          }
1740       }
1741    }
1742
1743    void Init()
1744    {
1745       Platform p;
1746       SelectorButton button;
1747
1748       activeConfigName = project.config ? CopyString(project.config.name) : null;
1749
1750       compilerTab.AddNode(project.topNode, null);
1751
1752       CreateConfigButtons();
1753
1754       platformButton = button =
1755       {
1756          platformSelector, master = this, text = $"Common", id = 0;  font = { font.faceName, font.size, true };
1757          NotifyClicked = PlatformClicked; checked = true;
1758       };
1759
1760       platform = 0;
1761
1762       for(p = (Platform)1; p < Platform::enumSize; p++)
1763       {
1764          SelectorButton button
1765          {
1766             platformSelector, master = this, text = p.OnGetString(0,0,0), id = (int64)p;
1767             NotifyClicked = PlatformClicked;
1768          };
1769       }
1770    }
1771    SelectorButton platformButton;
1772
1773    bool OnPostCreate()
1774    {
1775       // Backup Current Settings
1776       backupNode = project.topNode.Backup();
1777
1778       buildTabControl.Activate();
1779
1780       {
1781          Iterator<Window> it { configSelector.controls };
1782          while(it.Next())
1783          {
1784             SelectorButton configButton = (SelectorButton)it.data;
1785             ProjectConfig buttonConfig = (ProjectConfig)configButton.id;
1786             if(buttonConfig == project.config)
1787             {
1788                configButton.Activate();
1789                configButton.checked = true;
1790                ConfigClicked(configButton, 0, 0, 0);
1791                break;
1792             }
1793          }
1794       }
1795       if(platformButton)
1796       {
1797          platformButton.MakeActive();
1798          platformButton = null;
1799       }
1800       return true;
1801    }
1802
1803    void OnDestroy()
1804    {
1805       delete backupNode;
1806
1807       lastSelectedNode = null;
1808
1809       project.config = null;
1810
1811       /* // THIS IS NOW AUTOMATED WITH A project CHECK IN ProjectNode
1812       project.configurations = project.topNode.configurations;
1813       project.platforms = project.topNode.platforms;
1814       project.options = project.topNode.options;
1815       */
1816
1817       if(project.topNode.configurations)
1818       {
1819          for(c : project.topNode.configurations)
1820          {
1821             if(!strcmpi(c.name, activeConfigName))
1822             {
1823                project.config = c;
1824                break;
1825             }
1826          }
1827       }
1828       if(!project.config)
1829       {
1830          List<ProjectConfig> configs = project.topNode.configurations;
1831          if(configs && configs.count)
1832             project.config = configs[0];
1833       }
1834
1835       ide.UpdateToolBarActiveConfigs(false);
1836    }
1837
1838    void RevertChanges()
1839    {
1840       String configName = config ? CopyString(config.name) : null;
1841
1842       // Revert to saved project options
1843       project.topNode.Revert(backupNode);
1844
1845       configSelector.DestroyChildren();
1846       CreateConfigButtons();
1847
1848       // Reselect Configuration
1849       if(configName)
1850       {
1851          Iterator<Window> it { configSelector.controls };
1852          while(it.Next())
1853          {
1854             Button button = (Button)it.data;
1855             ProjectConfig c = (ProjectConfig)button.id;
1856             if(c && !strcmp(c.name, configName))
1857             {
1858                config = c;
1859                button.Activate();
1860                button.checked = true;
1861                ConfigClicked(button, 0,0, 0);
1862                break;
1863             }
1864          }
1865       }
1866
1867       SelectNode(project.topNode, false);
1868
1869       delete configName;
1870    }
1871
1872    bool OnClose(bool parentClosing)
1873    {
1874       if(modifiedDocument)
1875       {
1876          DialogResult diagRes = MessageBox
1877          {
1878             type = yesNoCancel, master = ide,
1879             text = $"Save changes to project settings?",
1880             contents = $"Would you like to save changes made to the build options?"
1881          }.Modal();
1882          if(diagRes == no)
1883             RevertChanges();
1884          if(diagRes == cancel)
1885             return false;
1886          if(diagRes == yes)
1887          {
1888             project.MarkChanges(backupNode);
1889             project.topNode.modified = true;
1890             ide.projectView.modifiedDocument = true;
1891             ide.UpdateToolBarActiveConfigs(false);
1892             ide.projectView.Update(null);
1893          }
1894          modifiedDocument = false;
1895       }
1896       return true;
1897    }
1898 }
1899
1900 class CompilerTab : Tab
1901 {
1902    background = formColor;
1903    text = $"Compiler";
1904
1905    Window leftPane { this, size = { 180 }, anchor = { left = 0, top = 0, bottom = 0 }, background = formColor };
1906
1907    Label labelFileList { leftPane, this, position = { 8, 8 }, labeledWindow = fileList };
1908    ListBox fileList
1909    {
1910       leftPane, this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true;
1911       // THIS WOULD BE EVEN MORE FUN: multiSelect = true,
1912       fullRowSelect = false, collapseControl = true, treeBranches = true;
1913       alwaysHighLight = true;
1914       selectionColor = unfocusedSelectorColor;
1915       size = { 180 };
1916       anchor = Anchor { left = 8, top = 24, right = 4, bottom = 8 };
1917       text = $"Files";
1918
1919       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1920       {
1921          BuildTab buildTab = (BuildTab)master;
1922          ProjectNode node = (ProjectNode)row.tag;
1923          buildTab.SelectNode(node, false);
1924          return true;
1925       }
1926
1927       void OnRedraw(Surface surface)
1928       {
1929          ide.projectView.drawingInProjectSettingsDialog = true;
1930          ListBox::OnRedraw(surface);
1931          ide.projectView.drawingInProjectSettingsDialog = false;
1932       }
1933
1934       bool NotifyActivate(Window window, bool active, Window previous)
1935       {
1936          if(active)
1937          {
1938             //subclass(Skin) skinClass = (subclass(Skin))eSystem_FindClass(app, app.skin);
1939             fileList.selectionColor = Color { 10, 36, 106 }; //skinClass.selectionColor; // darkBlue;
1940          }
1941          else if(fileList.currentRow)
1942          {
1943             DataRow currentRow = fileList.currentRow;
1944             //int headerSize = ((fileList.style.header) ? fileList.rowHeight : 0);
1945             int height = fileList.clientSize.h + 1;// - fileList.headerSize;
1946             fileList.selectionColor = unfocusedSelectorColor;
1947             if(currentRow && currentRow.index * fileList.rowHeight > fileList.scroll.y + height - fileList.rowHeight)
1948                fileList.SetScrollPosition(fileList.scroll.x, currentRow.index * fileList.rowHeight - height + fileList.rowHeight);
1949             else if(!currentRow || currentRow.index * fileList.rowHeight < fileList.scroll.y)
1950                fileList.SetScrollPosition(fileList.scroll.x, currentRow ? currentRow.index * fileList.rowHeight : 0);
1951
1952          }
1953
1954          return true;
1955       }
1956    };
1957
1958    Window rightPane
1959    {
1960       this, anchor = { left = 196, top = 0, right = 0, bottom = 0 }, background = formColor, tabCycle = true;
1961    };
1962
1963    Window rightPaneHeader
1964    {
1965       rightPane, this, size = { h = 21 }, anchor = { left = 0, top = 0, right = 0 }, background = Color { 70, 96, 166 };//0x0F3F66;
1966       foreground = white; visible = false;
1967
1968       void OnRedraw(Surface surface)
1969       {
1970          if(id)
1971          {
1972             ide.projectView.drawingInProjectSettingsDialogHeader = true;
1973             ((void (*)(void *, void *, void *, int, int, int, void *, uint, uint))(void *)class(ProjectNode)._vTbl[__ecereVMethodID_class_OnDisplay])(class(ProjectNode),
1974                (void *)id, surface, 8, 2, clientSize.w, ide.projectView, Alignment::left, DataDisplayFlags { selected = true });
1975             ide.projectView.drawingInProjectSettingsDialogHeader = false;
1976          }
1977       }
1978    };
1979
1980    PaneSplitter splitter
1981    {
1982       this, leftPane = leftPane, rightPane = rightPane, split = 188
1983    };
1984
1985    Label labelObjDir { rightPane, this, position = { 8, 8 }, labeledWindow = objDir };
1986    PathOptionBox objDir
1987    {
1988       rightPane, this, size = { 250, 22 }, anchor = { left = 8, top = 24, right = 8 };
1989       text = $"Intermediate Objects Directory", hotKey = altJ, option = OPTION(objectsDirectory);
1990    };
1991
1992    BoolOptionBox excludeFromBuild
1993    {
1994       rightPane, this, position = { 8, 28 },
1995       text = $"Exclude from Build", visible = false, option = OPTION(excludeFromBuild);
1996    };
1997
1998    Label labelPreprocessorDefs { rightPane, this, position = { 8, 50 }, labeledWindow = preprocessorDefs };
1999    StringArrayOptionBox preprocessorDefs
2000    {
2001       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2002       text = $"Preprocessor Definitions", hotKey = altD, option = OPTION(preprocessorDefinitions);
2003    };
2004
2005    Label labelDefaultNameSpace { rightPane, this, position = { 8, 92 }, labeledWindow = defaultNameSpace };
2006    StringOptionBox defaultNameSpace
2007    {
2008       rightPane, this, size = { 160, 22 }, position = { 8, 108 };
2009       text = $"Default Name Space", option = OPTION(defaultNameSpace);
2010    };
2011    BoolOptionBox strictNameSpaces
2012    {
2013       rightPane, this, position = { 172, 112 },
2014       text = $"Strict Name Spaces", option = OPTION(strictNameSpaces);
2015    };
2016
2017    BoolOptionBox fastMath
2018    {
2019       rightPane, this, position = { 316, 112 },
2020       text = $"Fast Math", option = OPTION(fastMath);
2021    };
2022
2023    BoolOptionBox memoryGuard
2024    {
2025       rightPane, this, position = { 8, 154 };
2026       text = $"MemoryGuard", hotKey = altM, option = OPTION(memoryGuard);
2027    };
2028
2029    Label labelWarnings { rightPane, position = { 116, 138 }, labeledWindow = warnings };
2030    WarningsDB warnings
2031    {
2032       rightPane, this, position = { 116, 154 };
2033       text = $"Warnings", hotKey = altW, option = OPTION(warnings);
2034    };
2035
2036    Label labelOptimization { rightPane, position = { 220, 138 }, labeledWindow = optimization };
2037    OptimizationDB optimization
2038    {
2039       rightPane, this, position = { 220, 154 }, size = { 120, 22 };
2040       text = $"Optimization", hotKey = altO, option = OPTION(optimization);
2041    };
2042
2043    BoolOptionBox debug
2044    {
2045       rightPane, this, position = { 8, 188 };
2046       text = $"Debuggable", hotKey = altG, option = OPTION(debug);
2047    };
2048
2049    BoolOptionBox profiling
2050    {
2051       rightPane, this, position = { 116, 188 };
2052       text = $"Profiling Data", hotKey = altP, option = OPTION(profile);
2053    };
2054
2055    BoolOptionBox noLineNumbers
2056    {
2057       rightPane, this, position = { 220, 188 };
2058       text = $"No Line Numbers", hotKey = altN, option = OPTION(noLineNumbers);
2059    };
2060
2061    Label labelCompilerOptions { rightPane, this, position = { 8, 208 }, labeledWindow = compilerOptions };
2062    StringArrayOptionBox compilerOptions
2063    {
2064       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 224, right = 8 };
2065       text = $"Compiler Options", hotKey = altO, option = OPTION(compilerOptions);
2066       configReplaces = true;
2067    };
2068
2069    Label labelIncludeDirs { includeDirs.editor, labeledWindow = includeDirs, position = { 0, 6 }; };
2070    DirsArrayOptionBox includeDirs
2071    {
2072       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 250, right = 8, bottom = 8 };
2073       text = $"Additional Include Directories", hotKey = altI, option = OPTION(includeDirs), switchToKeep = "I";
2074    };
2075
2076    CompilerTab()
2077    {
2078       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false,
2079          userData = null /* Now set in the ProjectNode directly to know we're in ProjectSettings Dialog -- ide.projectView*/ });
2080    }
2081
2082    bool OnCreate()
2083    {
2084       BuildTab buildTab = (BuildTab)master;
2085       buildTab.SelectNode(buildTab.lastSelectedNode, true);
2086       return true;
2087    }
2088
2089    void AddNode(ProjectNode node, DataRow addTo)
2090    {
2091       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
2092
2093       row.tag = (int64)node;
2094
2095       row.SetData(null, node);
2096
2097       if(node.files && node.files.first && node.parent &&
2098             !(!node.parent.parent &&
2099                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") ||
2100                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
2101          row.collapsed = true;
2102       else if(node.type == folder)
2103          node.icon = openFolder;
2104
2105       if(node.files)
2106       {
2107          for(child : node.files)
2108             AddNode(child, row);
2109       }
2110    }
2111
2112    void LoadSettings()
2113    {
2114       OptionBox ob;
2115       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2116          if(eClass_IsDerived(ob._class, class(OptionBox)))
2117             ob.Load();
2118
2119       if(activeChild && activeChild.active)
2120       {
2121          Window control = activeChild;
2122          control.Deactivate();
2123          control.Activate();
2124       }
2125    }
2126
2127    bool OnPostCreate()
2128    {
2129       objDir.editor.Activate();
2130       return true;
2131    }
2132 }
2133
2134 class LinkerTab : Tab
2135 {
2136    background = formColor;
2137    text = $"Linker";
2138
2139    Label labelTargetName { this, position = { 8, 8 }, labeledWindow = targetName };
2140    StringOptionBox targetName
2141    {
2142       this, position = { 8, 24 }, size = { 200, 22 };
2143       text = $"Target Name", hotKey = altN, option = OPTION(targetFileName);
2144    };
2145
2146    Label labelTargetType { this, position = { 216, 8 }, labeledWindow = targetType };
2147    TargetTypeDB targetType
2148    {
2149       this, position = { 216, 24 }, size = { 120, 22 };
2150       text = $"Target Type", hotKey = altT, option = OPTION(targetType);
2151    };
2152
2153    Label labelTargetDirectory { this, position = { 344, 8 }, labeledWindow = targetDirectory };
2154    PathOptionBox targetDirectory
2155    {
2156       this, size = { 270, 22 }, anchor = { left = 344, top = 24, right = 8 };
2157       hotKey = altR, text = $"Target Directory", option = OPTION(targetDirectory);
2158    };
2159
2160    Label labelLibraries { this, position = { 8, 50 }, labeledWindow = libraries };
2161    StringArrayOptionBox libraries
2162    {
2163       this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2164       text = $"Additional Libraries", hotKey = altL, option = OPTION(libraries), switchToKeep = "l";
2165       configReplaces = true;
2166    };
2167
2168    Label labelLinkerOptions { this, position = { 8, 92 }, labeledWindow = linkerOptions };
2169    StringArrayOptionBox linkerOptions
2170    {
2171       this, size = { 290, 22 }, anchor = { left = 8, top = 108, right = 8 };
2172       text = $"Linker Options", hotKey = altO, option = OPTION(linkerOptions);
2173       configReplaces = true;
2174    };
2175
2176    BoolOptionBox console
2177    {
2178       this, position = { 8, 138 };
2179       text = $"Console Application", hotKey = altC, option = OPTION(console);
2180    };
2181
2182    BoolOptionBox compress
2183    {
2184       this, position = { 8, 162 };
2185       text = $"Compress", hotKey = altW, option = OPTION(compress);
2186    };
2187
2188    Label labelLibraryDirs { libraryDirs.editor, labeledWindow = libraryDirs, position = { 0, 6 }; };
2189    DirsArrayOptionBox libraryDirs
2190    {
2191       this, size = { 290, 22 }, anchor = { left = 8, top = 182, right = 8, bottom = 8 };
2192       text = $"Additional Library Directories", hotKey = altY, option = OPTION(libraryDirs), switchToKeep = "L";
2193    };
2194
2195    bool OnCreate()
2196    {
2197       ((BuildTab)master).SelectNode(project.topNode, true);
2198       return true;
2199    }
2200
2201    void LoadSettings()
2202    {
2203       OptionBox ob;
2204       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2205          if(eClass_IsDerived(ob._class, class(OptionBox)))
2206             ob.Load();
2207       compress.disabled = (config && config.options && config.options.debug == true) || project.topNode.options.debug == true;
2208
2209       if(activeChild && activeChild.active)
2210       {
2211          Window control = activeChild;
2212          control.Deactivate();
2213          control.Activate();
2214       }
2215    }
2216 }
2217
2218 class BuilderTab : Tab
2219 {
2220    background = formColor;
2221    text = $"Builder";
2222
2223    Label labelPrebuildCommands { prebuildCommands.editor, labeledWindow = prebuildCommands, position = { 0, 6 }; };
2224    StringsArrayOptionBox prebuildCommands
2225    {
2226       this, size = { 290, 92 }, anchor = { left = 8, top = 8, right = 8, bottom = 200 };
2227       text = $"Pre-build Commands", hotKey = altE, option = OPTION(prebuildCommands);
2228    };
2229
2230    Label labelPostbuildCommands { postbuildCommands.editor, labeledWindow = postbuildCommands, position = { 0, 6 }; };
2231    StringsArrayOptionBox postbuildCommands
2232    {
2233       this, size = { 290, 92 }, anchor = { left = 8, top = 100, right = 8, bottom = 100 };
2234       text = $"Post-build Commands", hotKey = altT, option = OPTION(postbuildCommands);
2235    };
2236
2237    Label labelInstallCommands { installCommands.editor, labeledWindow = installCommands, position = { 0, 6 }; };
2238    StringsArrayOptionBox installCommands
2239    {
2240       this, size = { 290, 92 }, anchor = { left = 8, top = 200, right = 8, bottom = 8 };
2241       text = $"Install Commands", hotKey = altT, option = OPTION(installCommands);
2242    };
2243
2244    void LoadSettings()
2245    {
2246       OptionBox ob;
2247       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2248          if(eClass_IsDerived(ob._class, class(OptionBox)))
2249             ob.Load();
2250
2251       if(activeChild && activeChild.active)
2252       {
2253          Window control = activeChild;
2254          control.Deactivate();
2255          control.Activate();
2256       }
2257    }
2258
2259    void OnResize(int width, int height)
2260    {
2261       int h = (height - 8 * 4) / 3;
2262       prebuildCommands.anchor = { left = 8, top = 8, right = 8, bottom = h * 2 + 8 * 3 };
2263       postbuildCommands.anchor = { left = 8, top = h + 8 * 2, right = 8, bottom = h + 8 * 2 };
2264       installCommands.anchor = { left = 8, top = h * 2 + 8 * 3, right = 8, bottom = 8 };
2265    }
2266 }