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