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