ide/GlobalSettingsDialog: More screen estate for translations
[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(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<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    }
675
676    void Load()
677    {
678       CompilerConfig compiler = loadedCompiler;
679       if(compiler)
680       {
681          dirs[includes].strings = compiler.includeDirs;
682          dirs[libraries].strings = compiler.libraryDirs;
683          dirs[executables].strings = compiler.executableDirs;
684          dirs[includes].list.scroll = { 0, 0 };
685          dirs[libraries].list.scroll = { 0, 0 };
686          dirs[executables].list.scroll = { 0, 0 };
687       }
688    }
689 }
690
691 class CompilerToolchainTab : CompilersSubTab
692 {
693    background = formColor;
694    text = $"Toolchain";
695
696    int margin;
697    margin = 130;
698
699    Label ecpLabel { this, position = { 8, 12 }, labeledWindow = ecp, tabCycle = false, inactive = true };
700    PathBox ecp
701    {
702       this, anchor = { left = margin, top = 8, right = 8 };
703       text = $"eC Precompiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
704    };
705    Label eccLabel { this, position = { 8, 38 }, labeledWindow = ecc, tabCycle = false, inactive = true };
706    PathBox ecc
707    {
708       this, anchor = { left = margin, top = 34, right = 8 };
709       text = $"eC Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
710    };
711    Label ecsLabel { this, position = { 8, 64 }, labeledWindow = ecs, tabCycle = false, inactive = true };
712    PathBox ecs
713    {
714       this, anchor = { left = margin, top = 60, right = 8 };
715       text = $"eC Symbol Loader", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
716    };
717    Label earLabel { this, position = { 8, 90 }, labeledWindow = ear, tabCycle = false, inactive = true };
718    PathBox ear
719    {
720       this, anchor = { left = margin, top = 86, right = 8 };
721       text = $"Ecere Archiver", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
722    };
723    Label cppLabel { this, position = { 8, 116 }, labeledWindow = cpp, tabCycle = false, inactive = true };
724    PathBox cpp
725    {
726       this, anchor = { left = margin, top = 112, right = 8 };
727       text = $"C Preprocessor", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
728    };
729    Label ccLabel { this, position = { 8, 142 }, labeledWindow = cc, tabCycle = false, inactive = true };
730    PathBox cc
731    {
732       this, anchor = { left = margin, top = 138, right = 8 };
733       text = $"C Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
734    };
735    Label cxxLabel { this, position = { 8, 168 }, labeledWindow = cxx, tabCycle = false, inactive = true };
736    PathBox cxx
737    {
738       this, anchor = { left = margin, top = 164, right = 8 };
739       text = $"C++ Compiler", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
740    };
741    Label makeLabel { this, position = { 8, 194 }, labeledWindow = make, tabCycle = false, inactive = true };
742    PathBox make
743    {
744       this, anchor = { left = margin, top = 190, right = 8 };
745       text = $"GNU Make", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
746    };
747    Label gnuToolchainPrefixLabel { this, position = { 8, 220 }, labeledWindow = gnuToolchainPrefix, tabCycle = false, inactive = true };
748    PathBox gnuToolchainPrefix
749    {
750       this, anchor = { left = margin, top = 216, right = 8 };
751       text = $"GNU Toolchain Prefix", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
752    };
753    Label sysrootLabel { this, position = { 8, 246 }, labeledWindow = sysroot, tabCycle = false, inactive = true };
754    PathBox sysroot
755    {
756       this, anchor = { left = margin, top = 242, right = 8 };
757       text = $"SYSROOT", typeExpected = directory, browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
758    };
759    Label executableLauncherLabel { this, position = { 8, 272 }, labeledWindow = executableLauncher, tabCycle = false, inactive = true };
760    PathBox executableLauncher
761    {
762       this, anchor = { left = margin, top = 268, right = 8 };
763       text = $"Executable Launcher", browseDialog = toolchainFileDialog, NotifyModified = NotifyModifiedDocument;
764    };
765
766    bool NotifyModifiedDocument(PathBox pathBox)
767    {
768       CompilerConfig compiler = loadedCompiler;
769       if(compiler)
770       {
771          BasicValidatePathBoxPath(pathBox);
772          if(pathBox == ecp)
773             compiler.ecpCommand = pathBox.slashPath;
774          else if(pathBox == ecc)
775             compiler.eccCommand = pathBox.slashPath;
776          else if(pathBox == ecs)
777             compiler.ecsCommand = pathBox.slashPath;
778          else if(pathBox == ear)
779             compiler.earCommand = pathBox.slashPath;
780          else if(pathBox == cpp)
781             compiler.cppCommand = pathBox.slashPath;
782          else if(pathBox == cc)
783             compiler.ccCommand = pathBox.slashPath;
784          else if(pathBox == cxx)
785             compiler.cxxCommand = pathBox.slashPath;
786          else if(pathBox == make)
787             compiler.makeCommand = pathBox.slashPath;
788          else if(pathBox == executableLauncher)
789             compiler.execPrefixCommand = pathBox.slashPath;
790          else if(pathBox == gnuToolchainPrefix)
791             compiler.gccPrefix = pathBox.slashPath;
792          else if(pathBox == sysroot)
793             compiler.sysroot = pathBox.slashPath;
794          modifiedDocument = true;
795          compilersTab.modifiedDocument = true;
796       }
797       return true;
798    }
799
800    void Load()
801    {
802       CompilerConfig compiler = loadedCompiler;
803       if(compiler)
804       {
805          bool disabled = compiler.readOnly;
806          bool isVC = compiler.type.isVC;
807          ecp.path = compiler.ecpCommand;
808          ecc.path = compiler.eccCommand;
809          ecs.path = compiler.ecsCommand;
810          ear.path = compiler.earCommand;
811          cpp.path = compiler.cppCommand;
812          cc.path = compiler.ccCommand;
813          cxx.path = compiler.cxxCommand;
814          make.path = compiler.makeCommand;
815          executableLauncher.path = compiler.executableLauncher;
816          gnuToolchainPrefix.path = compiler.gnuToolchainPrefix;
817          sysroot.path = compiler.sysroot;
818
819          ecpLabel.disabled = ecp.disabled = disabled;
820          eccLabel.disabled = ecc.disabled = disabled;
821          ecsLabel.disabled = ecs.disabled = disabled;
822          earLabel.disabled = ear.disabled = disabled;
823          cppLabel.disabled = cpp.disabled = isVC || disabled;
824          cxxLabel.disabled = cxx.disabled = isVC || disabled;
825          ccLabel.disabled = cc.disabled = isVC || disabled;
826          makeLabel.disabled = make.disabled = disabled;
827          executableLauncherLabel.disabled = executableLauncher.disabled = disabled;
828          gnuToolchainPrefixLabel.disabled = gnuToolchainPrefix.disabled = disabled;
829          sysrootLabel.disabled = sysroot.disabled = disabled;
830       }
831       modifiedDocument = false;
832    }
833 }
834
835 class CompilerEnvironmentTab : CompilersSubTab
836 {
837    background = formColor;
838    text = $"Environment";
839
840    Label labelEnvVars { envVars, labeledWindow = envVars, position = { 0, 6 }; };
841    NamedStringsBox envVars
842    {
843       this, size = { 290, 22 }, anchor = { left = 8, top = 8, right = 8, bottom = 8 };
844       text = $"Environment Variables", hotKey = altE; //, option = OPTION(postbuildCommands);
845
846       bool NotifyModified(NamedStringsBox stringsBox)
847       {
848          CompilerConfig compiler = loadedCompiler;
849          if(compiler)
850          {
851             compiler.environmentVars = stringsBox.namedStrings;
852             modifiedDocument = true;
853             compilersTab.modifiedDocument = true;
854          }
855          return true;
856       }
857    };
858
859    CompilerEnvironmentTab()
860    {
861    }
862
863    void Load()
864    {
865       CompilerConfig compiler = loadedCompiler;
866       if(compiler)
867       {
868          envVars.namedStrings = compiler.environmentVars;
869
870          modifiedDocument = false;
871       }
872    }
873 }
874
875 class CompilerOptionsTab : CompilersSubTab
876 {
877    background = formColor;
878    text = $"Options";
879
880    Label labelTargetPlatform { this, position = { 8, 12 }, labeledWindow = targetPlatform };   // TOCHECK: nameless instances dissapear when selecting tabs?
881    DropBox targetPlatform
882    {
883       this, position = { 110, 8 }, size = { 160 };
884       text = $"Target Platform", hotKey = altT;
885       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
886       {
887          CompilerConfig compiler = loadedCompiler;
888          if(compiler && row)
889          {
890             compiler.targetPlatform = (Platform)row.tag;
891             modifiedDocument = true;
892             compilersTab.modifiedDocument = true;
893          }
894          return true;
895       }
896    };
897
898    int numJobs;
899    Label numJobsLabel { this, position = { 8, 40 }, labeledWindow = numJobsBox };
900    DataBox numJobsBox
901    {
902       this, text = $"Number of parallel build jobs", hotKey = altJ, borderStyle = deep;
903       position = { 244, 36 }, size = { 80, 20 }, type = class(int), data = &numJobs;
904
905       bool OnKeyDown(Key key, unichar ch)
906       {
907          if((SmartKey)key == enter)
908          {
909             DataBox::OnKeyDown(key, ch);
910             return true;
911          }
912          else
913             return DataBox::OnKeyDown(key, ch);
914       }
915
916       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
917       {
918          if(!active)
919          {
920             if(!SaveData())
921                Refresh();
922          }
923          return true;
924       }
925
926       bool NotifyChanged(DataBox dataBox, bool closingDropDown)
927       {
928          CompilerConfig compiler = loadedCompiler;
929          if(compiler)
930          {
931             compiler.numJobs = numJobs;
932             modifiedDocument = true;
933             compilersTab.modifiedDocument = true;
934          }
935          return true;
936       }
937    };
938
939    Button ccacheEnabled
940    {
941       this, text = $"Use ccache", hotKey = altC, position = { 8, 68 };
942       isCheckbox = true;
943
944       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
945       {
946          CompilerConfig compiler = loadedCompiler;
947          if(compiler)
948          {
949             compiler.ccacheEnabled = button.checked;
950             modifiedDocument = true;
951             compilersTab.modifiedDocument = true;
952          }
953          return true;
954       }
955    };
956
957    Button distccEnabled
958    {
959       this, text = $"Use distcc", hotKey = altD, position = { 158, 68 };
960       isCheckbox = true;
961
962       bool NotifyClicked(Button button, int x, int y, Modifiers mods)
963       {
964          CompilerConfig compiler = loadedCompiler;
965          if(compiler)
966          {
967             distccHosts.disabled = !button.checked;
968             compiler.distccEnabled = button.checked;
969             modifiedDocument = true;
970             compilersTab.modifiedDocument = true;
971          }
972          return true;
973       }
974    };
975
976    Label distccHostsLabel { this, position = { 8, 96 }, labeledWindow = distccHosts };
977    EditBox distccHosts
978    {
979       this, text = $"distcc hosts", hotKey = altH;
980       position = { 88, 92 }, size = { 300, 22 };
981
982       bool NotifyModified(EditBox editBox)
983       {
984          CompilerConfig compiler = loadedCompiler;
985          if(compiler)
986          {
987             compiler.distccHosts = editBox.contents;
988             modifiedDocument = true;
989             compilersTab.modifiedDocument = true;
990          }
991          return true;
992       }
993    }
994
995    Label lblPrepDefs { this, position = { 8, 126 }, labeledWindow = prepDefs };
996    StringListBox prepDefs
997    {
998       this, text = $"Preprocessor directives", hotKey = altP;
999       position = { 148, 124 }, size = { 300, 22 }, anchor = { left = 148, top = 124, right = 8 };
1000
1001       bool NotifyModified(EditBox editBox)
1002       {
1003          if(loadedCompiler)
1004          {
1005             CompilerConfig compiler = loadedCompiler;
1006             compiler.prepDirectives = ((StringListBox)editBox).strings;
1007             modifiedDocument = true;
1008             compilersTab.modifiedDocument = true;
1009          }
1010          return true;
1011       }
1012    }
1013
1014    Label leCcompilerFlags { this, position = { 8, 156 }, labeledWindow = eCcompilerFlags };
1015    StringListBox eCcompilerFlags
1016    {
1017       this, text = $"Additional eC compiler flags", hotKey = altG;
1018       position = { 148, 154 }, size = { 300, 22 }, anchor = { left = 148, top = 154, right = 8 };
1019
1020       bool NotifyModified(EditBox editBox)
1021       {
1022          if(loadedCompiler)
1023          {
1024             CompilerConfig compiler = loadedCompiler;
1025             compiler.eCcompilerFlags = ((StringListBox)editBox).strings;
1026             modifiedDocument = true;
1027             compilersTab.modifiedDocument = true;
1028          }
1029          return true;
1030       }
1031    }
1032
1033    Label lblCompilerFlags { this, position = { 8, 186 }, labeledWindow = compilerFlags };
1034    StringListBox compilerFlags
1035    {
1036       this, text = $"Additional compiler flags", hotKey = altR;
1037       position = { 148, 184 }, size = { 300, 22 }, anchor = { left = 148, top = 184, right = 8 };
1038
1039       bool NotifyModified(EditBox editBox)
1040       {
1041          if(loadedCompiler)
1042          {
1043             CompilerConfig compiler = loadedCompiler;
1044             compiler.compilerFlags = ((StringListBox)editBox).strings;
1045             modifiedDocument = true;
1046             compilersTab.modifiedDocument = true;
1047          }
1048          return true;
1049       }
1050    }
1051
1052    Label lblLinkerFlags { this, position = { 8, 216 }, labeledWindow = linkerFlags };
1053    StringListBox linkerFlags
1054    {
1055       this, text = $"Additional linker flags", hotKey = altL;
1056       position = { 148, 214 }, size = { 300, 22 }, anchor = { left = 148, top = 214, right = 8 };
1057
1058       bool NotifyModified(EditBox editBox)
1059       {
1060          if(loadedCompiler)
1061          {
1062             CompilerConfig compiler = loadedCompiler;
1063             compiler.linkerFlags = ((StringListBox)editBox).strings;
1064             modifiedDocument = true;
1065             compilersTab.modifiedDocument = true;
1066          }
1067          return true;
1068       }
1069    }
1070
1071    Label lblExcludedLibraries { this, position = { 8, 246 }, labeledWindow = excludedLibraries };
1072    StringListBox excludedLibraries
1073    {
1074       this, text = $"Libraries to exclude", hotKey = altX;
1075       position = { 148, 244 }, size = { 300, 22 }, anchor = { left = 148, top = 244, right = 8 };
1076
1077       bool NotifyModified(EditBox editBox)
1078       {
1079          if(loadedCompiler)
1080          {
1081             CompilerConfig compiler = loadedCompiler;
1082             compiler.excludeLibs = ((StringListBox)editBox).strings;
1083             modifiedDocument = true;
1084             compilersTab.modifiedDocument = true;
1085          }
1086          return true;
1087       }
1088    }
1089
1090    CompilerOptionsTab()
1091    {
1092       Platform p;
1093       DataRow row;
1094       for(p = (Platform)1; p < Platform::enumSize; p++)
1095       {
1096          row = targetPlatform.AddRow();
1097          row.tag = p;
1098          row.string = p;
1099       }
1100    }
1101
1102    void Load()
1103    {
1104       CompilerConfig compiler = loadedCompiler;
1105       if(compiler)
1106       {
1107          bool disabled = compiler.readOnly;
1108          targetPlatform.currentRow = targetPlatform.FindRow(compiler.targetPlatform);
1109          numJobs = compiler.numJobs;
1110          numJobsBox.Refresh();
1111          ccacheEnabled.checked = compiler.ccacheEnabled;
1112          distccEnabled.checked = compiler.distccEnabled;
1113          distccHosts.disabled = !compiler.distccEnabled;
1114          distccHosts.contents = compiler.distccHosts;
1115          prepDefs.strings = compiler.prepDirectives;
1116          excludedLibraries.strings = compiler.excludeLibs;
1117          eCcompilerFlags.strings = compiler.eCcompilerFlags;
1118          compilerFlags.strings = compiler.compilerFlags;
1119          linkerFlags.strings = compiler.linkerFlags;
1120
1121          labelTargetPlatform.disabled = disabled;
1122          targetPlatform.disabled = disabled;
1123
1124       }
1125       modifiedDocument = false;
1126    }
1127 }
1128
1129 class CompilersSubTab : Tab
1130 {
1131    property CompilersTab compilersTab
1132    {
1133       get
1134       {
1135          CompilersTab tab = (CompilersTab)master;
1136          while(tab && tab._class != class(CompilersTab))
1137             tab = (CompilersTab)tab.master;
1138          return tab;
1139       }
1140    };
1141
1142    property CompilerConfig loadedCompiler
1143    {
1144       get
1145       {
1146          CompilersTab tab = compilersTab;
1147          return tab ? tab.activeCompiler : null;
1148       }
1149    };
1150 }
1151
1152 class ProjectOptionsTab : GlobalSettingsSubTab
1153 {
1154    background = formColor;
1155    text = $"Project";
1156
1157    Label defaultTargetDirLabel { this, position = { 8, 34 }, labeledWindow = defaultTargetDir };
1158    PathBox defaultTargetDir
1159    {
1160       this, size = { 160, 21 }, position = { 8, 52 }, anchor = { left = 8, top = 52, right = 8 };
1161       text = $"Default Target Directory", hotKey = altT;
1162
1163       bool NotifyModified(PathBox pathBox)
1164       {
1165          BasicValidatePathBoxPath(pathBox);
1166          modifiedDocument = true;
1167          return true;
1168       }
1169    };
1170
1171    Label defaultIntermediateObjDirLabel { this, position = { 8, 78 }, labeledWindow = defaultIntermediateObjDir };
1172    PathBox defaultIntermediateObjDir
1173    {
1174       this, size = { 160, 21 }, position = { 8, 96 }, anchor = { left = 8, top = 96, right = 8 };
1175       text = $"Default Intermediate Objects Directory", hotKey = altI;
1176
1177       bool NotifyModified(PathBox pathBox)
1178       {
1179          BasicValidatePathBoxPath(pathBox);
1180          modifiedDocument = true;
1181          return true;
1182       }
1183    };
1184 }
1185
1186 // COMPILER TOFIX: if class GlobalSettingsSubTab is after class WorkspaceOptionsTab the OnPostCreate
1187 //                 of WorkspaceOptionsTab will *not* be called!
1188 class GlobalSettingsSubTab : Tab
1189 {
1190    property GlobalSettingsDialog dialog
1191    {
1192       get
1193       {
1194          GlobalSettingsDialog dialog = (GlobalSettingsDialog)master;
1195          while(dialog && dialog._class != class(GlobalSettingsDialog))
1196             dialog = (GlobalSettingsDialog)dialog.master;
1197          return dialog;
1198       }
1199    };
1200 }
1201
1202 class WorkspaceOptionsTab : GlobalSettingsSubTab
1203 {
1204    background = formColor;
1205    text = $"Workspace";
1206
1207    Label defaultCompilerLabel { this, position = { 8, 14 }, labeledWindow = defaultCompilerDropBox };
1208    DropBox defaultCompilerDropBox
1209    {
1210       this, position = { 140, 8 }, size = { 220 };
1211       text = $"Default Compiler", hotKey = altA;
1212
1213       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
1214       {
1215          modifiedDocument = true;
1216          return true;
1217       }
1218    };
1219
1220    bool OnCreate()
1221    {
1222       GlobalSettingsDialog dialog = this.dialog;
1223       if(dialog && dialog.compilersTab.compilerConfigs && dialog.ideSettings)
1224       {
1225          DataRow row;
1226          for(compiler : dialog.ideSettings.compilerConfigs)
1227          {
1228             row = defaultCompilerDropBox.AddString(compiler.name);
1229             if(dialog.ideSettings.defaultCompiler && dialog.ideSettings.defaultCompiler[0] &&
1230                   !strcmp(compiler.name, dialog.ideSettings.defaultCompiler))
1231                defaultCompilerDropBox.currentRow = row;
1232          }
1233          if(!defaultCompilerDropBox.currentRow && defaultCompilerDropBox)
1234             defaultCompilerDropBox.currentRow = defaultCompilerDropBox.firstRow;
1235       }
1236       return true;
1237    }
1238
1239    void OnDestroy()
1240    {
1241       // TOFIX: The selection will be lost upon changing tab...
1242       // Should either warn, or leave it modified and put in place
1243       // checks to save/find the compiler by name
1244       defaultCompilerDropBox.Clear();
1245       modifiedDocument = false;
1246    }
1247 }
1248
1249 //static define app = ((GuiApplication)__thisModule);