5c8560cf8a0983dd11fcf2300a31807700dcb32d
[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 * 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    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          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       char * nodeName = currentNode && currentNode != project.topNode ? currentNode.name : "";
138       char * config = buildTab.selectedConfigName;
139       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)(&((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 char * text { set { editor.text = value; Window::text = 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       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       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       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       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             Array<String> ts = tempStrings;
843             while(it.Next())
844             {
845                String s = it.data;
846                bool found = false;
847                for(i : tempStrings; i && s && !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
848                if(found && (!configReplaces || platform))   // ADDED || platform here...
849                {
850                   delete s;
851                   it.Remove();
852                }
853             }
854          }
855          delete tempStrings;
856       }
857
858       if(!mergeValues || (configReplaces && isCfgOrPlt && !platform))
859          *strings = newStrings;
860       else
861       {
862          *strings = (newStrings && newStrings.count) ? newStrings : null;
863          if(newStrings && !newStrings.count) delete newStrings;
864       }
865
866       Load();
867    }
868
869    void LoadOption(ProjectOptions options)
870    {
871       if(mergeValues)
872       {
873          Array<String> strings = options ? *((Array<String>*)((byte *)options + option) : null;
874          if(strings)
875          {
876             if(!tempStrings)
877                tempStrings = { };
878             for(s : strings)
879             {
880                bool found = false;
881                for(i : tempStrings; i && s && !(caseSensitive ? strcmp : strcmpi)(i, s)) { found = true; break; }
882                if(!found) tempStrings.Add(s);
883             }
884          }
885       }         
886       else
887       {
888          SetStrings(options ? *(Array<String>*)((byte *)options + option) : null);
889       }
890    }
891
892    void FinalizeLoading()
893    {
894       if(mergeValues)
895       {
896          SetStrings(tempStrings);
897          delete tempStrings;
898       }
899    }
900
901    bool OptionSet(ProjectOptions options)
902    {
903       Array<String> strings = *(Array<String>*)((byte *)options + option);
904       if(mergeValues && !configReplaces)
905       {
906          return strings && strings.count;
907       }
908       else
909          return strings != null;
910    }
911
912    bool OptionCheck(ProjectOptions options)
913    {
914       Array<String> strings = *(Array<String>*)((byte *)options + option);
915       return strings && strings.count;
916    }
917
918    void UnsetOption(ProjectOptions options)
919    {
920       Array<String> * strings = (Array<String>*)((byte *)options + option);
921       if(*strings) { strings->Free(); delete *strings; }
922    }
923 }
924
925 class StringArrayOptionBox : MultiStringOptionBox
926 {
927    editor = StringListBox
928    {
929       bool NotifyModified(EditBox editBox)
930       {
931          ((OptionBox)editBox.id).Retrieve();
932          return true;
933       }
934    };
935
936    // NO VIRTUAL PROPERTIES YET...
937    Array<String> GetStrings() { return ((StringListBox)editor).strings; }
938    void SetStrings(Array<String> value) { ((StringListBox)editor).strings = value; }
939
940    property char * switchToKeep { set { ((StringListBox)editor).switchToKeep = value; ((StringListBox)editor).lenSwitchToKeep = strlen(value); } };
941 }
942
943 class StringsArrayOptionBox : MultiStringOptionBox
944 {
945    editor = StringsBox
946    {
947       bool OnCreate()
948       {
949          project = ::project;
950          return true;
951       }
952
953       bool NotifyModified(StringsBox stringsBox)
954       {
955          ((OptionBox)stringsBox.id).Retrieve();
956          return true;
957       }
958    };
959
960    Array<String> GetStrings() { return ((StringsBox)editor).strings; }
961    void SetStrings(Array<String> value) { ((StringsBox)editor).strings = value; }
962 }
963
964 bool eString_IsPathRelatedTo(char * path, char * to)
965 {
966    if(path[0] && to[0])
967    {
968       char rest[MAX_FILENAME];
969       char pathPart[MAX_FILENAME], pathRest[MAX_LOCATION] = "";
970       char toPart[MAX_FILENAME], toRest[MAX_LOCATION] = "";
971       SplitDirectory(path, pathPart, pathRest);
972       SplitDirectory(to, toPart, toRest);
973       if(!fstrcmp(pathPart, toPart))
974       {
975          if(pathRest[0] && toRest[0])
976          {
977             SplitDirectory(pathRest, pathPart, pathRest);
978             SplitDirectory(toRest, toPart, toRest);
979             if(!fstrcmp(pathPart, toPart))
980                return true;
981          }
982       }
983    }
984    return false;
985 }
986
987 static void FixPathOnPathBoxNotifyModified(PathBox pathBox)
988 {
989    int len;
990    char path[MAX_LOCATION];
991    ValidPathBufCopy(path, pathBox.path);
992    len = strlen(path);
993    if(len && !(path[0] == '.' && (len == 1 || (len == 2 && path[1] == DIR_SEP) || (len > 1 && path[1] == '.'))))
994    {
995       char cwdBackup[MAX_LOCATION];
996       if(project)
997       {
998          GetWorkingDir(cwdBackup, sizeof(cwdBackup));
999          ChangeWorkingDir(project.topNode.path);
1000       }
1001       FileFixCase(path);
1002       if(project)
1003          ChangeWorkingDir(cwdBackup);
1004       if(eString_IsPathRelatedTo(path, project.topNode.path))
1005          MakePathRelative(path, project.topNode.path, path);
1006       if(!path[0])
1007          strcpy(path, ".");
1008       len = strlen(path);
1009    }
1010    if(len>1 && path[len-1] == DIR_SEP)
1011       path[--len] = '\0';
1012    pathBox.path = path;
1013 }
1014
1015 class DirsArrayOptionBox : MultiStringOptionBox
1016 {
1017 public:
1018    property char * switchToKeep { set { switchToKeep = value; lenSwitchToKeep = strlen(value); } };
1019 private:
1020    char * switchToKeep;
1021    int lenSwitchToKeep;
1022
1023    editor = DirectoriesBox
1024    {
1025       browseDialog = { };
1026       bool NotifyModified(DirectoriesBox dirsBox)
1027       {
1028          char * switchToKeep = ((DirsArrayOptionBox)dirsBox.id).switchToKeep;
1029          if(switchToKeep && switchToKeep[0])
1030          {
1031             bool change = false;
1032             int lenSwitchToKeep = ((DirsArrayOptionBox)dirsBox.id).lenSwitchToKeep;
1033             Array<String> dirs { };
1034             Array<String> previousDirs = dirsBox.strings;
1035             for(d : previousDirs)
1036             {
1037                int c;
1038                char * buffer = new char[strlen(d)+64];
1039                char * tokens[1024];
1040                uint count;
1041                strcpy(buffer, d);
1042                count = Tokenize(buffer, sizeof(tokens)/sizeof(tokens[0]), tokens, (BackSlashEscaping)false);
1043                for(c=0; c<count; c++)
1044                {
1045                   if(tokens[c][0] == '-')
1046                   {
1047                      if(strstr(tokens[c]+1, switchToKeep) == tokens[c]+1)
1048                         tokens[c] += lenSwitchToKeep+1;
1049                      else
1050                         tokens[c][0] = '\0';
1051                      change = true;
1052                   }
1053                   dirs.Add(CopyString(tokens[c]));
1054                }
1055                delete buffer;
1056             }
1057             if(change)
1058                dirsBox.strings = dirs;
1059             dirs.Free();
1060             delete dirs;
1061             previousDirs.Free();
1062             delete previousDirs;
1063          }
1064          ((OptionBox)dirsBox.id).Retrieve();
1065          return true;
1066       }
1067
1068       bool NotifyPathBoxModified(DirectoriesBox dirsBox, PathBox pathBox)
1069       {
1070          FixPathOnPathBoxNotifyModified(pathBox);
1071          return true;
1072       }
1073    };
1074
1075    Array<String> GetStrings() { return ((DirectoriesBox)editor).strings; }
1076    void SetStrings(Array<String> value) { ((DirectoriesBox)editor).strings = value; }
1077 }
1078
1079 class BoolOptionBox : OptionBox<SetBool>
1080 {
1081    editor = Button
1082    {
1083       isCheckbox = true;
1084
1085       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1086       {
1087          ((OptionBox)button.id).Retrieve();
1088          return true;
1089       }
1090    };
1091
1092    bool OptionCheck(ProjectOptions options)
1093    {
1094       return *(SetBool*)((byte *)options + option) == true;
1095    }
1096
1097    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1098    {
1099       bool checked = ((Button)editor).checked;
1100       *(SetBool*)((byte *)options + option) = checked ? true : 
1101          ((currentNode.parent || isCfgOrPlt) ? false : unset);
1102    }
1103
1104    void LoadOption(ProjectOptions options)
1105    {
1106       ((Button)editor).checked = options && (*(SetBool*)((byte *)options + option) == true);
1107    }
1108 }
1109
1110 class CheckBoxForEnumOptionBox : OptionBox
1111 {
1112    editor = Button
1113    {
1114       isCheckbox = true;
1115
1116       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1117       {
1118          ((OptionBox)button.id).Retrieve();
1119          {
1120             Window slave;
1121             for(slave = master.firstSlave; slave; slave = slave.nextSlave)
1122             {
1123                if(eClass_IsDerived(slave._class, class(CheckBoxForEnumOptionBox)) &&
1124                      slave != (Window)button.id &&
1125                      ((OptionBox)slave).option == ((OptionBox)button.id).option)
1126                   ((OptionBox)slave).Load();
1127             }
1128          }
1129          return true;
1130       }
1131    };
1132
1133    Z enumValue;
1134    void LoadOption(ProjectOptions options)
1135    {
1136       Z value = options ? *(Z*)((byte *)options + option) : (Z)0;
1137       ((Button)editor).checked = value == enumValue;
1138    }
1139
1140    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1141    {
1142       Button checkBox = (Button)editor;
1143       if(checkBox.checked)
1144          *(Z*)((byte *)options + option) = enumValue;
1145    }
1146 }
1147
1148 class BuildBitDepthOptionBox : CheckBoxForEnumOptionBox<BuildBitDepth> { }
1149
1150 class DropOptionBox : OptionBox
1151 {
1152    editor = DropBox
1153    {
1154       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
1155       {
1156          ((OptionBox)dropBox.id).Retrieve();
1157          return true;
1158       }
1159    };   
1160
1161    void LoadOption(ProjectOptions options)
1162    {
1163       DropBox dropBox = (DropBox)editor;
1164       Z value = options ? *(Z*)((byte *)options + option) : (Z)0;
1165       dropBox.currentRow = value ? dropBox.FindRow((int64)value) : dropBox.firstRow;
1166    }
1167
1168    void RetrieveOption(ProjectOptions options, bool isCfgOrPlt)
1169    {
1170       DropBox dropBox = (DropBox)editor;
1171       DataRow row = dropBox.currentRow;
1172       Z value = (Z)(row ? row.tag : 0);
1173       *(Z*)((byte *)options + option) = value;
1174    }
1175 }
1176
1177 class TargetTypeDB : DropOptionBox<TargetTypes>
1178 {
1179    TargetTypeDB()
1180    {
1181       DataRow row;
1182
1183       row = ((DropBox)editor).AddRow();
1184       row.tag = TargetTypes::executable;
1185       row.SetData(null, $"Executable");
1186
1187       row = ((DropBox)editor).AddRow();
1188       row.tag = TargetTypes::sharedLibrary;
1189       row.SetData(null, $"Shared Library");
1190
1191       row = ((DropBox)editor).AddRow();
1192       row.tag = TargetTypes::staticLibrary;
1193       row.SetData(null, $"Static Library");
1194    }
1195
1196    bool OptionCheck(ProjectOptions options)
1197    {
1198       TargetTypes value = *(TargetTypes*)((byte *)options + option);
1199       return value && value != executable;
1200    }
1201 }
1202
1203 class OptimizationDB : DropOptionBox<OptimizationStrategy>
1204 {
1205    OptimizationDB()
1206    {
1207       DataRow row;
1208       row = ((DropBox)editor).AddRow();
1209       row.tag = OptimizationStrategy::none;
1210       row.SetData(null, $"None");
1211
1212       row = ((DropBox)editor).AddRow();
1213       row.tag = OptimizationStrategy::speed;
1214       row.SetData(null, $"For Speed (-O2)");
1215
1216       row = ((DropBox)editor).AddRow();
1217       row.tag = OptimizationStrategy::size;
1218       row.SetData(null, $"For Size (-Os)");
1219    }
1220
1221    bool OptionCheck(ProjectOptions options)
1222    {
1223       OptimizationStrategy value = *(OptimizationStrategy*)((byte *)options + option);
1224       return value && value != none;
1225    }
1226 }
1227
1228 class WarningsDB : DropOptionBox<WarningsOption>
1229 {
1230    WarningsDB()
1231    {
1232       DataRow row;
1233       row = ((DropBox)editor).AddRow();
1234       row.tag = WarningsOption::normal;
1235       row.SetData(null, $"Normal");
1236
1237       row = ((DropBox)editor).AddRow();
1238       row.tag = WarningsOption::none;
1239       row.SetData(null, $"None");
1240
1241       row = ((DropBox)editor).AddRow();
1242       row.tag = WarningsOption::all;
1243       row.SetData(null, $"All");
1244    }
1245
1246    bool OptionCheck(ProjectOptions options)
1247    {
1248       WarningsOption value = *(WarningsOption*)((byte *)options + option);
1249       return value && value != none;
1250    }
1251 }
1252
1253 void DrawStipple(Surface surface, Size clientSize)
1254 {
1255    int x1 = 0;
1256    int y1 = 0;
1257    int x2 = clientSize.w - 1;
1258    int y2 = clientSize.h - 1;
1259    if((x2 - x1) & 1) x2--;
1260    if((y2 - y1) & 1) y2--;
1261
1262    surface.LineStipple(0x5555);
1263    surface.Rectangle(x1, y1, x2, y2);
1264    surface.LineStipple(0);            
1265 }
1266
1267 class BuildTab : Tab
1268 {
1269    text = $"Build";
1270    background = formColor;
1271    tabCycle = true;
1272
1273    ProjectNode backupNode;
1274    String activeConfigName;
1275
1276    ProjectNode lastSelectedNode;
1277
1278    property char * selectedConfigName
1279    {
1280       get
1281       {
1282          if(created)
1283          {
1284             SelectorButton button = (SelectorButton)configSelector.selectedButton;
1285             if(button && button.id)
1286             {
1287                ProjectConfig config = (ProjectConfig)button.id;
1288                return config.name;
1289             }
1290          }
1291          return "";
1292       }
1293    }
1294
1295    property char * selectedPlatformName
1296    {
1297       get
1298       {
1299          if(created)
1300          {
1301             SelectorButton button = (SelectorButton)platformSelector.selectedButton;
1302             if(button && button.id)
1303             {
1304                Platform platform = (Platform)button.id;
1305                char * platformName = platform ? platform.OnGetString(0,0,0) : null; // all these platformName are leaking, no? 
1306                return platformName;
1307             }
1308          }
1309          return "";
1310       }
1311    }
1312
1313    Label labelConfigurations
1314    {
1315       this, anchor = { left = 8, top = 14 }, labeledWindow = configSelector;
1316
1317       void OnRedraw(Surface surface)
1318       {
1319          Label::OnRedraw(surface);
1320          if(labeledWindow.active) DrawStipple(surface, clientSize);
1321       }
1322    };
1323    SelectorBar configSelector
1324    {
1325       this, text = $"Configurations: ", anchor = { left = 98, top = 8, right = 54 }; size = { 0, 26 };
1326       opacity = 0;
1327       direction = horizontal, scrollable = true;
1328
1329       bool OnKeyDown(Key key, unichar ch)
1330       {
1331          if(key == insert)
1332          {
1333             ((BuildTab)parent).createConfig.NotifyClicked(parent, ((BuildTab)parent).createConfig, 0, 0, 0);
1334             return false;
1335          }
1336          else if(key == del)
1337          {
1338             ((BuildTab)parent).deleteConfig.NotifyClicked(parent, ((BuildTab)parent).deleteConfig, 0, 0, 0);
1339             return false;
1340          }
1341          return SelectorBar::OnKeyDown(key, ch);
1342       }
1343       
1344       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1345       {
1346          ((BuildTab)master).labelConfigurations.Update(null);
1347          return true;
1348       }
1349    };
1350
1351    Button createConfig
1352    {
1353       parent = this, bevelOver = true, inactive = true;
1354       size = { 22, 22 };
1355       anchor = { top = 10, right = 31 };
1356       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/docNew.png", alphaBlend = true };
1357
1358       bool NotifyClicked(Button b, int x, int y, Modifiers mods)
1359       {
1360          char tmp[MAX_F_STRING];
1361          ProjectConfig config;
1362          EditableSelectorButton button;
1363
1364          FindUniqueConfigName("NewConfig", false, tmp);
1365
1366          config =
1367          {
1368             makingModified = true;
1369             compilingModified = true;
1370             linkingModified = true;
1371             name = CopyString(tmp);
1372             options =
1373             {
1374                // objectsDirectory = /*CopyString(*/defaultObjDirExpression/*)*/;
1375             };
1376          };
1377          if(!project.topNode.configurations) project.topNode.configurations = { };
1378          project.topNode.configurations.Add(config);
1379          /*
1380          targetType = project.config.options.targetType;
1381          config.options.
1382          config.options.targetFileName = project.moduleName;
1383          config.options.targetDir.dir = "";
1384          config.options.objectsDirectory = defaultObjDirExpression);
1385          config.options.debug = true;
1386          config.options.optimization = none;
1387          config.options.warnings = all;
1388          */         
1389
1390          button =
1391          {
1392             configSelector, renameable = true, master = this, text = config.name, id = (int64)config;
1393             NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1394          };
1395
1396          configSelector.Select(button);
1397          modifiedDocument = true;
1398          return true;
1399       }
1400    };
1401    /*Button duplicateConfig
1402    {
1403       parent = this, bevelOver = true, inactive = true;
1404       size = { 22, 22 };
1405       anchor = { top = 10, right = 31 };
1406       hotKey = altU, bitmap = BitmapResource { fileName = ":actions/editCopy.png", alphaBlend = true };
1407    };*/
1408    Button deleteConfig
1409    {
1410       parent = this, bevelOver = true, inactive = true;
1411       size = { 22, 22 };
1412       anchor = { top = 10, right = 8 };
1413       hotKey = altD, bitmap = BitmapResource { fileName = ":actions/delete2.png", alphaBlend = true };
1414
1415       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
1416       {
1417          if(config)
1418          {
1419             String title = PrintString($"Delete ", config.name, $" Configuration");
1420             String msg = PrintString($"Are you sure you wish to delete the ", config.name, $" configuration?");
1421             if(MessageBox { type = okCancel, text = title, contents = msg }.Modal() == ok)
1422             {
1423                Iterator<Window> it { configSelector.controls };
1424                ProjectConfig configToDelete = config;
1425                /*
1426                while(it.Next())
1427                {
1428                   SelectorButton button = (SelectorButton)it.data;
1429                   if((ProjectConfig)button.id == config)
1430                   {
1431                      button.visible = false;
1432                      button.Destroy(0);
1433
1434                      if(it.Prev())
1435                      {
1436                         button = (SelectorButton)it.data;
1437                         config = (ProjectConfig)button.id;
1438                         configSelector.Select(button);
1439                      }
1440                      break;
1441                   }
1442                }
1443                */
1444                SelectorButton button = configSelector.FindButtonByID((int64)configToDelete);
1445                if(button)
1446                   configSelector.RemoveButton(button);
1447
1448                project.topNode.DeleteConfig(configToDelete);
1449
1450                modifiedDocument = true;
1451             }
1452             delete title;
1453             delete msg;
1454          }
1455          return true;
1456       }
1457    };
1458    
1459    Label labelPlatforms
1460    {
1461       this, anchor = { left = 8, top = 44 }, labeledWindow = platformSelector;
1462
1463       void OnRedraw(Surface surface)
1464       {
1465          Label::OnRedraw(surface);
1466          if(labeledWindow.active) DrawStipple(surface, clientSize);
1467       }
1468    };
1469    SelectorBar platformSelector
1470    {
1471       this, text = $"Platforms: ", anchor = { left = 64, top = 38, right = 54 }; size = { 0, 26 };
1472       opacity = 0;
1473       direction = horizontal, scrollable = true;
1474
1475       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
1476       {
1477          ((BuildTab)master).labelPlatforms.Update(null);
1478          return true;
1479       }
1480    };
1481
1482    TabControl buildTabControl
1483    {
1484       this, background = formColor, anchor = { left = 8, top = 64, right = 8, bottom = 8 };
1485       curTab = compilerTab;
1486    };
1487    CompilerTab compilerTab { this, tabControl = buildTabControl };
1488    LinkerTab linkerTab { this, tabControl = buildTabControl };
1489    BuilderTab builderTab { this, tabControl = buildTabControl };
1490    Label rightClick
1491    {
1492       this, font = { font.faceName, font.size, italic = true }, stayOnTop = true,
1493       text = $"(Right click or press Ctrl-Del to revert an option to inherited value)", anchor = { top = 72, right = 16 }
1494    };
1495
1496    void FindUniqueConfigName(char * baseName, bool startWithNumber, char * output)
1497    {
1498       int num = 0;
1499       char tmp[MAX_F_STRING];
1500       if(startWithNumber)
1501          sprintf(tmp, "%s%d", baseName, num);
1502       else
1503          strcpy(tmp, baseName);
1504       while(true)
1505       {
1506          ProjectConfig config = null;
1507          for(c : project.topNode.configurations)
1508          {     // TOFIX: Error when omitting these brackets, c not found
1509             if(c.name && !strcmp(c.name, tmp))
1510             {
1511                config = c;
1512                break;
1513             }
1514          }
1515          if(config)
1516          {
1517             num++;
1518             sprintf(tmp, "%s%d", baseName, num);
1519          }
1520          else
1521             break;
1522       }
1523       strcpy(output, tmp);
1524    }
1525
1526    bool PlatformClicked(Button clickedButton, int x, int y, Modifiers mods)
1527    {
1528       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1529       {
1530          platform = (Platform)clickedButton.id;
1531
1532          // Load Settings Into Dialog
1533          compilerTab.LoadSettings();
1534          linkerTab.LoadSettings();
1535          builderTab.LoadSettings();
1536
1537          if(!mods)
1538             buildTabControl.Activate();
1539
1540          if(compilerTab.rightPaneHeader.visible)
1541             compilerTab.rightPaneHeader.Update(null);
1542          ((ProjectSettings)master).UpdateDialogTitle();
1543       }
1544       return true;
1545    }
1546
1547    ~BuildTab()
1548    {
1549       platformSelector.DestroyChildren();
1550       configSelector.DestroyChildren();
1551
1552       delete activeConfigName;
1553    }
1554
1555    bool ConfigOnRename(EditableSelectorButton button, char * * oldName, char * * newName)
1556    {
1557       int c, d = 0;
1558       char ch;
1559
1560       for(c = 0; (ch = (*newName)[c]); c++)
1561       {
1562          if(ch == '_' || isalpha(ch) || (isdigit(ch) && d))
1563             (*newName)[d++] = ch;
1564       }
1565       (*newName)[d] = 0;
1566
1567       {
1568          bool found = false;
1569          for(c : project.topNode.configurations; c != config)
1570          {
1571             if(!strcmpi(c.name, *newName))
1572             {
1573                found = true;
1574                break;
1575             }
1576          }
1577          if(found || !(*newName)[0])
1578          {
1579             char tmp[MAX_F_STRING];
1580             char * tmpName = config.name;
1581             config.name = null;
1582             FindUniqueConfigName("NewConfig", false, tmp);
1583             config.name = tmpName;
1584             delete *newName;
1585             *newName = CopyString(tmp);
1586          }
1587       }
1588
1589       if(activeConfigName && !strcmp(activeConfigName, *oldName))
1590       {
1591          delete activeConfigName;
1592          activeConfigName = CopyString(*newName);
1593       }
1594
1595       project.topNode.RenameConfig(config.name, *newName);
1596       
1597       modifiedDocument = true;
1598       return true;
1599    }
1600
1601    bool ConfigClicked(Button clickedButton, int x, int y, Modifiers mods)
1602    {
1603       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
1604       {
1605          config = (ProjectConfig)clickedButton.id;
1606
1607          // Load Settings Into Dialog
1608          compilerTab.LoadSettings();
1609          linkerTab.LoadSettings();
1610          builderTab.LoadSettings();
1611
1612          deleteConfig.disabled = (clickedButton._class == class(SelectorButton));
1613
1614          if(!mods)
1615             buildTabControl.Activate();
1616
1617          compilerTab.fileList.Update(null);
1618          if(compilerTab.rightPaneHeader.visible)
1619             compilerTab.rightPaneHeader.Update(null);
1620          ((ProjectSettings)master).UpdateDialogTitle();
1621       }
1622       return true;
1623    }
1624
1625    void SelectNode(ProjectNode node, bool ignoreAsLastSelection)
1626    {
1627       if(node != currentNode)
1628       {
1629          Window ac = compilerTab.rightPane.activeChild;
1630          bool prevNodeRes = currentNode ? currentNode.isInResources : false;
1631          bool newNodeRes;
1632
1633          if(!node) node = project.topNode;
1634
1635          newNodeRes = node.isInResources;
1636          
1637          currentNode = node;
1638          if(!ignoreAsLastSelection)
1639             lastSelectedNode = node;
1640
1641          ((ProjectSettings)master).UpdateDialogTitle();
1642          if(node.type == project)
1643          {
1644             compilerTab.rightPaneHeader.visible = false;
1645          }
1646          else
1647          {
1648             compilerTab.rightPaneHeader.id = (int64)node;
1649             compilerTab.rightPaneHeader.Update(null);
1650             compilerTab.rightPaneHeader.visible = true;
1651          }
1652
1653          {
1654             DataRow row = compilerTab.fileList.FindSubRow((int64)currentNode);
1655             if(row)
1656             {
1657                compilerTab.fileList.currentRow = row;
1658                while((row = row.parent))
1659                   row.collapsed = false;
1660             }
1661          }
1662
1663          if(prevNodeRes != newNodeRes)
1664          {
1665             compilerTab.labelObjDir.visible = !newNodeRes;
1666             compilerTab.objDir.visible = !newNodeRes;
1667             compilerTab.excludeFromBuild.visible = !newNodeRes;
1668             compilerTab.labelPreprocessorDefs.visible = !newNodeRes;
1669             compilerTab.preprocessorDefs.visible = !newNodeRes;
1670             compilerTab.labelDefaultNameSpace.visible = !newNodeRes;
1671             compilerTab.defaultNameSpace.visible = !newNodeRes;
1672             compilerTab.strictNameSpaces.visible = !newNodeRes;
1673             compilerTab.memoryGuard.visible = !newNodeRes;
1674             compilerTab.noLineNumbers.visible = !newNodeRes;
1675             compilerTab.debug.visible = !newNodeRes;
1676             compilerTab.labelWarnings.visible = !newNodeRes;
1677             compilerTab.warnings.visible = !newNodeRes;
1678             compilerTab.profiling.visible = !newNodeRes;
1679             compilerTab.labelOptimization.visible = !newNodeRes;
1680             compilerTab.optimization.visible = !newNodeRes;
1681             compilerTab.fastMath.visible = !newNodeRes;
1682             compilerTab.labelIncludeDirs.visible = !newNodeRes;
1683             compilerTab.includeDirs.visible = !newNodeRes;
1684          }
1685          
1686          if(node == project.topNode)
1687          {
1688             compilerTab.objDir.visible = true;
1689             compilerTab.labelObjDir.visible = true;
1690
1691             compilerTab.excludeFromBuild.visible = false;
1692          }
1693          else
1694          {
1695             compilerTab.objDir.visible = false;
1696             compilerTab.labelObjDir.visible = false;
1697
1698             compilerTab.excludeFromBuild.visible = (node != project.resNode);
1699          }
1700
1701          // Load Settings Into Dialog
1702          compilerTab.LoadSettings();
1703          linkerTab.LoadSettings();
1704          builderTab.LoadSettings();
1705
1706          if(ac)
1707          {
1708             if(!ac.visible)
1709             {
1710                if(ac == compilerTab.excludeFromBuild.editor)
1711                   ac = compilerTab.objDir.editor;
1712                else if(compilerTab.excludeFromBuild.editor.visible)
1713                   ac = compilerTab.excludeFromBuild.editor;
1714             }
1715             ac.MakeActive();
1716          }
1717       }
1718    }
1719
1720    void CreateConfigButtons()
1721    {
1722       SelectorButton commonButton;
1723
1724       // Create Config Buttons
1725       commonButton = SelectorButton
1726       {
1727          configSelector, master = this, text = $"Common", id = (int64)null; font = { font.faceName, font.size, true };
1728          checked = true;
1729          NotifyClicked = ConfigClicked;
1730       };
1731       
1732       config = null;
1733
1734       if(project.topNode.configurations)
1735       {
1736          for(c : project.topNode.configurations)
1737          {
1738             EditableSelectorButton button
1739             {
1740                configSelector, master = this, renameable = true, text = c.name, id = (int64)c;
1741                NotifyClicked = ConfigClicked, OnRename = ConfigOnRename;
1742             };
1743          }
1744       }
1745    }
1746    
1747    void Init()
1748    {
1749       Platform p;
1750       SelectorButton button;
1751
1752       activeConfigName = project.config ? CopyString(project.config.name) : null;
1753       
1754       compilerTab.AddNode(project.topNode, null);
1755
1756       CreateConfigButtons();
1757
1758       platformButton = button =
1759       {
1760          platformSelector, master = this, text = $"Common", id = 0;  font = { font.faceName, font.size, true };
1761          NotifyClicked = PlatformClicked; checked = true;
1762       };
1763
1764       platform = 0;
1765
1766       for(p = (Platform)1; p < Platform::enumSize; p++)
1767       {
1768          SelectorButton button
1769          {
1770             platformSelector, master = this, text = p.OnGetString(0,0,0), id = (int64)p; 
1771             NotifyClicked = PlatformClicked;
1772          };
1773       }
1774    }
1775    SelectorButton platformButton;
1776
1777    bool OnPostCreate()
1778    {
1779       // Backup Current Settings
1780       backupNode = project.topNode.Backup();
1781
1782       buildTabControl.Activate();
1783
1784       {
1785          Iterator<Window> it { configSelector.controls };
1786          while(it.Next())
1787          {
1788             SelectorButton configButton = (SelectorButton)it.data;
1789             ProjectConfig buttonConfig = (ProjectConfig)configButton.id;
1790             if(buttonConfig == project.config)
1791             {
1792                configButton.Activate();
1793                configButton.checked = true;
1794                ConfigClicked(configButton, 0, 0, (Modifiers)null);
1795                break;
1796             }
1797          }
1798       }
1799       if(platformButton)
1800       {
1801          platformButton.MakeActive();
1802          platformButton = null;
1803       }
1804       return true;
1805    }
1806
1807    void OnDestroy()
1808    {
1809       delete backupNode;
1810
1811       lastSelectedNode = null;
1812
1813       project.config = null;
1814
1815       /* // THIS IS NOW AUTOMATED WITH A project CHECK IN ProjectNode
1816       project.configurations = project.topNode.configurations;
1817       project.platforms = project.topNode.platforms;
1818       project.options = project.topNode.options;
1819       */
1820
1821       if(project.topNode.configurations)
1822       {
1823          for(c : project.topNode.configurations)
1824          {
1825             if(!strcmpi(c.name, activeConfigName))
1826             {
1827                project.config = c;
1828                break;
1829             }
1830          }
1831       }
1832       if(!project.config)
1833       {
1834          List<ProjectConfig> configs = project.topNode.configurations;
1835          if(configs && configs.count)
1836             project.config = configs[0];
1837       }
1838
1839       ide.UpdateToolBarActiveConfigs(false);
1840    }
1841
1842    void RevertChanges()
1843    {
1844       String configName = config ? CopyString(config.name) : null;
1845
1846       // Revert to saved project options
1847       project.topNode.Revert(backupNode);
1848
1849       configSelector.DestroyChildren();
1850       CreateConfigButtons();
1851
1852       // Reselect Configuration
1853       if(configName)
1854       {
1855          Iterator<Window> it { configSelector.controls };
1856          while(it.Next())
1857          {
1858             Button button = (Button)it.data;
1859             ProjectConfig c = (ProjectConfig)button.id;
1860             if(c && !strcmp(c.name, configName))
1861             {
1862                config = c;
1863                button.Activate();
1864                button.checked = true;
1865                ConfigClicked(button, 0,0, 0);
1866                break;
1867             }
1868          }
1869       }
1870
1871       SelectNode(project.topNode, false);
1872
1873       delete configName;
1874    }
1875
1876    bool OnClose(bool parentClosing)
1877    {
1878       if(modifiedDocument)
1879       {
1880          DialogResult diagRes = MessageBox
1881          {
1882             type = yesNoCancel, master = ide,
1883             text = $"Save changes to project settings?",
1884             contents = $"Would you like to save changes made to the build options?"
1885          }.Modal();
1886          if(diagRes == no)
1887             RevertChanges();
1888          if(diagRes == cancel)
1889             return false;
1890          if(diagRes == yes)
1891          {
1892             project.MarkChanges(backupNode);
1893             project.topNode.modified = true;
1894             ide.projectView.modifiedDocument = true;
1895             ide.UpdateToolBarActiveConfigs(false);
1896             ide.projectView.Update(null);
1897          }
1898          modifiedDocument = false;
1899       }
1900       return true;
1901    }
1902 }
1903
1904 class CompilerTab : Tab
1905 {
1906    background = formColor;
1907    text = $"Compiler";
1908
1909    Window leftPane { this, size = { 180 }, anchor = { left = 0, top = 0, bottom = 0 }, background = formColor };
1910
1911    Label labelFileList { leftPane, this, position = { 8, 8 }, labeledWindow = fileList };
1912    ListBox fileList
1913    {
1914       leftPane, this, borderStyle = deep, hasVertScroll = true, hasHorzScroll = true;
1915       // THIS WOULD BE EVEN MORE FUN: multiSelect = true,
1916       fullRowSelect = false, collapseControl = true, treeBranches = true;
1917       alwaysHighLight = true;
1918       selectionColor = unfocusedSelectorColor;
1919       size = { 180 };
1920       anchor = Anchor { left = 8, top = 24, right = 4, bottom = 8 };
1921       text = $"Files";
1922
1923       bool NotifySelect(ListBox listBox, DataRow row, Modifiers mods)
1924       {
1925          BuildTab buildTab = (BuildTab)master;
1926          ProjectNode node = (ProjectNode)row.tag;
1927          buildTab.SelectNode(node, false);
1928          return true;
1929       }
1930
1931       void OnRedraw(Surface surface)
1932       {
1933          ide.projectView.drawingInProjectSettingsDialog = true;
1934          ListBox::OnRedraw(surface);
1935          ide.projectView.drawingInProjectSettingsDialog = false;
1936       }
1937
1938       bool NotifyActivate(Window window, bool active, Window previous)
1939       {
1940          if(active)
1941          {
1942             //subclass(Skin) skinClass = (subclass(Skin))eSystem_FindClass(app, app.skin);
1943             fileList.selectionColor = Color { 10, 36, 106 }; //skinClass.selectionColor; // darkBlue;
1944          }
1945          else if(fileList.currentRow)
1946          {
1947             DataRow currentRow = fileList.currentRow;
1948             //int headerSize = ((fileList.style.header) ? fileList.rowHeight : 0);
1949             int height = fileList.clientSize.h + 1;// - fileList.headerSize;
1950             fileList.selectionColor = unfocusedSelectorColor;
1951             if(currentRow && currentRow.index * fileList.rowHeight > fileList.scroll.y + height - fileList.rowHeight)
1952                fileList.SetScrollPosition(fileList.scroll.x, currentRow.index * fileList.rowHeight - height + fileList.rowHeight);
1953             else if(!currentRow || currentRow.index * fileList.rowHeight < fileList.scroll.y)
1954                fileList.SetScrollPosition(fileList.scroll.x, currentRow ? currentRow.index * fileList.rowHeight : 0);
1955
1956          }
1957
1958          return true;
1959       }
1960    };
1961
1962    Window rightPane 
1963    {
1964       this, anchor = { left = 196, top = 0, right = 0, bottom = 0 }, background = formColor, tabCycle = true;
1965    };
1966
1967    Window rightPaneHeader
1968    {
1969       rightPane, this, size = { h = 21 }, anchor = { left = 0, top = 0, right = 0 }, background = Color { 70, 96, 166 };//0x0F3F66;
1970       foreground = white; visible = false;
1971
1972       void OnRedraw(Surface surface)
1973       {
1974          if(id)
1975          {
1976             ide.projectView.drawingInProjectSettingsDialogHeader = true;
1977             ((void (*)(void *, void *, void *, int, int, int, void *, uint, uint))(void *)class(ProjectNode)._vTbl[__ecereVMethodID_class_OnDisplay])(class(ProjectNode),
1978                (void *)id, surface, 8, 2, clientSize.w, ide.projectView, Alignment::left, DataDisplayFlags { selected = true });
1979             ide.projectView.drawingInProjectSettingsDialogHeader = false;
1980          }
1981       }
1982    };
1983
1984    PaneSplitter splitter
1985    {
1986       this, leftPane = leftPane, rightPane = rightPane, split = 188
1987    };
1988
1989    Label labelObjDir { rightPane, this, position = { 8, 8 }, labeledWindow = objDir };
1990    PathOptionBox objDir
1991    {
1992       rightPane, this, size = { 250, 22 }, anchor = { left = 8, top = 24, right = 8 };
1993       text = $"Intermediate Objects Directory", hotKey = altJ, option = OPTION(objectsDirectory);
1994    };
1995
1996    BoolOptionBox excludeFromBuild
1997    {
1998       rightPane, this, position = { 8, 28 },
1999       text = $"Exclude from Build", visible = false, option = OPTION(excludeFromBuild);
2000    };
2001
2002    Label labelPreprocessorDefs { rightPane, this, position = { 8, 50 }, labeledWindow = preprocessorDefs };
2003    StringArrayOptionBox preprocessorDefs
2004    {
2005       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2006       text = $"Preprocessor Definitions", hotKey = altD, option = OPTION(preprocessorDefinitions);
2007    };
2008
2009    Label labelDefaultNameSpace { rightPane, this, position = { 8, 92 }, labeledWindow = defaultNameSpace };
2010    StringOptionBox defaultNameSpace
2011    {
2012       rightPane, this, size = { 160, 22 }, position = { 8, 108 };
2013       text = $"Default Name Space", option = OPTION(defaultNameSpace);
2014    };
2015    BoolOptionBox strictNameSpaces
2016    {
2017       rightPane, this, position = { 172, 112 },
2018       text = $"Strict Name Spaces", option = OPTION(strictNameSpaces);
2019    };
2020
2021    BoolOptionBox fastMath
2022    {
2023       rightPane, this, position = { 316, 112 },
2024       text = $"Fast Math", option = OPTION(fastMath);
2025    };
2026
2027    BoolOptionBox memoryGuard
2028    {
2029       rightPane, this, position = { 8, 154 };
2030       text = $"MemoryGuard", hotKey = altM, option = OPTION(memoryGuard);
2031    };
2032
2033    Label labelWarnings { rightPane, position = { 116, 138 }, labeledWindow = warnings };
2034    WarningsDB warnings
2035    {
2036       rightPane, this, position = { 116, 154 };
2037       text = $"Warnings", hotKey = altW, option = OPTION(warnings);
2038    };
2039
2040    Label labelOptimization { rightPane, position = { 220, 138 }, labeledWindow = optimization };
2041    OptimizationDB optimization
2042    {
2043       rightPane, this, position = { 220, 154 }, size = { 120, 22 };
2044       text = $"Optimization", hotKey = altO, option = OPTION(optimization);
2045    };
2046
2047    BoolOptionBox debug
2048    {
2049       rightPane, this, position = { 8, 188 };
2050       text = $"Debuggable", hotKey = altG, option = OPTION(debug);
2051    };
2052
2053    BoolOptionBox profiling
2054    {
2055       rightPane, this, position = { 116, 188 };
2056       text = $"Profiling Data", hotKey = altP, option = OPTION(profile);
2057    };
2058
2059    BoolOptionBox noLineNumbers
2060    {
2061       rightPane, this, position = { 220, 188 };
2062       text = $"No Line Numbers", hotKey = altN, option = OPTION(noLineNumbers);
2063    };
2064
2065    Label labelCompilerOptions { rightPane, this, position = { 8, 208 }, labeledWindow = compilerOptions };
2066    StringArrayOptionBox compilerOptions
2067    {
2068       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 224, right = 8 };
2069       text = $"Compiler Options", hotKey = altO, option = OPTION(compilerOptions);
2070       configReplaces = true;
2071    };
2072
2073    Label labelIncludeDirs { includeDirs.editor, labeledWindow = includeDirs, position = { 0, 6 }; };
2074    DirsArrayOptionBox includeDirs
2075    {
2076       rightPane, this, size = { 290, 22 }, anchor = { left = 8, top = 250, right = 8, bottom = 8 };
2077       text = $"Additional Include Directories", hotKey = altI, option = OPTION(includeDirs), switchToKeep = "I";
2078    };
2079
2080    CompilerTab()
2081    {
2082       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false,
2083          userData = null /* Now set in the ProjectNode directly to know we're in ProjectSettings Dialog -- ide.projectView*/ });
2084    }
2085
2086    bool OnCreate()
2087    {
2088       BuildTab buildTab = (BuildTab)master;
2089       buildTab.SelectNode(buildTab.lastSelectedNode, true);
2090       return true;
2091    }
2092
2093    void AddNode(ProjectNode node, DataRow addTo)
2094    {
2095       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
2096
2097       row.tag = (int64)node;
2098
2099       row.SetData(null, node);
2100
2101       if(node.files && node.files.first && node.parent && 
2102             !(!node.parent.parent && 
2103                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") || 
2104                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
2105          row.collapsed = true;
2106       else if(node.type == folder)
2107          node.icon = openFolder;
2108
2109       if(node.files)
2110       {
2111          for(child : node.files)
2112             AddNode(child, row);
2113       }
2114    }
2115
2116    void LoadSettings()
2117    {
2118       OptionBox ob;
2119       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2120          if(eClass_IsDerived(ob._class, class(OptionBox)))
2121             ob.Load();
2122
2123       if(activeChild && activeChild.active)
2124       {
2125          Window control = activeChild;
2126          control.Deactivate();         
2127          control.Activate();
2128       }
2129    }
2130
2131    bool OnPostCreate()
2132    {
2133       objDir.editor.Activate();
2134       return true;
2135    }
2136 }
2137
2138 class LinkerTab : Tab
2139 {
2140    background = formColor;
2141    text = $"Linker";
2142
2143    Label labelTargetName { this, position = { 8, 8 }, labeledWindow = targetName };
2144    StringOptionBox targetName
2145    {
2146       this, position = { 8, 24 }, size = { 200, 22 };
2147       text = $"Target Name", hotKey = altN, option = OPTION(targetFileName);
2148    };
2149    
2150    Label labelTargetType { this, position = { 216, 8 }, labeledWindow = targetType };
2151    TargetTypeDB targetType
2152    {
2153       this, position = { 216, 24 }, size = { 120, 22 };
2154       text = $"Target Type", hotKey = altT, option = OPTION(targetType);
2155    };
2156    
2157    Label labelTargetDirectory { this, position = { 344, 8 }, labeledWindow = targetDirectory };
2158    PathOptionBox targetDirectory
2159    {
2160       this, size = { 270, 22 }, anchor = { left = 344, top = 24, right = 8 };
2161       hotKey = altR, text = $"Target Directory", option = OPTION(targetDirectory);
2162    };
2163
2164    Label labelLibraries { this, position = { 8, 50 }, labeledWindow = libraries };
2165    StringArrayOptionBox libraries
2166    {
2167       this, size = { 290, 22 }, anchor = { left = 8, top = 66, right = 8 };
2168       text = $"Additional Libraries", hotKey = altL, option = OPTION(libraries), switchToKeep = "l";
2169       configReplaces = true;
2170    };
2171
2172    Label labelLinkerOptions { this, position = { 8, 92 }, labeledWindow = linkerOptions };
2173    StringArrayOptionBox linkerOptions
2174    {
2175       this, size = { 290, 22 }, anchor = { left = 8, top = 108, right = 8 };
2176       text = $"Linker Options", hotKey = altO, option = OPTION(linkerOptions);
2177       configReplaces = true;
2178    };
2179
2180    BoolOptionBox console
2181    {
2182       this, position = { 8, 138 };
2183       text = $"Console Application", hotKey = altC, option = OPTION(console);
2184    };
2185
2186    BoolOptionBox compress
2187    {
2188       this, position = { 8, 162 };
2189       text = $"Compress", hotKey = altW, option = OPTION(compress);
2190    };
2191
2192    Label labelLibraryDirs { libraryDirs.editor, labeledWindow = libraryDirs, position = { 0, 6 }; };
2193    DirsArrayOptionBox libraryDirs
2194    {
2195       this, size = { 290, 22 }, anchor = { left = 8, top = 182, right = 8, bottom = 8 };
2196       text = $"Additional Library Directories", hotKey = altY, option = OPTION(libraryDirs), switchToKeep = "L";
2197    };
2198
2199    bool OnCreate()
2200    {
2201       ((BuildTab)master).SelectNode(project.topNode, true);
2202       return true;
2203    }
2204
2205    void LoadSettings()
2206    {
2207       OptionBox ob;
2208       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2209          if(eClass_IsDerived(ob._class, class(OptionBox)))
2210             ob.Load();
2211       compress.disabled = (config && config.options && config.options.debug == true) || project.topNode.options.debug == true;
2212
2213       if(activeChild && activeChild.active)
2214       {
2215          Window control = activeChild;
2216          control.Deactivate();         
2217          control.Activate();
2218       }
2219    }
2220 }
2221
2222 class BuilderTab : Tab
2223 {
2224    background = formColor;
2225    text = $"Builder";
2226
2227    Label labelPrebuildCommands { prebuildCommands.editor, labeledWindow = prebuildCommands, position = { 0, 6 }; };
2228    StringsArrayOptionBox prebuildCommands
2229    {
2230       this, size = { 290, 100 }, anchor = { left = 8, top = 8, right = 8, bottom = 0.5 };
2231       text = $"Pre-build Commands", hotKey = altE, option = OPTION(prebuildCommands);
2232    };
2233
2234    Label labelPostbuildCommands { postbuildCommands.editor, labeledWindow = postbuildCommands, position = { 0, 6 }; };
2235    StringsArrayOptionBox postbuildCommands
2236    {
2237       this, size = { 290 }, anchor = { left = 8, top = 0.5, right = 8, bottom = 8 };
2238       text = $"Post-build Commands", hotKey = altT, option = OPTION(postbuildCommands);
2239    };
2240
2241    void LoadSettings()
2242    {
2243       bool disabled = strlen(((BuildTab)master).selectedPlatformName) > 0;
2244       OptionBox ob;
2245       for(ob = (OptionBox)firstSlave; ob; ob = (OptionBox)ob.nextSlave)
2246          if(eClass_IsDerived(ob._class, class(OptionBox)))
2247             ob.Load();
2248
2249       if(activeChild && activeChild.active)
2250       {
2251          Window control = activeChild;
2252          control.Deactivate();         
2253          control.Activate();
2254       }
2255    }
2256 }