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