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