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