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