c7cec7f3698874f3acae638acdafa1f4a4f532a5
[sdk] / ide / src / dialogs / GlobalSettingsDialog.ec
1 import "IDESettings"
2
3 // import "SelectorBar"
4 import "CompilersDetectionDialog"
5 import "ide"
6
7 FileDialog settingsFileDialog { type = selectDir, text = $"Select directory" };
8
9 FileDialog toolchainFileDialog { type = open, text = $"Open"; mayNotExist = true; };
10
11 class GlobalSettingsDialog : Window
12 {
13    autoCreate = false;
14    tabCycle = true;
15    background = formColor;
16    hasClose = true;
17    borderStyle = sizable;
18    text = $"Global Settings";
19    minClientSize = { 560, 506 };
20    nativeDecorations = true;
21
22    IDESettings ideSettings;
23    IDESettingsContainer settingsContainer;
24    String workspaceActiveCompiler;
25
26    TabControl tabControl { this, background = formColor, anchor = { left = 8, top = 8, right = 8, bottom = 40 } };
27
28    EditorTab editorTab { this, tabControl = tabControl };
29    CompilersTab compilersTab { this, tabControl = tabControl };
30    ProjectOptionsTab projectOptionsTab { this, tabControl = tabControl };
31    WorkspaceOptionsTab workspaceOptionsTab { this, tabControl = tabControl };
32
33    property bool settingsModified
34    {
35       get
36       {
37          return editorTab.modifiedDocument || compilersTab.modifiedDocument ||
38                projectOptionsTab.modifiedDocument || workspaceOptionsTab.modifiedDocument;
39       }
40    }
41
42    bool OnClose(bool parentClosing)
43    {
44       if(!settingsModified || MessageBox {
45          type = okCancel, master = ide,
46          text = $"Lose Changes?",
47          contents = $"Are you sure you wish to discard changes?"
48           }.Modal() == ok)
49          return true;
50       return false;
51    }
52
53    Button cancel
54    {
55       parent = this, hotKey = escape, text = $"Cancel", id = DialogResult::cancel;
56       position = { 290, 290 }, size = { 80 };
57       anchor = { right = 8, bottom = 8 };
58       NotifyClicked = ButtonCloseDialog;
59    };
60
61    Button ok
62    {
63       parent = this, isDefault = true, text = $"OK";
64       position = { 200, 290 }, size = { 90 };
65       anchor = { right = 96, bottom = 8 };
66
67       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
68       {
69          if(settingsModified)
70          {
71             bool editorSettingsChanged = false;
72             bool compilerSettingsChanged = false;
73             bool projectOptionsChanged = false;
74             bool workspaceOptionsChanged = false;
75
76             if(editorTab.modifiedDocument)
77             {
78                if(editorTab.useFreeCaret.checked != ideSettings.useFreeCaret ||
79                      editorTab.showLineNumbers.checked != ideSettings.showLineNumbers ||
80                      editorTab.caretFollowsScrolling.checked != ideSettings.caretFollowsScrolling)
81                {
82                   ideSettings.useFreeCaret = editorTab.useFreeCaret.checked;
83                   ideSettings.showLineNumbers = editorTab.showLineNumbers.checked;
84                   ideSettings.caretFollowsScrolling = editorTab.caretFollowsScrolling.checked;
85                   editorSettingsChanged = true;
86                }
87             }
88
89             if(compilersTab.modifiedDocument)
90             {
91                Workspace workspace = ide.workspace;
92                if(strcmp(compilersTab.compilerConfigsDir.path, ideSettings.compilerConfigsDir))
93                   ideSettings.compilerConfigsDir = compilersTab.compilerConfigsDir.path;
94                ideSettings.compilerConfigs.Free();
95                for(compiler : compilersTab.compilerConfigs)
96                {
97                   ideSettings.compilerConfigs.Add(compiler.Copy());
98                }
99
100                compilerSettingsChanged = true;
101             }
102
103             if(projectOptionsTab.modifiedDocument)
104             {
105                if(strcmp(projectOptionsTab.defaultTargetDir.path, ideSettings.projectDefaultTargetDir)
106                      || strcmp(projectOptionsTab.defaultIntermediateObjDir.path, ideSettings.projectDefaultIntermediateObjDir))
107                {
108                   ideSettings.projectDefaultTargetDir = projectOptionsTab.defaultTargetDir.path;
109                   ideSettings.projectDefaultIntermediateObjDir = projectOptionsTab.defaultIntermediateObjDir.path;
110                   projectOptionsChanged = true;
111                }
112             }
113
114             if(workspaceOptionsTab.modifiedDocument)
115             {
116                DataRow row = workspaceOptionsTab.defaultCompilerDropBox.currentRow;
117                if(row && row.string)
118                {
119                   if(!ideSettings.defaultCompiler || strcmp(row.string, ideSettings.defaultCompiler))
120                   {
121                      ideSettings.defaultCompiler = row.string;
122                      workspaceOptionsChanged = true;
123                   }
124                }
125             }
126
127             settingsContainer.Save();
128
129             if(compilerSettingsChanged)
130                OnGlobalSettingChange(GlobalSettingsChange::compilerSettings);
131             if(editorSettingsChanged)
132                OnGlobalSettingChange(GlobalSettingsChange::editorSettings);
133             if(projectOptionsChanged)
134                OnGlobalSettingChange(GlobalSettingsChange::projectOptions);
135
136             editorTab.modifiedDocument = false;
137             compilersTab.modifiedDocument = false;
138             projectOptionsTab.modifiedDocument = false;
139             workspaceOptionsTab.modifiedDocument = false;
140          }
141
142          Destroy(DialogResult::ok);
143          return true;
144       }
145    };
146
147    /*
148    void Temp()
149    {
150       DirTypes c;
151       for(c = 0; c < DirTypes::enumSize; c++)
152       {
153          CompilerDir compilerDir;
154
155          for(systemDir : ideSettings.systemDirs[c])
156          {
157             compilerDir = CompilerDir { type = c; compilerConfig = null; path = CopyString(systemDir) };
158             dirs.Add(compilerDir);
159          }
160
161          row = compilersTab.dirs[c].AddRow();
162          row.SetData(null, "");
163          compilersTab.dirs[c].currentRow = compilersTab.dirs[c].firstRow;
164          compilersTab.dirs[c].modifiedDocument = false;
165       }
166    }
167    */
168
169    bool OnCreate()
170    {
171       CompilerConfig activateCompiler = null;
172       CompilerConfig readonlyCompiler = null;
173
174       // EditorTab
175       editorTab.useFreeCaret.checked = ideSettings.useFreeCaret;
176       editorTab.showLineNumbers.checked = ideSettings.showLineNumbers;
177       editorTab.caretFollowsScrolling.checked = ideSettings.caretFollowsScrolling;
178
179       // CompilersTab
180       if(workspaceActiveCompiler)
181       {
182          for(compiler : ideSettings.compilerConfigs)
183          {
184             if(!activateCompiler && !strcmp(workspaceActiveCompiler, compiler.name))
185                activateCompiler = compiler;
186             if(!readonlyCompiler && compiler.readOnly)
187                readonlyCompiler = compiler;
188             if(activateCompiler && readonlyCompiler)
189                break;
190          }
191       }
192       if(!activateCompiler && readonlyCompiler)
193          activateCompiler = readonlyCompiler;
194       if(!activateCompiler && ideSettings.compilerConfigs.count)
195          activateCompiler = ideSettings.compilerConfigs[0];
196
197       for(compiler : ideSettings.compilerConfigs)
198          compilersTab.AddCompiler(compiler.Copy(), compiler == activateCompiler);
199       compilersTab.compilerConfigsDir.path = ideSettings.compilerConfigsDir;
200
201       // ProjectOptionsTab
202       projectOptionsTab.defaultTargetDir.path = ideSettings.projectDefaultTargetDir;
203       projectOptionsTab.defaultIntermediateObjDir.path = ideSettings.projectDefaultIntermediateObjDir;
204
205       return true;
206    }
207
208    void OnDestroy()
209    {
210       editorTab.modifiedDocument = false;
211       compilersTab.modifiedDocument = false;
212       compilersTab.dirsTab.modifiedDocument = false;
213       compilersTab.toolchainTab.modifiedDocument = false;
214       compilersTab.optionsTab.modifiedDocument = false;
215       compilersTab.activeCompiler = null;
216       compilersTab.compilerConfigs.Free();
217       compilersTab.compilerSelector.Clear();
218       projectOptionsTab.modifiedDocument = false;
219       workspaceOptionsTab.modifiedDocument = false;
220    }
221
222    virtual void OnGlobalSettingChange(GlobalSettingsChange globalSettingsChange);
223 }
224
225 class EditorTab : GlobalSettingsSubTab
226 {
227    background = formColor;
228    text = $"Editor";
229
230    Button useFreeCaret
231    {
232       this, text = $"Move code editor caret freely past end of line", position = { 16, 68 }, isCheckbox = true;
233       NotifyClicked = NotifyClickedModifiedDocument;
234    };
235
236    Button caretFollowsScrolling
237    {
238       this, text = $"Keep caret visible (move along) when scrolling", position = { 16, 88 }, isCheckbox = true;
239       NotifyClicked = NotifyClickedModifiedDocument;
240    };
241
242    Button showLineNumbers
243    {
244       this, text = $"Show line numbers in code editor", position = { 16, 108 }, isCheckbox = true;
245       NotifyClicked = NotifyClickedModifiedDocument;
246    };
247
248    bool NotifyClickedModifiedDocument(Button button, int x, int y, Modifiers mods)
249    {
250       modifiedDocument = true;
251       return true;
252    }
253 }
254
255 static void DrawStipple(Surface surface, Size clientSize)
256 {
257    int x1 = 0;
258    int y1 = 0;
259    int x2 = clientSize.w - 1;
260    int y2 = clientSize.h - 1;
261    if((x2 - x1) & 1) x2--;
262    if((y2 - y1) & 1) y2--;
263
264    surface.LineStipple(0x5555);
265    surface.Rectangle(x1, y1, x2, y2);
266    surface.LineStipple(0);
267 }
268
269 class CompilersTab : GlobalSettingsSubTab
270 {
271    background = formColor;
272    text = $"Compilers";
273
274    Label compilerConfigsDirLabel { this, position = { 8, 12 }, labeledWindow = compilerConfigsDir, tabCycle = false, inactive = true };
275    PathBox compilerConfigsDir
276    {
277       this, anchor = { left = 230, top = 8, right = 8 };
278       text = $"Compiler Configurations Directory", browseDialog = settingsFileDialog, NotifyModified = NotifyModifiedDocument;
279    };
280
281    SelectorBar compilerSelector
282    {
283       this, text = $"Compiler Configurations:", anchor = { left = 148, top = 38, right = 99 }; size = { 0, 26 };
284       opacity = 0;
285       direction = horizontal, scrollable = true;
286
287       bool OnKeyDown(Key key, unichar ch)
288       {
289          if(key == insert)
290          {
291             ((CompilersTab)parent).createCompiler.NotifyClicked(parent, ((CompilersTab)parent).createCompiler, 0, 0, 0);
292             return false;
293          }
294          else if(key == del)
295          {
296             ((CompilersTab)parent).deleteCompiler.NotifyClicked(parent, ((CompilersTab)parent).deleteCompiler, 0, 0, 0);
297             return false;
298          }
299          return SelectorBar::OnKeyDown(key, ch);
300       }
301
302       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
303       {
304          ((CompilersTab)master).labelCompilers.Update(null);
305          return true;
306       }
307
308       bool OnPostCreate()
309       {
310          CompilersTab compilers = (CompilersTab)parent;
311          SelectorBar::OnPostCreate();
312          if(compilers.selectedButton)
313          {
314             Button sb = compilers.selectedButton;
315             sb.Activate();
316             sb.checked = true;
317             // Why was this being set to null? On going back to compilers the 2nd time, the selectedButton was lost and so was not made visible...
318             // compilers.selectedButton = null;
319          }
320          return true;
321       }
322    };
323
324    TabControl tabControl { this, background = formColor, anchor = { left = 8, top = 68, right = 8, bottom = 8 } };
325
326    CompilerDirectoriesTab dirsTab { this, tabControl = tabControl };
327    CompilerToolchainTab toolchainTab { this, tabControl = tabControl };
328    CompilerEnvironmentTab environmentTab { this, tabControl = tabControl };
329    CompilerOptionsTab optionsTab { this, tabControl = tabControl };
330
331    List<CompilerConfig> compilerConfigs { };
332    CompilerConfig activeCompiler;
333
334    Label labelCompilers
335    {
336       this, anchor = { left = 8, top = 44 }, labeledWindow = compilerSelector;
337
338       void OnRedraw(Surface surface)
339       {
340          Label::OnRedraw(surface);
341          if(labeledWindow.active)
342             DrawStipple(surface, clientSize);
343       }
344    };
345
346    void FindUniqueCompilerName(const char * baseName, CompilerConfig compiler/*, bool startWithNumber*/, char * output)
347    {
348       int num = 0;
349       char tmp[MAX_F_STRING];
350       /*if(startWithNumber)
351          sprintf(tmp, "%s%d", baseName, num);
352       else*/
353          strcpy(tmp, baseName);
354       while(true)
355       {
356          CompilerConfig matchingCompiler = null;
357          for(c : compilerConfigs)
358          {     // TOFIX: Error when omitting these brackets, c not found
359             if((!compiler || c != compiler) && c.name && !strcmp(c.name, tmp))
360             {
361                matchingCompiler = c;
362                break;
363             }
364          }
365          if(matchingCompiler)
366          {
367             num++;
368             sprintf(tmp, "%s%d", baseName, num);
369          }
370          else
371             break;
372       }
373       strcpy(output, tmp);
374    }
375
376    Button createCompiler
377    {
378       parent = this, bevelOver = true, inactive = true;
379       size = { 22, 22 };
380       anchor = { top = 40, right = 77 };
381       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/docNew.png", alphaBlend = true };
382
383       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
384       {
385          char compilerName[MAX_F_STRING];
386          CompilerConfig newCompiler;
387          FindUniqueCompilerName("New Compiler", null, compilerName);
388          newCompiler = MakeDefaultCompiler(compilerName, false);
389          AddCompiler(newCompiler, true);
390          modifiedDocument = true;
391          return true;
392       }
393    };
394    Button detectCompiler
395    {
396       parent = this, bevelOver = true, inactive = true;
397       size = { 22, 22 };
398       anchor = { top = 40, right = 54 };
399       hotKey = altC, bitmap = BitmapResource { fileName = ":actions/attach.png", alphaBlend = true };
400
401       bool NotifyClicked(Button b, int x, int y, Modifiers mods)
402       {
403          CompilersDetectionDialog compilersDetectionDialog
404          {
405             dialog.parent;
406
407          };
408          if(compilersDetectionDialog.Modal())
409          {
410             if(compilersDetectionDialog.selectedCompilerType)
411             {
412                char uniqueName[MAX_F_STRING];
413                CompilerConfig newCompiler = compilersDetectionDialog.compilerConfig;
414                FindUniqueCompilerName(newCompiler.name, null, uniqueName);
415                newCompiler.name = uniqueName;
416                AddCompiler(newCompiler, true);
417                modifiedDocument = true;
418             }
419          }
420          return true;
421       }
422    };
423    Button duplicateCompiler
424    {
425       parent = this, bevelOver = true, inactive = true;
426       size = { 22, 22 };
427       anchor = { top = 40, right = 31 };
428       hotKey = altU, bitmap = BitmapResource { fileName = ":actions/editCopy.png", alphaBlend = true };
429
430       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
431       {
432          char copyName[MAX_F_STRING];
433          CompilerConfig copyCompiler = activeCompiler.Copy();
434          FindUniqueCompilerName(copyCompiler.name, null, copyName);
435          copyCompiler.readOnly = false;
436          copyCompiler.name = copyName;
437          AddCompiler(copyCompiler, true);
438          modifiedDocument = true;
439          return true;
440       }
441    };
442    Button deleteCompiler
443    {
444       parent = this, bevelOver = true, inactive = true;
445       size = { 22, 22 };
446       anchor = { top = 40, right = 8 };
447       hotKey = altD, bitmap = BitmapResource { fileName = ":actions/delete2.png", alphaBlend = true };
448
449       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
450       {
451          if(activeCompiler)
452          {
453             CompilerConfig compilerToDelete = activeCompiler;
454             String title = PrintString($"Delete ", compilerToDelete.name, $" Compiler Configuration");
455             String msg = PrintString($"Are you sure you wish to delete the ", compilerToDelete.name, $" compiler configuration?");
456             if(MessageBox { type = okCancel, text = title, contents = msg }.Modal() == ok)
457             {
458                SelectorButton button = compilerSelector.FindButtonByID((int64)compilerToDelete);
459                if(button)
460                   compilerSelector.RemoveButton(button);
461                //DeleteCompiler(compilerToDelete);
462                {
463                   Iterator<CompilerConfig> it { compilerConfigs };
464                   if(it.Find(compilerToDelete))
465                      compilerConfigs.Delete(it.pointer);
466                }
467                modifiedDocument = true;
468             }
469             delete title;
470             delete msg;
471          }
472          return true;
473       }
474    };
475
476    void AddCompiler(CompilerConfig compiler, bool load)
477    {
478       SelectorButton selectButton;
479       if(compiler.readOnly)
480       {
481          SelectorButton button
482          {
483             compilerSelector, master = this, text = compiler.name, id = (int64)compiler;
484             NotifyClicked = CompilerClicked;
485          };
486          selectButton = button;
487       }
488       else
489       {
490          EditableSelectorButton button
491          {
492             compilerSelector, master = this, renameable = true, text = compiler.name, id = (int64)compiler;
493             NotifyClicked = CompilerClicked;
494
495             bool OnRename(EditableSelectorButton button, char ** oldName, char ** newName)
496             {
497                if(*newName && (*newName)[0])
498                {
499                   char compilerName[MAX_F_STRING];
500                   FindUniqueCompilerName(*newName, activeCompiler, compilerName);
501                   if(strcmp(*newName, compilerName))
502                   {
503                      delete *newName;
504                      *newName = CopyString(compilerName);
505                   }
506                   activeCompiler.name = compilerName;
507                   modifiedDocument = true;
508                   return true;
509                }
510                return false;
511             }
512          };
513          selectButton = (SelectorButton)button;
514       }
515       compilerConfigs.Add(compiler);
516       if(load)
517       {
518          LoadCompiler(compiler);
519          selectedButton = selectButton;
520          compilerSelector.Select(selectedButton);
521       }
522    }
523    SelectorButton selectedButton;
524
525    void LoadCompiler(CompilerConfig compiler)
526    {
527       bool modified = modifiedDocument;
528       activeCompiler = compiler;
529
530       dirsTab.Load();
531       toolchainTab.Load();
532       environmentTab.Load();
533       optionsTab.Load();
534
535       // Restore original modifiedDocument
536       modifiedDocument = modified;
537
538       deleteCompiler.disabled = compiler.readOnly;
539    }
540
541    bool CompilerClicked(Button clickedButton, int x, int y, Modifiers mods)
542    {
543       if(!eClass_IsDerived(clickedButton._class, class(EditableSelectorButton)) || !((EditableSelectorButton)clickedButton).editBox)
544       {
545          LoadCompiler((CompilerConfig)clickedButton.id);
546          selectedButton = (SelectorButton)clickedButton;
547       }
548       return true;
549    }
550
551    bool NotifyModifiedDocument(PathBox pathBox)
552    {
553       BasicValidatePathBoxPath(pathBox);
554       modifiedDocument = true;
555       return true;
556    }
557 }
558
559 Array<const String> displayDirectoryNames
560 { [
561    $"Include Files",
562    $"Library Files",
563    $"Executable Files"
564 ] };
565
566 class CompilerDirectoriesTab : CompilersSubTab
567 {
568    background = formColor;
569    text = $"Directories";
570
571    Button dirTypeTglBtn[DirTypes];
572    DirectoriesBox dirs[DirTypes], currentDirs;
573
574    ~CompilerDirectoriesTab()
575    {
576       DirTypes c;
577       for(c = 0; c < DirTypes::enumSize; c++)
578       {
579          delete dirs[c];
580          delete dirTypeTglBtn[c];
581       }
582    }
583    CompilerDirectoriesTab()
584    {
585       DirTypes c;
586       int v = 8;
587       for(c = 0; c < DirTypes::enumSize; c++)
588       {
589          dirs[c] = DirectoriesBox
590          {
591             this;//, alwaysHighLight = true
592             anchor = { left = 8, top = 8, right = 8, bottom = 8 };
593             id = c;
594
595    /*   MAKE SURE THINGS ARE DONE PROPERLY IN THE NEW DIRECTORIES BOX WHEN BROWSING FOR A DIR?
596             settingsFileDialog.filePath = directory;
597          if(settingsFileDialog.Modal())
598             row.SetData(null, (s = CopyUnixPath(settingsFileDialog.filePath)));
599    */
600
601             bool NotifyModified(DirectoriesBox dirsBox)
602             {
603                CompilerConfig compiler = loadedCompiler;
604                if(compiler)
605                {
606                   DirTypes dirType = (DirTypes)dirsBox.id;
607                   if(dirType == includes)
608                      compiler.includeDirs = dirsBox.strings;
609                   else if(dirType == libraries)
610                      compiler.libraryDirs = dirsBox.strings;
611                   else if(dirType == executables)
612                      compiler.executableDirs = dirsBox.strings;
613
614                   compilersTab.modifiedDocument = true;
615                }
616                return true;
617             }
618             bool NotifyPathBoxModified(DirectoriesBox dirsBox, PathBox pathBox)
619             {
620                BasicValidatePathBoxPath(pathBox);
621                return true;
622             }
623          };
624          incref dirs[c];
625
626          if(c)
627             dirs[c].visible = false;
628
629          // (width) Should be 324 for text...
630          //field[c] = { dataType = class(char *), editable = true };
631          //dirs[c].AddField(field[c]);
632
633          dirTypeTglBtn[c] = Button
634          {
635             this, inactive = true, text = displayDirectoryNames[c], bevelOver = true, isRadio = true, bitmap = null;
636             stayOnTop = true;
637             id = c;
638             minClientSize = { 99, 20 };
639             anchor = { left = v, top = 8 }; // ((int)c) * 100 + 8
640
641             bool NotifyClicked(Button button, int x, int y, Modifiers mods)
642             {
643                DirTypes dirType = (DirTypes)button.id;
644                currentDirs.visible = false;
645                dirs[dirType].visible = true;
646                currentDirs = dirs[dirType];
647                return true;
648             }
649          };
650          v += dirTypeTglBtn[c].size.w + 1;
651          incref dirTypeTglBtn[c];
652
653          if(c == includes)
654             dirTypeTglBtn[c].hotKey = altI;
655          else if(c == libraries)
656             dirTypeTglBtn[c].hotKey = altL;
657          else if(c == executables)
658             dirTypeTglBtn[c].hotKey = altE;
659       }
660       currentDirs = dirs[includes];
661       dirTypeTglBtn[includes].checked = true;
662       return true;
663    }
664
665    bool OnLoadGraphics()
666    {
667       DirTypes c;
668       int v = 8;
669       for(c = 0; c < DirTypes::enumSize; c++)
670       {
671          dirTypeTglBtn[c].anchor.left = v;
672          v += dirTypeTglBtn[c].size.w + 1;
673       }
674       return CompilersSubTab::OnLoadGraphics();
675    }
676
677    void Load()
678    {
679       CompilerConfig compiler = loadedCompiler;
680       if(compiler)
681       {
682          dirs[includes].strings = compiler.includeDirs;
683          dirs[libraries].strings = compiler.libraryDirs;
684          dirs[executables].strings = compiler.executableDirs;
685          dirs[includes].list.scroll = { 0, 0 };
686          dirs[libraries].list.scroll = { 0, 0 };
687          dirs[executables].list.scroll = { 0, 0 };
688       }
689    }
690 }
691
692 class CompilerToolchainTab : CompilersSubTab
693 {
694    background = formColor;
695    text = $"Toolchain";
696
697    int margin;
698    margin = 130;
699
700    Label ecpLabel { this, position = { 8, 12 }, labeledWindow = ecp, tabCycle = false, inactive = true };
701    PathBox ecp
702    {
703       this, anchor = { left = margin, top = 8, right = 8 };
704       text = $"eC Precompiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
705    };
706    Label eccLabel { this, position = { 8, 38 }, labeledWindow = ecc, tabCycle = false, inactive = true };
707    PathBox ecc
708    {
709       this, anchor = { left = margin, top = 34, right = 8 };
710       text = $"eC Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
711    };
712    Label ecsLabel { this, position = { 8, 64 }, labeledWindow = ecs, tabCycle = false, inactive = true };
713    PathBox ecs
714    {
715       this, anchor = { left = margin, top = 60, right = 8 };
716       text = $"eC Symbol Loader", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
717    };
718    Label earLabel { this, position = { 8, 90 }, labeledWindow = ear, tabCycle = false, inactive = true };
719    PathBox ear
720    {
721       this, anchor = { left = margin, top = 86, right = 8 };
722       text = $"Ecere Archiver", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
723    };
724    Label cppLabel { this, position = { 8, 116 }, labeledWindow = cpp, tabCycle = false, inactive = true };
725    PathBox cpp
726    {
727       this, anchor = { left = margin, top = 112, right = 8 };
728       text = $"C Preprocessor", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
729    };
730    Label ccLabel { this, position = { 8, 142 }, labeledWindow = cc, tabCycle = false, inactive = true };
731    PathBox cc
732    {
733       this, anchor = { left = margin, top = 138, right = 8 };
734       text = $"C Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
735    };
736    Label cxxLabel { this, position = { 8, 168 }, labeledWindow = cxx, tabCycle = false, inactive = true };
737    PathBox cxx
738    {
739       this, anchor = { left = margin, top = 164, right = 8 };
740       text = $"C++ Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
741    };
742    Label makeLabel { this, position = { 8, 194 }, labeledWindow = make, tabCycle = false, inactive = true };
743    PathBox make
744    {
745       this, anchor = { left = margin, top = 190, right = 8 };
746       text = $"GNU Make", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
747    };
748    Label gnuToolchainPrefixLabel { this, position = { 8, 220 }, labeledWindow = gnuToolchainPrefix, tabCycle = false, inactive = true };
749    PathBox gnuToolchainPrefix
750    {
751       this, anchor = { left = margin, top = 216, right = 8 };
752       text = $"GNU Toolchain Prefix", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
753    };
754    Label sysrootLabel { this, position = { 8, 246 }, labeledWindow = sysroot, tabCycle = false, inactive = true };
755    PathBox sysroot
756    {
757       this, anchor = { left = margin, top = 242, right = 8 };
758       text = $"SYSROOT", typeExpected = directory, browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
759    };
760    Label executableLauncherLabel { this, position = { 8, 272 }, labeledWindow = executableLauncher, tabCycle = false, inactive = true };
761    PathBox executableLauncher
762    {
763       this, anchor = { left = margin, top = 268, right = 8 };
764       text = $"Executable Launcher", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
765    };
766
767    bool NotifyModifiedDocument(PathBox pathBox)
768    {
769       CompilerConfig compiler = loadedCompiler;
770       if(compiler)
771       {
772          BasicValidatePathBoxPath(pathBox);
773          if(pathBox == ecp)
774             compiler.ecpCommand = pathBox.slashPath;
775          else if(pathBox == ecc)
776             compiler.eccCommand = pathBox.slashPath;
777          else if(pathBox == ecs)
778             compiler.ecsCommand = pathBox.slashPath;
779          else if(pathBox == ear)
780             compiler.earCommand = pathBox.slashPath;
781          else if(pathBox == cpp)
782             compiler.cppCommand = pathBox.slashPath;
783          else if(pathBox == cc)
784             compiler.ccCommand = pathBox.slashPath;
785          else if(pathBox == cxx)
786             compiler.cxxCommand = pathBox.slashPath;
787          else if(pathBox == make)
788             compiler.makeCommand = pathBox.slashPath;
789          else if(pathBox == executableLauncher)
790             compiler.execPrefixCommand = pathBox.slashPath;
791          else if(pathBox == gnuToolchainPrefix)
792             compiler.gccPrefix = pathBox.slashPath;
793          else if(pathBox == sysroot)
794             compiler.sysroot = pathBox.slashPath;
795          modifiedDocument = true;
796          compilersTab.modifiedDocument = true;
797       }
798       return true;
799    }
800
801    void Load()
802    {
803       CompilerConfig compiler = loadedCompiler;
804       if(compiler)
805       {
806          bool disabled = compiler.readOnly;
807          bool isVC = compiler.type.isVC;
808          ecp.path = compiler.ecpCommand;
809          ecc.path = compiler.eccCommand;
810          ecs.path = compiler.ecsCommand;
811          ear.path = compiler.earCommand;
812          cpp.path = compiler.cppCommand;
813          cc.path = compiler.ccCommand;
814          cxx.path = compiler.cxxCommand;
815          make.path = compiler.makeCommand;
816          executableLauncher.path = compiler.executableLauncher;
817          gnuToolchainPrefix.path = compiler.gnuToolchainPrefix;
818          sysroot.path = compiler.sysroot;
819
820          ecpLabel.disabled = ecp.disabled = disabled;
821          eccLabel.disabled = ecc.disabled = disabled;
822          ecsLabel.disabled = ecs.disabled = disabled;
823          earLabel.disabled = ear.disabled = disabled;
824          cppLabel.disabled = cpp.disabled = isVC || disabled;
825          cxxLabel.disabled = cxx.disabled = isVC || disabled;
826          ccLabel.disabled = cc.disabled = isVC || disabled;
827          makeLabel.disabled = make.disabled = disabled;
828          executableLauncherLabel.disabled = executableLauncher.disabled = disabled;
829          gnuToolchainPrefixLabel.disabled = gnuToolchainPrefix.disabled = disabled;
830          sysrootLabel.disabled = sysroot.disabled = disabled;
831       }
832       modifiedDocument = false;
833    }
834 }
835
836 class CompilerEnvironmentTab : CompilersSubTab
837 {
838    background = formColor;
839    text = $"Environment";
840
841    Label labelEnvVars { envVars, labeledWindow = envVars, position = { 0, 6 }; };
842    NamedStringsBox envVars
843    {
844       this, size = { 290, 22 }, anchor = { left = 8, top = 8, right = 8, bottom = 8 };
845       text = $"Environment Variables", hotKey = altE; //, option = OPTION(postbuildCommands);
846
847       bool NotifyModified(NamedStringsBox stringsBox)
848       {
849          CompilerConfig compiler = loadedCompiler;
850          if(compiler)
851          {
852             compiler.environmentVars = stringsBox.namedStrings;
853             modifiedDocument = true;
854             compilersTab.modifiedDocument = true;
855          }
856          return true;
857       }
858    };
859
860    CompilerEnvironmentTab()
861    {
862    }
863
864    void Load()
865    {
866       CompilerConfig compiler = loadedCompiler;
867       if(compiler)
868       {
869          envVars.namedStrings = compiler.environmentVars;
870
871          modifiedDocument = false;
872       }
873    }
874 }
875
876 class CompilerOptionsTab : CompilersSubTab
877 {
878    background = formColor;
879    text = $"Options";
880
881    Label labelTargetPlatform { this, position = { 8, 12 }, labeledWindow = targetPlatform };   // TOCHECK: nameless instances dissapear when selecting tabs?
882    DropBox targetPlatform
883    {
884       this, position = { 110, 8 }, size = { 160 };
885       text = $"Target Platform", hotKey = altT;
886       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
887       {
888          CompilerConfig compiler = loadedCompiler;
889          if(compiler && row)
890          {
891             compiler.targetPlatform = (Platform)row.tag;
892             modifiedDocument = true;
893             compilersTab.modifiedDocument = true;
894          }
895          return true;
896       }
897    };
898
899    int numJobs;
900    Label numJobsLabel { this, position = { 8, 40 }, labeledWindow = numJobsBox };
901    DataBox numJobsBox
902    {
903       this, text = $"Number of parallel build jobs", hotKey = altJ, borderStyle = deep;
904       position = { 244, 36 }, size = { 80, 20 }, type = class(int), data = &numJobs;
905
906       bool OnKeyDown(Key key, unichar ch)
907       {
908          if((SmartKey)key == enter)
909          {
910             DataBox::OnKeyDown(key, ch);
911             return true;
912          }
913          else
914             return DataBox::OnKeyDown(key, ch);
915       }
916
917       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
918       {
919          if(!active)
920          {
921             if(!SaveData())
922                Refresh();
923          }
924          return true;
925       }
926
927       bool NotifyChanged(DataBox dataBox, bool closingDropDown)
928       {
929          CompilerConfig compiler = loadedCompiler;
930          if(compiler)
931          {
932             compiler.numJobs = numJobs;
933             modifiedDocument = true;
934             compilersTab.modifiedDocument = true;
935          }
936          return true;
937       }
938    };
939
940    Button ccacheEnabled
941    {
942       this, text = $"Use ccache", hotKey = altC, position = { 8, 68 };
943       isCheckbox = true;
944
945       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
946       {
947          CompilerConfig compiler = loadedCompiler;
948          if(compiler)
949          {
950             compiler.ccacheEnabled = button.checked;
951             modifiedDocument = true;
952             compilersTab.modifiedDocument = true;
953          }
954          return true;
955       }
956    };
957
958    Button distccEnabled
959    {
960       this, text = $"Use distcc", hotKey = altD, position = { 158, 68 };
961       isCheckbox = true;
962
963       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
964       {
965          CompilerConfig compiler = loadedCompiler;
966          if(compiler)
967          {
968             distccHosts.disabled = !button.checked;
969             compiler.distccEnabled = button.checked;
970             modifiedDocument = true;
971             compilersTab.modifiedDocument = true;
972          }
973          return true;
974       }
975    };
976
977    Label distccHostsLabel { this, position = { 8, 96 }, labeledWindow = distccHosts };
978    EditBox distccHosts
979    {
980       this, text = $"distcc hosts", hotKey = altH;
981       position = { 88, 92 }, size = { 300, 22 };
982
983       bool NotifyModified(EditBox editBox)
984       {
985          CompilerConfig compiler = loadedCompiler;
986          if(compiler)
987          {
988             compiler.distccHosts = editBox.contents;
989             modifiedDocument = true;
990             compilersTab.modifiedDocument = true;
991          }
992          return true;
993       }
994    };
995
996    Label lblPrepDefs { this, position = { 8, 126 }, labeledWindow = prepDefs };
997    StringListBox prepDefs
998    {
999       this, text = $"Preprocessor directives", hotKey = altP;
1000       position = { 148, 124 }, size = { 300, 22 }, anchor = { left = 148, top = 124, right = 8 };
1001
1002       bool NotifyModified(EditBox editBox)
1003       {
1004          if(loadedCompiler)
1005          {
1006             CompilerConfig compiler = loadedCompiler;
1007             compiler.prepDirectives = ((StringListBox)editBox).strings;
1008             modifiedDocument = true;
1009             compilersTab.modifiedDocument = true;
1010          }
1011          return true;
1012       }
1013    };
1014
1015    Label leCcompilerFlags { this, position = { 8, 156 }, labeledWindow = eCcompilerFlags };
1016    StringListBox eCcompilerFlags
1017    {
1018       this, text = $"Additional eC compiler flags", hotKey = altG;
1019       position = { 148, 154 }, size = { 300, 22 }, anchor = { left = 148, top = 154, right = 8 };
1020
1021       bool NotifyModified(EditBox editBox)
1022       {
1023          if(loadedCompiler)
1024          {
1025             CompilerConfig compiler = loadedCompiler;
1026             compiler.eCcompilerFlags = ((StringListBox)editBox).strings;
1027             modifiedDocument = true;
1028             compilersTab.modifiedDocument = true;
1029          }
1030          return true;
1031       }
1032    };
1033
1034    Label lblCompilerFlags { this, position = { 8, 186 }, labeledWindow = compilerFlags };
1035    StringListBox compilerFlags
1036    {
1037       this, text = $"Additional compiler flags", hotKey = altR;
1038       position = { 148, 184 }, size = { 300, 22 }, anchor = { left = 148, top = 184, right = 8 };
1039
1040       bool NotifyModified(EditBox editBox)
1041       {
1042          if(loadedCompiler)
1043          {
1044             CompilerConfig compiler = loadedCompiler;
1045             compiler.compilerFlags = ((StringListBox)editBox).strings;
1046             modifiedDocument = true;
1047             compilersTab.modifiedDocument = true;
1048          }
1049          return true;
1050       }
1051    };
1052
1053    Label lblLinkerFlags { this, position = { 8, 216 }, labeledWindow = linkerFlags };
1054    StringListBox linkerFlags
1055    {
1056       this, text = $"Additional linker flags", hotKey = altL;
1057       position = { 148, 214 }, size = { 300, 22 }, anchor = { left = 148, top = 214, right = 8 };
1058
1059       bool NotifyModified(EditBox editBox)
1060       {
1061          if(loadedCompiler)
1062          {
1063             CompilerConfig compiler = loadedCompiler;
1064             compiler.linkerFlags = ((StringListBox)editBox).strings;
1065             modifiedDocument = true;
1066             compilersTab.modifiedDocument = true;
1067          }
1068          return true;
1069       }
1070    };
1071
1072    Label lblExcludedLibraries { this, position = { 8, 246 }, labeledWindow = excludedLibraries };
1073    StringListBox excludedLibraries
1074    {
1075       this, text = $"Libraries to exclude", hotKey = altX;
1076       position = { 148, 244 }, size = { 300, 22 }, anchor = { left = 148, top = 244, right = 8 };
1077
1078       bool NotifyModified(EditBox editBox)
1079       {
1080          if(loadedCompiler)
1081          {
1082             CompilerConfig compiler = loadedCompiler;
1083             compiler.excludeLibs = ((StringListBox)editBox).strings;
1084             modifiedDocument = true;
1085             compilersTab.modifiedDocument = true;
1086          }
1087          return true;
1088       }
1089    };
1090
1091    CompilerOptionsTab()
1092    {
1093       Platform p;
1094       DataRow row;
1095       for(p = (Platform)1; p < Platform::enumSize; p++)
1096       {
1097          row = targetPlatform.AddRow();
1098          row.tag = p;
1099          row.string = p;
1100       }
1101    }
1102
1103    void Load()
1104    {
1105       CompilerConfig compiler = loadedCompiler;
1106       if(compiler)
1107       {
1108          bool disabled = compiler.readOnly;
1109          targetPlatform.currentRow = targetPlatform.FindRow(compiler.targetPlatform);
1110          numJobs = compiler.numJobs;
1111          numJobsBox.Refresh();
1112          ccacheEnabled.checked = compiler.ccacheEnabled;
1113          distccEnabled.checked = compiler.distccEnabled;
1114          distccHosts.disabled = !compiler.distccEnabled;
1115          distccHosts.contents = compiler.distccHosts;
1116          prepDefs.strings = compiler.prepDirectives;
1117          excludedLibraries.strings = compiler.excludeLibs;
1118          eCcompilerFlags.strings = compiler.eCcompilerFlags;
1119          compilerFlags.strings = compiler.compilerFlags;
1120          linkerFlags.strings = compiler.linkerFlags;
1121
1122          labelTargetPlatform.disabled = disabled;
1123          targetPlatform.disabled = disabled;
1124
1125       }
1126       modifiedDocument = false;
1127    }
1128 }
1129
1130 class CompilersSubTab : Tab
1131 {
1132    property CompilersTab compilersTab
1133    {
1134       get
1135       {
1136          CompilersTab tab = (CompilersTab)master;
1137          while(tab && tab._class != class(CompilersTab))
1138             tab = (CompilersTab)tab.master;
1139          return tab;
1140       }
1141    };
1142
1143    property CompilerConfig loadedCompiler
1144    {
1145       get
1146       {
1147          CompilersTab tab = compilersTab;
1148          return tab ? tab.activeCompiler : null;
1149       }
1150    };
1151 }
1152
1153 class ProjectOptionsTab : GlobalSettingsSubTab
1154 {
1155    background = formColor;
1156    text = $"Project";
1157
1158    Label defaultTargetDirLabel { this, position = { 8, 34 }, labeledWindow = defaultTargetDir };
1159    PathBox defaultTargetDir
1160    {
1161       this, size = { 160, 21 }, position = { 8, 52 }, anchor = { left = 8, top = 52, right = 8 };
1162       text = $"Default Target Directory", hotKey = altT;
1163
1164       bool NotifyModified(PathBox pathBox)
1165       {
1166          BasicValidatePathBoxPath(pathBox);
1167          modifiedDocument = true;
1168          return true;
1169       }
1170    };
1171
1172    Label defaultIntermediateObjDirLabel { this, position = { 8, 78 }, labeledWindow = defaultIntermediateObjDir };
1173    PathBox defaultIntermediateObjDir
1174    {
1175       this, size = { 160, 21 }, position = { 8, 96 }, anchor = { left = 8, top = 96, right = 8 };
1176       text = $"Default Intermediate Objects Directory", hotKey = altI;
1177
1178       bool NotifyModified(PathBox pathBox)
1179       {
1180          BasicValidatePathBoxPath(pathBox);
1181          modifiedDocument = true;
1182          return true;
1183       }
1184    };
1185 }
1186
1187 // COMPILER TOFIX: if class GlobalSettingsSubTab is after class WorkspaceOptionsTab the OnPostCreate
1188 //                 of WorkspaceOptionsTab will *not* be called!
1189 class GlobalSettingsSubTab : Tab
1190 {
1191    property GlobalSettingsDialog dialog
1192    {
1193       get
1194       {
1195          GlobalSettingsDialog dialog = (GlobalSettingsDialog)master;
1196          while(dialog && dialog._class != class(GlobalSettingsDialog))
1197             dialog = (GlobalSettingsDialog)dialog.master;
1198          return dialog;
1199       }
1200    };
1201 }
1202
1203 class WorkspaceOptionsTab : GlobalSettingsSubTab
1204 {
1205    background = formColor;
1206    text = $"Workspace";
1207
1208    Label defaultCompilerLabel { this, position = { 8, 14 }, labeledWindow = defaultCompilerDropBox };
1209    DropBox defaultCompilerDropBox
1210    {
1211       this, position = { 140, 8 }, size = { 220 };
1212       text = $"Default Compiler", hotKey = altA;
1213
1214       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
1215       {
1216          modifiedDocument = true;
1217          return true;
1218       }
1219    };
1220
1221    bool OnCreate()
1222    {
1223       GlobalSettingsDialog dialog = this.dialog;
1224       if(dialog && dialog.compilersTab.compilerConfigs && dialog.ideSettings)
1225       {
1226          DataRow row;
1227          for(compiler : dialog.ideSettings.compilerConfigs)
1228          {
1229             row = defaultCompilerDropBox.AddString(compiler.name);
1230             if(dialog.ideSettings.defaultCompiler && dialog.ideSettings.defaultCompiler[0] &&
1231                   !strcmp(compiler.name, dialog.ideSettings.defaultCompiler))
1232                defaultCompilerDropBox.currentRow = row;
1233          }
1234          if(!defaultCompilerDropBox.currentRow && defaultCompilerDropBox)
1235             defaultCompilerDropBox.currentRow = defaultCompilerDropBox.firstRow;
1236       }
1237       return true;
1238    }
1239
1240    void OnDestroy()
1241    {
1242       // TOFIX: The selection will be lost upon changing tab...
1243       // Should either warn, or leave it modified and put in place
1244       // checks to save/find the compiler by name
1245       defaultCompilerDropBox.Clear();
1246       modifiedDocument = false;
1247    }
1248 }
1249
1250 //static define app = ((GuiApplication)__thisModule);