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