ide/debugger/rubber duck: Work around for hiding duck
[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<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(char * line, bool noParsing)
441       {
442          ide.GoToError(line, noParsing);
443       }
444
445       void OnCodeLocationParseAndGoTo(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, activeThread, hitThread;
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                   char ** 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(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, 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 inDebugMode = debugger.isActive;
1761          bool callStackVisible = expand ? false : callStackView.visible;
1762          bool threadsVisible = expand ? false : threadsView.visible;
1763          bool watchesVisible = expand ? false : watchesView.visible;
1764          bool breakpointsVisible = expand ? false : breakpointsView.visible;
1765          bool toolBoxVisible = toolBox.visible;
1766          bool outputVisible = expand ? false : outputView.visible;
1767          int topDistance = (callStackVisible || threadsVisible) ? 200 : 0;
1768          int bottomDistance = (outputVisible || watchesVisible || breakpointsVisible) ? 240 : 0;
1769
1770          for(child = firstChild; child; child = child.next)
1771          {
1772             if(child._class == class(CodeEditor) || child._class == class(Designer) ||
1773                child._class == class(Sheet) || child._class == class(ProjectView))
1774             {
1775                Anchor anchor = child.anchor;
1776                anchor.top = topDistance;
1777                anchor.bottom = bottomDistance;
1778                if(child._class == class(CodeEditor) || child._class == class(Designer))
1779                {
1780                   anchor.left = (sheet.visible || (projectView && projectView.visible)) ? 300 : 0;
1781                   anchor.right = toolBoxVisible ? 150 : 0;
1782                }
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, char * fileName)
1815    {
1816       ideSettings.AddRecentFile(fileName);
1817       ide.UpdateRecentMenus();
1818       ide.AdjustFileMenus();
1819       settingsContainer.Save();
1820    }
1821
1822    bool Window::OnFileModified(FileChange fileChange, 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       bool noBreakpointToggle = !project;
2129
2130       bool isNotRunning    = unavailable || !running;
2131       bool isNotNotRunning = unavailable || running;
2132       bool isNotStopped    = unavailable || !stopped;
2133       bool isNotActive     = unavailable || !active;
2134
2135       debugStartResumeItem.disabled       = isNotNotRunning;
2136       debugStartResumeItem.text           = active ? $"Resume" : $"Start";
2137       debugStartResumeItem.NotifySelect   = active ? MenuDebugResume : MenuDebugStart;
2138       if(toolBar)
2139       {
2140          toolBar.buttonDebugStartResume.disabled      = isNotNotRunning;
2141          toolBar.buttonDebugStartResume.toolTip       = active ? $"Resume" : $"Start";
2142       }
2143
2144       debugBreakItem.disabled             = isNotRunning;
2145       debugStopItem.disabled              = isNotActive;
2146       debugRestartItem.disabled           = isNotActive;
2147       if(toolBar)
2148       {
2149          toolBar.buttonDebugPause.disabled            = isNotRunning;
2150          toolBar.buttonDebugStop.disabled             = isNotActive;
2151          toolBar.buttonDebugRestart.disabled          = isNotActive;
2152       }
2153
2154       debugStepIntoItem.disabled          = isNotNotRunning;
2155       debugStepOverItem.disabled          = isNotNotRunning;
2156       debugSkipStepOverItem.disabled      = isNotNotRunning;
2157       debugStepOutItem.disabled           = isNotStopped;
2158       debugSkipStepOutItem.disabled       = isNotStopped;
2159 #if 0
2160       debugStepUntilItem.disabled         = isNotStopped;
2161       debugSkipStepUntilItem.disabled     = isNotStopped;
2162 #endif
2163       if(toolBar)
2164       {
2165          toolBar.buttonDebugStepInto.disabled         = isNotNotRunning;
2166          toolBar.buttonDebugStepOver.disabled         = isNotNotRunning;
2167          toolBar.buttonDebugSkipStepOver.disabled     = isNotNotRunning;
2168          toolBar.buttonDebugStepOut.disabled          = isNotStopped;
2169          //toolBar.buttonDebugSkipStepOutItem.disabled  = isNotNotRunning;
2170       }
2171       if((Designer)GetActiveDesigner())
2172       {
2173          CodeEditor codeEditor = ((Designer)GetActiveDesigner()).codeEditor;
2174          if(codeEditor)
2175             codeEditor.AdjustDebugMenus();
2176       }
2177    }
2178
2179    void ChangeFileDialogsDirectory(char * directory, bool saveSettings)
2180    {
2181       char tempString[MAX_LOCATION];
2182       strcpy(tempString, directory);
2183       if(saveSettings && !projectView)
2184       {
2185          ideSettings.ideFileDialogLocation = directory;
2186          settingsContainer.Save();
2187       }
2188
2189       ideFileDialog.currentDirectory = tempString;
2190       codeEditorFileDialog.currentDirectory = tempString;
2191       codeEditorFormFileDialog.currentDirectory = tempString;
2192    }
2193
2194    void ChangeProjectFileDialogDirectory(char * directory)
2195    {
2196       ideSettings.ideProjectFileDialogLocation = directory;
2197       settingsContainer.Save();
2198    }
2199
2200    Window FindWindow(char * filePath)
2201    {
2202       Window document = null;
2203
2204       // TOCHECK: Do we need to change slashes here?
2205       for(document = firstChild; document; document = document.next)
2206       {
2207          char * fileName = document.fileName;
2208          if(document.isDocument && fileName && !fstrcmp(fileName, filePath))
2209          {
2210             document.visible = true;
2211             document.Activate();
2212             return document;
2213          }
2214       }
2215       return null;
2216    }
2217
2218    bool DontTerminateDebugSession(char * title)
2219    {
2220       if(debugger.isActive)
2221       {
2222          if(MessageBox { type = yesNo, master = ide,
2223                            contents = $"Do you want to terminate the debugging session in progress?",
2224                            text = title }.Modal() == no)
2225             return true;
2226          /*
2227          MessageBox msg { type = yesNo, master = ide,
2228                            contents = "Do you want to terminate the debugging session in progress?",
2229                            text = title };
2230          if(msg.Modal() == no)
2231          {
2232             msg.Destroy(0);
2233             delete msg;
2234             return true;
2235          }
2236          msg.Destroy(0);
2237          delete msg;*/
2238       }
2239       return false;
2240    }
2241
2242    Window OpenFile(char * origFilePath, bool dontMaximize, bool visible, char * type, OpenCreateIfFails createIfFails, OpenMethod openMethod, bool noParsing)
2243    {
2244       char extension[MAX_EXTENSION] = "";
2245       Window document = null;
2246       bool isProject = false;
2247       bool needFileModified = true;
2248       char winFilePath[MAX_LOCATION];
2249       char * filePath = strstr(origFilePath, "http://") == origFilePath ? strcpy(winFilePath, origFilePath) : GetSystemPathBuffer(winFilePath, origFilePath);
2250       Window currentDoc = activeClient;
2251       bool maximizeDoc = !dontMaximize && ((currentDoc && currentDoc.state == maximized) || (!currentDoc && !projectView));
2252       if(!type)
2253       {
2254          GetExtension(filePath, extension);
2255          strlwr(extension);
2256       }
2257       else
2258          strcpy(extension, type);
2259
2260       if(strcmp(extension, ProjectExtension))
2261       {
2262          for(document = firstChild; document; document = document.next)
2263          {
2264             char * fileName = document.fileName;
2265             if(document.isDocument && fileName && !fstrcmp(fileName, filePath) && document.created)
2266             {
2267                document.visible = true;
2268                if(visible)
2269                   document.Activate();
2270                return document;
2271             }
2272          }
2273       }
2274
2275       if(createIfFails == whatever)
2276          ;
2277       else if(!strcmp(extension, ProjectExtension) || !strcmp(extension, WorkspaceExtension))
2278       {
2279          needFileModified = false;
2280          if(openMethod == normal)
2281          {
2282             if(DontTerminateDebugSession($"Open Project"))
2283                return null;
2284             isProject = true;
2285             if(ProjectClose())
2286             {
2287                if(!projectView)
2288                {
2289                   for(;;)
2290                   {
2291                      Project project;
2292                      Workspace workspace = null;
2293
2294                      if(FileExists(filePath))
2295                      {
2296                         if(!strcmp(extension, ProjectExtension))
2297                         {
2298                            char workspaceFile[MAX_LOCATION];
2299                            strcpy(workspaceFile, filePath);
2300                            ChangeExtension(workspaceFile, WorkspaceExtension, workspaceFile);
2301                            workspace = LoadWorkspace(workspaceFile, filePath);
2302                         }
2303                         else if(!strcmp(extension, WorkspaceExtension))
2304                            workspace = LoadWorkspace(filePath, null);
2305                         else
2306                            return null;
2307                         //project = LoadProject(filePath, null);
2308                      }
2309
2310                      if(workspace)
2311                      {
2312                         char absolutePath[MAX_LOCATION];
2313                         CreateProjectView(workspace, filePath);
2314                         document = projectView;
2315
2316                         toolBox.visible = true;
2317                         sheet.visible = true;
2318                         projectView.MakeActive();
2319
2320                         workspace.ParseLoadedBreakpoints();
2321                         workspace.DropInvalidBreakpoints(null);
2322                         workspace.Save();
2323
2324                         ide.projectView.ShowOutputBuildLog(true);
2325                         {
2326                            CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
2327                            ide.projectView.DisplayCompiler(compiler, false);
2328                            delete compiler;
2329                         }
2330                         UpdateCompilerConfigs(false);
2331                         UpdateMakefiles();
2332                         {
2333                            char newWorkingDir[MAX_LOCATION];
2334                            StripLastDirectory(filePath, newWorkingDir);
2335                            ChangeFileDialogsDirectory(newWorkingDir, false);
2336                         }
2337                         if(document)
2338                            document.fileName = filePath;
2339
2340                         ideMainFrame.SetText("%s - %s", filePath, titleECEREIDE);
2341
2342                         // this crashes on starting ide with epj file, solution please?
2343                         // app.UpdateDisplay();
2344
2345                         workspace.holdTracking = true;
2346                         for(ofi : workspace.openedFiles)
2347                         {
2348                            if(ofi.state != closed)
2349                            {
2350                               Window file = OpenFile(ofi.path, false, true, null, no, normal, noParsing);
2351                               if(file)
2352                               {
2353                                  char fileName[MAX_LOCATION];
2354                                  ProjectNode node;
2355                                  GetLastDirectory(ofi.path, fileName);
2356                                  node = projectView.project.topNode.Find(fileName, true);
2357                                  if(node)
2358                                     node.EnsureVisible();
2359                               }
2360                            }
2361                         }
2362                         ide.RepositionWindows(false);
2363                         workspace.holdTracking = false;
2364
2365                         workspace.timer.Start();
2366
2367 #if !defined(__WIN32__)
2368                         // Valgrind Debug menu updates
2369                         debugUseValgrindItem.checked = workspace.useValgrind;
2370
2371                         debugValgrindNoLeakCheckItem.checked      = workspace.vgLeakCheck == no;
2372                         debugValgrindSummaryLeakCheckItem.checked = workspace.vgLeakCheck == summary;
2373                         debugValgrindYesLeakCheckItem.checked     = workspace.vgLeakCheck == yes;
2374                         debugValgrindFullLeakCheckItem.checked    = workspace.vgLeakCheck == full;
2375
2376                         debugValgrindRSDefaultItem.checked = workspace.vgRedzoneSize == -1;
2377                         debugValgrindRS0Item.checked       = workspace.vgRedzoneSize == 0;
2378                         debugValgrindRS16Item.checked      = workspace.vgRedzoneSize == 16;
2379                         debugValgrindRS32Item.checked      = workspace.vgRedzoneSize == 32;
2380                         debugValgrindRS64Item.checked      = workspace.vgRedzoneSize == 64;
2381                         debugValgrindRS128Item.checked     = workspace.vgRedzoneSize == 128;
2382                         debugValgrindRS256Item.checked     = workspace.vgRedzoneSize == 256;
2383                         debugValgrindRS512Item.checked     = workspace.vgRedzoneSize == 512;
2384
2385                         debugValgrindTrackOriginsItem.checked = workspace.vgTrackOrigins;
2386 #endif
2387
2388                         findInFilesDialog.mode = FindInFilesMode::project;
2389                         findInFilesDialog.currentDirectory = ide.project.topNode.path;
2390
2391                         {
2392                            char location[MAX_LOCATION];
2393                            StripLastDirectory(ide.project.topNode.path, location);
2394                            ChangeProjectFileDialogDirectory(location);
2395                         }
2396
2397                         break;
2398                      }
2399                      else
2400                      {
2401                         if(MessageBox { type = yesNo, master = this, text = $"Error opening project", contents = $"Open a different project?" }.Modal() == yes)
2402                         {
2403                            ideProjectFileDialog.text = openProjectFileDialogTitle;
2404                            if(ideProjectFileDialog.Modal() == cancel)
2405                               return null;
2406                            filePath = ideProjectFileDialog.filePath;
2407                            GetExtension(filePath, extension);
2408                         }
2409                         else
2410                            return null;
2411                      }
2412                   }
2413                }
2414             }
2415             else
2416                return null;
2417          }
2418          else if(openMethod == add)
2419          {
2420             if(workspace)
2421             {
2422                Project prj = null;
2423                char slashFilePath[MAX_LOCATION];
2424                GetSlashPathBuffer(slashFilePath, filePath);
2425                for(p : workspace.projects)
2426                {
2427                   if(!fstrcmp(p.filePath, slashFilePath))
2428                   {
2429                      prj = p;
2430                      break;
2431                   }
2432                }
2433                if(prj)
2434                {
2435                   MessageBox { type = ok, parent = parent, master = this, text = $"Same Project",
2436                         contents = $"This project is already present in workspace." }.Modal();
2437                }
2438                else
2439                {
2440                   prj = LoadProject(filePath, null);
2441                   if(prj)
2442                   {
2443                      char * activeConfigName = null;
2444                      CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
2445                      prj.StartMonitoring();
2446                      workspace.projects.Add(prj);
2447                      if(toolBar.activeConfig.currentRow && toolBar.activeConfig.currentRow != toolBar.activeConfig.firstRow &&
2448                            toolBar.activeConfig.currentRow.string && toolBar.activeConfig.currentRow.string[0])
2449                         activeConfigName = toolBar.activeConfig.currentRow.string;
2450                      if(activeConfigName)
2451                      {
2452                         for(cfg : prj.configurations)
2453                         {
2454                            if(cfg.name && !strcmp(cfg.name, activeConfigName))
2455                            {
2456                               prj.config = cfg;
2457                               break;
2458                            }
2459                         }
2460                      }
2461                      if(projectView)
2462                         projectView.AddNode(prj.topNode, null);
2463                      workspace.modified = true;
2464                      workspace.Save();
2465                      findInFilesDialog.AddProjectItem(prj);
2466                      projectView.ShowOutputBuildLog(true);
2467                      projectView.DisplayCompiler(compiler, false);
2468                      projectView.ProjectUpdateMakefileForAllConfigs(prj);
2469                      delete compiler;
2470
2471                      {
2472                         char location[MAX_LOCATION];
2473                         StripLastDirectory(prj.topNode.path, location);
2474                         ChangeProjectFileDialogDirectory(location);
2475                      }
2476
2477                      // projectView is associated with the main project and not with the one just added but
2478                      return projectView; // just to let the caller know something was opened
2479                   }
2480                }
2481             }
2482             else
2483                return null;
2484          }
2485       }
2486       else if(!strcmp(extension, "bmp") || !strcmp(extension, "pcx") ||
2487             !strcmp(extension, "jpg") || !strcmp(extension, "gif") ||
2488             !strcmp(extension, "jpeg") || !strcmp(extension, "png"))
2489       {
2490          if(FileExists(filePath))
2491             document = PictureEdit { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2492                                        hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2493                                        visible = visible, bitmapFile = filePath, OnClose = PictureEditOnClose/*why?--GenericDocumentOnClose*/;
2494                                     };
2495          if(!document)
2496             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2497       }
2498 #ifndef NO3D
2499       else if(!strcmp(extension, "3ds"))
2500       {
2501          if(FileExists(filePath))
2502             document = ModelView { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2503                                     hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2504                                     visible = visible, modelFile = filePath, OnClose = ModelViewOnClose/*why?--GenericDocumentOnClose*/
2505                                     };
2506
2507          if(!document)
2508             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2509       }
2510 #endif
2511       else if(!strcmp(extension, "txt") || !strcmp(extension, "text") ||
2512             !strcmp(extension, "nfo") || !strcmp(extension, "info") ||
2513             !strcmp(extension, "htm") || !strcmp(extension, "html") ||
2514             !strcmp(extension, "css") || !strcmp(extension, "php") ||
2515             !strcmp(extension, "js"))
2516       {
2517          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2518          editor.updatingCode = true;
2519          if(editor.LoadFile(filePath))
2520          {
2521             document = editor;
2522             editor.visible = true;
2523          }
2524          else
2525             delete editor;
2526          needFileModified = false;
2527       }
2528       else
2529       {
2530          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2531          if(editor.LoadFile(filePath))
2532          {
2533             document = editor;
2534             editor.visible = true;
2535          }
2536          else
2537             delete editor;
2538          needFileModified = false;
2539       }
2540
2541       if(document && (document._class == class(PictureEdit) ||
2542             document._class == class(ModelView)))
2543       {
2544          document.Create();
2545          if(document)
2546          {
2547             document.fileName = filePath;
2548             if(workspace && !workspace.holdTracking)
2549                workspace.UpdateOpenedFileInfo(filePath, opened);
2550          }
2551       }
2552
2553       if(!document && createIfFails != no)
2554       {
2555          if(createIfFails != yes && !needFileModified &&
2556                MessageBox { type = yesNo, master = this, text = filePath, contents = $"File doesn't exist. Create?" }.Modal() == yes)
2557             createIfFails = yes;
2558          if(createIfFails == yes || createIfFails == whatever)
2559          {
2560             document = (Window)NewCodeEditor(this, maximizeDoc ? maximized : normal, true);
2561             if(document)
2562                document.fileName = filePath;
2563          }
2564       }
2565
2566       if(document)
2567       {
2568          if(projectView && document._class == class(CodeEditor) && workspace)
2569          {
2570             int lineNumber, position;
2571             Point scroll;
2572             CodeEditor editor = (CodeEditor)document;
2573             editor.openedFileInfo = workspace.UpdateOpenedFileInfo(filePath, opened);
2574             editor.openedFileInfo.holdTracking = true;
2575             lineNumber = Max(editor.openedFileInfo.lineNumber - 1, 0);
2576             position = Max(editor.openedFileInfo.position - 1, 0);
2577             if(editor.editBox.GoToLineNum(lineNumber))
2578                editor.editBox.GoToPosition(editor.editBox.line, lineNumber, position);
2579             scroll.x = Max(editor.openedFileInfo.scroll.x, 0);
2580             scroll.y = Max(editor.openedFileInfo.scroll.y, 0);
2581             editor.editBox.scroll = scroll;
2582             editor.openedFileInfo.holdTracking = false;
2583          }
2584
2585          if(needFileModified)
2586             document.OnFileModified = OnFileModified;
2587          document.NotifySaved = DocumentSaved;
2588          if(maximizeDoc && document.hasMaximize)
2589             document.state = maximized;
2590
2591          if(isProject)
2592             ideSettings.AddRecentProject(document.fileName);
2593          else
2594             ideSettings.AddRecentFile(document.fileName);
2595          ide.UpdateRecentMenus();
2596          ide.AdjustFileMenus();
2597          settingsContainer.Save();
2598
2599          return document;
2600       }
2601       else
2602          return null;
2603    }
2604
2605    // TOCHECK: I can't use a generic one for both ModelView and PictureEdit both derived from Window
2606    /*bool Window::GenericDocumentOnClose(bool parentClosing)
2607    {
2608       if(!parentClosing && ide.workspace)
2609          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2610       return true;
2611    }*/
2612    bool ModelView::ModelViewOnClose(bool parentClosing)
2613    {
2614       if(!parentClosing && ide.workspace)
2615          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2616       return true;
2617    }
2618    bool PictureEdit::PictureEditOnClose(bool parentClosing)
2619    {
2620       if(!parentClosing && ide.workspace)
2621          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2622       return true;
2623    }
2624
2625    /*
2626    void OnUnloadGraphics(Window window)
2627    {
2628       display.ClearMaterials();
2629       display.ClearTextures();
2630       display.ClearMeshes();
2631    }
2632    */
2633
2634    void UpdateStateLight(StatusField fld, bool on)
2635    {
2636       fld.color = on ? lime : Color { 128,128,128 };
2637       fld.backColor = on ? dimGray : 0;
2638       fld.bold = on;
2639    }
2640
2641    bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
2642    {
2643       UpdateStateLight(caps, app.GetKeyState(capsState));
2644       UpdateStateLight(num, app.GetKeyState(numState));
2645       return true;
2646    }
2647
2648    bool OnKeyDown(Key key, unichar ch)
2649    {
2650       switch(key)
2651       {
2652          case b: projectView.Update(null); break;
2653          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2654          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2655       }
2656       return true;
2657    }
2658
2659    bool OnKeyUp(Key key, unichar ch)
2660    {
2661       switch(key)
2662       {
2663          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2664          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2665       }
2666       return true;
2667    }
2668
2669    void GoToError(const char * line, bool noParsing)
2670    {
2671       if(projectView)
2672          projectView.GoToError(line, noParsing);
2673    }
2674
2675    void CodeLocationParseAndGoTo(const char * text, Project project, const char * dir)
2676    {
2677       char *s = null;
2678       char *path = text;
2679       char *colon = strchr(text, ':');
2680       char filePath[MAX_LOCATION] = "";
2681       char completePath[MAX_LOCATION];
2682       int line = 0, col = 0;
2683       int len = strlen(text);
2684       Project prj = null;
2685       FileAttribs fileAttribs;
2686
2687       // support for valgrind output
2688       if((s = strstr(text, "==")) && (s = strstr(s+2, "==")) && (s = strstr(s+2, ":")) && (s = strstr(s+1, ":")))
2689       {
2690          colon = s;
2691          for(; s>text; s--)
2692          {
2693             if(*s == '(')
2694             {
2695                path = s+1;
2696                break;
2697             }
2698          }
2699          /*for(s=colon; *s; s++)
2700          {
2701             if(*s == ')')
2702             {
2703                *s = '\0';;
2704                break;
2705             }
2706          }*/
2707          //*colon = '\0';
2708          //line = atoi(colon+1);
2709       }
2710       // support for "Found n match(es) in "file/path";
2711       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)
2712       {
2713          path = s+1;
2714       }
2715       else
2716       {
2717          if(colon && (colon[1] == '/' || colon[1] == '\\'))
2718          {
2719             path = (colon - 1 > path) ? colon - 1 : path;
2720             colon = strstr(colon + 1, ":");
2721          }
2722          if(*path == '*' && (s = strchr(path+1, '*')))
2723             path = s+1;
2724          while(isspace(*path)) path++;
2725       }
2726       if(*path == '(')
2727       {
2728          char * close = strchr(path, ')');
2729          if(close)
2730          {
2731             char name[256];
2732             strncpy(name, path+1, close - path - 1);
2733             name[close - path - 1] = '\0';
2734             for(p : ide.workspace.projects)
2735             {
2736                if(!strcmp(p.name, name))
2737                {
2738                   path = close + 1;
2739                   prj = p;
2740                   break;
2741                }
2742             }
2743          }
2744       }
2745       if(!prj)
2746          prj = project ? project : (dir ? null : ide.project);
2747       if(colon)
2748       {
2749          strncpy(filePath, path, colon - path);
2750          filePath[colon - path] = '\0';
2751          line = atoi(colon + 1);
2752          colon = strstr(colon + 1, ":");
2753          if(colon)
2754             col = atoi(colon + 1);
2755       }
2756       else if(path - 1 >= text && *(path - 1) == '\"')
2757       {
2758          colon = strchr(path, '\"');
2759          if(colon)
2760          {
2761             strncpy(filePath, path, colon - path);
2762             filePath[colon - path] = '\0';
2763          }
2764       }
2765       else if(path && !colon)
2766       {
2767          strcpy(filePath, path);
2768       }
2769
2770       if(filePath[0])
2771       {
2772          if(prj)
2773             strcpy(completePath, prj.topNode.path);
2774          else if(dir && dir[0])
2775             strcpy(completePath, dir);
2776          else
2777             completePath[0] = '\0';
2778          PathCat(completePath, filePath);
2779
2780          if((fileAttribs = FileExists(completePath)))
2781             CodeLocationGoTo(completePath, fileAttribs, line, col);
2782          else if(ide.workspace)
2783          {
2784             bool done = false;
2785             for(p : ide.workspace.projects)
2786             {
2787                strcpy(completePath, p.topNode.path);
2788                PathCat(completePath, filePath);
2789                if((fileAttribs = FileExists(completePath)).isFile)
2790                {
2791                   CodeLocationGoTo(completePath, fileAttribs, line, col);
2792                   done = true;
2793                   break;
2794                }
2795             }
2796             if(!done)
2797             {
2798                for(p : ide.workspace.projects)
2799                {
2800                   ProjectNode node = p.topNode.Find(filePath, false);
2801                   if(node)
2802                   {
2803                      node.GetFullFilePath(completePath);
2804                      if((fileAttribs = FileExists(completePath)).isFile)
2805                      {
2806                         CodeLocationGoTo(completePath, fileAttribs, line, col);
2807                         break;
2808                      }
2809                   }
2810                }
2811             }
2812          }
2813       }
2814    }
2815
2816    void CodeLocationGoTo(const char * path, const FileAttribs fileAttribs, int line, int col)
2817    {
2818       if(fileAttribs.isFile)
2819       {
2820          char ext[MAX_EXTENSION];
2821          GetExtension(path, ext);
2822          strlwr(ext);
2823          if(binaryDocExt.Find(ext))
2824             ShellOpen(path);
2825          else if(!strcmp(ext, "a") || !strcmp(ext, "o") || !strcmp(ext, "lib") || !strcmp(ext, "dll") || !strcmp(ext, "exe"))
2826          {
2827             char dirPath[MAX_LOCATION];
2828             StripLastDirectory(path, dirPath);
2829             ShellOpen(dirPath);
2830          }
2831          else
2832          {
2833             CodeEditor codeEditor = (CodeEditor)OpenFile(path, false, true, ext, no, normal, false);
2834             if(codeEditor && line)
2835             {
2836                EditBox editBox = codeEditor.editBox;
2837                editBox.GoToLineNum(line - 1);
2838                editBox.GoToPosition(editBox.line, line - 1, col ? (col - 1) : 0);
2839             }
2840          }
2841       }
2842       else if(fileAttribs.isDirectory)
2843          ShellOpen(path);
2844    }
2845
2846    void OnRedraw(Surface surface)
2847    {
2848       Bitmap bitmap = back.bitmap;
2849       if(bitmap)
2850          surface.Blit(bitmap, (clientSize.w - bitmap.width) / 2, (clientSize.h - bitmap.height) / 2, 0, 0, bitmap.width, bitmap.height);
2851    }
2852
2853    void SheetSelected(SheetType sheetSelected)
2854    {
2855       if(activeChild == sheet)
2856       {
2857          if(sheetSelected == methods)
2858          {
2859             viewPropertiesItem.accelerator = f4;
2860             viewPropertiesItem.parent = viewMenu;
2861             viewMethodsItem.parent = null;
2862          }
2863          else
2864          {
2865             viewMethodsItem.accelerator = f4;
2866             viewMethodsItem.parent = viewMenu;
2867             viewPropertiesItem.parent = null;
2868          }
2869       }
2870       else
2871       {
2872          viewMethodsItem.parent = viewMenu;
2873          viewPropertiesItem.parent = viewMenu;
2874          if(sheetSelected == methods)
2875          {
2876             viewMethodsItem.accelerator = f4;
2877             viewPropertiesItem.accelerator = 0;
2878          }
2879          else
2880          {
2881             viewMethodsItem.accelerator = 0;
2882             viewPropertiesItem.accelerator = f4;
2883          }
2884       }
2885    }
2886
2887    void OnActivateClient(Window client, Window previous)
2888    {
2889       //if(!client || client != previous)
2890       {
2891          Class dataType;
2892          if(!client || client != previous)
2893          {
2894             if(previous)
2895                dataType = previous._class;
2896             if(previous && !strcmp(dataType.name, "CodeEditor"))
2897             {
2898                ((CodeEditor)previous).UpdateFormCode();
2899             }
2900             else if(previous && !strcmp(dataType.name, "Designer"))
2901             {
2902                ((Designer)previous).codeEditor.UpdateFormCode();
2903             }
2904          }
2905
2906          if(client)
2907             dataType = client._class;
2908          if(client && !strcmp(dataType.name, "CodeEditor"))
2909          {
2910             CodeEditor codeEditor = (CodeEditor)client;
2911             SetPrivateModule(codeEditor.privateModule);
2912             SetCurrentContext(codeEditor.globalContext);
2913             SetTopContext(codeEditor.globalContext);
2914             SetGlobalContext(codeEditor.globalContext);
2915
2916             SetDefines(&codeEditor.defines);
2917             SetImports(&codeEditor.imports);
2918
2919             SetActiveDesigner(codeEditor.designer);
2920
2921             sheet.codeEditor = codeEditor;
2922             toolBox.codeEditor = codeEditor;
2923
2924             viewDesignerItem.parent = viewMenu;
2925             if(activeChild != codeEditor)
2926             {
2927                viewCodeItem.parent = viewMenu;
2928                viewDesignerItem.accelerator = 0;
2929                viewCodeItem.accelerator = f8;
2930             }
2931             else
2932             {
2933                viewCodeItem.parent = null;
2934                viewDesignerItem.accelerator = f8;
2935             }
2936          }
2937          else if(client && !strcmp(dataType.name, "Designer"))
2938          {
2939             CodeEditor codeEditor = ((Designer)client).codeEditor;
2940             if(codeEditor)
2941             {
2942                SetPrivateModule(codeEditor.privateModule);
2943                SetCurrentContext(codeEditor.globalContext);
2944                SetTopContext(codeEditor.globalContext);
2945                SetGlobalContext(codeEditor.globalContext);
2946                SetDefines(&codeEditor.defines);
2947                SetImports(&codeEditor.imports);
2948             }
2949             else
2950             {
2951                SetPrivateModule(null);
2952                SetCurrentContext(null);
2953                SetTopContext(null);
2954                SetGlobalContext(null);
2955                SetDefines(null);
2956                SetImports(null);
2957             }
2958
2959             SetActiveDesigner((Designer)client);
2960
2961             sheet.codeEditor = codeEditor;
2962             toolBox.codeEditor = codeEditor;
2963
2964             viewCodeItem.parent = viewMenu;
2965             if(activeChild != client)
2966             {
2967                viewDesignerItem.parent = viewMenu;
2968                viewDesignerItem.accelerator = f8;
2969                viewCodeItem.accelerator = 0;
2970             }
2971             else
2972             {
2973                viewDesignerItem.parent = null;
2974                viewCodeItem.accelerator = f8;
2975             }
2976          }
2977          else
2978          {
2979             if(!client && !projectView && sheet.visible)
2980             {
2981                if(sheet)
2982                   sheet.visible = false;
2983                toolBox.visible = false;
2984             }
2985             if(sheet)
2986                sheet.codeEditor = null;
2987             toolBox.codeEditor = null;
2988             SetActiveDesigner(null);
2989
2990             viewDesignerItem.parent = null;
2991             viewCodeItem.parent = null;
2992          }
2993          if(sheet)
2994             SheetSelected(sheet.sheetSelected);
2995       }
2996
2997       projectCompileItem = null;
2998
2999       if(statusBar)
3000       {
3001          statusBar.Clear();
3002          if(client && client._class == eSystem_FindClass(__thisModule, "CodeEditor")) // !strcmp(client._class.name, "CodeEditor")
3003          {
3004             CodeEditor codeEditor = (CodeEditor)client;
3005             EditBox editBox = codeEditor.editBox;
3006
3007             statusBar.AddField(pos);
3008
3009             caps = { width = 40, text = $"CAPS" };
3010             statusBar.AddField(caps);
3011             UpdateStateLight(caps, app.GetKeyState(capsState));
3012
3013             ovr = { width = 36, text = $"OVR" };
3014             statusBar.AddField(ovr);
3015             UpdateStateLight(ovr, (editBox && editBox.overwrite));
3016
3017             num = { width = 36, text = $"NUM" };
3018             statusBar.AddField(num);
3019             UpdateStateLight(num, app.GetKeyState(numState));
3020
3021             //statusBar.text = "Ready";
3022
3023             if(projectView && projectView.project)
3024             {
3025                bool isCObject = false;
3026                ProjectNode node = projectView.GetNodeFromWindow(client, null, true, false, null);
3027                if(!node && (node = projectView.GetNodeFromWindow(client, null, true, true, null)))
3028                   isCObject = true;
3029                if(node)
3030                {
3031                   char nodeName[MAX_FILENAME];
3032                   char name[MAX_FILENAME+96];
3033                   if(isCObject)
3034                      ChangeExtension(node.name, "c", nodeName);
3035                   sprintf(name, $"Compile %s", isCObject ? nodeName : node.name);
3036                   projectCompileItem =
3037                   {
3038                      copyText = true, text = name, c, ctrlF7, disabled = projectView.buildInProgress;
3039
3040                      bool NotifySelect(MenuItem selection, Modifiers mods)
3041                      {
3042                         if(projectView)
3043                         {
3044                            bool isCObject = false;
3045                            bool isExcluded = false;
3046                            ProjectNode node = projectView.GetNodeForCompilationFromWindow(activeClient, true, &isExcluded, &isCObject);
3047                            if(node)
3048                            {
3049                               if(isExcluded)
3050                                  ide.outputView.buildBox.Logf($"%s %s is excluded from current build configuration.\n", isCObject ? "Object file" : "File", node.name);
3051                               else
3052                               {
3053                                  List<ProjectNode> nodes { };
3054                                  nodes.Add(node);
3055                                  projectView.Compile(node.project, nodes, mods.ctrl && mods.shift, isCObject ? cObject : normal);
3056                                  delete nodes;
3057                               }
3058                            }
3059                         }
3060                         return true;
3061                      }
3062                   };
3063                   projectMenu.AddDynamic(projectCompileItem, ide, false);
3064                }
3065             }
3066          }
3067          else
3068          {
3069             caps = ovr = num = null;
3070          }
3071       }
3072    }
3073
3074    bool OnClose(bool parentClosing)
3075    {
3076       //return !projectView.buildInProgress;
3077       if(projectView && projectView.buildInProgress)
3078          return false;
3079       if(DontTerminateDebugSession($"Close IDE"))
3080          return false;
3081       if(findInFilesDialog)
3082          findInFilesDialog.SearchStop();
3083       if(workspace)
3084       {
3085          workspace.timer.Stop();
3086          workspace.Save();
3087       }
3088       ideMainFrame.Destroy(0);
3089       return true;
3090    }
3091
3092    bool OnPostCreate()
3093    {
3094       int c;
3095       bool passThrough = false;
3096       bool debugStart = false;
3097       bool debugWorkDir = false;
3098       char * passDebugWorkDir = null;
3099       bool openAsText = false;
3100       DynamicString passArgs { };
3101       int ptArg = 0;
3102
3103       for(c = 1; c<app.argc; c++)
3104       {
3105          if(passThrough)
3106          {
3107             char * arg = app.argv[c];
3108             char * buf = new char[strlen(arg)*2+1];
3109             if(ptArg++ > 0)
3110                passArgs.concat(" ");
3111             PassArg(buf, arg);
3112             passArgs.concat(buf);
3113             delete buf;
3114          }
3115          else if(debugWorkDir)
3116          {
3117             passDebugWorkDir = CopyString(app.argv[c]);
3118             StripQuotes(passDebugWorkDir, passDebugWorkDir);
3119             debugWorkDir = false;
3120          }
3121          else if(!strcmp(app.argv[c], "-t"))
3122             openAsText = true;
3123          else if(!strcmp(app.argv[c], "-no-parsing"))
3124             ide.noParsing = true;
3125          else if(!strcmp(app.argv[c], "-debug-start"))
3126             debugStart = true;
3127          else if(!strcmp(app.argv[c], "-debug-work-dir"))
3128             debugWorkDir = true;
3129          else if(!strcmp(app.argv[c], "-@"))
3130             passThrough = true;
3131          else
3132          {
3133             char fullPath[MAX_LOCATION];
3134             char parentPath[MAX_LOCATION];
3135             char ext[MAX_EXTENSION];
3136             bool isProject;
3137             FileAttribs dirAttribs;
3138             GetWorkingDir(fullPath, MAX_LOCATION);
3139             PathCat(fullPath, app.argv[c]);
3140             StripLastDirectory(fullPath, parentPath);
3141             GetExtension(app.argv[c], ext);
3142             isProject = !openAsText && !strcmpi(ext, "epj");
3143
3144             if(isProject && c > (debugStart ? 2 : 1)) continue;
3145
3146             // Create directory for projects (only)
3147             if(((dirAttribs = FileExists(parentPath)) && dirAttribs.isDirectory) || isProject)
3148             {
3149                if(isProject && !FileExists(fullPath))
3150                {
3151                   char name[MAX_LOCATION];
3152                   NewProjectDialog newProjectDialog;
3153
3154                   if(projectView)
3155                   {
3156                      projectView.visible = false;
3157                      if(!projectView.Destroy(0))
3158                         return true;
3159                   }
3160
3161                   newProjectDialog = { master = this };
3162
3163                   strcpy(name, app.argv[c]);
3164                   StripExtension(name);
3165                   GetLastDirectory(name, name);
3166                   newProjectDialog.projectName.contents = name;
3167                   newProjectDialog.projectName.NotifyModified(newProjectDialog, newProjectDialog.projectName);
3168                   newProjectDialog.locationEditBox.path = parentPath;
3169                   newProjectDialog.NotifyModifiedLocation(newProjectDialog.locationEditBox);
3170
3171                   incref newProjectDialog;
3172                   newProjectDialog.Modal();
3173                   if(projectView)
3174                   {
3175                      ideSettings.AddRecentProject(projectView.fileName);
3176                      ide.UpdateRecentMenus();
3177                      settingsContainer.Save();
3178                   }
3179                   delete newProjectDialog;
3180                   // Open only one project
3181                   break;
3182                }
3183                else
3184                   ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3185             }
3186             else if(strstr(fullPath, "http://") == fullPath)
3187                ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3188          }
3189       }
3190       if(passThrough && projectView && projectView.project && workspace)
3191          workspace.commandLineArgs = passArgs;
3192       if(passDebugWorkDir && projectView && projectView.project && workspace)
3193       {
3194          workspace.debugDir = passDebugWorkDir;
3195          delete passDebugWorkDir;
3196       }
3197       if(debugStart)
3198          ;//MenuDebugStart(debugStartResumeItem, 0); // <-- how TODO this without getting into the app.Wait lock
3199
3200       UpdateToolBarActiveConfigs(false);
3201       UpdateToolBarActiveCompilers();
3202       delete passArgs;
3203       return true;
3204    }
3205
3206    void OnDestroy()
3207    {
3208       // IS THIS NEEDED? WASN'T HERE BEFORE...  Crashes on getting node's projectView otherwise
3209       if(projectView)
3210       {
3211          projectView.visible = false;
3212          projectView.Destroy(0);
3213          projectView = null;
3214       }
3215 #ifdef GDB_DEBUG_GUI
3216       gdbDialog.Destroy(0);
3217       delete gdbDialog;
3218 #endif
3219    }
3220
3221    void SetPath(bool projectsDirs, CompilerConfig compiler, ProjectConfig config, int bitDepth)
3222    {
3223       int c, len, count;
3224       char * newList;
3225       char * oldPaths[128];
3226       String oldList = new char[maxPathLen];
3227       Array<String> newExePaths { };
3228       //Map<String, bool> exePathExists { };
3229       bool found = false;
3230 #if defined(__unix__) || defined(__APPLE__)
3231       Array<String> newLibPaths { };
3232       Map<String, bool> libPathExists { };
3233 #endif
3234
3235       if(projectsDirs)
3236       {
3237          for(prj : workspace.projects)
3238          {
3239             DirExpression targetDirExp;
3240
3241             // SKIP FIRST PROJECT...
3242             if(prj == workspace.projects.firstIterator.data) continue;
3243
3244             // NOTE: Right now the additional project config dir will be
3245             //       obtained when the debugger is started, so toggling it
3246             //       while building will change which library gets used.
3247             //       To go with the initial state, e.g. when F5 was pressed,
3248             //       we nould need to keep a list of all project's active
3249             //       config upon startup.
3250             targetDirExp = prj.GetTargetDir(compiler, prj.config, bitDepth);
3251
3252             /*if(prj.config.targetType == sharedLibrary && prj.config.debug)
3253                cfg = prj.config;
3254             else
3255             {
3256                for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3257                   if(cfg.targetType == sharedLibrary && cfg.debug && !strcmpi(cfg.name, "Debug"))
3258                      break;
3259                if(!cfg)
3260                {
3261                   for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3262                      if(cfg.targetType == sharedLibrary && cfg.debug)
3263                         break;
3264                }
3265             }*/
3266             if(targetDirExp.dir)
3267             {
3268                char buffer[MAX_LOCATION];
3269 #if defined(__WIN32__)
3270                Array<String> paths = newExePaths;
3271 #else
3272                Array<String> paths = newLibPaths;
3273 #endif
3274                GetSystemPathBuffer(buffer, prj.topNode.path);
3275                PathCat(buffer, targetDirExp.dir);
3276                for(p : paths)
3277                {
3278                   if(!fstrcmp(p, buffer))
3279                   {
3280                      found = true;
3281                      break;
3282                   }
3283                }
3284                if(!found)
3285                   paths.Add(CopyString(buffer));
3286             }
3287             delete targetDirExp;
3288          }
3289       }
3290
3291       for(item : compiler.executableDirs)
3292       {
3293          found = false;
3294          for(p : newExePaths)
3295          {
3296             if(!fstrcmp(p, item))
3297             {
3298                found = true;
3299                break;
3300             }
3301          }
3302          if(!found)
3303             newExePaths.Add(CopySystemPath(item));
3304       }
3305
3306       GetEnvironment("PATH", oldList, maxPathLen);
3307 /*#ifdef _DEBUG
3308       printf("Old value of PATH: %s\n", oldList);
3309 #endif*/
3310       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3311       for(c = 0; c < count; c++)
3312       {
3313          found = false;
3314          for(p : newExePaths)
3315          {
3316             if(!fstrcmp(p, oldPaths[c]))
3317             {
3318                found = true;
3319                break;
3320             }
3321          }
3322          if(!found)
3323             newExePaths.Add(CopySystemPath(oldPaths[c]));
3324       }
3325
3326       len = 0;
3327       for(path : newExePaths)
3328          len += strlen(path) + 1;
3329       newList = new char[len + 1];
3330       newList[0] = '\0';
3331       for(path : newExePaths)
3332       {
3333          strcat(newList, path);
3334          strcat(newList, pathListSep);
3335       }
3336       newList[len - 1] = '\0';
3337       SetEnvironment("PATH", newList);
3338 /*#ifdef _DEBUG
3339       printf("New value of PATH: %s\n", newList);
3340 #endif*/
3341       delete newList;
3342
3343       newExePaths.Free();
3344       delete newExePaths;
3345
3346 #if defined(__unix__) || defined(__APPLE__)
3347
3348       for(item : compiler.libraryDirs)
3349       {
3350          if(!libPathExists[item])  // fstrcmp should be used
3351          {
3352             String s = CopyString(item);
3353             newLibPaths.Add(s);
3354             libPathExists[s] = true;
3355          }
3356       }
3357
3358 #if defined(__APPLE__)
3359       GetEnvironment("DYLD_LIBRARY_PATH", oldList, maxPathLen);
3360 #else
3361       GetEnvironment("LD_LIBRARY_PATH", oldList, maxPathLen);
3362 #endif
3363 /*#ifdef _DEBUG
3364       printf("Old value of [DY]LD_LIBRARY_PATH: %s\n", oldList);
3365 #endif*/
3366       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3367       for(c = 0; c < count; c++)
3368       {
3369          if(!libPathExists[oldPaths[c]])  // fstrcmp should be used
3370          {
3371             String s = CopyString(oldPaths[c]);
3372             newLibPaths.Add(s);
3373             libPathExists[s] = true;
3374          }
3375       }
3376
3377       len = 0;
3378       for(path : newLibPaths)
3379          len += strlen(path) + 1;
3380       newList = new char[len + 1];
3381       newList[0] = '\0';
3382       for(path : newLibPaths)
3383       {
3384          strcat(newList, path);
3385          strcat(newList, pathListSep);
3386       }
3387       newList[len - 1] = '\0';
3388 #if defined(__APPLE__)
3389       SetEnvironment("DYLD_LIBRARY_PATH", newList);
3390 #else
3391       SetEnvironment("LD_LIBRARY_PATH", newList);
3392 #endif
3393 /*#ifdef _DEBUG
3394       printf("New value of [DY]LD_LIBRARY_PATH: %s\n", newList);
3395 #endif*/
3396       delete newList;
3397
3398       newLibPaths.Free();
3399       delete newLibPaths;
3400       delete libPathExists;
3401 #endif
3402
3403       if(compiler.distccEnabled && compiler.distccHosts)
3404          SetEnvironment("DISTCC_HOSTS", compiler.distccHosts);
3405
3406       delete oldList;
3407    }
3408
3409    void DestroyTemporaryProjectDir()
3410    {
3411       if(tmpPrjDir && tmpPrjDir[0])
3412       {
3413          if(FileExists(tmpPrjDir).isDirectory)
3414             DestroyDir(tmpPrjDir);
3415          property::tmpPrjDir = null;
3416       }
3417    }
3418
3419    IDEWorkSpace()
3420    {
3421       // Graphics Driver Menu
3422       int c;
3423
3424       /*
3425       app.currentSkin.selectionColor = selectionColor;
3426       app.currentSkin.selectionText = selectionText;
3427       */
3428
3429 /*
3430       driverItems = new MenuItem[app.numDrivers];
3431       for(c = 0; c < app.numDrivers; c++)
3432       {
3433          driverItems[c] = MenuItem { driversMenu, app.drivers[c], NotifySelect = NotifySelectDisplayDriver };
3434          driverItems[c].id = c;
3435          driverItems[c].isRadio = true;
3436       }
3437 */
3438       driverItems = new MenuItem[2];
3439 #if defined(__unix__)
3440          driverItems[0] = MenuItem { driversMenu, "X", NotifySelect = NotifySelectDisplayDriver };
3441          driverItems[0].id = 0;
3442          driverItems[0].isRadio = true;
3443 #else
3444          driverItems[0] = MenuItem { driversMenu, "GDI", NotifySelect = NotifySelectDisplayDriver };
3445          driverItems[0].id = 0;
3446          driverItems[0].isRadio = true;
3447 #endif
3448          driverItems[1] = MenuItem { driversMenu, "OpenGL", NotifySelect = NotifySelectDisplayDriver };
3449          driverItems[1].id = 1;
3450          driverItems[1].isRadio = true;
3451
3452 /*      skinItems = new MenuItem[app.numSkins];
3453       for(c = 0; c < app.numSkins; c++)
3454       {
3455          skinItems[c] = MenuItem {skinsMenu, app.skins[c], NotifySelect = NotifySelectDisplaySkin };
3456          skinItems[c].id = c;
3457          skinItems[c].isRadio = true;
3458       }
3459 */
3460       ideFileDialog.master = this;
3461       ideProjectFileDialog.master = this;
3462
3463       //SetDriverAndSkin();
3464       return true;
3465    }
3466
3467    void UpdateRecentMenus()
3468    {
3469       int c;
3470       Menu fileMenu = menu.FindMenu($"File");
3471       Menu recentFiles = fileMenu.FindMenu($"Recent Files");
3472       Menu recentProjects = fileMenu.FindMenu($"Recent Projects");
3473       char * itemPath = new char[MAX_LOCATION];
3474       char * itemName = new char[MAX_LOCATION+4];
3475       MenuItem item;
3476
3477       recentFiles.Clear();
3478       c = 0;
3479
3480       for(recent : ideSettings.recentFiles)
3481       {
3482          strncpy(itemPath, recent, MAX_LOCATION); itemPath[MAX_LOCATION-1] = '\0';
3483          MakeSystemPath(itemPath);
3484          snprintf(itemName, MAX_LOCATION+4, "%d %s", 1 + c, itemPath); itemName[MAX_LOCATION+4-1] = '\0';
3485          recentFiles.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentFile }, ide, true);
3486          c++;
3487       }
3488
3489       recentProjects.Clear();
3490       c = 0;
3491       for(recent : ideSettings.recentProjects)
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          recentProjects.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentProject }, ide, true);
3497          c++;
3498       }
3499
3500       delete itemPath;
3501       delete itemName;
3502    }
3503
3504    ~IDEWorkSpace()
3505    {
3506       delete driverItems;
3507       delete skinItems;
3508       delete languageItems;
3509       delete ideSettings;
3510       if(documentor)
3511       {
3512          documentor.Puts("Quit\n");
3513          documentor.Wait();
3514          delete documentor;
3515       }
3516    }
3517 }
3518
3519 void DestroyDir(char * path)
3520 {
3521    RecursiveDeleteFolderFSI fsi { };
3522    fsi.Iterate(path);
3523    delete fsi;
3524 }
3525
3526 #if defined(__WIN32__)
3527 define sdkDirName = "Ecere SDK";
3528 #else
3529 define sdkDirName = "ecere";
3530 #endif
3531
3532 bool GetInstalledFileOrFolder(char * subDir, char * name, char * path, FileAttribs attribs)
3533 {
3534    bool found = false;
3535    char * v = new char[maxPathLen];
3536    v[0] = '\0';
3537    if(found)
3538    {
3539       strncpy(path, settingsContainer.moduleLocation, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3540       StripLastDirectory(path, path);
3541       PathCat(path, subDir);
3542       if(name) PathCat(path, name);
3543       if(FileExists(path) & attribs) found = true;
3544    }
3545 #if defined(__WIN32__)
3546    if(!found)
3547    {
3548       GetEnvironment("ECERE_SDK_SRC", v, maxPathLen);
3549       if(v[0])
3550       {
3551          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3552          PathCat(path, subDir);
3553          if(name) PathCat(path, name);
3554          if(FileExists(path) & attribs) found = true;
3555       }
3556    }
3557    if(!found)
3558    {
3559       GetEnvironment("AppData", v, maxPathLen);
3560       if(v[0])
3561       {
3562          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3563          PathCat(path, sdkDirName);
3564          PathCat(path, subDir);
3565          if(name) PathCat(path, name);
3566          if(FileExists(path) & attribs) found = true;
3567       }
3568    }
3569    if(!found)
3570    {
3571       GetEnvironment("ProgramData", v, maxPathLen);
3572       if(v[0])
3573       {
3574          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3575          PathCat(path, sdkDirName);
3576          PathCat(path, subDir);
3577          if(name) PathCat(path, name);
3578          if(FileExists(path) & attribs) found = true;
3579       }
3580    }
3581    if(!found)
3582    {
3583       GetEnvironment("ProgramFiles", v, maxPathLen);
3584       if(v[0])
3585       {
3586          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3587          PathCat(path, sdkDirName);
3588          PathCat(path, subDir);
3589          if(name) PathCat(path, name);
3590          if(FileExists(path) & attribs) found = true;
3591       }
3592    }
3593    if(!found)
3594    {
3595       GetEnvironment("ProgramFiles(x86)", v, maxPathLen);
3596       if(v[0])
3597       {
3598          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3599          PathCat(path, sdkDirName);
3600          PathCat(path, subDir);
3601          if(name) PathCat(path, name);
3602          if(FileExists(path) & attribs) found = true;
3603       }
3604    }
3605    if(!found)
3606    {
3607       GetEnvironment("SystemDrive", v, maxPathLen);
3608       if(v[0])
3609       {
3610          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3611          PathCat(path, "Program Files");
3612          PathCat(path, sdkDirName);
3613          PathCat(path, subDir);
3614          if(name) PathCat(path, name);
3615          if(FileExists(path) & attribs) found = true;
3616       }
3617    }
3618 #else
3619    if(!found)
3620    {
3621       byte * tokens[256];
3622       int c, numTokens;
3623
3624       GetEnvironment("XDG_DATA_DIRS", v, maxPathLen);
3625       numTokens = TokenizeWith(v, sizeof(tokens) / sizeof(byte *), tokens, ":", false);
3626       for(c=0; c<numTokens; c++)
3627       {
3628          strncpy(path, tokens[c], MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3629          PathCat(path, sdkDirName);
3630          PathCat(path, subDir);
3631          if(name) PathCat(path, name);
3632          if(FileExists(path) & attribs) found = true;
3633       }
3634    }
3635 #endif
3636    delete v;
3637    return found;
3638 }
3639
3640 void FindAndShellOpenInstalledFolder(char * name)
3641 {
3642    char path[MAX_LOCATION];
3643    if(GetInstalledFileOrFolder(name, null, path, { isDirectory = true }))
3644       ShellOpen(path);
3645 }
3646
3647 void FindAndShellOpenInstalledFile(char * subdir, char * name)
3648 {
3649    char path[MAX_LOCATION];
3650    if(GetInstalledFileOrFolder(subdir, name, path, { isFile = true }))
3651       ShellOpen(path);
3652 }
3653
3654 class RecursiveDeleteFolderFSI : NormalFileSystemIterator
3655 {
3656    bool preserveRootFolder;
3657
3658    void OutFolder(char * folderPath, bool isRoot)
3659    {
3660       if(!(preserveRootFolder && isRoot))
3661          RemoveDir(folderPath);
3662    }
3663
3664    bool OnFile(char * filePath)
3665    {
3666       DeleteFile(filePath);
3667       return true;
3668    }
3669 }
3670
3671 class IDEApp : GuiApplication
3672 {
3673    //driver = "Win32Console";
3674    // driver = "OpenGL";
3675    // skin = "Aqua";
3676    //skin = "TVision";
3677
3678    TempFile includeFile { };
3679    int argFilesCount;
3680
3681    bool Init()
3682    {
3683       char ext[MAX_EXTENSION];
3684       SetLoggingMode(stdOut, null);
3685       //SetLoggingMode(debug, null);
3686
3687       settingsContainer.Load();
3688
3689       if(ideSettings.language)
3690       {
3691          String language = GetLanguageString();
3692          if(ideSettings.language.OnCompare(language))
3693          {
3694             LanguageRestart(ideSettings.language, app, null, null, null, null, true);
3695             return false;
3696          }
3697       }
3698
3699       // First count files arg to decide whether to maximize
3700       {
3701          bool passThrough = false, debugWorkDir = false;
3702          int c;
3703          argFilesCount = 0;
3704          for(c = 1; c<app.argc; c++)
3705          {
3706             if(passThrough);
3707             else if(debugWorkDir)
3708                debugWorkDir = false;
3709             else if(!strcmp(app.argv[c], "-t"));
3710             else if(!strcmp(app.argv[c], "-no-parsing"));
3711             else if(!strcmp(app.argv[c], "-debug-start"));
3712             else if(!strcmp(app.argv[c], "-debug-work-dir"))
3713                debugWorkDir = true;
3714             else if(!strcmp(app.argv[c], "-@"))
3715                passThrough = true;
3716             else
3717                argFilesCount++;
3718          }
3719       }
3720
3721       if(app.argFilesCount > 0 && !strcmpi(GetExtension(argv[1], ext), "3ds"))
3722       {
3723          app.driver = "OpenGL";
3724          ide.driverItems[1].checked = true;
3725       }
3726       else
3727       {
3728 #if defined(__unix__) || defined(__APPLE__)
3729          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "X";
3730 #else
3731          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "GDI";
3732 #endif
3733          ide.driverItems[ideSettings.displayDriver && !strcmp(ideSettings.displayDriver,"OpenGL")].checked = true;
3734       }
3735
3736       {
3737          char model[MAX_LOCATION];
3738          if(GetInstalledFileOrFolder("samples", "3D/ModelViewer/models/duck/duck.3DS", model, { isFile = true }))
3739          {
3740             ide.duck.modelFile = model;
3741             ide.duck.parent = ideMainFrame;
3742          }
3743       }
3744       if(ide.duck.modelFile && !strcmpi(app.driver, "OpenGL"))
3745          ide.debugRubberDuck.disabled = false;
3746
3747       SetInIDE(true);
3748
3749       desktop.caption = titleECEREIDE;
3750       /*
3751       int c;
3752       for(c = 1; c<app.argc; c++)
3753       {
3754          char fullPath[MAX_LOCATION];
3755          GetWorkingDir(fullPath, MAX_LOCATION);
3756          PathCat(fullPath, app.argv[c]);
3757          ide.OpenFile(fullPath, app.argFilesCount > 1, true, null, yes, normal, false);
3758       }
3759       */
3760
3761       // Default to language specified by environment if no language selected
3762       if(!ideSettings.language)
3763       {
3764          ideSettings.language = GetLanguageString();
3765          settingsContainer.Save();
3766       }
3767
3768       // Default to home directory if no directory yet set up
3769       if(!ideSettings.ideProjectFileDialogLocation[0])
3770       {
3771          bool found = false;
3772          char location[MAX_LOCATION];
3773          char * home = getenv("HOME");
3774          char * homeDrive = getenv("HOMEDRIVE");
3775          char * homePath = getenv("HOMEPATH");
3776          char * userProfile = getenv("USERPROFILE");
3777          char * systemDrive = getenv("SystemDrive");
3778          if(home && FileExists(home).isDirectory)
3779          {
3780             strcpy(location, home);
3781             found = true;
3782          }
3783          if(!found && homeDrive && homePath)
3784          {
3785             strcpy(location, homeDrive);
3786             PathCat(location, homePath);
3787             if(FileExists(location).isDirectory)
3788                found = true;
3789          }
3790          if(!found && FileExists(userProfile).isDirectory)
3791          {
3792             strcpy(location, userProfile);
3793             found = true;
3794          }
3795          if(!found && FileExists(systemDrive).isDirectory)
3796          {
3797             strcpy(location, systemDrive);
3798             found = true;
3799          }
3800          if(found)
3801          {
3802             ideSettings.ideProjectFileDialogLocation = location;
3803             if(!ideSettings.ideFileDialogLocation[0])
3804                ideSettings.ideFileDialogLocation = location;
3805          }
3806       }
3807
3808       if(!LoadIncludeFile())
3809          PrintLn("error: unable to load :crossplatform.mk file inside ide binary.");
3810
3811       // Create language menu
3812       {
3813          String language = ideSettings.language;
3814          int i = 0;
3815          bool found = false;
3816
3817          ide.languageItems = new MenuItem[languages.count];
3818          for(l : languages)
3819          {
3820             ide.languageItems[i] =
3821             {
3822                ide.languageMenu, l.name;
3823                bitmap = { l.bitmap };
3824                id = i;
3825                isRadio = true;
3826
3827                bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3828                {
3829                   if(!LanguageRestart(languages[(int)selection.id].code, app, ideSettings, settingsContainer, ide, ide.projectView, false))
3830                   {
3831                      // Re-select previous selected language if aborted
3832                      String language = ideSettings.language;
3833                      int i = 0;
3834                      for(l : languages)
3835                      {
3836                         if(((!language || !language[0]) && i == 0) ||
3837                            (language && !strcmpi(l.code, language)))
3838                         {
3839                            ide.languageItems[i].checked = true;
3840                            break;
3841                         }
3842                         i++;
3843                      }
3844                   }
3845                   return true;
3846                }
3847             };
3848             i++;
3849          }
3850
3851          // Try to find country-specific language first
3852          if(language)
3853          {
3854             i = 0;
3855             for(l : languages)
3856             {
3857                if(!strcmpi(l.code, language) || (i == 0 && !strcmpi("en", language)))
3858                {
3859                   ide.languageItems[i].checked = true;
3860                   found = true;
3861                   break;
3862                }
3863                i++;
3864             }
3865          }
3866
3867          // Try generalizing locale
3868          if(!found && language)
3869          {
3870             char * under;
3871             char genericLocale[256];
3872             i = 0;
3873             strncpy(genericLocale, language, sizeof(genericLocale));
3874             genericLocale[sizeof(genericLocale)] = 0;
3875
3876             under = strchr(genericLocale, '_');
3877             if(under)
3878                *under = 0;
3879             if(!strcmpi(genericLocale, "zh"))
3880                strcpy(genericLocale, "zh_CN");
3881             if(strcmp(genericLocale, language))
3882             {
3883                for(l : languages)
3884                {
3885                   if(!strcmpi(l.code, genericLocale) || (i == 0 && !strcmpi("en", genericLocale)))
3886                   {
3887                      ide.languageItems[i].checked = true;
3888                      found = true;
3889                      break;
3890                   }
3891                   i++;
3892                }
3893             }
3894          }
3895
3896          if(!found)
3897             ide.languageItems[0].checked = true;
3898
3899          MenuDivider { ide.languageMenu };
3900          MenuItem
3901          {
3902             ide.languageMenu, "Help Translate";
3903
3904             bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3905             {
3906                ShellOpen("http://translations.launchpad.net/ecere");
3907                return true;
3908             }
3909          };
3910       }
3911
3912       ideMainFrame.Create();
3913       if(app.argFilesCount > 1)
3914          ide.MenuWindowTileVert(null, 0);
3915       return true;
3916    }
3917
3918    bool Cycle(bool idle)
3919    {
3920       if(ide.documentor)
3921       {
3922          if(ide.documentor.Peek())
3923          {
3924             char line[1024];
3925             ide.documentor.GetLine(line, sizeof(line));
3926             if(!strcmpi(line, "Exited"))
3927             {
3928                ide.documentor.CloseInput();
3929                ide.documentor.CloseOutput();
3930                ide.documentor.Wait();
3931                delete ide.documentor;
3932             }
3933          }
3934          if(ide.documentor && ide.documentor.eof)
3935          {
3936             ide.documentor.CloseInput();
3937             ide.documentor.CloseOutput();
3938             ide.documentor.Wait();
3939             delete ide.documentor;
3940          }
3941       }
3942       return true;
3943    }
3944
3945    bool LoadIncludeFile()
3946    {
3947       bool result = false;
3948       File include = FileOpen(":crossplatform.mk", read);
3949       if(include)
3950       {
3951          File f = includeFile;
3952          if(f)
3953          {
3954             for(; !include.Eof(); )
3955             {
3956                char buffer[4096];
3957                int count = include.Read(buffer, 1, 4096);
3958                f.Write(buffer, 1, count);
3959             }
3960             result = true;
3961          }
3962          delete include;
3963       }
3964       return result;
3965    }
3966 }
3967
3968 IDEMainFrame ideMainFrame { };
3969
3970 define app = ((IDEApp)__thisModule);
3971 #ifdef _DEBUG
3972 define titleECEREIDE = $"Ecere IDE (Debug)";
3973 #else
3974 define titleECEREIDE = $"Ecere IDE";
3975 #endif