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