4670acafaa8b767b0c824105f77a7ec2a250768d
[sdk] / ide / src / ide.ec
1 #ifdef ECERE_STATIC
2 public import static "ecere"
3 public import static "ec"
4 #else
5 public import "ecere"
6 public import "ec"
7 #endif
8
9 import "GlobalSettingsDialog"
10 import "NewProjectDialog"
11 import "FindInFilesDialog"
12
13 #ifdef GDB_DEBUG_GUI
14 import "GDBDialog"
15 #endif
16
17 import "Project"
18 import "ProjectConfig"
19 import "ProjectNode"
20 import "NodeProperties"
21 import "ProjectSettings"
22 import "ProjectView"
23 import "Workspace"
24
25 import "CodeEditor"
26 import "Designer"
27 import "ToolBox"
28 import "Sheet"
29
30 import "Debugger"
31
32 import "OutputView"
33 import "BreakpointsView"
34 import "CallStackView"
35 import "ThreadsView"
36 import "WatchesView"
37
38 #ifndef NO3D
39 import "ModelView"
40 #endif
41 import "PictureEdit"
42
43 import "about"
44
45 import "FileSystemIterator"
46
47 #if defined(__WIN32__)
48 define pathListSep = ";";
49 #else
50 define pathListSep = ":";
51 #endif
52
53 define maxPathLen = 65 * MAX_LOCATION;
54
55 class PathBackup : struct
56 {
57    String oldLDPath;
58    String oldPath;
59
60    PathBackup()
61    {
62       oldPath = new char[maxPathLen];
63       oldLDPath = new char[maxPathLen];
64
65       GetEnvironment("PATH", oldPath, maxPathLen);
66 #if defined(__APPLE__)
67       GetEnvironment("DYLD_LIBRARY_PATH", oldLDPath, maxPathLen);
68 #else
69       GetEnvironment("LD_LIBRARY_PATH", oldLDPath, maxPathLen);
70 #endif
71    }
72
73    ~PathBackup()
74    {
75       SetEnvironment("PATH", oldPath);
76 #if defined(__APPLE__)
77       SetEnvironment("DYLD_LIBRARY_PATH", oldLDPath);
78 #else
79       SetEnvironment("LD_LIBRARY_PATH", oldLDPath);
80 #endif
81       delete oldPath;
82       delete oldLDPath;
83    }
84 };
85
86 enum OpenCreateIfFails { no, yes, something, whatever };
87 enum OpenMethod { normal, add };
88
89 static Array<FileFilter> fileFilters
90 { [
91    { $"C/C++/eC Files (*.ec, *.eh, *.c, *.cpp, *.cc, *.cxx, *.h, *.hpp, *.hh, *.hxx)", "ec, eh, c, cpp, cc, cxx, h, hpp, hh, hxx" },
92    { $"Header Files for eC/C/C++ (*.eh, *.h, *.hpp, *.hh, *.hxx)", "eh, h, hpp, hh, hxx" },
93    { $"C/C++/eC Source Files (*.ec, *.c, *.cpp, *.cc, *.cxx)", "ec, c, cpp, cc, cxx" },
94    { $"Text files (*.txt, *.text, *.nfo, *.info)", "txt, text, nfo, info" },
95    { $"Web files (*.html, *.htm, *.xhtml, *.css, *.php, *.js, *.jsi, *.rb, *.xml)", "html, htm, xhtml, css, php, js, jsi, rb, xml" },
96    { $"Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png, *.gif)", "jpg, jpeg, bmp, pcx, png, gif" },
97    { $"3D Studio Model Files (*.3ds)", "3ds" },
98    { $"All files", null }
99 ] };
100
101 static Array<FileType> fileTypes
102 { [
103    { $"Based on extension", null },
104    { $"Text",               "txt" },
105    { $"Image",              "jpg" },
106    { $"3D Studio Model",    "3ds" }
107 ] };
108
109 static Array<FileFilter> projectFilters
110 { [
111    { $"Project Files (*.epj)", ProjectExtension }
112 ] };
113
114 static Array<FileType> projectTypes
115 { [
116    { $"Project File", ProjectExtension }
117 ] };
118
119 static Array<FileFilter> findInFilesFileFilters
120 { [
121    { $"eC Files (*.ec, *.eh)", "ec, eh" },
122    { $"C/C++/eC Files (*.ec, *.eh, *.c, *.cpp, *.cc, *.cxx, *.h, *.hpp, *.hh, *.hxx)", "ec, eh, c, cpp, cc, cxx, h, hpp, hh, hxx" },
123    { $"Header Files for eC/C/C++ (*.eh, *.h, *.hpp, *.hh, *.hxx)", "eh, h, hpp, hh, hxx" },
124    { $"C/C++/eC Source Files (*.ec, *.c, *.cpp, *.cc, *.cxx)", "ec, c, cpp, cc, cxx" },
125    { $"Text files (*.txt)", "txt" },
126    { $"All files", null }
127 ] };
128
129 FileDialog ideFileDialog
130 {
131    type = multiOpen, text = $"Open";
132    types = fileTypes.array, sizeTypes = fileTypes.count * sizeof(FileType), filters = fileFilters.array, sizeFilters = fileFilters.count * sizeof(FileFilter);
133 };
134
135 define openProjectFileDialogTitle = $"Open Project";
136 define addProjectFileDialogTitle = $"Open Additional Project";
137 FileDialog ideProjectFileDialog
138 {
139    type = open;
140    types = projectTypes.array, sizeTypes = projectTypes.count * sizeof(FileType), filters = projectFilters.array, sizeFilters = projectFilters.count * sizeof(FileFilter);
141 };
142
143 GlobalSettingsDialog globalSettingsDialog
144 {
145    ideSettings = ideSettings;
146    settingsContainer = settingsContainer;
147
148    void OnGlobalSettingChange(GlobalSettingsChange globalSettingsChange)
149    {
150       switch(globalSettingsChange)
151       {
152          case editorSettings:
153          {
154             Window child;
155             for(child = ide.firstChild; child; child = child.next)
156             {
157                if(child._class == class(CodeEditor))
158                {
159                   CodeEditor codeEditor = (CodeEditor) child;
160                   codeEditor.editBox.freeCaret = ideSettings.useFreeCaret;
161                   // codeEditor.editBox.lineNumbers = ideSettings.showLineNumbers;
162                   codeEditor.editBox.caretFollowsScrolling = ideSettings.caretFollowsScrolling;
163                   codeEditor.OnPostCreate(); // Update editBox margin size
164                }
165             }
166             break;
167          }
168          case projectOptions:
169             break;
170          case compilerSettings:
171          {
172             ide.UpdateCompilerConfigs(true);
173             break;
174          }
175       }
176    }
177 };
178
179 void DrawLineMarginIcon(Surface surface, BitmapResource resource, int line, int lineH, int scrollY, int boxH)
180 {
181    int lineY;
182    if(line)
183    {
184       lineY = (line - 1) * lineH;
185       if(lineY + lineH > scrollY && lineY + lineH < scrollY + boxH)
186       {
187          Bitmap bitmap = resource.bitmap;
188          if(bitmap)
189             surface.Blit(bitmap, 0, lineY - scrollY + (lineH - bitmap.height) / 2 + 1, 0, 0, bitmap.width, bitmap.height);
190       }
191    }
192 }
193
194 #define IDEItem(x)   (&((IDEWorkSpace)0).x)
195
196 class IDEToolbar : ToolBar
197 {
198    /* File options */
199    // New
200    ToolButton buttonNewFile { this, toolTip = $"New file", menuItemPtr = IDEItem(fileNewItem) };
201    // Open
202    ToolButton buttonOpenFile { this, toolTip = $"Open file", menuItemPtr = IDEItem(fileOpenItem) };
203    // Close
204    // ToolButton buttonCloseFile { this, toolTip = $"Close file", menuItemPtr = IDEItem(fileCloseItem) };
205    // Save
206    ToolButton buttonSaveFile { this, toolTip = $"Save file", menuItemPtr = IDEItem(fileSaveItem) };
207    // Save All
208    ToolButton buttonSaveAllFile { this, toolTip = $"Save all", menuItemPtr = IDEItem(fileSaveAllItem) };
209
210    ToolSeparator separator1 { this };
211
212    /* Edit options */
213    // Cut
214    // Copy
215    // Paste
216    // Undo
217    // Redo
218
219    // ToolSeparator separator2 { this };
220
221    /* Project  options */
222    // New project
223    ToolButton buttonNewProject { this, toolTip = $"New project", menuItemPtr = IDEItem(projectNewItem) };
224    // Open project
225    ToolButton buttonOpenProject { this, toolTip = $"Open project", menuItemPtr = IDEItem(projectOpenItem) };
226    // Add project to workspace
227    ToolButton buttonAddProject { this, toolTip = $"Add project to workspace", menuItemPtr = IDEItem(projectAddItem), disabled = true; };
228    // Close project
229    // ToolButton buttonCloseProject { this, toolTip = $"Close project", menuItemPtr = IDEItem(projectCloseItem), disabled = true; };
230
231    ToolSeparator separator3 { this };
232
233    /* Build/Execution options */
234    // Build
235    ToolButton buttonBuild { this, toolTip = $"Build project", menuItemPtr = IDEItem(projectBuildItem), disabled = true; };
236    // Re-link
237    ToolButton buttonReLink { this, toolTip = $"Relink project", menuItemPtr = IDEItem(projectLinkItem), disabled = true; };
238    // Rebuild
239    ToolButton buttonRebuild { this, toolTip = $"Rebuild project", menuItemPtr = IDEItem(projectRebuildItem), disabled = true; };
240    // Clean
241    ToolButton buttonClean { this, toolTip = $"Clean project", menuItemPtr = IDEItem(projectCleanItem), disabled = true; };
242    // Real Clean
243    // ToolButton buttonRealClean { this, toolTip = $"Real clean project", menuItemPtr = IDEItem(projectRealCleanItem), disabled = true; };
244    // Regenerate Makefile
245    ToolButton buttonRegenerateMakefile { this, toolTip = $"Regenerate Makefile", menuItemPtr = IDEItem(projectRegenerateItem), disabled = true; };
246    // Compile actual file
247    // Execute
248    ToolButton buttonRun { this, toolTip = $"Run", menuItemPtr = IDEItem(projectRunItem), disabled = true; };
249 #ifdef IDE_SHOW_INSTALL_MENU_BUTTON
250    ToolButton buttonInstall { this, toolTip = $"Install", menuItemPtr = IDEItem(projectInstallItem), disabled = true; };
251 #endif
252
253    ToolSeparator separator4 { this };
254
255    /* Debug options */
256    // Start/Resume
257    ToolButton buttonDebugStartResume { this, toolTip = $"Start", menuItemPtr = IDEItem(debugStartResumeItem), disabled = true; };
258    // Restart
259    ToolButton buttonDebugRestart { this, toolTip = $"Restart", menuItemPtr = IDEItem(debugRestartItem), disabled = true; };
260    // Pause
261    ToolButton buttonDebugPause { this, toolTip = $"Break", menuItemPtr = IDEItem(debugBreakItem), disabled = true; };
262    // Stop
263    ToolButton buttonDebugStop { this, toolTip = $"Stop", menuItemPtr = IDEItem(debugStopItem), disabled = true; };
264    // Breakpoints
265    //ToolButton buttonRun { this, toolTip = $"Run", menuItemPtr = IDEItem(projectRunItem) };
266    // F11
267    ToolButton buttonDebugStepInto { this, toolTip = $"Step Into", menuItemPtr = IDEItem(debugStepIntoItem), disabled = true; };
268    // F10
269    ToolButton buttonDebugStepOver { this, toolTip = $"Step Over", menuItemPtr = IDEItem(debugStepOverItem), disabled = true; };
270    // Shift+F11
271    ToolButton buttonDebugStepOut { this, toolTip = $"Step Out", menuItemPtr = IDEItem(debugStepOutItem), disabled = true; };
272    // Shift+F10
273    ToolButton buttonDebugSkipStepOver { this, toolTip = $"Step Over Skipping Breakpoints", menuItemPtr = IDEItem(debugSkipStepOverItem), disabled = true; };
274
275    ToolSeparator separator5 { this };
276
277    Window spacer5 { this, size = { 4 } };
278
279    DropBox activeConfig
280    {
281       this, toolTip = $"Active Configuration(s)", size = { 160 }, disabled = true;
282       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
283       {
284          if(row)
285             ide.workspace.SelectActiveConfig(row.string);
286          return true;
287       }
288    };
289
290    Window spacer6 { this, size = { 4 } };
291
292    DropBox activeCompiler
293    {
294       this, toolTip = $"Active Compiler", size = { 160 }, disabled = true;
295       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
296       {
297          if(ide.workspace && ide.projectView && row && strcmp(row.string, ide.workspace.compiler))
298          {
299             bool silent = ide.projectView.buildInProgress == none ? false : true;
300             CompilerConfig compiler = ideSettings.GetCompilerConfig(row.string);
301             ide.workspace.compiler = row.string;
302             ide.projectView.ShowOutputBuildLog(!silent);
303             if(!silent)
304                ide.projectView.DisplayCompiler(compiler, false);
305             for(prj : ide.workspace.projects)
306                ide.projectView.ProjectPrepareCompiler(prj, compiler, silent);
307             delete compiler;
308             ide.workspace.Save();
309          }
310          return true;
311       }
312    };
313
314    DropBox activeBitDepth
315    {
316       this, toolTip = $"Active Bit Length", size = { 60 }, disabled = true;
317       bool NotifySelect(DropBox dropBox, DataRow row, Modifiers mods)
318       {
319          if(ide.workspace && ide.projectView && row)
320          {
321             bool silent = ide.projectView.buildInProgress == none ? false : true;
322             CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
323             ide.workspace.bitDepth = (int)row.tag;
324             ide.projectView.ShowOutputBuildLog(!silent);
325             if(!silent)
326                ide.projectView.DisplayCompiler(compiler, false);
327             for(prj : ide.workspace.projects)
328                ide.projectView.ProjectPrepareCompiler(prj, compiler, silent);
329             delete compiler;
330             ide.workspace.Save();
331          }
332          return true;
333       }
334    };
335
336    Window spacer7 { this, size = { 4 } };
337
338    void IDEToolbar()
339    {
340       DataRow row;
341       row = activeBitDepth.AddString("Auto");
342       row.tag = 0;
343       activeBitDepth.AddString("32 bit").tag = 32;
344       activeBitDepth.AddString("64 bit").tag = 64;
345       activeBitDepth.currentRow = row;
346    }
347
348 }
349
350 class IDEMainFrame : Window
351 {
352    background = formColor;
353    borderStyle = sizable;
354    hasMaximize = true;
355    hasMinimize = true;
356    hasClose = true;
357    minClientSize = { 600, 300 };
358    hasMenuBar = true;
359    icon = { ":icon.png" };
360    text = titleECEREIDE;
361 #if 0 //def _DEBUG
362    //stayOnTop = true;
363    size = { 800, 600 };
364    anchor = { top = 0, right = 0, bottom = 0 };
365 #else
366    state = maximized;
367    anchor = { left = 0, top = 0, right = 0, bottom = 0 };
368 #endif
369
370    Stacker stack
371    {
372       this;
373       menu = { };
374       isActiveClient = true;
375       gap = 0;
376       direction = vertical;
377       background = formColor;
378       anchor = { left = 0, top = 0, right = 0, bottom = 0 };
379    };
380    IDEToolbar toolBar
381    {
382       stack, ideWorkSpace;
383
384       void OnDestroy(void)
385       {
386          ((IDEWorkSpace)master).toolBar = null;
387       }
388    };
389    IDEWorkSpace ideWorkSpace { stack, this, toolBar = toolBar };
390 }
391
392 define ide = ideMainFrame.ideWorkSpace;
393
394 class IDEWorkSpace : Window
395 {
396    background = Color { 85, 85, 85 };
397
398    //tabCycle = true;
399    hasVertScroll = true;
400    hasHorzScroll = true;
401    hasStatusBar = true;
402    isActiveClient = true;
403    anchor = { left = 0, top = 0, right = 0, bottom = 0 };
404    menu = Menu {  };
405    IDEToolbar toolBar;
406
407    MenuItem * driverItems, * skinItems, * languageItems;
408    StatusField pos { width = 150 };
409    StatusField ovr, caps, num;
410    DualPipe documentor;
411
412    BitmapResource back                 { ":ecereBack.jpg", window = this };
413    BitmapResource bmpBp                { ":codeMarks/breakpoint.png", window = this };
414    BitmapResource bmpBpDisabled        { ":codeMarks/breakpointDisabled.png", window = this };
415    BitmapResource bmpBpHalf            { ":codeMarks/breakpointHalf.png", window = this };
416    BitmapResource bmpBpHalfDisabled    { ":codeMarks/breakpointHalfDisabled.png", window = this };
417    BitmapResource bmpCursor            { ":codeMarks/cursor.png", window = this };
418    BitmapResource bmpCursorError       { ":codeMarks/cursorError.png", window = this };
419    BitmapResource bmpTopFrame          { ":codeMarks/topFrame.png", window = this };
420    BitmapResource bmpTopFrameError     { ":codeMarks/topFrameError.png", window = this };
421    BitmapResource bmpTopFrameHalf      { ":codeMarks/topFrameHalf.png", window = this };
422    BitmapResource bmpTopFrameHalfError { ":codeMarks/topFrameHalfError.png", window = this };
423
424    Debugger debugger { };
425
426    ProjectView projectView;
427
428    OutputView outputView
429    {
430       parent = this;
431
432       void OnGotoError(char * line, bool noParsing)
433       {
434          ide.GoToError(line, noParsing);
435       }
436
437       void OnCodeLocationParseAndGoTo(char * line)
438       {
439          ide.CodeLocationParseAndGoTo(line, ide.findInFilesDialog.findProject, ide.findInFilesDialog.findDir);
440       }
441
442       bool OnKeyDown(Key key, unichar ch)
443       {
444          switch(key)
445          {
446             case escape:
447                if(activeBox != findBox || !ide.findInFilesDialog || !ide.findInFilesDialog.SearchAbort())
448                   ide.ShowCodeEditor();
449                break;
450             default:
451             {
452                OutputView::OnKeyDown(key, ch);
453                break;
454             }
455          }
456          return true;
457       }
458
459       bool OnClose(bool parentClosing)
460       {
461          visible = false;
462          if(!parentClosing)
463             ide.RepositionWindows(false);
464          return parentClosing;
465       }
466    };
467
468    CallStackView callStackView
469    {
470       parent = this, font = { panelFont.faceName, panelFont.size };
471
472       void OnSelectFrame(int frameIndex)
473       {
474          ide.debugger.GoToStackFrameLine(frameIndex, true, true);
475          if(frameIndex >= 0)
476             ide.debugger.SelectFrame(frameIndex);
477       }
478
479       void OnToggleBreakpoint()
480       {
481          Debugger debugger = ide.debugger;
482          if(debugger.activeFrame && debugger.activeFrame.absoluteFile)
483          {
484             int line = debugger.activeFrame.line;
485             debugger.ToggleBreakpoint(debugger.activeFrame.absoluteFile, line);
486             Update(null);
487             {
488                CodeEditor codeEditor = (CodeEditor)ide.FindWindow(debugger.activeFrame.absoluteFile);
489                if(codeEditor) { codeEditor.Update(null); Activate(); }
490             }
491          }
492       }
493
494       bool OnKeyDown(Key key, unichar ch)
495       {
496          switch(key)
497          {
498             case escape: ide.ShowCodeEditor(); break;
499          }
500          return true;
501       }
502
503       bool OnClose(bool parentClosing)
504       {
505          visible = false;
506          if(!parentClosing)
507             ide.RepositionWindows(false);
508          return parentClosing;
509       }
510
511       void OnRedraw(Surface surface)
512       {
513          Debugger debugger = ide.debugger;
514          Frame activeFrame = debugger.activeFrame;
515          if(activeFrame)
516          {
517             bool error;
518             int lineCursor, lineTopFrame, activeThread, hitThread;
519             int lineH, scrollY, boxH;
520             BitmapResource bmp;
521             Breakpoint bp = null;
522
523             boxH = clientSize.h;
524             scrollY = editBox.scroll.y;
525             displaySystem.FontExtent(editBox.font.font, " ", 1, null, &lineH);
526             activeThread = debugger.activeThread;
527             hitThread = debugger.hitThread;
528             debugger.GetCallStackCursorLine(&error, &lineCursor, &lineTopFrame);
529
530             // TODO: improve bp drawing... it should be visible even if it's not on the activeFrame
531             if(activeFrame.absoluteFile)
532             {
533                for(i : ide.workspace.breakpoints; i.type == user)
534                {
535                   if(i.absoluteFilePath && i.absoluteFilePath[0] &&
536                      !fstrcmp(i.absoluteFilePath, activeFrame.absoluteFile) &&
537                      activeFrame.line == i.line)
538                   {
539                      bp = i;
540                      break;
541                   }
542                }
543             }
544             if(bp)
545                DrawLineMarginIcon(surface,
546                      /*(lineCursor == 1 || lineTopFrame == 1) ? */ide.bmpBpHalf/* : ide.bmpBp*/,
547                      lineCursor /*1*/, lineH, scrollY, boxH);
548             /*
549             if(activeThread && activeThread == hitThread && debugger.bpHit && debugger.bpHit.type == user)
550                DrawLineMarginIcon(surface,
551                      (lineCursor == 1 || lineTopFrame == 1) ? ide.bmpBpHalf : ide.bmpBp,
552                      1, lineH, scrollY, boxH);
553             */
554             DrawLineMarginIcon(surface, error ? ide.bmpCursorError : ide.bmpCursor, lineCursor, lineH, scrollY, boxH);
555             if(bp && lineCursor == 1) //activeThread && activeThread == hitThread && debugger.bpHit && debugger.bpHit.type == user)
556                bmp = error ? ide.bmpTopFrameHalfError : ide.bmpTopFrameHalf;
557             else
558                bmp = error ? ide.bmpTopFrameError : ide.bmpTopFrame;
559             DrawLineMarginIcon(surface, bmp, lineTopFrame, lineH, scrollY, boxH);
560          }
561          if(editBox.horzScroll && editBox.horzScroll.visible)
562          {
563             surface.SetBackground(control);
564             surface.Area(0, editBox.clientSize.h, 20 - 1, clientSize.h - 1);
565          }
566       }
567    };
568
569    WatchesView watchesView { parent = this };
570    ThreadsView threadsView
571    {
572       parent = this, font = { panelFont.faceName, panelFont.size };
573
574       bool OnKeyDown(Key key, unichar ch)
575       {
576          switch(key)
577          {
578             case escape: ide.ShowCodeEditor(); break;
579          }
580          return true;
581       }
582
583       bool OnClose(bool parentClosing)
584       {
585          visible = false;
586          if(!parentClosing)
587             ide.RepositionWindows(false);
588          return parentClosing;
589       }
590
591       void OnSelectThread(int threadId)
592       {
593          if(threadId)
594             ide.debugger.SelectThread(threadId);
595       }
596
597       bool OnGetThreadsInfo(int * activeThread, int * hitThread, int * signalThread)
598       {
599          bool result = false;
600          Debugger debugger = ide.debugger;
601          *activeThread = debugger.activeThread;
602          *hitThread = debugger.hitThread;
603          *signalThread = debugger.signalThread;
604          result = true;
605          return result;
606       }
607    };
608    BreakpointsView breakpointsView { parent = this };
609
610    ToolBox toolBox { parent = this, visible = false };
611    Sheet sheet { parent = this, visible = false };
612
613    char * tmpPrjDir;
614    property char * tmpPrjDir { set { delete tmpPrjDir; if(value) tmpPrjDir = CopyString(value); } get { return tmpPrjDir; } };
615
616    Menu fileMenu { menu, $"File", f, hasMargin = true };
617       MenuItem fileNewItem
618       {
619          fileMenu, $"New", n, ctrlN;
620          bitmap = { ":actions/docNew.png" };
621          bool NotifySelect(MenuItem selection, Modifiers mods)
622          {
623             Window currentDoc = activeClient;
624             bool maximizeDoc = ((currentDoc && currentDoc.state == maximized) || (!currentDoc && !projectView));
625             Window document = (Window)NewCodeEditor(this, maximizeDoc ? maximized : normal, false);
626             RepositionWindows(false);
627             document.NotifySaved = DocumentSaved;
628             return true;
629          }
630       }
631       MenuItem fileOpenItem
632       {
633          fileMenu, $"Open...", o, ctrlO;
634          bitmap = { ":actions/docOpen.png" };
635          bool NotifySelect(MenuItem selection, Modifiers mods)
636          {
637             if(!projectView && ideSettings.ideFileDialogLocation)
638                ideFileDialog.currentDirectory = ideSettings.ideFileDialogLocation;
639             for(;;)
640             {
641                if(ideFileDialog.Modal() == ok)
642                {
643                   bool gotWhatWeWant = false;
644                   int c;
645                   int numSelections = ideFileDialog.numSelections;
646                   char ** multiFilePaths = ideFileDialog.multiFilePaths;
647
648                   for(c = 0; c < numSelections; c++)
649                   {
650                      if(OpenFile(multiFilePaths[c], false, true, fileTypes[ideFileDialog.fileType].typeExtension, no, normal, mods.ctrl && mods.shift))
651                         gotWhatWeWant = true;
652                   }
653                   if(gotWhatWeWant ||
654                      MessageBox { type = yesNo, master = this, text = $"Error opening file",
655                      contents = $"Open a different file?" }.Modal() == no)
656                   {
657                      if(!projectView && gotWhatWeWant)
658                         ChangeFileDialogsDirectory(ideFileDialog.currentDirectory, true);
659                      ide.RepositionWindows(false);
660                      break;
661                   }
662                }
663                else
664                   break;
665             }
666             return true;
667          }
668       }
669       MenuItem fileCloseItem { fileMenu, $"Close", c, ctrlF4, NotifySelect = MenuFileClose };
670       MenuDivider { fileMenu };
671       MenuItem fileSaveItem
672       {
673          fileMenu, $"Save", s, ctrlS, bitmap = { ":actions/docSave.png" };
674
675          // For the toolbar button; clients can still override that for the menu item
676          bool Window::NotifySelect(MenuItem selection, Modifiers mods)
677          {
678             Window w = activeClient;
679             if(w)
680                w.MenuFileSave(null, 0);
681             return true;
682          }
683       };
684       MenuItem fileSaveAsItem { fileMenu, $"Save As...", a };
685       MenuItem fileSaveAllItem { fileMenu, $"Save All", l, NotifySelect = MenuFileSaveAll, bitmap = { ":actions/docSaveAll.png" } };
686       MenuDivider { fileMenu };
687       MenuItem findInFiles
688       {
689          fileMenu, $"Find In Files...", f, Key { f, ctrl = true , shift = true };
690          bool NotifySelect(MenuItem selection, Modifiers mods)
691          {
692             findInFilesDialog.replaceMode = false;
693             findInFilesDialog.Show();
694             return true;
695          }
696       }
697       MenuItem replaceInFiles
698       {
699          fileMenu, $"Replace In Files...", e, Key { r, ctrl = true , shift = true };
700          bool NotifySelect(MenuItem selection, Modifiers mods)
701          {
702             findInFilesDialog.replaceMode = true;
703             findInFilesDialog.Show();
704             return true;
705          }
706       }
707       MenuDivider { fileMenu };
708       MenuItem globalSettingsItem
709       {
710          fileMenu, $"Global Settings...", g;
711          bool NotifySelect(MenuItem selection, Modifiers mods)
712          {
713             globalSettingsDialog.master = this;
714             if(ide.workspace && ide.workspace.compiler)
715                globalSettingsDialog.workspaceActiveCompiler = ide.workspace.compiler;
716             else if(ideSettings.defaultCompiler)
717                globalSettingsDialog.workspaceActiveCompiler = ideSettings.defaultCompiler;
718             globalSettingsDialog.Modal();
719             return true;
720          }
721       }
722       MenuDivider { fileMenu };
723       Menu recentFiles { fileMenu, $"Recent Files", r };
724       Menu recentProjects { fileMenu, $"Recent Projects", p };
725       MenuDivider { fileMenu };
726       MenuItem exitItem
727       {
728          fileMenu, $"Exit", x, altF4;
729
730          bool NotifySelect(MenuItem selection, Modifiers mods)
731          {
732             ideMainFrame.Destroy(0);
733             return true;
734          }
735       };
736
737       bool FileRecentFile(MenuItem selection, Modifiers mods)
738       {
739          int id = 0;
740          for(file : ideSettings.recentFiles)
741          {
742             if(id == selection.id)
743             {
744                bool isProjectFile;
745                char extension[MAX_EXTENSION] = "";
746                GetExtension(file, extension);
747                isProjectFile = (!strcmpi(extension, "epj") || !strcmpi(extension, "ews"));
748                if(mods.ctrl)
749                {
750                   char * command = PrintString("ide ", isProjectFile ? "-t " : "", file);
751                   Execute(command);
752                   delete command;
753                }
754                else
755                {
756                   OpenFile(file, false, true, isProjectFile ? "txt" : null, no, normal, mods.ctrl && mods.shift);
757                   ide.RepositionWindows(false);
758                }
759                break;
760             }
761             id++;
762          }
763          return true;
764       }
765
766       bool FileRecentProject(MenuItem selection, Modifiers mods)
767       {
768          int id = 0;
769          for(file : ideSettings.recentProjects)
770          {
771             if(id == selection.id)
772             {
773                if(mods.ctrl)
774                {
775                   char * command = PrintString("ide ", file);
776                   Execute(command);
777                   delete command;
778                }
779                else
780                   OpenFile(file, false, true, null, no, normal, mods.ctrl && mods.shift);
781                break;
782             }
783             id++;
784          }
785          return true;
786       }
787
788    MenuPlacement editMenu { menu, $"Edit", e };
789
790    Menu projectMenu { menu, $"Menu"."Project", p, hasMargin = true };
791       MenuItem projectNewItem
792       {
793          projectMenu, $"New...", n, Key { n, true, true };
794          bitmap = { ":actions/projNew.png" };
795          bool NotifySelect(MenuItem selection, Modifiers mods)
796          {
797             if(!DontTerminateDebugSession($"New Project"))
798             {
799                DialogResult result;
800                NewProjectDialog newProjectDialog { master = this };
801                incref newProjectDialog;
802                result = newProjectDialog.Modal();
803                if(result == ok)
804                {
805                   if(ProjectClose())
806                   {
807                      newProjectDialog.CreateNewProject();
808                      if(projectView)
809                      {
810                         ideSettings.AddRecentProject(projectView.fileName);
811                         ide.UpdateRecentMenus();
812                         settingsContainer.Save();
813                      }
814                   }
815                }
816                delete newProjectDialog;
817             }
818             return true;
819          }
820       }
821       MenuItem projectOpenItem
822       {
823          projectMenu, $"Open...", o, Key { o, true, true };
824          bitmap = { ":actions/projOpen.png" };
825          bool NotifySelect(MenuItem selection, Modifiers mods)
826          {
827             if(ideSettings.ideProjectFileDialogLocation)
828                ideProjectFileDialog.currentDirectory = ideSettings.ideProjectFileDialogLocation;
829
830             ideProjectFileDialog.text = openProjectFileDialogTitle;
831             if(ideProjectFileDialog.Modal() == ok)
832             {
833                OpenFile(ideProjectFileDialog.filePath, false, true, projectTypes[ideProjectFileDialog.fileType].typeExtension, no, normal, mods.ctrl && mods.shift);
834                //ChangeProjectFileDialogDirectory(ideProjectFileDialog.currentDirectory);
835             }
836             return true;
837          }
838       }
839       MenuItem projectQuickItem
840       {
841          projectMenu, $"Quick...", q, f7, disabled = true;
842          bool NotifySelect(MenuItem selection, Modifiers mods)
843          {
844             if(!projectView)
845                QuickProjectDialog { this }.Modal();
846             return true;
847          }
848       }
849       MenuItem projectAddItem
850       {
851          projectMenu, $"Add project to workspace...", a, Key { a, true, true };
852          bitmap = { ":actions/projAdd.png" };
853          disabled = true;
854          bool NotifySelect(MenuItem selection, Modifiers mods)
855          {
856             if(ideSettings.ideProjectFileDialogLocation)
857                ideProjectFileDialog.currentDirectory = ideSettings.ideProjectFileDialogLocation;
858
859             ideProjectFileDialog.text = addProjectFileDialogTitle;
860             for(;;)
861             {
862                if(ideProjectFileDialog.Modal() == ok)
863                {
864                   if(OpenFile(ideProjectFileDialog.filePath, false, true, projectTypes[ideProjectFileDialog.fileType].typeExtension, no, add, mods.ctrl && mods.shift))
865                      break;
866                   if(MessageBox { type = yesNo, master = this, text = $"Error opening project file",
867                         contents = $"Add a different project?" }.Modal() == no)
868                   {
869                      break;
870                   }
871                }
872                else
873                   break;
874             }
875             return true;
876          }
877       }
878       MenuItem projectCloseItem
879       {
880          projectMenu, $"Close", c, disabled = true;
881          bool NotifySelect(MenuItem selection, Modifiers mods)
882          {
883             if(projectView)
884             {
885                if(!ide.DontTerminateDebugSession($"Project Close"))
886                   ProjectClose();
887             }
888             return true;
889          }
890       }
891       MenuDivider { projectMenu };
892       MenuItem projectSettingsItem
893       {
894          projectMenu, $"Settings...", s, altF7, disabled = true;
895          bool NotifySelect(MenuItem selection, Modifiers mods)
896          {
897             projectView.MenuSettings(projectView.active ? selection : null, mods);
898             return true;
899          }
900       }
901       MenuDivider { projectMenu };
902       MenuItem projectBrowseFolderItem
903       {
904          projectMenu, $"Browse Project Folder", p, disabled = true;
905          bool NotifySelect(MenuItem selection, Modifiers mods)
906          {
907             if(projectView)
908                projectView.MenuBrowseFolder(null, mods);
909             return true;
910          }
911       }
912       MenuDivider { projectMenu };
913       MenuItem projectRunItem
914       {
915          projectMenu, $"Run", r, ctrlF5, disabled = true;
916          bitmap = { ":actions/run.png" };
917          bool NotifySelect(MenuItem selection, Modifiers mods)
918          {
919             if(projectView)
920                projectView.Run(null, mods);
921             return true;
922          }
923       }
924       MenuItem projectBuildItem
925       {
926          projectMenu, $"Build", b, f7, disabled = true;
927          bitmap = { ":actions/build.png" };
928          bool NotifySelect(MenuItem selection, Modifiers mods)
929          {
930             if(projectView)
931             {
932                if(projectView.buildInProgress == none)
933                   projectView.ProjectBuild(projectView.active ? selection : null, mods);
934                else
935                   projectView.stopBuild = true;
936             }
937             return true;
938          }
939       }
940       MenuItem projectLinkItem
941       {
942          projectMenu, $"Relink", l, disabled = true;
943          bitmap = { ":actions/relink.png" };
944          bool NotifySelect(MenuItem selection, Modifiers mods)
945          {
946             if(projectView)
947                projectView.ProjectLink(projectView.active ? selection : null, mods);
948             return true;
949          }
950       }
951       MenuItem projectRebuildItem
952       {
953          projectMenu, $"Rebuild", d, shiftF7, disabled = true;
954          bitmap = { ":actions/rebuild.png" };
955          bool NotifySelect(MenuItem selection, Modifiers mods)
956          {
957             if(projectView)
958                projectView.ProjectRebuild(projectView.active ? selection : null, mods);
959             return true;
960          }
961       }
962       MenuItem projectCleanTargetItem
963       {
964          projectMenu, $"Clean Target", g, disabled = true;
965          bitmap = { ":actions/clean.png" };
966          bool NotifySelect(MenuItem selection, Modifiers mods)
967          {
968             if(projectView)
969             {
970                debugger.Stop();
971                projectView.ProjectCleanTarget(projectView.active ? selection : null, mods);
972             }
973             return true;
974          }
975       }
976       MenuItem projectCleanItem
977       {
978          projectMenu, $"Clean", e, disabled = true;
979          bitmap = { ":actions/clean.png" };
980          bool NotifySelect(MenuItem selection, Modifiers mods)
981          {
982             if(projectView)
983             {
984                debugger.Stop();
985                projectView.ProjectClean(projectView.active ? selection : null, mods);
986             }
987             return true;
988          }
989       }
990       MenuItem projectRealCleanItem
991       {
992          projectMenu, $"Real Clean", disabled = true;
993          bitmap = { ":actions/clean.png" };
994          bool NotifySelect(MenuItem selection, Modifiers mods)
995          {
996             if(projectView)
997             {
998                debugger.Stop();
999                projectView.ProjectRealClean(projectView.active ? selection : null, mods);
1000             }
1001             return true;
1002          }
1003       }
1004       MenuItem projectRegenerateItem
1005       {
1006          projectMenu, $"Regenerate Makefile", m, disabled = true;
1007          bitmap = { ":actions/regMakefile.png" };
1008          bool NotifySelect(MenuItem selection, Modifiers mods)
1009          {
1010             if(projectView)
1011                projectView.ProjectRegenerate(projectView.active ? selection : null, mods);
1012             return true;
1013          }
1014       }
1015       MenuItem projectInstallItem
1016       {
1017 #ifdef IDE_SHOW_INSTALL_MENU_BUTTON
1018          projectMenu, $"Install", t, disabled = true;
1019 #endif
1020          bitmap = { ":status/software-update-available.png" };
1021          bool NotifySelect(MenuItem selection, Modifiers mods)
1022          {
1023             if(projectView)
1024                projectView.ProjectInstall(projectView.active ? selection : null, mods);
1025             return true;
1026          }
1027       }
1028       MenuItem projectCompileItem;
1029    Menu debugMenu { menu, $"Debug", d, hasMargin = true };
1030       MenuItem debugStartResumeItem
1031       {
1032          debugMenu, $"Start", s, f5, disabled = true;
1033          bitmap = { ":actions/debug.png" };
1034          NotifySelect = MenuDebugStart;
1035       }
1036       bool MenuDebugStart(MenuItem selection, Modifiers mods)
1037       {
1038          if(projectView)
1039          {
1040             debugStartResumeItem.disabled = true; // a very rare exception to calling AdjustDebugMenus
1041             if(!projectView.DebugStart())
1042                debugStartResumeItem.disabled = false; // same exception
1043          }
1044          return true;
1045       }
1046       bool MenuDebugResume(MenuItem selection, Modifiers mods)
1047       {
1048          if(projectView)
1049             projectView.DebugResume();
1050          return true;
1051       }
1052       MenuItem debugRestartItem
1053       {
1054          debugMenu, $"Restart", r, Key { f5, ctrl = true, shift = true }, disabled = true;
1055          bitmap = { ":actions/restart.png" };
1056          bool NotifySelect(MenuItem selection, Modifiers mods)
1057          {
1058             if(projectView)
1059                projectView.DebugRestart();
1060             return true;
1061          }
1062       }
1063       MenuItem debugBreakItem
1064       {
1065          debugMenu, $"Break", b, Key { pauseBreak, ctrl = true }, disabled = true;
1066          bitmap = { ":actions/pause.png" };
1067          bool NotifySelect(MenuItem selection, Modifiers mods)
1068          {
1069             if(projectView && projectView.buildInProgress != none)
1070                return true;
1071             if(projectView)
1072                projectView.DebugBreak();
1073             return true;
1074          }
1075       }
1076       MenuItem debugStopItem
1077       {
1078          debugMenu, $"Stop", p, shiftF5, disabled = true;
1079          bitmap = { ":actions/stopDebug.png" };
1080          bool NotifySelect(MenuItem selection, Modifiers mods)
1081          {
1082             if(projectView)
1083                projectView.DebugStop();
1084             return true;
1085          }
1086       }
1087 #ifndef __WIN32__
1088       MenuDivider { debugMenu };
1089       MenuItem debugUseValgrindItem
1090       {
1091          debugMenu, $"Use Valgrind", d, disabled = true, checkable = true;
1092          bool NotifySelect(MenuItem selection, Modifiers mods)
1093          {
1094             if(ide.workspace)
1095             {
1096                ide.workspace.useValgrind = selection.checked;
1097                ide.workspace.Save();
1098             }
1099             ide.AdjustValgrindMenus();
1100             return true;
1101          }
1102       }
1103       Menu debugValgrindLeakCheckItem { debugMenu, $"Valgrind Leak Check", h };
1104          MenuItem debugValgrindNoLeakCheckItem      { debugValgrindLeakCheckItem, $"No"     , f, id = ValgrindLeakCheck::no     , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1105          MenuItem debugValgrindSummaryLeakCheckItem { debugValgrindLeakCheckItem, $"Summary", f, id = ValgrindLeakCheck::summary, checkable = true, disabled = true; NotifySelect = ValgrindLCSelect, checked = true; }
1106          MenuItem debugValgrindYesLeakCheckItem     { debugValgrindLeakCheckItem, $"Yes"    , f, id = ValgrindLeakCheck::yes    , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1107          MenuItem debugValgrindFullLeakCheckItem    { debugValgrindLeakCheckItem, $"Full"   , f, id = ValgrindLeakCheck::full   , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1108          bool ValgrindLCSelect(MenuItem selection, Modifiers mods)
1109          {
1110             if(ide.workspace)
1111             {
1112                if(selection.checked)
1113                {
1114                   ValgrindLeakCheck vgLeakCheck = (ValgrindLeakCheck)selection.id;
1115
1116                   debugValgrindNoLeakCheckItem.checked      = debugValgrindNoLeakCheckItem.id      == vgLeakCheck;
1117                   debugValgrindSummaryLeakCheckItem.checked = debugValgrindSummaryLeakCheckItem.id == vgLeakCheck;
1118                   debugValgrindYesLeakCheckItem.checked     = debugValgrindYesLeakCheckItem.id     == vgLeakCheck;
1119                   debugValgrindFullLeakCheckItem.checked    = debugValgrindFullLeakCheckItem.id    == vgLeakCheck;
1120
1121                   ide.workspace.vgLeakCheck = vgLeakCheck;
1122                   ide.workspace.Save();
1123                }
1124                else
1125                   selection.checked = true;
1126             }
1127             return true;
1128          }
1129       Menu debugValgrindRedzoneSizeItem { debugMenu, $"Valgrind Redzone Size", z };
1130          MenuItem debugValgrindRSDefaultItem { debugValgrindRedzoneSizeItem, $"Default", f, id =  -1, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect, checked = true; }
1131          MenuItem debugValgrindRS0Item       { debugValgrindRedzoneSizeItem, "0"      , f, id =   0, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1132          MenuItem debugValgrindRS16Item      { debugValgrindRedzoneSizeItem, "16"     , f, id =  16, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1133          MenuItem debugValgrindRS32Item      { debugValgrindRedzoneSizeItem, "32"     , f, id =  32, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1134          MenuItem debugValgrindRS64Item      { debugValgrindRedzoneSizeItem, "64"     , f, id =  64, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1135          MenuItem debugValgrindRS128Item     { debugValgrindRedzoneSizeItem, "128"    , f, id = 128, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1136          MenuItem debugValgrindRS256Item     { debugValgrindRedzoneSizeItem, "256"    , f, id = 256, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1137          MenuItem debugValgrindRS512Item     { debugValgrindRedzoneSizeItem, "512"    , f, id = 512, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1138          bool ValgrindRSSelect(MenuItem selection, Modifiers mods)
1139          {
1140             if(ide.workspace)
1141             {
1142                if(selection.checked)
1143                {
1144                   int vgRedzoneSize = (int)selection.id;
1145
1146                   debugValgrindRSDefaultItem.checked = debugValgrindRSDefaultItem.id == vgRedzoneSize;
1147                   debugValgrindRS0Item.checked       = debugValgrindRS0Item.id       == vgRedzoneSize;
1148                   debugValgrindRS16Item.checked      = debugValgrindRS16Item.id      == vgRedzoneSize;
1149                   debugValgrindRS32Item.checked      = debugValgrindRS32Item.id      == vgRedzoneSize;
1150                   debugValgrindRS64Item.checked      = debugValgrindRS64Item.id      == vgRedzoneSize;
1151                   debugValgrindRS128Item.checked     = debugValgrindRS128Item.id     == vgRedzoneSize;
1152                   debugValgrindRS256Item.checked     = debugValgrindRS256Item.id     == vgRedzoneSize;
1153                   debugValgrindRS512Item.checked     = debugValgrindRS512Item.id     == vgRedzoneSize;
1154
1155                   ide.workspace.vgRedzoneSize = vgRedzoneSize;
1156                   ide.workspace.Save();
1157                }
1158                else
1159                   selection.checked = true;
1160             }
1161             return true;
1162          }
1163       MenuItem debugValgrindTrackOriginsItem
1164       {
1165          debugMenu, $"Valgrind Track Origins", k, checkable = true, disabled = true;
1166          bool NotifySelect(MenuItem selection, Modifiers mods)
1167          {
1168             if(ide.workspace)
1169             {
1170                ide.workspace.vgTrackOrigins = selection.checked;
1171                ide.workspace.Save();
1172             }
1173             return true;
1174          }
1175       };
1176 #endif
1177       MenuDivider { debugMenu };
1178       MenuItem debugStepIntoItem
1179       {
1180          debugMenu, $"Step Into", i, f11, disabled = true;
1181          bitmap = { ":actions/stepInto.png" };
1182          bool NotifySelect(MenuItem selection, Modifiers mods)
1183          {
1184             if(projectView) projectView.DebugStepInto();
1185             return true;
1186          }
1187       }
1188       MenuItem debugStepOverItem
1189       {
1190          debugMenu, $"Step Over", v, f10, disabled = true;
1191          bitmap = { ":actions/stepOver.png" };
1192          bool NotifySelect(MenuItem selection, Modifiers mods)
1193          {
1194             if(projectView) projectView.DebugStepOver(false);
1195             return true;
1196          }
1197       }
1198       MenuItem debugSkipStepOverItem
1199       {
1200          debugMenu, $"Step Over Skipping Breakpoints", e, shiftF10, disabled = true;
1201          bitmap = { ":actions/stepOverSkipBreak.png" };
1202          bool NotifySelect(MenuItem selection, Modifiers mods)
1203          {
1204             if(projectView) projectView.DebugStepOver(true);
1205             return true;
1206          }
1207       }
1208       MenuItem debugStepOutItem
1209       {
1210          debugMenu, $"Step Out", o, shiftF11, disabled = true;
1211          bitmap = { ":actions/stepOut.png" };
1212          bool NotifySelect(MenuItem selection, Modifiers mods)
1213          {
1214             if(projectView) projectView.DebugStepOut(false);
1215             return true;
1216          }
1217       }
1218       MenuItem debugSkipStepOutItem
1219       {
1220          debugMenu, $"Step Out Skipping Breakpoints", n, Key { f11, ctrl = true, shift = true }, disabled = true;
1221          bitmap = { ":actions/skipBreaks.png" };
1222          bool NotifySelect(MenuItem selection, Modifiers mods)
1223          {
1224             if(projectView) projectView.DebugStepOut(true);
1225             return true;
1226          }
1227       }
1228 #if 0
1229       MenuItem debugStepUntilItem
1230       {
1231          debugMenu, $"Step Over Until Next Line", x, disabled = true;
1232          bool NotifySelect(MenuItem selection, Modifiers mods)
1233          {
1234             if(projectView) projectView.DebugStepUntil(false);
1235             return true;
1236          }
1237       }
1238       MenuItem debugSkipStepUntilItem
1239       {
1240          debugMenu, $"Step Over Until Next Line Skipping Breakpoints", e, Key { f10, shift = true, alt = true }, disabled = true;
1241          bool NotifySelect(MenuItem selection, Modifiers mods)
1242          {
1243             if(projectView) projectView.DebugStepUntil(true);
1244             return true;
1245          }
1246       }
1247 #endif
1248       MenuPlacement debugRunToCursorItem { debugMenu, $"Run To Cursor", c };
1249       MenuPlacement debugSkipRunToCursorItem { debugMenu, $"Run To Cursor Skipping Breakpoints", u };
1250       MenuPlacement debugRunToCursorAtSameLevelItem { debugMenu, $"Run To Cursor At Same Level", l };
1251       MenuPlacement debugSkipRunToCursorAtSameLevelItem { debugMenu, $"Run To Cursor At Same Level Skipping Breakpoints", g };
1252 #if 0
1253       MenuPlacement debugBpRunToCursorItem { debugMenu, $"BP Run To Cursor" };
1254       MenuPlacement debugBpSkipRunToCursorItem { debugMenu, $"BP Run To Cursor Skipping Breakpoints" };
1255       MenuPlacement debugBpRunToCursorAtSameLevelItem { debugMenu, $"BP Run To Cursor At Same Level" };
1256       MenuPlacement debugBpSkipRunToCursorAtSameLevelItem { debugMenu, $"BP Run To Cursor At Same Level Skipping Breakpoints" };
1257 #endif
1258       //MenuDivider { debugMenu };
1259       //MenuPlacement debugToggleBreakpoint { debugMenu, "Toggle Breakpoint", t };
1260    MenuPlacement imageMenu { menu, $"Image", i };
1261    Menu viewMenu { menu, $"View", v };
1262       MenuItem viewProjectItem
1263       {
1264          viewMenu, $"Project View", j, alt0, disabled = true;
1265          bool NotifySelect(MenuItem selection, Modifiers mods)
1266          {
1267             if(projectView)
1268             {
1269                projectView.visible = true;
1270                projectView.Activate();
1271             }
1272             return true;
1273          }
1274       }
1275       MenuPlacement { viewMenu, $"View Designer" };
1276       MenuPlacement { viewMenu, $"View Code" };
1277       MenuPlacement { viewMenu, $"View Properties" };
1278       MenuPlacement { viewMenu, $"View Methods" };
1279       MenuItem viewDesignerItem
1280       {
1281          viewMenu, $"View Designer", d, f8;
1282          bool NotifySelect(MenuItem selection, Modifiers mods)
1283          {
1284             Window client = activeClient;
1285             Class dataType = client._class;
1286             if(!strcmp(dataType.name, "Designer"))
1287             {
1288                client.visible = true;
1289                client.Activate();
1290             }
1291             else
1292                ((CodeEditor)client).ViewDesigner();
1293             return true;
1294          }
1295       }
1296       MenuItem viewCodeItem
1297       {
1298          viewMenu, $"View Code", c, f8;
1299          bool NotifySelect(MenuItem selection, Modifiers mods)
1300          {
1301             Window client = activeClient;
1302             Class dataType = client._class;
1303             if(!strcmp(dataType.name, "Designer"))
1304                client = ((Designer)client).codeEditor;
1305
1306             client.Activate();
1307             // Do this after so the caret isn't moved yet...
1308             client.visible = true;
1309             return true;
1310          }
1311       }
1312       MenuItem viewPropertiesItem
1313       {
1314          viewMenu, $"View Properties", p, f4;
1315          bool NotifySelect(MenuItem selection, Modifiers mods)
1316          {
1317             sheet.visible = true;
1318             sheet.sheetSelected = properties;
1319             sheet.Activate();
1320             return true;
1321          }
1322       }
1323       MenuItem viewMethodsItem
1324       {
1325          viewMenu, $"View Methods", m, f4;
1326          bool NotifySelect(MenuItem selection, Modifiers mods)
1327          {
1328             sheet.visible = true;
1329             sheet.sheetSelected = methods;
1330             sheet.Activate();
1331             return true;
1332          }
1333       }
1334       MenuItem viewToolBoxItem
1335       {
1336          viewMenu, $"View Toolbox", x, f12;
1337          bool NotifySelect(MenuItem selection, Modifiers mods)
1338          {
1339             toolBox.visible = true;
1340             toolBox.Activate();
1341             return true;
1342          }
1343       }
1344       MenuItem viewOutputItem
1345       {
1346          viewMenu, $"Output", o, alt2;
1347          bool NotifySelect(MenuItem selection, Modifiers mods)
1348          {
1349             outputView.Show();
1350             return true;
1351          }
1352       }
1353       MenuItem viewWatchesItem
1354       {
1355          viewMenu, $"Watches", w, alt3;
1356          bool NotifySelect(MenuItem selection, Modifiers mods)
1357          {
1358             watchesView.Show();
1359             return true;
1360          }
1361       }
1362       MenuItem viewThreadsItem
1363       {
1364          viewMenu, $"Threads", t, alt4;
1365          bool NotifySelect(MenuItem selection, Modifiers mods)
1366          {
1367             threadsView.Show();
1368             return true;
1369          }
1370       }
1371       MenuItem viewBreakpointsItem
1372       {
1373          viewMenu, $"Breakpoints", b, alt5;
1374          bool NotifySelect(MenuItem selection, Modifiers mods)
1375          {
1376             breakpointsView.Show();
1377             return true;
1378          }
1379       }
1380       MenuItem viewCallStackItem
1381       {
1382          viewMenu, $"Call Stack", s, alt7;
1383          bool NotifySelect(MenuItem selection, Modifiers mods)
1384          {
1385             callStackView.Show();
1386             return true;
1387          }
1388       }
1389       MenuItem viewAllDebugViews
1390       {
1391          viewMenu, $"All Debug Views", a, alt9;
1392          bool NotifySelect(MenuItem selection, Modifiers mods)
1393          {
1394             outputView.Show();
1395             watchesView.Show();
1396             threadsView.Show();
1397             callStackView.Show();
1398             breakpointsView.Show();
1399             return true;
1400          }
1401       }
1402 #ifdef GDB_DEBUG_GUI
1403       MenuDivider { viewMenu };
1404       MenuItem viewGDBItem
1405       {
1406          viewMenu, $"GDB Dialog", g, Key { f9, shift = true, ctrl = true };
1407          bool NotifySelect(MenuItem selection, Modifiers mods)
1408          {
1409             gdbDialog.Show();
1410             return true;
1411          }
1412       }
1413 #endif
1414       MenuDivider { viewMenu };
1415       MenuItem viewColorPicker
1416       {
1417          viewMenu, $"Color Picker...", c, Key { c, ctrl = true , shift = true };
1418          bool NotifySelect(MenuItem selection, Modifiers mods)
1419          {
1420             ColorPicker colorPicker { master = this };
1421             colorPicker.Modal();
1422             return true;
1423          }
1424       }
1425       MenuDivider { viewMenu };
1426       /*
1427       MenuItem
1428       {
1429          viewMenu, "Full Screen", f, checkable = true;
1430
1431          bool NotifySelect(MenuItem selection, Modifiers mods)
1432          {
1433             app.fullScreen ^= true;
1434             SetDriverAndSkin();
1435             anchor = { 0, 0, 0, 0 };
1436             return true;
1437          }
1438       };
1439       */
1440       Menu driversMenu { viewMenu, $"Graphics Driver", v };
1441
1442       MenuDivider { viewMenu };
1443
1444       Menu languageMenu { viewMenu, "Language", l };
1445
1446       //Menu skinsMenu { viewMenu, "GUI Skins", k };
1447    Menu windowMenu { menu, $"Window", w };
1448       MenuItem { windowMenu, $"Close All", l, NotifySelect = MenuWindowCloseAll };
1449       MenuDivider { windowMenu };
1450       MenuItem { windowMenu, $"Next", n, f6, NotifySelect = MenuWindowNext };
1451       MenuItem { windowMenu, $"Previous", p, shiftF6, NotifySelect = MenuWindowPrevious };
1452       MenuDivider { windowMenu };
1453       MenuItem { windowMenu, $"Cascade", c, NotifySelect = MenuWindowCascade };
1454       MenuItem { windowMenu, $"Tile Horizontally", h, NotifySelect = MenuWindowTileHorz };
1455       MenuItem { windowMenu, $"Tile Vertically", v, NotifySelect = MenuWindowTileVert };
1456       MenuItem { windowMenu, $"Arrange Icons", a, NotifySelect = MenuWindowArrangeIcons };
1457       MenuDivider { windowMenu };
1458       MenuItem { windowMenu, $"Windows...", w, NotifySelect = MenuWindowWindows };
1459    Menu helpMenu { menu, $"Help", h };
1460       MenuItem
1461       {
1462          helpMenu, $"API Reference", r, f1;
1463          bool NotifySelect(MenuItem selection, Modifiers mods)
1464          {
1465             if(!documentor)
1466             {
1467                char * p = new char[MAX_LOCATION];
1468                p[0] = '\0';
1469                strncpy(p, settingsContainer.moduleLocation, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
1470                PathCat(p, "documentor");
1471    #if defined(__WIN32__)
1472                ChangeExtension(p, "exe", p);
1473    #endif
1474                if(!FileExists(p).isFile)
1475                   strcpy(p, "documentor");
1476
1477                documentor = DualPipeOpen({ input = true, output = true, showWindow = true }, p);
1478                delete p;
1479             }
1480             else
1481             {
1482                Process_ShowWindows(documentor.GetProcessID());
1483                // documentor.Puts("Activate\n");
1484             }
1485             return true;
1486          }
1487       }
1488       MenuDivider { helpMenu };
1489       MenuItem
1490       {
1491          helpMenu, $"Ecere Tao of Programming [work in progress]", t;
1492          bool NotifySelect(MenuItem selection, Modifiers mods)
1493          {
1494             FindAndShellOpenInstalledFile("doc", "Ecere Tao of Programming [work in progress].pdf");
1495             return true;
1496          }
1497       }
1498       MenuDivider { helpMenu };
1499       MenuItem
1500       {
1501          helpMenu, $"Documentation Folder", d;
1502          bool NotifySelect(MenuItem selection, Modifiers mods)
1503          {
1504             FindAndShellOpenInstalledFolder("doc");
1505             return true;
1506          }
1507       }
1508       MenuItem
1509       {
1510          helpMenu, $"Samples Folder", s;
1511          bool NotifySelect(MenuItem selection, Modifiers mods)
1512          {
1513             FindAndShellOpenInstalledFolder("samples");
1514             return true;
1515          }
1516       }
1517       MenuItem
1518       {
1519          helpMenu, $"Extras Folder", x;
1520          bool NotifySelect(MenuItem selection, Modifiers mods)
1521          {
1522             FindAndShellOpenInstalledFolder("extras");
1523             return true;
1524          }
1525       }
1526       MenuDivider { helpMenu };
1527       MenuItem
1528       {
1529          helpMenu, $"Community Forums", f;
1530          bool NotifySelect(MenuItem selection, Modifiers mods)
1531          {
1532             ShellOpen("http://ecere.com/forums");
1533             return true;
1534          }
1535       }
1536       MenuDivider { helpMenu };
1537       MenuItem
1538       {
1539          helpMenu, $"About...", a;
1540          bool NotifySelect(MenuItem selection, Modifiers mods)
1541          {
1542             AboutIDE { master = this }.Modal();
1543             return true;
1544          }
1545       }
1546
1547    property ToolBox toolBox
1548    {
1549       get { return toolBox; }
1550    }
1551
1552    property Sheet sheet
1553    {
1554       get { return sheet; }
1555    }
1556
1557    property Project project
1558    {
1559       get { return projectView ? projectView.project : null; }
1560    }
1561
1562    property Workspace workspace
1563    {
1564       get { return projectView ? projectView.workspace : null; }
1565    }
1566
1567    FindInFilesDialog findInFilesDialog
1568    {
1569       master = this,
1570       filters = findInFilesFileFilters.array, sizeFilters = findInFilesFileFilters.count * sizeof(FileFilter);
1571       filter = 1;
1572    };
1573
1574    bool noParsing;
1575
1576 #ifdef GDB_DEBUG_GUI
1577    GDBDialog gdbDialog
1578    {
1579       master = this, parent = this;
1580       //anchor = { left = 100, top = 100, right = 100, bottom = 100 };
1581
1582       void OnCommand(char * string)
1583       {
1584          if(ide)
1585             ide.debugger.SendGDBCommand(string);
1586       }
1587    };
1588 #endif
1589
1590    bool NotifySelectDisplayDriver(MenuItem selection, Modifiers mods)
1591    {
1592       //app.driver = app.drivers[selection.id];
1593 #if defined(__unix__) || defined(__APPLE__)
1594       app.driver = selection.id ? "OpenGL" : "X";
1595 #else
1596       app.driver = selection.id ? "OpenGL" : "GDI";
1597 #endif
1598       delete ideSettings.displayDriver;
1599       ideSettings.displayDriver = CopyString(selection.id ? "OpenGL" : "Default");
1600
1601       settingsContainer.Save();
1602       //SetDriverAndSkin();
1603       return true;
1604    }
1605
1606    bool NotifySelectDisplaySkin(MenuItem selection, Modifiers mods)
1607    {
1608       app.skin = app.skins[selection.id];
1609       SetDriverAndSkin();
1610       return true;
1611    }
1612
1613    void SetDriverAndSkin()
1614    {
1615       int c;
1616       for(c = 0; c < app.numSkins; c++)
1617          if(!strcmp(app.skins[c], app.skin))
1618          {
1619             skinItems[c].checked = true;
1620             break;
1621          }
1622       for(c = 0; c < app.numDrivers; c++)
1623          if(!strcmp(app.drivers[c], app.driver))
1624          {
1625             driverItems[c].checked = true;
1626             break;
1627          }
1628    }
1629
1630    ProjectView CreateProjectView(Workspace workspace, char * fileName)
1631    {
1632       Project project = workspace.projects.firstIterator.data;
1633       projectView = ProjectView
1634       {
1635          this;
1636          fileName = fileName;
1637
1638          void NotifyDestroyed(Window window, DialogResult result)
1639          {
1640             projectView = null;
1641             text = titleECEREIDE;
1642
1643             AdjustMenus();
1644          }
1645       };
1646       projectView.Create();
1647       RepositionWindows(false);
1648
1649       // Leave it after Create to avoid flicker due to seeing IDE without a project view
1650       projectView.workspace = workspace;
1651       projectView.project = project;
1652       ideMainFrame.SetText("%s - %s", project.topNode.name, titleECEREIDE);
1653
1654       AdjustMenus();
1655
1656       ide.breakpointsView.LoadFromWorkspace();
1657       ide.watchesView.LoadFromWorkspace();
1658
1659       findInFilesDialog.projectNodeField.userData = projectView;
1660
1661       {
1662          char fileName[MAX_LOCATION];
1663          strcpy(fileName, project.topNode.path);
1664          PathCat(fileName, project.topNode.name);
1665       }
1666       return projectView;
1667    }
1668
1669    bool ProjectClose()
1670    {
1671       projectView.visible = false;
1672       if((!projectView || projectView.created == false || projectView.Destroy(0)) && MenuWindowCloseAll(null, 0))
1673       {
1674          if(findInFilesDialog)
1675          {
1676             char workingDir[MAX_LOCATION];
1677             GetWorkingDir(workingDir, MAX_LOCATION);
1678             findInFilesDialog.SearchStop();
1679             findInFilesDialog.currentDirectory = workingDir;
1680          }
1681          sheet.visible = false;
1682          toolBox.visible = false;
1683          outputView.visible = false;
1684          ideMainFrame.text = titleECEREIDE;
1685          ide.AdjustMenus();
1686          return true;
1687       }
1688       return false;
1689    }
1690
1691    void RepositionWindows(bool expand)
1692    {
1693       if(this)
1694       {
1695          Window child;
1696          bool inDebugMode = debugger.isActive;
1697          bool callStackVisible = expand ? false : callStackView.visible;
1698          bool threadsVisible = expand ? false : threadsView.visible;
1699          bool watchesVisible = expand ? false : watchesView.visible;
1700          bool breakpointsVisible = expand ? false : breakpointsView.visible;
1701          bool toolBoxVisible = toolBox.visible;
1702          bool outputVisible = expand ? false : outputView.visible;
1703          int topDistance = (callStackVisible || threadsVisible) ? 200 : 0;
1704          int bottomDistance = (outputVisible || watchesVisible || breakpointsVisible) ? 240 : 0;
1705
1706          for(child = firstChild; child; child = child.next)
1707          {
1708             if(child._class == class(CodeEditor) || child._class == class(Designer) ||
1709                child._class == class(Sheet) || child._class == class(ProjectView))
1710             {
1711                Anchor anchor = child.anchor;
1712                anchor.top = topDistance;
1713                anchor.bottom = bottomDistance;
1714                if(child._class == class(CodeEditor) || child._class == class(Designer))
1715                {
1716                   anchor.left = (sheet.visible || (projectView && projectView.visible)) ? 300 : 0;
1717                   anchor.right = toolBoxVisible ? 150 : 0;
1718                }
1719                child.anchor = anchor;
1720             }
1721             else if(expand)
1722             {
1723                if(child._class == class(OutputView) || child._class == class(CallStackView) || child._class == class(ThreadsView) || child._class == class(WatchesView) ||
1724                   child._class == class(BreakpointsView))
1725                   child.visible = false;
1726             }
1727          }
1728          // If this is not here, the IDE is not updated when doing Debug/Break then Alt-4 to show call stack (IDE not updated)
1729          Update(null);
1730       }
1731    }
1732
1733    bool ShowCodeEditor()
1734    {
1735       if(activeClient)
1736          activeClient.Activate();
1737       else if(projectView)
1738       {
1739          projectView.visible = true;
1740          projectView.Activate();
1741       }
1742       else if(sheet.visible)
1743          sheet.Activate();
1744       else
1745          outputView.visible = false;
1746       return false;
1747    }
1748
1749    void DocumentSaved(Window document, char * fileName)
1750    {
1751       ideSettings.AddRecentFile(fileName);
1752       ide.UpdateRecentMenus();
1753       ide.AdjustFileMenus();
1754       settingsContainer.Save();
1755    }
1756
1757    bool Window::OnFileModified(FileChange fileChange, char * param)
1758    {
1759       char temp[4096];
1760       sprintf(temp, $"The document %s was modified by another application.\n"
1761             "Would you like to reload it and lose your changes?", this.fileName);
1762       if(MessageBox { type = yesNo, master = this/*.parent*/,
1763             text = $"Document has been modified", contents = temp }.Modal() == yes)
1764       {
1765          bool noParsing = (this._class == class(CodeEditor) && ((CodeEditor)this).noParsing) ? true : false;
1766          char * fileName = CopyString(this.fileName);
1767          WindowState state = this.state;
1768          Anchor anchor = this.anchor;
1769          Size size = this.size;
1770
1771          this.modifiedDocument = false;
1772          this.Destroy(0);
1773          this = ide.OpenFile(fileName, false, true, null, no, normal, noParsing);
1774          if(this)
1775          {
1776             this.anchor = anchor;
1777             this.size = size;
1778             this.SetState(state, true, 0);
1779          }
1780          delete fileName;
1781          return true;
1782       }
1783       return true;
1784    }
1785
1786    void UpdateMakefiles()
1787    {
1788       if(workspace)
1789       {
1790          CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
1791          for(prj : workspace.projects)
1792             projectView.ProjectUpdateMakefileForAllConfigs(prj);
1793          delete compiler;
1794       }
1795    }
1796
1797    void UpdateCompilerConfigs(bool mute)
1798    {
1799       UpdateToolBarActiveCompilers();
1800       if(workspace)
1801       {
1802          bool silent = mute || (ide.projectView.buildInProgress == none ? false : true);
1803          CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
1804          if(!silent)
1805          {
1806             projectView.ShowOutputBuildLog(true);
1807             projectView.DisplayCompiler(compiler, false);
1808          }
1809          for(prj : workspace.projects)
1810             projectView.ProjectPrepareCompiler(prj, compiler, silent);
1811          delete compiler;
1812       }
1813    }
1814
1815    void UpdateToolBarActiveCompilers()
1816    {
1817       toolBar.activeCompiler.Clear();
1818       for(compiler : ideSettings.compilerConfigs)
1819       {
1820          DataRow row = toolBar.activeCompiler.AddString(compiler.name);
1821          if(workspace && workspace.compiler && !strcmp(compiler.name, workspace.compiler))
1822             toolBar.activeCompiler.currentRow = row;
1823       }
1824       if(!toolBar.activeCompiler.currentRow && toolBar.activeCompiler.firstRow)
1825          toolBar.activeCompiler.SelectRow(toolBar.activeCompiler.firstRow);
1826    }
1827
1828    void UpdateToolBarActiveConfigs(bool selectionOnly)
1829    {
1830       bool commonSelected = false;
1831       DataRow row = toolBar.activeConfig.currentRow;
1832       if(selectionOnly)
1833          row = toolBar.activeConfig.FindRow(1);
1834       else
1835       {
1836          toolBar.activeConfig.Clear();
1837          row = toolBar.activeConfig.AddString($"(Mixed)");
1838          row.tag = 1;
1839       }
1840       if(workspace)
1841       {
1842          char * configName = null;
1843          if(!selectionOnly)
1844          {
1845             Map<String, int> configs { }; // TOIMP: just need sort but using map until containers have sort
1846             for(prj : workspace.projects)
1847             {
1848                for(cfg : prj.configurations)
1849                {
1850                   if(cfg.name)
1851                      configs[cfg.name] = 1;
1852                }
1853             }
1854             for(name : configs)
1855             {
1856                toolBar.activeConfig.AddString(&name);
1857             }
1858             delete configs;
1859          }
1860          if(projectView && projectView.project)
1861          {
1862             for(prj : workspace.projects)
1863             {
1864                if(prj.config && prj.config.name)
1865                {
1866                   configName = prj.config.name;
1867                   break;
1868                }
1869             }
1870             if(configName)
1871             {
1872                commonSelected = true;
1873                for(prj : workspace.projects)
1874                {
1875                   if(prj.config && (!prj.config.name || strcmp(prj.config.name, configName)))
1876                   {
1877                      commonSelected = false;
1878                      break;
1879                   }
1880                }
1881             }
1882          }
1883          if(commonSelected)
1884          {
1885             commonSelected = false;
1886             for(row = toolBar.activeConfig.firstRow; row; row = row.next)
1887             {
1888                if(!strcmp(row.string, configName))
1889                {
1890                   toolBar.activeConfig.currentRow = row;
1891                   commonSelected = true;
1892                   break;
1893                }
1894             }
1895          }
1896       }
1897       if(!selectionOnly)
1898          toolBar.activeConfig.Sort(null, 0);
1899       if(!commonSelected)
1900          toolBar.activeConfig.currentRow = row;
1901    }
1902
1903    void AdjustMenus()
1904    {
1905       bool unavailable = !project;
1906
1907       projectAddItem.disabled             = unavailable;
1908       toolBar.buttonAddProject.disabled   = unavailable;
1909
1910       projectSettingsItem.disabled        = unavailable;
1911
1912       projectBrowseFolderItem.disabled    = unavailable;
1913
1914       viewProjectItem.disabled            = unavailable;
1915
1916       toolBar.activeConfig.disabled       = unavailable;
1917       toolBar.activeCompiler.disabled     = unavailable;
1918       toolBar.activeBitDepth.disabled     = unavailable;
1919
1920 #ifndef __WIN32__
1921       debugUseValgrindItem.disabled       = unavailable;
1922       AdjustValgrindMenus();
1923 #endif
1924
1925       AdjustFileMenus();
1926       AdjustBuildMenus();
1927       AdjustDebugMenus();
1928    }
1929
1930 #ifndef __WIN32__
1931    void AdjustValgrindMenus()
1932    {
1933       bool unavailable = !project || !debugUseValgrindItem.checked;
1934       debugValgrindNoLeakCheckItem.disabled        = unavailable;
1935       debugValgrindSummaryLeakCheckItem.disabled   = unavailable;
1936       debugValgrindYesLeakCheckItem.disabled       = unavailable;
1937       debugValgrindFullLeakCheckItem.disabled      = unavailable;
1938
1939       debugValgrindTrackOriginsItem.disabled       = unavailable;
1940
1941       debugValgrindRSDefaultItem.disabled          = unavailable;
1942       debugValgrindRS0Item.disabled                = unavailable;
1943       debugValgrindRS16Item.disabled               = unavailable;
1944       debugValgrindRS32Item.disabled               = unavailable;
1945       debugValgrindRS64Item.disabled               = unavailable;
1946       debugValgrindRS128Item.disabled              = unavailable;
1947       debugValgrindRS256Item.disabled              = unavailable;
1948       debugValgrindRS512Item.disabled              = unavailable;
1949    }
1950 #endif
1951
1952    property bool hasOpenedCodeEditors
1953    {
1954       get
1955       {
1956          Window w;
1957          for(w = firstChild; w; w = w.next)
1958             if(w._class == class(CodeEditor) &&
1959                   w.isDocument && !w.closing && w.visible && w.created &&
1960                   w.fileName && w.fileName[0])
1961                return true;
1962          return false;
1963       }
1964    }
1965
1966    void AdjustFileMenus()
1967    {
1968       bool unavailable = project != null || !hasOpenedCodeEditors; // are they supported source code (ec, c, cpp, etc) ?
1969
1970       projectQuickItem.disabled           = unavailable;
1971    }
1972
1973    void AdjustBuildMenus()
1974    {
1975       bool unavailable = project && projectView.buildInProgress;
1976       bool naForRun = unavailable || !project || project.GetTargetType(project.config) != executable;
1977
1978       projectNewItem.disabled             = unavailable;
1979       toolBar.buttonNewProject.disabled   = unavailable;
1980       projectOpenItem.disabled            = unavailable;
1981       toolBar.buttonOpenProject.disabled  = unavailable;
1982
1983       unavailable = !project || projectView.buildInProgress;
1984
1985       projectCloseItem.disabled           = unavailable;
1986       // toolBar.buttonCloseProject.disabled = unavailable;
1987
1988       projectRunItem.disabled    = naForRun;
1989       toolBar.buttonRun.disabled = naForRun;
1990
1991       projectBuildItem.disabled = false;
1992       projectBuildItem.text     = unavailable ? $"Stop Build" : $"Build";
1993       projectBuildItem.accelerator = unavailable ? Key { pauseBreak, ctrl = true } : f7;
1994
1995       projectLinkItem.disabled                  = unavailable;
1996       toolBar.buttonReLink.disabled             = unavailable;
1997       projectRebuildItem.disabled               = unavailable;
1998       toolBar.buttonRebuild.disabled            = unavailable;
1999       projectCleanItem.disabled                 = unavailable;
2000       toolBar.buttonClean.disabled              = unavailable;
2001       projectCleanTargetItem.disabled           = unavailable;
2002       projectRealCleanItem.disabled             = unavailable;
2003       // toolBar.buttonRealClean.disabled          = unavailable;
2004       projectRegenerateItem.disabled            = unavailable;
2005       toolBar.buttonRegenerateMakefile.disabled = unavailable;
2006 #ifdef IDE_SHOW_INSTALL_MENU_BUTTON
2007       projectInstallItem.disabled               = unavailable;
2008       toolBar.buttonInstall.disabled            = unavailable;
2009 #endif
2010       projectCompileItem.disabled               = unavailable;
2011
2012       AdjustPopupBuildMenus();
2013    }
2014
2015    void AdjustPopupBuildMenus()
2016    {
2017       bool unavailable = !project || projectView.buildInProgress;
2018
2019       if(projectView && projectView.popupMenu && projectView.popupMenu.menu && projectView.popupMenu.created)
2020       {
2021          MenuItem menu;
2022          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectBuild, 0);
2023          if(menu)
2024          {
2025             menu.disabled = false;
2026             menu.text   = unavailable ? $"Stop Build" : $"Build";
2027             menu.accelerator = unavailable ? Key { pauseBreak, ctrl = true } : f7;
2028          }
2029
2030          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectLink, 0);              if(menu) menu.disabled = unavailable;
2031          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRebuild, 0);           if(menu) menu.disabled = unavailable;
2032          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectCleanTarget, 0);       if(menu) menu.disabled = unavailable;
2033          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectClean, 0);             if(menu) menu.disabled = unavailable;
2034          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRealClean, 0);         if(menu) menu.disabled = unavailable;
2035          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRegenerate, 0);        if(menu) menu.disabled = unavailable;
2036          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectInstall, 0);           if(menu) menu.disabled = unavailable;
2037          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRemove, 0);            if(menu) menu.disabled = unavailable;
2038          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileClean, 0);                if(menu) menu.disabled = unavailable;
2039          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileCompile, 0);              if(menu) menu.disabled = unavailable;
2040          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugPrecompile, 0);      if(menu) menu.disabled = unavailable;
2041          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugCompile, 0);         if(menu) menu.disabled = unavailable;
2042          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugGenerateSymbols, 0); if(menu) menu.disabled = unavailable;
2043          projectView.popupMenu.Update(null);
2044       }
2045    }
2046
2047    property bool areDebugMenusUnavailable { get {
2048       return !project ||
2049             project.GetTargetType(project.config) != executable ||
2050             projectView.buildInProgress == buildingMainProject;
2051    } }
2052
2053    property bool isBreakpointTogglingUnavailable { get { return !project; } }
2054    property bool isDebuggerRunning { get { if(ide.debugger) return ide.debugger.state == running; return false; } }
2055    property bool isDebuggerStopped { get { if(ide.debugger) return ide.debugger.state == stopped; return false; } }
2056
2057    void AdjustDebugMenus()
2058    {
2059       bool unavailable = areDebugMenusUnavailable;
2060       bool running = isDebuggerRunning;
2061       bool stopped = isDebuggerStopped;
2062       bool active = debugger.isActive;
2063       bool noBreakpointToggle = !project;
2064
2065       bool isNotRunning    = unavailable || !running;
2066       bool isNotNotRunning = unavailable || running;
2067       bool isNotStopped    = unavailable || !stopped;
2068       bool isNotActive     = unavailable || !active;
2069
2070       debugStartResumeItem.disabled       = isNotNotRunning;
2071       debugStartResumeItem.text           = active ? $"Resume" : $"Start";
2072       debugStartResumeItem.NotifySelect   = active ? MenuDebugResume : MenuDebugStart;
2073       if(toolBar)
2074       {
2075          toolBar.buttonDebugStartResume.disabled      = isNotNotRunning;
2076          toolBar.buttonDebugStartResume.toolTip       = active ? $"Resume" : $"Start";
2077       }
2078
2079       debugBreakItem.disabled             = isNotRunning;
2080       debugStopItem.disabled              = isNotActive;
2081       debugRestartItem.disabled           = isNotActive;
2082       if(toolBar)
2083       {
2084          toolBar.buttonDebugPause.disabled            = isNotRunning;
2085          toolBar.buttonDebugStop.disabled             = isNotActive;
2086          toolBar.buttonDebugRestart.disabled          = isNotActive;
2087       }
2088
2089       debugStepIntoItem.disabled          = isNotNotRunning;
2090       debugStepOverItem.disabled          = isNotNotRunning;
2091       debugSkipStepOverItem.disabled      = isNotNotRunning;
2092       debugStepOutItem.disabled           = isNotStopped;
2093       debugSkipStepOutItem.disabled       = isNotStopped;
2094 #if 0
2095       debugStepUntilItem.disabled         = isNotStopped;
2096       debugSkipStepUntilItem.disabled     = isNotStopped;
2097 #endif
2098       if(toolBar)
2099       {
2100          toolBar.buttonDebugStepInto.disabled         = isNotNotRunning;
2101          toolBar.buttonDebugStepOver.disabled         = isNotNotRunning;
2102          toolBar.buttonDebugSkipStepOver.disabled     = isNotNotRunning;
2103          toolBar.buttonDebugStepOut.disabled          = isNotStopped;
2104          //toolBar.buttonDebugSkipStepOutItem.disabled  = isNotNotRunning;
2105       }
2106       if((Designer)GetActiveDesigner())
2107       {
2108          CodeEditor codeEditor = ((Designer)GetActiveDesigner()).codeEditor;
2109          if(codeEditor)
2110             codeEditor.AdjustDebugMenus();
2111       }
2112    }
2113
2114    void ChangeFileDialogsDirectory(char * directory, bool saveSettings)
2115    {
2116       char tempString[MAX_LOCATION];
2117       strcpy(tempString, directory);
2118       if(saveSettings && !projectView)
2119       {
2120          ideSettings.ideFileDialogLocation = directory;
2121          settingsContainer.Save();
2122       }
2123
2124       ideFileDialog.currentDirectory = tempString;
2125       codeEditorFileDialog.currentDirectory = tempString;
2126       codeEditorFormFileDialog.currentDirectory = tempString;
2127    }
2128
2129    void ChangeProjectFileDialogDirectory(char * directory)
2130    {
2131       ideSettings.ideProjectFileDialogLocation = directory;
2132       settingsContainer.Save();
2133    }
2134
2135    Window FindWindow(char * filePath)
2136    {
2137       Window document = null;
2138
2139       // TOCHECK: Do we need to change slashes here?
2140       for(document = firstChild; document; document = document.next)
2141       {
2142          char * fileName = document.fileName;
2143          if(document.isDocument && fileName && !fstrcmp(fileName, filePath))
2144          {
2145             document.visible = true;
2146             document.Activate();
2147             return document;
2148          }
2149       }
2150       return null;
2151    }
2152
2153    bool DontTerminateDebugSession(char * title)
2154    {
2155       if(debugger.isActive)
2156       {
2157          if(MessageBox { type = yesNo, master = ide,
2158                            contents = $"Do you want to terminate the debugging session in progress?",
2159                            text = title }.Modal() == no)
2160             return true;
2161          /*
2162          MessageBox msg { type = yesNo, master = ide,
2163                            contents = "Do you want to terminate the debugging session in progress?",
2164                            text = title };
2165          if(msg.Modal() == no)
2166          {
2167             msg.Destroy(0);
2168             delete msg;
2169             return true;
2170          }
2171          msg.Destroy(0);
2172          delete msg;*/
2173       }
2174       return false;
2175    }
2176
2177    Window OpenFile(char * origFilePath, bool dontMaximize, bool visible, char * type, OpenCreateIfFails createIfFails, OpenMethod openMethod, bool noParsing)
2178    {
2179       char extension[MAX_EXTENSION] = "";
2180       Window document = null;
2181       bool isProject = false;
2182       bool needFileModified = true;
2183       char winFilePath[MAX_LOCATION];
2184       char * filePath = strstr(origFilePath, "http://") == origFilePath ? strcpy(winFilePath, origFilePath) : GetSystemPathBuffer(winFilePath, origFilePath);
2185       Window currentDoc = activeClient;
2186       bool maximizeDoc = !dontMaximize && ((currentDoc && currentDoc.state == maximized) || (!currentDoc && !projectView));
2187       if(!type)
2188       {
2189          GetExtension(filePath, extension);
2190          strlwr(extension);
2191       }
2192       else
2193          strcpy(extension, type);
2194
2195       if(strcmp(extension, ProjectExtension))
2196       {
2197          for(document = firstChild; document; document = document.next)
2198          {
2199             char * fileName = document.fileName;
2200             if(document.isDocument && fileName && !fstrcmp(fileName, filePath) && document.created)
2201             {
2202                document.visible = true;
2203                if(visible)
2204                   document.Activate();
2205                return document;
2206             }
2207          }
2208       }
2209
2210       if(createIfFails == whatever)
2211          ;
2212       else if(!strcmp(extension, ProjectExtension) || !strcmp(extension, WorkspaceExtension))
2213       {
2214          needFileModified = false;
2215          if(openMethod == normal)
2216          {
2217             if(DontTerminateDebugSession($"Open Project"))
2218                return null;
2219             isProject = true;
2220             if(ProjectClose())
2221             {
2222                if(!projectView)
2223                {
2224                   for(;;)
2225                   {
2226                      Project project;
2227                      Workspace workspace = null;
2228
2229                      if(FileExists(filePath))
2230                      {
2231                         if(!strcmp(extension, ProjectExtension))
2232                         {
2233                            char workspaceFile[MAX_LOCATION];
2234                            strcpy(workspaceFile, filePath);
2235                            ChangeExtension(workspaceFile, WorkspaceExtension, workspaceFile);
2236                            workspace = LoadWorkspace(workspaceFile, filePath);
2237                         }
2238                         else if(!strcmp(extension, WorkspaceExtension))
2239                            workspace = LoadWorkspace(filePath, null);
2240                         else
2241                            return null;
2242                         //project = LoadProject(filePath, null);
2243                      }
2244
2245                      if(workspace)
2246                      {
2247                         char absolutePath[MAX_LOCATION];
2248                         CreateProjectView(workspace, filePath);
2249                         document = projectView;
2250
2251                         toolBox.visible = true;
2252                         sheet.visible = true;
2253                         projectView.MakeActive();
2254
2255                         workspace.ParseLoadedBreakpoints();
2256                         workspace.DropInvalidBreakpoints(null);
2257                         workspace.Save();
2258
2259                         ide.projectView.ShowOutputBuildLog(true);
2260                         {
2261                            CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
2262                            ide.projectView.DisplayCompiler(compiler, false);
2263                            delete compiler;
2264                         }
2265                         UpdateCompilerConfigs(false);
2266                         UpdateMakefiles();
2267                         {
2268                            char newWorkingDir[MAX_LOCATION];
2269                            StripLastDirectory(filePath, newWorkingDir);
2270                            ChangeFileDialogsDirectory(newWorkingDir, false);
2271                         }
2272                         if(document)
2273                            document.fileName = filePath;
2274
2275                         ideMainFrame.SetText("%s - %s", filePath, titleECEREIDE);
2276
2277                         // this crashes on starting ide with epj file, solution please?
2278                         // app.UpdateDisplay();
2279
2280                         workspace.holdTracking = true;
2281                         for(ofi : workspace.openedFiles)
2282                         {
2283                            if(ofi.state != closed)
2284                            {
2285                               Window file = OpenFile(ofi.path, false, true, null, no, normal, noParsing);
2286                               if(file)
2287                               {
2288                                  char fileName[MAX_LOCATION];
2289                                  ProjectNode node;
2290                                  GetLastDirectory(ofi.path, fileName);
2291                                  node = projectView.project.topNode.Find(fileName, true);
2292                                  if(node)
2293                                     node.EnsureVisible();
2294                               }
2295                            }
2296                         }
2297                         ide.RepositionWindows(false);
2298                         workspace.holdTracking = false;
2299
2300                         workspace.timer.Start();
2301
2302 #if !defined(__WIN32__)
2303                         // Valgrind Debug menu updates
2304                         debugUseValgrindItem.checked = workspace.useValgrind;
2305
2306                         debugValgrindNoLeakCheckItem.checked      = workspace.vgLeakCheck == no;
2307                         debugValgrindSummaryLeakCheckItem.checked = workspace.vgLeakCheck == summary;
2308                         debugValgrindYesLeakCheckItem.checked     = workspace.vgLeakCheck == yes;
2309                         debugValgrindFullLeakCheckItem.checked    = workspace.vgLeakCheck == full;
2310
2311                         debugValgrindRSDefaultItem.checked = workspace.vgRedzoneSize == -1;
2312                         debugValgrindRS0Item.checked       = workspace.vgRedzoneSize == 0;
2313                         debugValgrindRS16Item.checked      = workspace.vgRedzoneSize == 16;
2314                         debugValgrindRS32Item.checked      = workspace.vgRedzoneSize == 32;
2315                         debugValgrindRS64Item.checked      = workspace.vgRedzoneSize == 64;
2316                         debugValgrindRS128Item.checked     = workspace.vgRedzoneSize == 128;
2317                         debugValgrindRS256Item.checked     = workspace.vgRedzoneSize == 256;
2318                         debugValgrindRS512Item.checked     = workspace.vgRedzoneSize == 512;
2319
2320                         debugValgrindTrackOriginsItem.checked = workspace.vgTrackOrigins;
2321 #endif
2322
2323                         findInFilesDialog.mode = FindInFilesMode::project;
2324                         findInFilesDialog.currentDirectory = ide.project.topNode.path;
2325
2326                         {
2327                            char location[MAX_LOCATION];
2328                            StripLastDirectory(ide.project.topNode.path, location);
2329                            ChangeProjectFileDialogDirectory(location);
2330                         }
2331
2332                         break;
2333                      }
2334                      else
2335                      {
2336                         if(MessageBox { type = yesNo, master = this, text = $"Error opening project", contents = $"Open a different project?" }.Modal() == yes)
2337                         {
2338                            ideProjectFileDialog.text = openProjectFileDialogTitle;
2339                            if(ideProjectFileDialog.Modal() == cancel)
2340                               return null;
2341                            filePath = ideProjectFileDialog.filePath;
2342                            GetExtension(filePath, extension);
2343                         }
2344                         else
2345                            return null;
2346                      }
2347                   }
2348                }
2349             }
2350             else
2351                return null;
2352          }
2353          else if(openMethod == add)
2354          {
2355             if(workspace)
2356             {
2357                Project prj = null;
2358                char slashFilePath[MAX_LOCATION];
2359                GetSlashPathBuffer(slashFilePath, filePath);
2360                for(p : workspace.projects)
2361                {
2362                   if(!fstrcmp(p.filePath, slashFilePath))
2363                   {
2364                      prj = p;
2365                      break;
2366                   }
2367                }
2368                if(prj)
2369                {
2370                   MessageBox { type = ok, parent = parent, master = this, text = $"Same Project",
2371                         contents = $"This project is already present in workspace." }.Modal();
2372                }
2373                else
2374                {
2375                   prj = LoadProject(filePath, null);
2376                   if(prj)
2377                   {
2378                      char * activeConfigName = null;
2379                      CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
2380                      prj.StartMonitoring();
2381                      workspace.projects.Add(prj);
2382                      if(toolBar.activeConfig.currentRow && toolBar.activeConfig.currentRow != toolBar.activeConfig.firstRow &&
2383                            toolBar.activeConfig.currentRow.string && toolBar.activeConfig.currentRow.string[0])
2384                         activeConfigName = toolBar.activeConfig.currentRow.string;
2385                      if(activeConfigName)
2386                      {
2387                         for(cfg : prj.configurations)
2388                         {
2389                            if(cfg.name && !strcmp(cfg.name, activeConfigName))
2390                            {
2391                               prj.config = cfg;
2392                               break;
2393                            }
2394                         }
2395                      }
2396                      if(projectView)
2397                         projectView.AddNode(prj.topNode, null);
2398                      workspace.modified = true;
2399                      workspace.Save();
2400                      findInFilesDialog.AddProjectItem(prj);
2401                      projectView.ShowOutputBuildLog(true);
2402                      projectView.DisplayCompiler(compiler, false);
2403                      projectView.ProjectUpdateMakefileForAllConfigs(prj);
2404                      delete compiler;
2405
2406                      {
2407                         char location[MAX_LOCATION];
2408                         StripLastDirectory(prj.topNode.path, location);
2409                         ChangeProjectFileDialogDirectory(location);
2410                      }
2411
2412                      // projectView is associated with the main project and not with the one just added but
2413                      return projectView; // just to let the caller know something was opened
2414                   }
2415                }
2416             }
2417             else
2418                return null;
2419          }
2420       }
2421       else if(!strcmp(extension, "bmp") || !strcmp(extension, "pcx") ||
2422             !strcmp(extension, "jpg") || !strcmp(extension, "gif") ||
2423             !strcmp(extension, "jpeg") || !strcmp(extension, "png"))
2424       {
2425          if(FileExists(filePath))
2426             document = PictureEdit { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2427                                        hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2428                                        visible = visible, bitmapFile = filePath, OnClose = PictureEditOnClose/*why?--GenericDocumentOnClose*/;
2429                                     };
2430          if(!document)
2431             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2432       }
2433 #ifndef NO3D
2434       else if(!strcmp(extension, "3ds"))
2435       {
2436          if(FileExists(filePath))
2437             document = ModelView { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2438                                     hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2439                                     visible = visible, modelFile = filePath, OnClose = ModelViewOnClose/*why?--GenericDocumentOnClose*/
2440                                     };
2441
2442          if(!document)
2443             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2444       }
2445 #endif
2446       else if(!strcmp(extension, "txt") || !strcmp(extension, "text") ||
2447             !strcmp(extension, "nfo") || !strcmp(extension, "info") ||
2448             !strcmp(extension, "htm") || !strcmp(extension, "html") ||
2449             !strcmp(extension, "css") || !strcmp(extension, "php") ||
2450             !strcmp(extension, "js"))
2451       {
2452          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2453          editor.updatingCode = true;
2454          if(editor.LoadFile(filePath))
2455          {
2456             document = editor;
2457             editor.visible = true;
2458          }
2459          else
2460             delete editor;
2461          needFileModified = false;
2462       }
2463       else
2464       {
2465          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2466          if(editor.LoadFile(filePath))
2467          {
2468             document = editor;
2469             editor.visible = true;
2470          }
2471          else
2472             delete editor;
2473          needFileModified = false;
2474       }
2475
2476       if(document && (document._class == class(PictureEdit) ||
2477             document._class == class(ModelView)))
2478       {
2479          document.Create();
2480          if(document)
2481          {
2482             document.fileName = filePath;
2483             if(workspace && !workspace.holdTracking)
2484                workspace.UpdateOpenedFileInfo(filePath, opened);
2485          }
2486       }
2487
2488       if(!document && createIfFails != no)
2489       {
2490          if(createIfFails != yes && !needFileModified &&
2491                MessageBox { type = yesNo, master = this, text = filePath, contents = $"File doesn't exist. Create?" }.Modal() == yes)
2492             createIfFails = yes;
2493          if(createIfFails == yes || createIfFails == whatever)
2494          {
2495             document = (Window)NewCodeEditor(this, maximizeDoc ? maximized : normal, true);
2496             if(document)
2497                document.fileName = filePath;
2498          }
2499       }
2500
2501       if(document)
2502       {
2503          if(projectView && document._class == class(CodeEditor) && workspace)
2504          {
2505             int lineNumber, position;
2506             Point scroll;
2507             CodeEditor editor = (CodeEditor)document;
2508             editor.openedFileInfo = workspace.UpdateOpenedFileInfo(filePath, opened);
2509             editor.openedFileInfo.holdTracking = true;
2510             lineNumber = Max(editor.openedFileInfo.lineNumber - 1, 0);
2511             position = Max(editor.openedFileInfo.position - 1, 0);
2512             if(editor.editBox.GoToLineNum(lineNumber))
2513                editor.editBox.GoToPosition(editor.editBox.line, lineNumber, position);
2514             scroll.x = Max(editor.openedFileInfo.scroll.x, 0);
2515             scroll.y = Max(editor.openedFileInfo.scroll.y, 0);
2516             editor.editBox.scroll = scroll;
2517             editor.openedFileInfo.holdTracking = false;
2518          }
2519
2520          if(needFileModified)
2521             document.OnFileModified = OnFileModified;
2522          document.NotifySaved = DocumentSaved;
2523          if(maximizeDoc && document.hasMaximize)
2524             document.state = maximized;
2525
2526          if(isProject)
2527             ideSettings.AddRecentProject(document.fileName);
2528          else
2529             ideSettings.AddRecentFile(document.fileName);
2530          ide.UpdateRecentMenus();
2531          ide.AdjustFileMenus();
2532          settingsContainer.Save();
2533
2534          return document;
2535       }
2536       else
2537          return null;
2538    }
2539
2540    // TOCHECK: I can't use a generic one for both ModelView and PictureEdit both derived from Window
2541    /*bool Window::GenericDocumentOnClose(bool parentClosing)
2542    {
2543       if(!parentClosing && ide.workspace)
2544          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2545       return true;
2546    }*/
2547    bool ModelView::ModelViewOnClose(bool parentClosing)
2548    {
2549       if(!parentClosing && ide.workspace)
2550          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2551       return true;
2552    }
2553    bool PictureEdit::PictureEditOnClose(bool parentClosing)
2554    {
2555       if(!parentClosing && ide.workspace)
2556          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2557       return true;
2558    }
2559
2560    /*
2561    void OnUnloadGraphics(Window window)
2562    {
2563       display.ClearMaterials();
2564       display.ClearTextures();
2565       display.ClearMeshes();
2566    }
2567    */
2568
2569    void UpdateStateLight(StatusField fld, bool on)
2570    {
2571       fld.color = on ? lime : Color { 128,128,128 };
2572       fld.backColor = on ? dimGray : 0;
2573       fld.bold = on;
2574    }
2575
2576    bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
2577    {
2578       UpdateStateLight(caps, app.GetKeyState(capsState));
2579       UpdateStateLight(num, app.GetKeyState(numState));
2580       return true;
2581    }
2582
2583    bool OnKeyDown(Key key, unichar ch)
2584    {
2585       switch(key)
2586       {
2587          case b: projectView.Update(null); break;
2588          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2589          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2590       }
2591       return true;
2592    }
2593
2594    bool OnKeyUp(Key key, unichar ch)
2595    {
2596       switch(key)
2597       {
2598          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2599          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2600       }
2601       return true;
2602    }
2603
2604    void GoToError(const char * line, bool noParsing)
2605    {
2606       if(projectView)
2607          projectView.GoToError(line, noParsing);
2608    }
2609
2610    void CodeLocationParseAndGoTo(const char * text, Project project, const char * dir)
2611    {
2612       char *s = null;
2613       char *path = text;
2614       char *colon = strchr(text, ':');
2615       char filePath[MAX_LOCATION] = "";
2616       char completePath[MAX_LOCATION];
2617       int line = 0, col = 0;
2618       int len = strlen(text);
2619       Project prj = null;
2620       FileAttribs fileAttribs;
2621
2622       // support for valgrind output
2623       if((s = strstr(text, "==")) && (s = strstr(s+2, "==")) && (s = strstr(s+2, ":")) && (s = strstr(s+1, ":")))
2624       {
2625          colon = s;
2626          for(; s>text; s--)
2627          {
2628             if(*s == '(')
2629             {
2630                path = s+1;
2631                break;
2632             }
2633          }
2634          /*for(s=colon; *s; s++)
2635          {
2636             if(*s == ')')
2637             {
2638                *s = '\0';;
2639                break;
2640             }
2641          }*/
2642          //*colon = '\0';
2643          //line = atoi(colon+1);
2644       }
2645       // support for "Found n match(es) in "file/path";
2646       else if(path[len-1] == '\"' && strstr(path, $"Found %d match%s in \"%s\"%s\n\n"."Found") && strstr(path, $"match") && strstr(path, $"in") && (s = strstr(path, "\"")) && s != path+len-1)
2647       {
2648          path = s+1;
2649       }
2650       else
2651       {
2652          if(colon && (colon[1] == '/' || colon[1] == '\\'))
2653          {
2654             path = (colon - 1 > path) ? colon - 1 : path;
2655             colon = strstr(colon + 1, ":");
2656          }
2657          if(*path == '*' && (s = strchr(path+1, '*')))
2658             path = s+1;
2659          while(isspace(*path)) path++;
2660       }
2661       if(*path == '(')
2662       {
2663          char * close = strchr(path, ')');
2664          if(close)
2665          {
2666             char name[256];
2667             strncpy(name, path+1, close - path - 1);
2668             name[close - path - 1] = '\0';
2669             for(p : ide.workspace.projects)
2670             {
2671                if(!strcmp(p.name, name))
2672                {
2673                   path = close + 1;
2674                   prj = p;
2675                   break;
2676                }
2677             }
2678          }
2679       }
2680       if(!prj)
2681          prj = project ? project : (dir ? null : ide.project);
2682       if(colon)
2683       {
2684          strncpy(filePath, path, colon - path);
2685          filePath[colon - path] = '\0';
2686          line = atoi(colon + 1);
2687          colon = strstr(colon + 1, ":");
2688          if(colon)
2689             col = atoi(colon + 1);
2690       }
2691       else if(path - 1 >= text && *(path - 1) == '\"')
2692       {
2693          colon = strchr(path, '\"');
2694          if(colon)
2695          {
2696             strncpy(filePath, path, colon - path);
2697             filePath[colon - path] = '\0';
2698          }
2699       }
2700       else if(path && !colon)
2701       {
2702          strcpy(filePath, path);
2703       }
2704
2705       if(filePath[0])
2706       {
2707          if(prj)
2708             strcpy(completePath, prj.topNode.path);
2709          else if(dir && dir[0])
2710             strcpy(completePath, dir);
2711          else
2712             completePath[0] = '\0';
2713          PathCat(completePath, filePath);
2714
2715          if((fileAttribs = FileExists(completePath)))
2716             CodeLocationGoTo(completePath, fileAttribs, line, col);
2717          else if(ide.workspace)
2718          {
2719             bool done = false;
2720             for(p : ide.workspace.projects)
2721             {
2722                strcpy(completePath, p.topNode.path);
2723                PathCat(completePath, filePath);
2724                if((fileAttribs = FileExists(completePath)).isFile)
2725                {
2726                   CodeLocationGoTo(completePath, fileAttribs, line, col);
2727                   done = true;
2728                   break;
2729                }
2730             }
2731             if(!done)
2732             {
2733                for(p : ide.workspace.projects)
2734                {
2735                   ProjectNode node = p.topNode.Find(filePath, false);
2736                   if(node)
2737                   {
2738                      node.GetFullFilePath(completePath);
2739                      if((fileAttribs = FileExists(completePath)).isFile)
2740                      {
2741                         CodeLocationGoTo(completePath, fileAttribs, line, col);
2742                         break;
2743                      }
2744                   }
2745                }
2746             }
2747          }
2748       }
2749    }
2750
2751    void CodeLocationGoTo(const char * path, const FileAttribs fileAttribs, int line, int col)
2752    {
2753       if(fileAttribs.isFile)
2754       {
2755          char ext[MAX_EXTENSION];
2756          GetExtension(path, ext);
2757          strlwr(ext);
2758          if(!strcmp(ext, "mp3") || !strcmp(ext, "flac") || !strcmp(ext, "ogg") || !strcmp(ext, "avi") || !strcmp(ext, "mkv"))
2759             ShellOpen(path);
2760          else if(!strcmp(ext, "a") || !strcmp(ext, "o") || !strcmp(ext, "lib") || !strcmp(ext, "dll") || !strcmp(ext, "exe"))
2761          {
2762             char dirPath[MAX_LOCATION];
2763             StripLastDirectory(path, dirPath);
2764             ShellOpen(dirPath);
2765          }
2766          else
2767          {
2768             CodeEditor codeEditor = (CodeEditor)OpenFile(path, false, true, ext, no, normal, false);
2769             if(codeEditor && line)
2770             {
2771                EditBox editBox = codeEditor.editBox;
2772                editBox.GoToLineNum(line - 1);
2773                editBox.GoToPosition(editBox.line, line - 1, col ? (col - 1) : 0);
2774             }
2775          }
2776       }
2777       else if(fileAttribs.isDirectory)
2778          ShellOpen(path);
2779    }
2780
2781    void OnRedraw(Surface surface)
2782    {
2783       Bitmap bitmap = back.bitmap;
2784       if(bitmap)
2785          surface.Blit(bitmap, (clientSize.w - bitmap.width) / 2, (clientSize.h - bitmap.height) / 2, 0, 0, bitmap.width, bitmap.height);
2786    }
2787
2788    void SheetSelected(SheetType sheetSelected)
2789    {
2790       if(activeChild == sheet)
2791       {
2792          if(sheetSelected == methods)
2793          {
2794             viewPropertiesItem.accelerator = f4;
2795             viewPropertiesItem.parent = viewMenu;
2796             viewMethodsItem.parent = null;
2797          }
2798          else
2799          {
2800             viewMethodsItem.accelerator = f4;
2801             viewMethodsItem.parent = viewMenu;
2802             viewPropertiesItem.parent = null;
2803          }
2804       }
2805       else
2806       {
2807          viewMethodsItem.parent = viewMenu;
2808          viewPropertiesItem.parent = viewMenu;
2809          if(sheetSelected == methods)
2810          {
2811             viewMethodsItem.accelerator = f4;
2812             viewPropertiesItem.accelerator = 0;
2813          }
2814          else
2815          {
2816             viewMethodsItem.accelerator = 0;
2817             viewPropertiesItem.accelerator = f4;
2818          }
2819       }
2820    }
2821
2822    void OnActivateClient(Window client, Window previous)
2823    {
2824       //if(!client || client != previous)
2825       {
2826          Class dataType;
2827          if(!client || client != previous)
2828          {
2829             if(previous)
2830                dataType = previous._class;
2831             if(previous && !strcmp(dataType.name, "CodeEditor"))
2832             {
2833                ((CodeEditor)previous).UpdateFormCode();
2834             }
2835             else if(previous && !strcmp(dataType.name, "Designer"))
2836             {
2837                ((Designer)previous).codeEditor.UpdateFormCode();
2838             }
2839          }
2840
2841          if(client)
2842             dataType = client._class;
2843          if(client && !strcmp(dataType.name, "CodeEditor"))
2844          {
2845             CodeEditor codeEditor = (CodeEditor)client;
2846             SetPrivateModule(codeEditor.privateModule);
2847             SetCurrentContext(codeEditor.globalContext);
2848             SetTopContext(codeEditor.globalContext);
2849             SetGlobalContext(codeEditor.globalContext);
2850
2851             SetDefines(&codeEditor.defines);
2852             SetImports(&codeEditor.imports);
2853
2854             SetActiveDesigner(codeEditor.designer);
2855
2856             sheet.codeEditor = codeEditor;
2857             toolBox.codeEditor = codeEditor;
2858
2859             viewDesignerItem.parent = viewMenu;
2860             if(activeChild != codeEditor)
2861             {
2862                viewCodeItem.parent = viewMenu;
2863                viewDesignerItem.accelerator = 0;
2864                viewCodeItem.accelerator = f8;
2865             }
2866             else
2867             {
2868                viewCodeItem.parent = null;
2869                viewDesignerItem.accelerator = f8;
2870             }
2871          }
2872          else if(client && !strcmp(dataType.name, "Designer"))
2873          {
2874             CodeEditor codeEditor = ((Designer)client).codeEditor;
2875             if(codeEditor)
2876             {
2877                SetPrivateModule(codeEditor.privateModule);
2878                SetCurrentContext(codeEditor.globalContext);
2879                SetTopContext(codeEditor.globalContext);
2880                SetGlobalContext(codeEditor.globalContext);
2881                SetDefines(&codeEditor.defines);
2882                SetImports(&codeEditor.imports);
2883             }
2884             else
2885             {
2886                SetPrivateModule(null);
2887                SetCurrentContext(null);
2888                SetTopContext(null);
2889                SetGlobalContext(null);
2890                SetDefines(null);
2891                SetImports(null);
2892             }
2893
2894             SetActiveDesigner((Designer)client);
2895
2896             sheet.codeEditor = codeEditor;
2897             toolBox.codeEditor = codeEditor;
2898
2899             viewCodeItem.parent = viewMenu;
2900             if(activeChild != client)
2901             {
2902                viewDesignerItem.parent = viewMenu;
2903                viewDesignerItem.accelerator = f8;
2904                viewCodeItem.accelerator = 0;
2905             }
2906             else
2907             {
2908                viewDesignerItem.parent = null;
2909                viewCodeItem.accelerator = f8;
2910             }
2911          }
2912          else
2913          {
2914             if(!client && !projectView && sheet.visible)
2915             {
2916                if(sheet)
2917                   sheet.visible = false;
2918                toolBox.visible = false;
2919             }
2920             if(sheet)
2921                sheet.codeEditor = null;
2922             toolBox.codeEditor = null;
2923             SetActiveDesigner(null);
2924
2925             viewDesignerItem.parent = null;
2926             viewCodeItem.parent = null;
2927          }
2928          if(sheet)
2929             SheetSelected(sheet.sheetSelected);
2930       }
2931
2932       projectCompileItem = null;
2933
2934       if(statusBar)
2935       {
2936          statusBar.Clear();
2937          if(client && client._class == eSystem_FindClass(__thisModule, "CodeEditor")) // !strcmp(client._class.name, "CodeEditor")
2938          {
2939             CodeEditor codeEditor = (CodeEditor)client;
2940             EditBox editBox = codeEditor.editBox;
2941
2942             statusBar.AddField(pos);
2943
2944             caps = { width = 40, text = $"CAPS" };
2945             statusBar.AddField(caps);
2946             UpdateStateLight(caps, app.GetKeyState(capsState));
2947
2948             ovr = { width = 36, text = $"OVR" };
2949             statusBar.AddField(ovr);
2950             UpdateStateLight(ovr, (editBox && editBox.overwrite));
2951
2952             num = { width = 36, text = $"NUM" };
2953             statusBar.AddField(num);
2954             UpdateStateLight(num, app.GetKeyState(numState));
2955
2956             //statusBar.text = "Ready";
2957
2958             if(projectView && projectView.project)
2959             {
2960                bool isCObject = false;
2961                ProjectNode node = projectView.GetNodeFromWindow(client, null, true, false, null);
2962                if(!node && (node = projectView.GetNodeFromWindow(client, null, true, true, null)))
2963                   isCObject = true;
2964                if(node)
2965                {
2966                   char nodeName[MAX_FILENAME];
2967                   char name[MAX_FILENAME+96];
2968                   if(isCObject)
2969                      ChangeExtension(node.name, "c", nodeName);
2970                   sprintf(name, $"Compile %s", isCObject ? nodeName : node.name);
2971                   projectCompileItem =
2972                   {
2973                      copyText = true, text = name, c, ctrlF7, disabled = projectView.buildInProgress;
2974
2975                      bool NotifySelect(MenuItem selection, Modifiers mods)
2976                      {
2977                         if(projectView)
2978                         {
2979                            bool isCObject = false;
2980                            bool isExcluded = false;
2981                            ProjectNode node = projectView.GetNodeForCompilationFromWindow(activeClient, true, &isExcluded, &isCObject);
2982                            if(node)
2983                            {
2984                               if(isExcluded)
2985                                  ide.outputView.buildBox.Logf($"%s %s is excluded from current build configuration.\n", isCObject ? "Object file" : "File", node.name);
2986                               else
2987                               {
2988                                  List<ProjectNode> nodes { };
2989                                  nodes.Add(node);
2990                                  projectView.Compile(node.project, nodes, mods.ctrl && mods.shift, isCObject ? cObject : normal);
2991                                  delete nodes;
2992                               }
2993                            }
2994                         }
2995                         return true;
2996                      }
2997                   };
2998                   projectMenu.AddDynamic(projectCompileItem, ide, false);
2999                }
3000             }
3001          }
3002          else
3003          {
3004             caps = ovr = num = null;
3005          }
3006       }
3007    }
3008
3009    bool OnClose(bool parentClosing)
3010    {
3011       //return !projectView.buildInProgress;
3012       if(projectView && projectView.buildInProgress)
3013          return false;
3014       if(DontTerminateDebugSession($"Close IDE"))
3015          return false;
3016       if(findInFilesDialog)
3017          findInFilesDialog.SearchStop();
3018       if(workspace)
3019       {
3020          workspace.timer.Stop();
3021          workspace.Save();
3022       }
3023       ideMainFrame.Destroy(0);
3024       return true;
3025    }
3026
3027    bool OnPostCreate()
3028    {
3029       int c;
3030       bool passThrough = false;
3031       bool debugStart = false;
3032       bool debugWorkDir = false;
3033       char * passDebugWorkDir = null;
3034       bool openAsText = false;
3035       DynamicString passArgs { };
3036       int ptArg = 0;
3037
3038       for(c = 1; c<app.argc; c++)
3039       {
3040          if(passThrough)
3041          {
3042             char * arg = app.argv[c];
3043             char * buf = new char[strlen(arg)*2+1];
3044             if(ptArg++ > 0)
3045                passArgs.concat(" ");
3046             PassArg(buf, arg);
3047             passArgs.concat(buf);
3048             delete buf;
3049          }
3050          else if(debugWorkDir)
3051          {
3052             passDebugWorkDir = CopyString(app.argv[c]);
3053             StripQuotes(passDebugWorkDir, passDebugWorkDir);
3054             debugWorkDir = false;
3055          }
3056          else if(!strcmp(app.argv[c], "-t"))
3057             openAsText = true;
3058          else if(!strcmp(app.argv[c], "-no-parsing"))
3059             ide.noParsing = true;
3060          else if(!strcmp(app.argv[c], "-debug-start"))
3061             debugStart = true;
3062          else if(!strcmp(app.argv[c], "-debug-work-dir"))
3063             debugWorkDir = true;
3064          else if(!strcmp(app.argv[c], "-@"))
3065             passThrough = true;
3066          else
3067          {
3068             char fullPath[MAX_LOCATION];
3069             char parentPath[MAX_LOCATION];
3070             char ext[MAX_EXTENSION];
3071             bool isProject;
3072             FileAttribs dirAttribs;
3073             GetWorkingDir(fullPath, MAX_LOCATION);
3074             PathCat(fullPath, app.argv[c]);
3075             StripLastDirectory(fullPath, parentPath);
3076             GetExtension(app.argv[c], ext);
3077             isProject = !openAsText && !strcmpi(ext, "epj");
3078
3079             if(isProject && c > (debugStart ? 2 : 1)) continue;
3080
3081             // Create directory for projects (only)
3082             if(((dirAttribs = FileExists(parentPath)) && dirAttribs.isDirectory) || isProject)
3083             {
3084                if(isProject && !FileExists(fullPath))
3085                {
3086                   char name[MAX_LOCATION];
3087                   NewProjectDialog newProjectDialog;
3088
3089                   if(projectView)
3090                   {
3091                      projectView.visible = false;
3092                      if(!projectView.Destroy(0))
3093                         return true;
3094                   }
3095
3096                   newProjectDialog = { master = this };
3097
3098                   strcpy(name, app.argv[c]);
3099                   StripExtension(name);
3100                   GetLastDirectory(name, name);
3101                   newProjectDialog.projectName.contents = name;
3102                   newProjectDialog.projectName.NotifyModified(newProjectDialog, newProjectDialog.projectName);
3103                   newProjectDialog.locationEditBox.path = parentPath;
3104                   newProjectDialog.NotifyModifiedLocation(newProjectDialog.locationEditBox);
3105
3106                   incref newProjectDialog;
3107                   newProjectDialog.Modal();
3108                   if(projectView)
3109                   {
3110                      ideSettings.AddRecentProject(projectView.fileName);
3111                      ide.UpdateRecentMenus();
3112                      settingsContainer.Save();
3113                   }
3114                   delete newProjectDialog;
3115                   // Open only one project
3116                   break;
3117                }
3118                else
3119                   ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3120             }
3121             else if(strstr(fullPath, "http://") == fullPath)
3122                ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3123          }
3124       }
3125       if(passThrough && projectView && projectView.project && workspace)
3126          workspace.commandLineArgs = passArgs;
3127       if(passDebugWorkDir && projectView && projectView.project && workspace)
3128       {
3129          workspace.debugDir = passDebugWorkDir;
3130          delete passDebugWorkDir;
3131       }
3132       if(debugStart)
3133          ;//MenuDebugStart(debugStartResumeItem, 0); // <-- how TODO this without getting into the app.Wait lock
3134
3135       UpdateToolBarActiveConfigs(false);
3136       UpdateToolBarActiveCompilers();
3137       delete passArgs;
3138       return true;
3139    }
3140
3141    void OnDestroy()
3142    {
3143       // IS THIS NEEDED? WASN'T HERE BEFORE...  Crashes on getting node's projectView otherwise
3144       if(projectView)
3145       {
3146          projectView.visible = false;
3147          projectView.Destroy(0);
3148          projectView = null;
3149       }
3150 #ifdef GDB_DEBUG_GUI
3151       gdbDialog.Destroy(0);
3152       delete gdbDialog;
3153 #endif
3154    }
3155
3156    void SetPath(bool projectsDirs, CompilerConfig compiler, ProjectConfig config, int bitDepth)
3157    {
3158       int c, len, count;
3159       char * newList;
3160       char * oldPaths[128];
3161       String oldList = new char[maxPathLen];
3162       Array<String> newExePaths { };
3163       //Map<String, bool> exePathExists { };
3164       bool found = false;
3165 #if defined(__unix__) || defined(__APPLE__)
3166       Array<String> newLibPaths { };
3167       Map<String, bool> libPathExists { };
3168 #endif
3169
3170       if(projectsDirs)
3171       {
3172          for(prj : workspace.projects)
3173          {
3174             DirExpression targetDirExp;
3175
3176             // SKIP FIRST PROJECT...
3177             if(prj == workspace.projects.firstIterator.data) continue;
3178
3179             // NOTE: Right now the additional project config dir will be
3180             //       obtained when the debugger is started, so toggling it
3181             //       while building will change which library gets used.
3182             //       To go with the initial state, e.g. when F5 was pressed,
3183             //       we nould need to keep a list of all project's active
3184             //       config upon startup.
3185             targetDirExp = prj.GetTargetDir(compiler, prj.config, bitDepth);
3186
3187             /*if(prj.config.targetType == sharedLibrary && prj.config.debug)
3188                cfg = prj.config;
3189             else
3190             {
3191                for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3192                   if(cfg.targetType == sharedLibrary && cfg.debug && !strcmpi(cfg.name, "Debug"))
3193                      break;
3194                if(!cfg)
3195                {
3196                   for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3197                      if(cfg.targetType == sharedLibrary && cfg.debug)
3198                         break;
3199                }
3200             }*/
3201             if(targetDirExp.dir)
3202             {
3203                char buffer[MAX_LOCATION];
3204 #if defined(__WIN32__)
3205                Array<String> paths = newExePaths;
3206 #else
3207                Array<String> paths = newLibPaths;
3208 #endif
3209                GetSystemPathBuffer(buffer, prj.topNode.path);
3210                PathCat(buffer, targetDirExp.dir);
3211                for(p : paths)
3212                {
3213                   if(!fstrcmp(p, buffer))
3214                   {
3215                      found = true;
3216                      break;
3217                   }
3218                }
3219                if(!found)
3220                   paths.Add(CopyString(buffer));
3221             }
3222             delete targetDirExp;
3223          }
3224       }
3225
3226       for(item : compiler.executableDirs)
3227       {
3228          found = false;
3229          for(p : newExePaths)
3230          {
3231             if(!fstrcmp(p, item))
3232             {
3233                found = true;
3234                break;
3235             }
3236          }
3237          if(!found)
3238             newExePaths.Add(CopySystemPath(item));
3239       }
3240
3241       GetEnvironment("PATH", oldList, maxPathLen);
3242 /*#ifdef _DEBUG
3243       printf("Old value of PATH: %s\n", oldList);
3244 #endif*/
3245       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3246       for(c = 0; c < count; c++)
3247       {
3248          found = false;
3249          for(p : newExePaths)
3250          {
3251             if(!fstrcmp(p, oldPaths[c]))
3252             {
3253                found = true;
3254                break;
3255             }
3256          }
3257          if(!found)
3258             newExePaths.Add(CopySystemPath(oldPaths[c]));
3259       }
3260
3261       len = 0;
3262       for(path : newExePaths)
3263          len += strlen(path) + 1;
3264       newList = new char[len + 1];
3265       newList[0] = '\0';
3266       for(path : newExePaths)
3267       {
3268          strcat(newList, path);
3269          strcat(newList, pathListSep);
3270       }
3271       newList[len - 1] = '\0';
3272       SetEnvironment("PATH", newList);
3273 /*#ifdef _DEBUG
3274       printf("New value of PATH: %s\n", newList);
3275 #endif*/
3276       delete newList;
3277
3278       newExePaths.Free();
3279       delete newExePaths;
3280
3281 #if defined(__unix__) || defined(__APPLE__)
3282
3283       for(item : compiler.libraryDirs)
3284       {
3285          if(!libPathExists[item])  // fstrcmp should be used
3286          {
3287             String s = CopyString(item);
3288             newLibPaths.Add(s);
3289             libPathExists[s] = true;
3290          }
3291       }
3292
3293 #if defined(__APPLE__)
3294       GetEnvironment("DYLD_LIBRARY_PATH", oldList, maxPathLen);
3295 #else
3296       GetEnvironment("LD_LIBRARY_PATH", oldList, maxPathLen);
3297 #endif
3298 /*#ifdef _DEBUG
3299       printf("Old value of [DY]LD_LIBRARY_PATH: %s\n", oldList);
3300 #endif*/
3301       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3302       for(c = 0; c < count; c++)
3303       {
3304          if(!libPathExists[oldPaths[c]])  // fstrcmp should be used
3305          {
3306             String s = CopyString(oldPaths[c]);
3307             newLibPaths.Add(s);
3308             libPathExists[s] = true;
3309          }
3310       }
3311
3312       len = 0;
3313       for(path : newLibPaths)
3314          len += strlen(path) + 1;
3315       newList = new char[len + 1];
3316       newList[0] = '\0';
3317       for(path : newLibPaths)
3318       {
3319          strcat(newList, path);
3320          strcat(newList, pathListSep);
3321       }
3322       newList[len - 1] = '\0';
3323 #if defined(__APPLE__)
3324       SetEnvironment("DYLD_LIBRARY_PATH", newList);
3325 #else
3326       SetEnvironment("LD_LIBRARY_PATH", newList);
3327 #endif
3328 /*#ifdef _DEBUG
3329       printf("New value of [DY]LD_LIBRARY_PATH: %s\n", newList);
3330 #endif*/
3331       delete newList;
3332
3333       newLibPaths.Free();
3334       delete newLibPaths;
3335       delete libPathExists;
3336 #endif
3337
3338       if(compiler.distccEnabled && compiler.distccHosts)
3339          SetEnvironment("DISTCC_HOSTS", compiler.distccHosts);
3340
3341       delete oldList;
3342    }
3343
3344    void DestroyTemporaryProjectDir()
3345    {
3346       if(tmpPrjDir && tmpPrjDir[0])
3347       {
3348          if(FileExists(tmpPrjDir).isDirectory)
3349             DestroyDir(tmpPrjDir);
3350          property::tmpPrjDir = null;
3351       }
3352    }
3353
3354    IDEWorkSpace()
3355    {
3356       // Graphics Driver Menu
3357       int c;
3358
3359       /*
3360       app.currentSkin.selectionColor = selectionColor;
3361       app.currentSkin.selectionText = selectionText;
3362       */
3363
3364 /*
3365       driverItems = new MenuItem[app.numDrivers];
3366       for(c = 0; c < app.numDrivers; c++)
3367       {
3368          driverItems[c] = MenuItem { driversMenu, app.drivers[c], NotifySelect = NotifySelectDisplayDriver };
3369          driverItems[c].id = c;
3370          driverItems[c].isRadio = true;
3371       }
3372 */
3373       driverItems = new MenuItem[2];
3374 #if defined(__unix__)
3375          driverItems[0] = MenuItem { driversMenu, "X", NotifySelect = NotifySelectDisplayDriver };
3376          driverItems[0].id = 0;
3377          driverItems[0].isRadio = true;
3378 #else
3379          driverItems[0] = MenuItem { driversMenu, "GDI", NotifySelect = NotifySelectDisplayDriver };
3380          driverItems[0].id = 0;
3381          driverItems[0].isRadio = true;
3382 #endif
3383          driverItems[1] = MenuItem { driversMenu, "OpenGL", NotifySelect = NotifySelectDisplayDriver };
3384          driverItems[1].id = 1;
3385          driverItems[1].isRadio = true;
3386
3387 /*      skinItems = new MenuItem[app.numSkins];
3388       for(c = 0; c < app.numSkins; c++)
3389       {
3390          skinItems[c] = MenuItem {skinsMenu, app.skins[c], NotifySelect = NotifySelectDisplaySkin };
3391          skinItems[c].id = c;
3392          skinItems[c].isRadio = true;
3393       }
3394 */
3395       ideFileDialog.master = this;
3396       ideProjectFileDialog.master = this;
3397
3398       //SetDriverAndSkin();
3399       return true;
3400    }
3401
3402    void UpdateRecentMenus()
3403    {
3404       int c;
3405       Menu fileMenu = menu.FindMenu($"File");
3406       Menu recentFiles = fileMenu.FindMenu($"Recent Files");
3407       Menu recentProjects = fileMenu.FindMenu($"Recent Projects");
3408       char * itemPath = new char[MAX_LOCATION];
3409       char * itemName = new char[MAX_LOCATION+4];
3410       MenuItem item;
3411
3412       recentFiles.Clear();
3413       c = 0;
3414
3415       for(recent : ideSettings.recentFiles)
3416       {
3417          strncpy(itemPath, recent, MAX_LOCATION); itemPath[MAX_LOCATION-1] = '\0';
3418          MakeSystemPath(itemPath);
3419          snprintf(itemName, MAX_LOCATION+4, "%d %s", 1 + c, itemPath); itemName[MAX_LOCATION+4-1] = '\0';
3420          recentFiles.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentFile }, ide, true);
3421          c++;
3422       }
3423
3424       recentProjects.Clear();
3425       c = 0;
3426       for(recent : ideSettings.recentProjects)
3427       {
3428          strncpy(itemPath, recent, MAX_LOCATION); itemPath[MAX_LOCATION-1] = '\0';
3429          MakeSystemPath(itemPath);
3430          snprintf(itemName, MAX_LOCATION+4, "%d %s", 1 + c, itemPath); itemName[MAX_LOCATION+4-1] = '\0';
3431          recentProjects.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentProject }, ide, true);
3432          c++;
3433       }
3434
3435       delete itemPath;
3436       delete itemName;
3437    }
3438
3439    ~IDEWorkSpace()
3440    {
3441       delete driverItems;
3442       delete skinItems;
3443       delete languageItems;
3444       delete ideSettings;
3445       if(documentor)
3446       {
3447          documentor.Puts("Quit\n");
3448          documentor.Wait();
3449          delete documentor;
3450       }
3451    }
3452 }
3453
3454 void DestroyDir(char * path)
3455 {
3456    RecursiveDeleteFolderFSI fsi { };
3457    fsi.Iterate(path);
3458    delete fsi;
3459 }
3460
3461 #if defined(__WIN32__)
3462 define sdkDirName = "Ecere SDK";
3463 #else
3464 define sdkDirName = "ecere";
3465 #endif
3466
3467 void FindAndShellOpenInstalledFolder(char * name)
3468 {
3469    char * p = new char[MAX_LOCATION];
3470    char * v = new char[maxPathLen];
3471    byte * tokens[256];
3472    int c, numTokens;
3473    Array<String> paths { };
3474    p[0] = v[0] = '\0';
3475    strncpy(p, settingsContainer.moduleLocation, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3476    StripLastDirectory(p, p);
3477    PathCat(p, name);
3478    paths.Add(CopyString(p));
3479 #if defined(__WIN32__)
3480    GetEnvironment("ECERE_SDK_SRC", v, maxPathLen);
3481    if(v[0])
3482    {
3483       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3484       PathCat(p, name); paths.Add(CopyString(p));
3485    }
3486    GetEnvironment("AppData", v, maxPathLen);
3487    if(v[0])
3488    {
3489       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3490       PathCat(p, sdkDirName); PathCat(p, name); paths.Add(CopyString(p));
3491    }
3492    GetEnvironment("ProgramFiles", v, maxPathLen);
3493    if(v[0])
3494    {
3495       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3496       PathCat(p, sdkDirName); PathCat(p, name); paths.Add(CopyString(p));
3497    }
3498    GetEnvironment("ProgramFiles(x86)", v, maxPathLen);
3499    if(v[0])
3500    {
3501       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3502       PathCat(p, sdkDirName); PathCat(p, name); paths.Add(CopyString(p));
3503    }
3504    GetEnvironment("SystemDrive", v, maxPathLen);
3505    if(v[0])
3506    {
3507       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3508       PathCat(p, "Program Files"); PathCat(p, sdkDirName); PathCat(p, name); paths.Add(CopyString(p));
3509    }
3510 #else
3511    GetEnvironment("XDG_DATA_DIRS", v, maxPathLen);
3512    numTokens = TokenizeWith(v, sizeof(tokens) / sizeof(byte *), tokens, ":", false);
3513    for(c=0; c<numTokens; c++)
3514    {
3515       strncpy(p, tokens[c], MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3516       PathCat(p, sdkDirName); PathCat(p, name); paths.Add(CopyString(p));
3517    }
3518 #endif
3519    for(path : paths)
3520    {
3521       strncpy(p, path, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3522       if(FileExists(p).isDirectory)
3523       {
3524          ShellOpen(p);
3525          break;
3526       }
3527    }
3528    delete p;
3529    delete v;
3530    paths.Free();
3531    delete paths;
3532 }
3533
3534 void FindAndShellOpenInstalledFile(char * subdir, char * name)
3535 {
3536    char * p = new char[MAX_LOCATION];
3537    char * v = new char[maxPathLen];
3538    byte * tokens[256];
3539    int c, numTokens;
3540    Array<String> paths { };
3541    p[0] = v[0] = '\0';
3542    strncpy(p, settingsContainer.moduleLocation, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3543    paths.Add(CopyString(p));
3544    StripLastDirectory(p, p);
3545    PathCat(p, subdir);
3546    paths.Add(CopyString(p));
3547 #if defined(__WIN32__)
3548    GetEnvironment("ECERE_SDK_SRC", v, maxPathLen);
3549    if(v[0])
3550    {
3551       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3552       PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3553    }
3554    GetEnvironment("AppData", v, maxPathLen);
3555    if(v[0])
3556    {
3557       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3558       PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3559    }
3560    GetEnvironment("ProgramFiles", v, maxPathLen);
3561    if(v[0])
3562    {
3563       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3564       PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3565    }
3566    GetEnvironment("ProgramFiles(x86)", v, maxPathLen);
3567    if(v[0])
3568    {
3569       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3570       PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3571    }
3572    GetEnvironment("SystemDrive", v, maxPathLen);
3573    if(v[0])
3574    {
3575       strncpy(p, v, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3576       PathCat(p, "Program Files"); PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3577    }
3578 #else
3579    GetEnvironment("XDG_DATA_DIRS", v, maxPathLen);
3580    numTokens = TokenizeWith(v, sizeof(tokens) / sizeof(byte *), tokens, ":", false);
3581    for(c=0; c<numTokens; c++)
3582    {
3583       strncpy(p, tokens[c], MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3584       PathCat(p, sdkDirName); PathCat(p, subdir); paths.Add(CopyString(p));
3585    }
3586 #endif
3587    for(path : paths)
3588    {
3589       strncpy(p, path, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
3590       PathCat(p, name);
3591       if(FileExists(p).isFile)
3592       {
3593          ShellOpen(p);
3594          break;
3595       }
3596    }
3597    delete p;
3598    delete v;
3599    paths.Free();
3600    delete paths;
3601 }
3602
3603 class RecursiveDeleteFolderFSI : NormalFileSystemIterator
3604 {
3605    bool preserveRootFolder;
3606
3607    void OutFolder(char * folderPath, bool isRoot)
3608    {
3609       if(!(preserveRootFolder && isRoot))
3610          RemoveDir(folderPath);
3611    }
3612
3613    bool OnFile(char * filePath)
3614    {
3615       DeleteFile(filePath);
3616       return true;
3617    }
3618 }
3619
3620 class IDEApp : GuiApplication
3621 {
3622    //driver = "Win32Console";
3623    // driver = "OpenGL";
3624    // skin = "Aqua";
3625    //skin = "TVision";
3626
3627    TempFile includeFile { };
3628    int argFilesCount;
3629
3630    bool Init()
3631    {
3632       char ext[MAX_EXTENSION];
3633       SetLoggingMode(stdOut, null);
3634       //SetLoggingMode(debug, null);
3635
3636       settingsContainer.Load();
3637
3638       if(ideSettings.language)
3639       {
3640          String language = GetLanguageString();
3641          if(ideSettings.language.OnCompare(language))
3642          {
3643             LanguageRestart(ideSettings.language, app, null, null, null, null, true);
3644             return false;
3645          }
3646       }
3647
3648       // First count files arg to decide whether to maximize
3649       {
3650          bool passThrough = false, debugWorkDir = false;
3651          int c;
3652          argFilesCount = 0;
3653          for(c = 1; c<app.argc; c++)
3654          {
3655             if(passThrough);
3656             else if(debugWorkDir)
3657                debugWorkDir = false;
3658             else if(!strcmp(app.argv[c], "-t"));
3659             else if(!strcmp(app.argv[c], "-no-parsing"));
3660             else if(!strcmp(app.argv[c], "-debug-start"));
3661             else if(!strcmp(app.argv[c], "-debug-work-dir"))
3662                debugWorkDir = true;
3663             else if(!strcmp(app.argv[c], "-@"))
3664                passThrough = true;
3665             else
3666                argFilesCount++;
3667          }
3668       }
3669
3670       if(app.argFilesCount > 1 && !strcmpi(GetExtension(argv[1], ext), "3ds"))
3671       {
3672          app.driver = "OpenGL";
3673          ide.driverItems[1].checked = true;
3674       }
3675       else
3676       {
3677 #if defined(__unix__) || defined(__APPLE__)
3678          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "X";
3679 #else
3680          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "GDI";
3681 #endif
3682          ide.driverItems[ideSettings.displayDriver && !strcmp(ideSettings.displayDriver,"OpenGL")].checked = true;
3683       }
3684
3685       SetInIDE(true);
3686
3687       desktop.caption = titleECEREIDE;
3688       /*
3689       int c;
3690       for(c = 1; c<app.argc; c++)
3691       {
3692          char fullPath[MAX_LOCATION];
3693          GetWorkingDir(fullPath, MAX_LOCATION);
3694          PathCat(fullPath, app.argv[c]);
3695          ide.OpenFile(fullPath, app.argFilesCount > 1, true, null, yes, normal, false);
3696       }
3697       */
3698
3699       // Default to language specified by environment if no language selected
3700       if(!ideSettings.language)
3701       {
3702          ideSettings.language = GetLanguageString();
3703          settingsContainer.Save();
3704       }
3705
3706       // Default to home directory if no directory yet set up
3707       if(!ideSettings.ideProjectFileDialogLocation[0])
3708       {
3709          bool found = false;
3710          char location[MAX_LOCATION];
3711          char * home = getenv("HOME");
3712          char * homeDrive = getenv("HOMEDRIVE");
3713          char * homePath = getenv("HOMEPATH");
3714          char * userProfile = getenv("USERPROFILE");
3715          char * systemDrive = getenv("SystemDrive");
3716          if(home && FileExists(home).isDirectory)
3717          {
3718             strcpy(location, home);
3719             found = true;
3720          }
3721          if(!found && homeDrive && homePath)
3722          {
3723             strcpy(location, homeDrive);
3724             PathCat(location, homePath);
3725             if(FileExists(location).isDirectory)
3726                found = true;
3727          }
3728          if(!found && FileExists(userProfile).isDirectory)
3729          {
3730             strcpy(location, userProfile);
3731             found = true;
3732          }
3733          if(!found && FileExists(systemDrive).isDirectory)
3734          {
3735             strcpy(location, systemDrive);
3736             found = true;
3737          }
3738          if(found)
3739          {
3740             ideSettings.ideProjectFileDialogLocation = location;
3741             if(!ideSettings.ideFileDialogLocation[0])
3742                ideSettings.ideFileDialogLocation = location;
3743          }
3744       }
3745
3746       if(!LoadIncludeFile())
3747          PrintLn("error: unable to load :crossplatform.mk file inside ide binary.");
3748
3749       // Create language menu
3750       {
3751          String language = ideSettings.language;
3752          int i = 0;
3753          bool found = false;
3754
3755          ide.languageItems = new MenuItem[languages.count];
3756          for(l : languages)
3757          {
3758             ide.languageItems[i] =
3759             {
3760                ide.languageMenu, l.name;
3761                bitmap = { l.bitmap };
3762                id = i;
3763                isRadio = true;
3764
3765                bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3766                {
3767                   if(!LanguageRestart(languages[(int)selection.id].code, app, ideSettings, settingsContainer, ide, ide.projectView, false))
3768                   {
3769                      // Re-select previous selected language if aborted
3770                      String language = ideSettings.language;
3771                      int i = 0;
3772                      for(l : languages)
3773                      {
3774                         if(((!language || !language[0]) && i == 0) ||
3775                            (language && !strcmpi(l.code, language)))
3776                         {
3777                            ide.languageItems[i].checked = true;
3778                            break;
3779                         }
3780                         i++;
3781                      }
3782                   }
3783                   return true;
3784                }
3785             };
3786             i++;
3787          }
3788
3789          // Try to find country-specific language first
3790          if(language)
3791          {
3792             i = 0;
3793             for(l : languages)
3794             {
3795                if(!strcmpi(l.code, language) || (i == 0 && !strcmpi("en", language)))
3796                {
3797                   ide.languageItems[i].checked = true;
3798                   found = true;
3799                   break;
3800                }
3801                i++;
3802             }
3803          }
3804
3805          // Try generalizing locale
3806          if(!found && language)
3807          {
3808             char * under;
3809             char genericLocale[256];
3810             i = 0;
3811             strncpy(genericLocale, language, sizeof(genericLocale));
3812             genericLocale[sizeof(genericLocale)] = 0;
3813
3814             under = strchr(genericLocale, '_');
3815             if(under)
3816                *under = 0;
3817             if(!strcmpi(genericLocale, "zh"))
3818                strcpy(genericLocale, "zh_CN");
3819             if(strcmp(genericLocale, language))
3820             {
3821                for(l : languages)
3822                {
3823                   if(!strcmpi(l.code, genericLocale) || (i == 0 && !strcmpi("en", genericLocale)))
3824                   {
3825                      ide.languageItems[i].checked = true;
3826                      found = true;
3827                      break;
3828                   }
3829                   i++;
3830                }
3831             }
3832          }
3833
3834          if(!found)
3835             ide.languageItems[0].checked = true;
3836
3837          MenuDivider { ide.languageMenu };
3838          MenuItem
3839          {
3840             ide.languageMenu, "Help Translate";
3841
3842             bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3843             {
3844                ShellOpen("http://translations.launchpad.net/ecere");
3845                return true;
3846             }
3847          };
3848       }
3849
3850       ideMainFrame.Create();
3851       if(app.argFilesCount > 1)
3852          ide.MenuWindowTileVert(null, 0);
3853       return true;
3854    }
3855
3856    bool Cycle(bool idle)
3857    {
3858       if(ide.documentor)
3859       {
3860          if(ide.documentor.Peek())
3861          {
3862             char line[1024];
3863             ide.documentor.GetLine(line, sizeof(line));
3864             if(!strcmpi(line, "Exited"))
3865             {
3866                ide.documentor.CloseInput();
3867                ide.documentor.CloseOutput();
3868                ide.documentor.Wait();
3869                delete ide.documentor;
3870             }
3871          }
3872          if(ide.documentor && ide.documentor.eof)
3873          {
3874             ide.documentor.CloseInput();
3875             ide.documentor.CloseOutput();
3876             ide.documentor.Wait();
3877             delete ide.documentor;
3878          }
3879       }
3880       return true;
3881    }
3882
3883    bool LoadIncludeFile()
3884    {
3885       bool result = false;
3886       File include = FileOpen(":crossplatform.mk", read);
3887       if(include)
3888       {
3889          File f = includeFile;
3890          if(f)
3891          {
3892             for(; !include.Eof(); )
3893             {
3894                char buffer[4096];
3895                int count = include.Read(buffer, 1, 4096);
3896                f.Write(buffer, 1, count);
3897             }
3898             result = true;
3899          }
3900          delete include;
3901       }
3902       return result;
3903    }
3904 }
3905
3906 IDEMainFrame ideMainFrame { };
3907
3908 define app = ((IDEApp)__thisModule);
3909 #ifdef _DEBUG
3910 define titleECEREIDE = $"Ecere IDE (Debug)";
3911 #else
3912 define titleECEREIDE = $"Ecere IDE";
3913 #endif