ide/Debugger: Rubber Ducking Support
[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, top = 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 = 290, pitch = 20 };
1118             camera.Update();
1119             Update(null);
1120             return true;
1121          }
1122
1123          bool OnRightButtonDown(int x, int y, Modifiers mods)
1124          {
1125             MenuWindowMove(null, 0);
1126             return false;
1127          }
1128
1129          bool OnRightButtonUp(int x, int y, Modifiers mods)
1130          {
1131             position = position;
1132             state = normal;
1133             return true;
1134          }
1135       };
1136       MenuItem debugRubberDuck
1137       {
1138          debugMenu, $"Rubber Duck", checkable = true, disabled = true;
1139          bool NotifySelect(MenuItem selection, Modifiers mods)
1140          {
1141             if(selection.checked)
1142                duck.Create();
1143             else
1144                duck.Destroy(0);
1145             return true;
1146          }
1147       }
1148 #ifndef __WIN32__
1149       MenuDivider { debugMenu };
1150       MenuItem debugUseValgrindItem
1151       {
1152          debugMenu, $"Use Valgrind", d, disabled = true, checkable = true;
1153          bool NotifySelect(MenuItem selection, Modifiers mods)
1154          {
1155             if(ide.workspace)
1156             {
1157                ide.workspace.useValgrind = selection.checked;
1158                ide.workspace.Save();
1159             }
1160             ide.AdjustValgrindMenus();
1161             return true;
1162          }
1163       }
1164       Menu debugValgrindLeakCheckItem { debugMenu, $"Valgrind Leak Check", h };
1165          MenuItem debugValgrindNoLeakCheckItem      { debugValgrindLeakCheckItem, $"No"     , f, id = ValgrindLeakCheck::no     , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1166          MenuItem debugValgrindSummaryLeakCheckItem { debugValgrindLeakCheckItem, $"Summary", f, id = ValgrindLeakCheck::summary, checkable = true, disabled = true; NotifySelect = ValgrindLCSelect, checked = true; }
1167          MenuItem debugValgrindYesLeakCheckItem     { debugValgrindLeakCheckItem, $"Yes"    , f, id = ValgrindLeakCheck::yes    , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1168          MenuItem debugValgrindFullLeakCheckItem    { debugValgrindLeakCheckItem, $"Full"   , f, id = ValgrindLeakCheck::full   , checkable = true, disabled = true; NotifySelect = ValgrindLCSelect; }
1169          bool ValgrindLCSelect(MenuItem selection, Modifiers mods)
1170          {
1171             if(ide.workspace)
1172             {
1173                if(selection.checked)
1174                {
1175                   ValgrindLeakCheck vgLeakCheck = (ValgrindLeakCheck)selection.id;
1176
1177                   debugValgrindNoLeakCheckItem.checked      = debugValgrindNoLeakCheckItem.id      == vgLeakCheck;
1178                   debugValgrindSummaryLeakCheckItem.checked = debugValgrindSummaryLeakCheckItem.id == vgLeakCheck;
1179                   debugValgrindYesLeakCheckItem.checked     = debugValgrindYesLeakCheckItem.id     == vgLeakCheck;
1180                   debugValgrindFullLeakCheckItem.checked    = debugValgrindFullLeakCheckItem.id    == vgLeakCheck;
1181
1182                   ide.workspace.vgLeakCheck = vgLeakCheck;
1183                   ide.workspace.Save();
1184                }
1185                else
1186                   selection.checked = true;
1187             }
1188             return true;
1189          }
1190       Menu debugValgrindRedzoneSizeItem { debugMenu, $"Valgrind Redzone Size", z };
1191          MenuItem debugValgrindRSDefaultItem { debugValgrindRedzoneSizeItem, $"Default", f, id =  -1, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect, checked = true; }
1192          MenuItem debugValgrindRS0Item       { debugValgrindRedzoneSizeItem, "0"      , f, id =   0, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1193          MenuItem debugValgrindRS16Item      { debugValgrindRedzoneSizeItem, "16"     , f, id =  16, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1194          MenuItem debugValgrindRS32Item      { debugValgrindRedzoneSizeItem, "32"     , f, id =  32, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1195          MenuItem debugValgrindRS64Item      { debugValgrindRedzoneSizeItem, "64"     , f, id =  64, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1196          MenuItem debugValgrindRS128Item     { debugValgrindRedzoneSizeItem, "128"    , f, id = 128, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1197          MenuItem debugValgrindRS256Item     { debugValgrindRedzoneSizeItem, "256"    , f, id = 256, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1198          MenuItem debugValgrindRS512Item     { debugValgrindRedzoneSizeItem, "512"    , f, id = 512, checkable = true, disabled = true; NotifySelect = ValgrindRSSelect; }
1199          bool ValgrindRSSelect(MenuItem selection, Modifiers mods)
1200          {
1201             if(ide.workspace)
1202             {
1203                if(selection.checked)
1204                {
1205                   int vgRedzoneSize = (int)selection.id;
1206
1207                   debugValgrindRSDefaultItem.checked = debugValgrindRSDefaultItem.id == vgRedzoneSize;
1208                   debugValgrindRS0Item.checked       = debugValgrindRS0Item.id       == vgRedzoneSize;
1209                   debugValgrindRS16Item.checked      = debugValgrindRS16Item.id      == vgRedzoneSize;
1210                   debugValgrindRS32Item.checked      = debugValgrindRS32Item.id      == vgRedzoneSize;
1211                   debugValgrindRS64Item.checked      = debugValgrindRS64Item.id      == vgRedzoneSize;
1212                   debugValgrindRS128Item.checked     = debugValgrindRS128Item.id     == vgRedzoneSize;
1213                   debugValgrindRS256Item.checked     = debugValgrindRS256Item.id     == vgRedzoneSize;
1214                   debugValgrindRS512Item.checked     = debugValgrindRS512Item.id     == vgRedzoneSize;
1215
1216                   ide.workspace.vgRedzoneSize = vgRedzoneSize;
1217                   ide.workspace.Save();
1218                }
1219                else
1220                   selection.checked = true;
1221             }
1222             return true;
1223          }
1224       MenuItem debugValgrindTrackOriginsItem
1225       {
1226          debugMenu, $"Valgrind Track Origins", k, checkable = true, disabled = true;
1227          bool NotifySelect(MenuItem selection, Modifiers mods)
1228          {
1229             if(ide.workspace)
1230             {
1231                ide.workspace.vgTrackOrigins = selection.checked;
1232                ide.workspace.Save();
1233             }
1234             return true;
1235          }
1236       };
1237 #endif
1238       MenuDivider { debugMenu };
1239       MenuItem debugStepIntoItem
1240       {
1241          debugMenu, $"Step Into", i, f11, disabled = true;
1242          bitmap = { ":actions/stepInto.png" };
1243          bool NotifySelect(MenuItem selection, Modifiers mods)
1244          {
1245             if(projectView) projectView.DebugStepInto();
1246             return true;
1247          }
1248       }
1249       MenuItem debugStepOverItem
1250       {
1251          debugMenu, $"Step Over", v, f10, disabled = true;
1252          bitmap = { ":actions/stepOver.png" };
1253          bool NotifySelect(MenuItem selection, Modifiers mods)
1254          {
1255             if(projectView) projectView.DebugStepOver(false);
1256             return true;
1257          }
1258       }
1259       MenuItem debugSkipStepOverItem
1260       {
1261          debugMenu, $"Step Over Skipping Breakpoints", e, shiftF10, disabled = true;
1262          bitmap = { ":actions/stepOverSkipBreak.png" };
1263          bool NotifySelect(MenuItem selection, Modifiers mods)
1264          {
1265             if(projectView) projectView.DebugStepOver(true);
1266             return true;
1267          }
1268       }
1269       MenuItem debugStepOutItem
1270       {
1271          debugMenu, $"Step Out", o, shiftF11, disabled = true;
1272          bitmap = { ":actions/stepOut.png" };
1273          bool NotifySelect(MenuItem selection, Modifiers mods)
1274          {
1275             if(projectView) projectView.DebugStepOut(false);
1276             return true;
1277          }
1278       }
1279       MenuItem debugSkipStepOutItem
1280       {
1281          debugMenu, $"Step Out Skipping Breakpoints", n, Key { f11, ctrl = true, shift = true }, disabled = true;
1282          bitmap = { ":actions/skipBreaks.png" };
1283          bool NotifySelect(MenuItem selection, Modifiers mods)
1284          {
1285             if(projectView) projectView.DebugStepOut(true);
1286             return true;
1287          }
1288       }
1289 #if 0
1290       MenuItem debugStepUntilItem
1291       {
1292          debugMenu, $"Step Over Until Next Line", x, disabled = true;
1293          bool NotifySelect(MenuItem selection, Modifiers mods)
1294          {
1295             if(projectView) projectView.DebugStepUntil(false);
1296             return true;
1297          }
1298       }
1299       MenuItem debugSkipStepUntilItem
1300       {
1301          debugMenu, $"Step Over Until Next Line Skipping Breakpoints", e, Key { f10, shift = true, alt = true }, disabled = true;
1302          bool NotifySelect(MenuItem selection, Modifiers mods)
1303          {
1304             if(projectView) projectView.DebugStepUntil(true);
1305             return true;
1306          }
1307       }
1308 #endif
1309       MenuPlacement debugRunToCursorItem { debugMenu, $"Run To Cursor", c };
1310       MenuPlacement debugSkipRunToCursorItem { debugMenu, $"Run To Cursor Skipping Breakpoints", u };
1311       MenuPlacement debugRunToCursorAtSameLevelItem { debugMenu, $"Run To Cursor At Same Level", l };
1312       MenuPlacement debugSkipRunToCursorAtSameLevelItem { debugMenu, $"Run To Cursor At Same Level Skipping Breakpoints", g };
1313 #if 0
1314       MenuPlacement debugBpRunToCursorItem { debugMenu, $"BP Run To Cursor" };
1315       MenuPlacement debugBpSkipRunToCursorItem { debugMenu, $"BP Run To Cursor Skipping Breakpoints" };
1316       MenuPlacement debugBpRunToCursorAtSameLevelItem { debugMenu, $"BP Run To Cursor At Same Level" };
1317       MenuPlacement debugBpSkipRunToCursorAtSameLevelItem { debugMenu, $"BP Run To Cursor At Same Level Skipping Breakpoints" };
1318 #endif
1319       //MenuDivider { debugMenu };
1320       //MenuPlacement debugToggleBreakpoint { debugMenu, "Toggle Breakpoint", t };
1321    MenuPlacement imageMenu { menu, $"Image", i };
1322    Menu viewMenu { menu, $"View", v };
1323       MenuItem viewProjectItem
1324       {
1325          viewMenu, $"Project View", j, alt0, disabled = true;
1326          bool NotifySelect(MenuItem selection, Modifiers mods)
1327          {
1328             if(projectView)
1329             {
1330                projectView.visible = true;
1331                projectView.Activate();
1332             }
1333             return true;
1334          }
1335       }
1336       MenuPlacement { viewMenu, $"View Designer" };
1337       MenuPlacement { viewMenu, $"View Code" };
1338       MenuPlacement { viewMenu, $"View Properties" };
1339       MenuPlacement { viewMenu, $"View Methods" };
1340       MenuItem viewDesignerItem
1341       {
1342          viewMenu, $"View Designer", d, f8;
1343          bool NotifySelect(MenuItem selection, Modifiers mods)
1344          {
1345             Window client = activeClient;
1346             Class dataType = client._class;
1347             if(!strcmp(dataType.name, "Designer"))
1348             {
1349                client.visible = true;
1350                client.Activate();
1351             }
1352             else
1353                ((CodeEditor)client).ViewDesigner();
1354             return true;
1355          }
1356       }
1357       MenuItem viewCodeItem
1358       {
1359          viewMenu, $"View Code", c, f8;
1360          bool NotifySelect(MenuItem selection, Modifiers mods)
1361          {
1362             Window client = activeClient;
1363             Class dataType = client._class;
1364             if(!strcmp(dataType.name, "Designer"))
1365                client = ((Designer)client).codeEditor;
1366
1367             client.Activate();
1368             // Do this after so the caret isn't moved yet...
1369             client.visible = true;
1370             return true;
1371          }
1372       }
1373       MenuItem viewPropertiesItem
1374       {
1375          viewMenu, $"View Properties", p, f4;
1376          bool NotifySelect(MenuItem selection, Modifiers mods)
1377          {
1378             sheet.visible = true;
1379             sheet.sheetSelected = properties;
1380             sheet.Activate();
1381             return true;
1382          }
1383       }
1384       MenuItem viewMethodsItem
1385       {
1386          viewMenu, $"View Methods", m, f4;
1387          bool NotifySelect(MenuItem selection, Modifiers mods)
1388          {
1389             sheet.visible = true;
1390             sheet.sheetSelected = methods;
1391             sheet.Activate();
1392             return true;
1393          }
1394       }
1395       MenuItem viewToolBoxItem
1396       {
1397          viewMenu, $"View Toolbox", x, f12;
1398          bool NotifySelect(MenuItem selection, Modifiers mods)
1399          {
1400             toolBox.visible = true;
1401             toolBox.Activate();
1402             return true;
1403          }
1404       }
1405       MenuItem viewOutputItem
1406       {
1407          viewMenu, $"Output", o, alt2;
1408          bool NotifySelect(MenuItem selection, Modifiers mods)
1409          {
1410             outputView.Show();
1411             return true;
1412          }
1413       }
1414       MenuItem viewWatchesItem
1415       {
1416          viewMenu, $"Watches", w, alt3;
1417          bool NotifySelect(MenuItem selection, Modifiers mods)
1418          {
1419             watchesView.Show();
1420             return true;
1421          }
1422       }
1423       MenuItem viewThreadsItem
1424       {
1425          viewMenu, $"Threads", t, alt4;
1426          bool NotifySelect(MenuItem selection, Modifiers mods)
1427          {
1428             threadsView.Show();
1429             return true;
1430          }
1431       }
1432       MenuItem viewBreakpointsItem
1433       {
1434          viewMenu, $"Breakpoints", b, alt5;
1435          bool NotifySelect(MenuItem selection, Modifiers mods)
1436          {
1437             breakpointsView.Show();
1438             return true;
1439          }
1440       }
1441       MenuItem viewCallStackItem
1442       {
1443          viewMenu, $"Call Stack", s, alt7;
1444          bool NotifySelect(MenuItem selection, Modifiers mods)
1445          {
1446             callStackView.Show();
1447             return true;
1448          }
1449       }
1450       MenuItem viewAllDebugViews
1451       {
1452          viewMenu, $"All Debug Views", a, alt9;
1453          bool NotifySelect(MenuItem selection, Modifiers mods)
1454          {
1455             outputView.Show();
1456             watchesView.Show();
1457             threadsView.Show();
1458             callStackView.Show();
1459             breakpointsView.Show();
1460             return true;
1461          }
1462       }
1463 #ifdef GDB_DEBUG_GUI
1464       MenuDivider { viewMenu };
1465       MenuItem viewGDBItem
1466       {
1467          viewMenu, $"GDB Dialog", g, Key { f9, shift = true, ctrl = true };
1468          bool NotifySelect(MenuItem selection, Modifiers mods)
1469          {
1470             gdbDialog.Show();
1471             return true;
1472          }
1473       }
1474 #endif
1475       MenuDivider { viewMenu };
1476       MenuItem viewColorPicker
1477       {
1478          viewMenu, $"Color Picker...", c, Key { c, ctrl = true , shift = true };
1479          bool NotifySelect(MenuItem selection, Modifiers mods)
1480          {
1481             ColorPicker colorPicker { master = this };
1482             colorPicker.Modal();
1483             return true;
1484          }
1485       }
1486       MenuDivider { viewMenu };
1487       /*
1488       MenuItem
1489       {
1490          viewMenu, "Full Screen", f, checkable = true;
1491
1492          bool NotifySelect(MenuItem selection, Modifiers mods)
1493          {
1494             app.fullScreen ^= true;
1495             SetDriverAndSkin();
1496             anchor = { 0, 0, 0, 0 };
1497             return true;
1498          }
1499       };
1500       */
1501       Menu driversMenu { viewMenu, $"Graphics Driver", v };
1502
1503       MenuDivider { viewMenu };
1504
1505       Menu languageMenu { viewMenu, "Language", l };
1506
1507       //Menu skinsMenu { viewMenu, "GUI Skins", k };
1508    Menu windowMenu { menu, $"Window", w };
1509       MenuItem { windowMenu, $"Close All", l, NotifySelect = MenuWindowCloseAll };
1510       MenuDivider { windowMenu };
1511       MenuItem { windowMenu, $"Next", n, f6, NotifySelect = MenuWindowNext };
1512       MenuItem { windowMenu, $"Previous", p, shiftF6, NotifySelect = MenuWindowPrevious };
1513       MenuDivider { windowMenu };
1514       MenuItem { windowMenu, $"Cascade", c, NotifySelect = MenuWindowCascade };
1515       MenuItem { windowMenu, $"Tile Horizontally", h, NotifySelect = MenuWindowTileHorz };
1516       MenuItem { windowMenu, $"Tile Vertically", v, NotifySelect = MenuWindowTileVert };
1517       MenuItem { windowMenu, $"Arrange Icons", a, NotifySelect = MenuWindowArrangeIcons };
1518       MenuDivider { windowMenu };
1519       MenuItem { windowMenu, $"Windows...", w, NotifySelect = MenuWindowWindows };
1520    Menu helpMenu { menu, $"Help", h };
1521       MenuItem
1522       {
1523          helpMenu, $"API Reference", r, f1;
1524          bool NotifySelect(MenuItem selection, Modifiers mods)
1525          {
1526             if(!documentor)
1527             {
1528                char * p = new char[MAX_LOCATION];
1529                p[0] = '\0';
1530                strncpy(p, settingsContainer.moduleLocation, MAX_LOCATION); p[MAX_LOCATION-1] = '\0';
1531                PathCat(p, "documentor");
1532    #if defined(__WIN32__)
1533                ChangeExtension(p, "exe", p);
1534    #endif
1535                if(!FileExists(p).isFile)
1536                   strcpy(p, "documentor");
1537
1538                documentor = DualPipeOpen({ input = true, output = true, showWindow = true }, p);
1539                delete p;
1540             }
1541             else
1542             {
1543                Process_ShowWindows(documentor.GetProcessID());
1544                // documentor.Puts("Activate\n");
1545             }
1546             return true;
1547          }
1548       }
1549       MenuDivider { helpMenu };
1550       MenuItem
1551       {
1552          helpMenu, $"Ecere Tao of Programming [work in progress]", t;
1553          bool NotifySelect(MenuItem selection, Modifiers mods)
1554          {
1555             FindAndShellOpenInstalledFile("doc", "Ecere Tao of Programming [work in progress].pdf");
1556             return true;
1557          }
1558       }
1559       MenuDivider { helpMenu };
1560       MenuItem
1561       {
1562          helpMenu, $"Documentation Folder", d;
1563          bool NotifySelect(MenuItem selection, Modifiers mods)
1564          {
1565             FindAndShellOpenInstalledFolder("doc");
1566             return true;
1567          }
1568       }
1569       MenuItem
1570       {
1571          helpMenu, $"Samples Folder", s;
1572          bool NotifySelect(MenuItem selection, Modifiers mods)
1573          {
1574             FindAndShellOpenInstalledFolder("samples");
1575             return true;
1576          }
1577       }
1578       MenuItem
1579       {
1580          helpMenu, $"Extras Folder", x;
1581          bool NotifySelect(MenuItem selection, Modifiers mods)
1582          {
1583             FindAndShellOpenInstalledFolder("extras");
1584             return true;
1585          }
1586       }
1587       MenuDivider { helpMenu };
1588       MenuItem
1589       {
1590          helpMenu, $"Community Forums", f;
1591          bool NotifySelect(MenuItem selection, Modifiers mods)
1592          {
1593             ShellOpen("http://ecere.com/forums");
1594             return true;
1595          }
1596       }
1597       MenuDivider { helpMenu };
1598       MenuItem
1599       {
1600          helpMenu, $"About...", a;
1601          bool NotifySelect(MenuItem selection, Modifiers mods)
1602          {
1603             AboutIDE { master = this }.Modal();
1604             return true;
1605          }
1606       }
1607
1608    property ToolBox toolBox
1609    {
1610       get { return toolBox; }
1611    }
1612
1613    property Sheet sheet
1614    {
1615       get { return sheet; }
1616    }
1617
1618    property Project project
1619    {
1620       get { return projectView ? projectView.project : null; }
1621    }
1622
1623    property Workspace workspace
1624    {
1625       get { return projectView ? projectView.workspace : null; }
1626    }
1627
1628    FindInFilesDialog findInFilesDialog
1629    {
1630       master = this,
1631       filters = findInFilesFileFilters.array, sizeFilters = findInFilesFileFilters.count * sizeof(FileFilter);
1632       filter = 1;
1633    };
1634
1635    bool noParsing;
1636
1637 #ifdef GDB_DEBUG_GUI
1638    GDBDialog gdbDialog
1639    {
1640       master = this, parent = this;
1641       //anchor = { left = 100, top = 100, right = 100, bottom = 100 };
1642
1643       void OnCommand(char * string)
1644       {
1645          if(ide)
1646             ide.debugger.SendGDBCommand(string);
1647       }
1648    };
1649 #endif
1650
1651    bool NotifySelectDisplayDriver(MenuItem selection, Modifiers mods)
1652    {
1653       //app.driver = app.drivers[selection.id];
1654 #if defined(__unix__) || defined(__APPLE__)
1655       app.driver = selection.id ? "OpenGL" : "X";
1656 #else
1657       app.driver = selection.id ? "OpenGL" : "GDI";
1658 #endif
1659       delete ideSettings.displayDriver;
1660       ideSettings.displayDriver = CopyString(selection.id ? "OpenGL" : "Default");
1661
1662       if(ide.duck.modelFile && !strcmpi(app.driver, "OpenGL"))
1663          ide.debugRubberDuck.disabled = false;
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       }
1795    }
1796
1797    bool ShowCodeEditor()
1798    {
1799       if(activeClient)
1800          activeClient.Activate();
1801       else if(projectView)
1802       {
1803          projectView.visible = true;
1804          projectView.Activate();
1805       }
1806       else if(sheet.visible)
1807          sheet.Activate();
1808       else
1809          outputView.visible = false;
1810       return false;
1811    }
1812
1813    void DocumentSaved(Window document, char * fileName)
1814    {
1815       ideSettings.AddRecentFile(fileName);
1816       ide.UpdateRecentMenus();
1817       ide.AdjustFileMenus();
1818       settingsContainer.Save();
1819    }
1820
1821    bool Window::OnFileModified(FileChange fileChange, char * param)
1822    {
1823       char temp[4096];
1824       sprintf(temp, $"The document %s was modified by another application.\n"
1825             "Would you like to reload it and lose your changes?", this.fileName);
1826       if(MessageBox { type = yesNo, master = this/*.parent*/,
1827             text = $"Document has been modified", contents = temp }.Modal() == yes)
1828       {
1829          bool noParsing = (this._class == class(CodeEditor) && ((CodeEditor)this).noParsing) ? true : false;
1830          char * fileName = CopyString(this.fileName);
1831          WindowState state = this.state;
1832          Anchor anchor = this.anchor;
1833          Size size = this.size;
1834
1835          this.modifiedDocument = false;
1836          this.Destroy(0);
1837          this = ide.OpenFile(fileName, false, true, null, no, normal, noParsing);
1838          if(this)
1839          {
1840             this.anchor = anchor;
1841             this.size = size;
1842             this.SetState(state, true, 0);
1843          }
1844          delete fileName;
1845          return true;
1846       }
1847       return true;
1848    }
1849
1850    void UpdateMakefiles()
1851    {
1852       if(workspace)
1853       {
1854          CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
1855          for(prj : workspace.projects)
1856             projectView.ProjectUpdateMakefileForAllConfigs(prj);
1857          delete compiler;
1858       }
1859    }
1860
1861    void UpdateCompilerConfigs(bool mute)
1862    {
1863       UpdateToolBarActiveCompilers();
1864       if(workspace)
1865       {
1866          bool silent = mute || (ide.projectView.buildInProgress == none ? false : true);
1867          CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
1868          if(!silent)
1869          {
1870             projectView.ShowOutputBuildLog(true);
1871             projectView.DisplayCompiler(compiler, false);
1872          }
1873          for(prj : workspace.projects)
1874             projectView.ProjectPrepareCompiler(prj, compiler, silent);
1875          delete compiler;
1876       }
1877    }
1878
1879    void UpdateToolBarActiveCompilers()
1880    {
1881       toolBar.activeCompiler.Clear();
1882       for(compiler : ideSettings.compilerConfigs)
1883       {
1884          DataRow row = toolBar.activeCompiler.AddString(compiler.name);
1885          if(workspace && workspace.compiler && !strcmp(compiler.name, workspace.compiler))
1886             toolBar.activeCompiler.currentRow = row;
1887       }
1888       if(!toolBar.activeCompiler.currentRow && toolBar.activeCompiler.firstRow)
1889          toolBar.activeCompiler.SelectRow(toolBar.activeCompiler.firstRow);
1890    }
1891
1892    void UpdateToolBarActiveConfigs(bool selectionOnly)
1893    {
1894       bool commonSelected = false;
1895       DataRow row = toolBar.activeConfig.currentRow;
1896       if(selectionOnly)
1897          row = toolBar.activeConfig.FindRow(1);
1898       else
1899       {
1900          toolBar.activeConfig.Clear();
1901          row = toolBar.activeConfig.AddString($"(Mixed)");
1902          row.tag = 1;
1903       }
1904       if(workspace)
1905       {
1906          char * configName = null;
1907          if(!selectionOnly)
1908          {
1909             Map<String, int> configs { }; // TOIMP: just need sort but using map until containers have sort
1910             for(prj : workspace.projects)
1911             {
1912                for(cfg : prj.configurations)
1913                {
1914                   if(cfg.name)
1915                      configs[cfg.name] = 1;
1916                }
1917             }
1918             for(name : configs)
1919             {
1920                toolBar.activeConfig.AddString(&name);
1921             }
1922             delete configs;
1923          }
1924          if(projectView && projectView.project)
1925          {
1926             for(prj : workspace.projects)
1927             {
1928                if(prj.config && prj.config.name)
1929                {
1930                   configName = prj.config.name;
1931                   break;
1932                }
1933             }
1934             if(configName)
1935             {
1936                commonSelected = true;
1937                for(prj : workspace.projects)
1938                {
1939                   if(prj.config && (!prj.config.name || strcmp(prj.config.name, configName)))
1940                   {
1941                      commonSelected = false;
1942                      break;
1943                   }
1944                }
1945             }
1946          }
1947          if(commonSelected)
1948          {
1949             commonSelected = false;
1950             for(row = toolBar.activeConfig.firstRow; row; row = row.next)
1951             {
1952                if(!strcmp(row.string, configName))
1953                {
1954                   toolBar.activeConfig.currentRow = row;
1955                   commonSelected = true;
1956                   break;
1957                }
1958             }
1959          }
1960       }
1961       if(!selectionOnly)
1962          toolBar.activeConfig.Sort(null, 0);
1963       if(!commonSelected)
1964          toolBar.activeConfig.currentRow = row;
1965    }
1966
1967    void AdjustMenus()
1968    {
1969       bool unavailable = !project;
1970
1971       projectAddItem.disabled             = unavailable;
1972       toolBar.buttonAddProject.disabled   = unavailable;
1973
1974       projectSettingsItem.disabled        = unavailable;
1975
1976       projectBrowseFolderItem.disabled    = unavailable;
1977
1978       viewProjectItem.disabled            = unavailable;
1979
1980       toolBar.activeConfig.disabled       = unavailable;
1981       toolBar.activeCompiler.disabled     = unavailable;
1982       toolBar.activeBitDepth.disabled     = unavailable;
1983
1984 #ifndef __WIN32__
1985       debugUseValgrindItem.disabled       = unavailable;
1986       AdjustValgrindMenus();
1987 #endif
1988
1989       AdjustFileMenus();
1990       AdjustBuildMenus();
1991       AdjustDebugMenus();
1992    }
1993
1994 #ifndef __WIN32__
1995    void AdjustValgrindMenus()
1996    {
1997       bool unavailable = !project || !debugUseValgrindItem.checked;
1998       debugValgrindNoLeakCheckItem.disabled        = unavailable;
1999       debugValgrindSummaryLeakCheckItem.disabled   = unavailable;
2000       debugValgrindYesLeakCheckItem.disabled       = unavailable;
2001       debugValgrindFullLeakCheckItem.disabled      = unavailable;
2002
2003       debugValgrindTrackOriginsItem.disabled       = unavailable;
2004
2005       debugValgrindRSDefaultItem.disabled          = unavailable;
2006       debugValgrindRS0Item.disabled                = unavailable;
2007       debugValgrindRS16Item.disabled               = unavailable;
2008       debugValgrindRS32Item.disabled               = unavailable;
2009       debugValgrindRS64Item.disabled               = unavailable;
2010       debugValgrindRS128Item.disabled              = unavailable;
2011       debugValgrindRS256Item.disabled              = unavailable;
2012       debugValgrindRS512Item.disabled              = unavailable;
2013    }
2014 #endif
2015
2016    property bool hasOpenedCodeEditors
2017    {
2018       get
2019       {
2020          Window w;
2021          for(w = firstChild; w; w = w.next)
2022             if(w._class == class(CodeEditor) &&
2023                   w.isDocument && !w.closing && w.visible && w.created &&
2024                   w.fileName && w.fileName[0])
2025                return true;
2026          return false;
2027       }
2028    }
2029
2030    void AdjustFileMenus()
2031    {
2032       bool unavailable = project != null || !hasOpenedCodeEditors; // are they supported source code (ec, c, cpp, etc) ?
2033
2034       projectQuickItem.disabled           = unavailable;
2035    }
2036
2037    void AdjustBuildMenus()
2038    {
2039       bool unavailable = project && projectView.buildInProgress;
2040       bool naForRun = unavailable || !project || project.GetTargetType(project.config) != executable;
2041
2042       projectNewItem.disabled             = unavailable;
2043       toolBar.buttonNewProject.disabled   = unavailable;
2044       projectOpenItem.disabled            = unavailable;
2045       toolBar.buttonOpenProject.disabled  = unavailable;
2046
2047       unavailable = !project || projectView.buildInProgress;
2048
2049       projectCloseItem.disabled           = unavailable;
2050       // toolBar.buttonCloseProject.disabled = unavailable;
2051
2052       projectRunItem.disabled    = naForRun;
2053       toolBar.buttonRun.disabled = naForRun;
2054
2055       projectBuildItem.disabled = false;
2056       projectBuildItem.text     = unavailable ? $"Stop Build" : $"Build";
2057       projectBuildItem.accelerator = unavailable ? Key { pauseBreak, ctrl = true } : f7;
2058
2059       projectLinkItem.disabled                  = unavailable;
2060       toolBar.buttonReLink.disabled             = unavailable;
2061       projectRebuildItem.disabled               = unavailable;
2062       toolBar.buttonRebuild.disabled            = unavailable;
2063       projectCleanItem.disabled                 = unavailable;
2064       toolBar.buttonClean.disabled              = unavailable;
2065       projectCleanTargetItem.disabled           = unavailable;
2066       projectRealCleanItem.disabled             = unavailable;
2067       // toolBar.buttonRealClean.disabled          = unavailable;
2068       projectRegenerateItem.disabled            = unavailable;
2069       toolBar.buttonRegenerateMakefile.disabled = unavailable;
2070 #ifdef IDE_SHOW_INSTALL_MENU_BUTTON
2071       projectInstallItem.disabled               = unavailable;
2072       toolBar.buttonInstall.disabled            = unavailable;
2073 #endif
2074       projectCompileItem.disabled               = unavailable;
2075
2076       AdjustPopupBuildMenus();
2077    }
2078
2079    void AdjustPopupBuildMenus()
2080    {
2081       bool unavailable = !project || projectView.buildInProgress;
2082
2083       if(projectView && projectView.popupMenu && projectView.popupMenu.menu && projectView.popupMenu.created)
2084       {
2085          MenuItem menu;
2086          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectBuild, 0);
2087          if(menu)
2088          {
2089             menu.disabled = false;
2090             menu.text   = unavailable ? $"Stop Build" : $"Build";
2091             menu.accelerator = unavailable ? Key { pauseBreak, ctrl = true } : f7;
2092          }
2093
2094          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectLink, 0);              if(menu) menu.disabled = unavailable;
2095          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRebuild, 0);           if(menu) menu.disabled = unavailable;
2096          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectCleanTarget, 0);       if(menu) menu.disabled = unavailable;
2097          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectClean, 0);             if(menu) menu.disabled = unavailable;
2098          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRealClean, 0);         if(menu) menu.disabled = unavailable;
2099          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRegenerate, 0);        if(menu) menu.disabled = unavailable;
2100          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectInstall, 0);           if(menu) menu.disabled = unavailable;
2101          menu = projectView.popupMenu.menu.FindItem(ProjectView::ProjectRemove, 0);            if(menu) menu.disabled = unavailable;
2102          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileClean, 0);                if(menu) menu.disabled = unavailable;
2103          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileCompile, 0);              if(menu) menu.disabled = unavailable;
2104          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugPrecompile, 0);      if(menu) menu.disabled = unavailable;
2105          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugCompile, 0);         if(menu) menu.disabled = unavailable;
2106          menu = projectView.popupMenu.menu.FindItem(ProjectView::FileDebugGenerateSymbols, 0); if(menu) menu.disabled = unavailable;
2107          projectView.popupMenu.Update(null);
2108       }
2109    }
2110
2111    property bool areDebugMenusUnavailable { get {
2112       return !project ||
2113             project.GetTargetType(project.config) != executable ||
2114             projectView.buildInProgress == buildingMainProject;
2115    } }
2116
2117    property bool isBreakpointTogglingUnavailable { get { return !project; } }
2118    property bool isDebuggerRunning { get { if(ide.debugger) return ide.debugger.state == running; return false; } }
2119    property bool isDebuggerStopped { get { if(ide.debugger) return ide.debugger.state == stopped; return false; } }
2120
2121    void AdjustDebugMenus()
2122    {
2123       bool unavailable = areDebugMenusUnavailable;
2124       bool running = isDebuggerRunning;
2125       bool stopped = isDebuggerStopped;
2126       bool active = debugger.isActive;
2127       bool noBreakpointToggle = !project;
2128
2129       bool isNotRunning    = unavailable || !running;
2130       bool isNotNotRunning = unavailable || running;
2131       bool isNotStopped    = unavailable || !stopped;
2132       bool isNotActive     = unavailable || !active;
2133
2134       debugStartResumeItem.disabled       = isNotNotRunning;
2135       debugStartResumeItem.text           = active ? $"Resume" : $"Start";
2136       debugStartResumeItem.NotifySelect   = active ? MenuDebugResume : MenuDebugStart;
2137       if(toolBar)
2138       {
2139          toolBar.buttonDebugStartResume.disabled      = isNotNotRunning;
2140          toolBar.buttonDebugStartResume.toolTip       = active ? $"Resume" : $"Start";
2141       }
2142
2143       debugBreakItem.disabled             = isNotRunning;
2144       debugStopItem.disabled              = isNotActive;
2145       debugRestartItem.disabled           = isNotActive;
2146       if(toolBar)
2147       {
2148          toolBar.buttonDebugPause.disabled            = isNotRunning;
2149          toolBar.buttonDebugStop.disabled             = isNotActive;
2150          toolBar.buttonDebugRestart.disabled          = isNotActive;
2151       }
2152
2153       debugStepIntoItem.disabled          = isNotNotRunning;
2154       debugStepOverItem.disabled          = isNotNotRunning;
2155       debugSkipStepOverItem.disabled      = isNotNotRunning;
2156       debugStepOutItem.disabled           = isNotStopped;
2157       debugSkipStepOutItem.disabled       = isNotStopped;
2158 #if 0
2159       debugStepUntilItem.disabled         = isNotStopped;
2160       debugSkipStepUntilItem.disabled     = isNotStopped;
2161 #endif
2162       if(toolBar)
2163       {
2164          toolBar.buttonDebugStepInto.disabled         = isNotNotRunning;
2165          toolBar.buttonDebugStepOver.disabled         = isNotNotRunning;
2166          toolBar.buttonDebugSkipStepOver.disabled     = isNotNotRunning;
2167          toolBar.buttonDebugStepOut.disabled          = isNotStopped;
2168          //toolBar.buttonDebugSkipStepOutItem.disabled  = isNotNotRunning;
2169       }
2170       if((Designer)GetActiveDesigner())
2171       {
2172          CodeEditor codeEditor = ((Designer)GetActiveDesigner()).codeEditor;
2173          if(codeEditor)
2174             codeEditor.AdjustDebugMenus();
2175       }
2176    }
2177
2178    void ChangeFileDialogsDirectory(char * directory, bool saveSettings)
2179    {
2180       char tempString[MAX_LOCATION];
2181       strcpy(tempString, directory);
2182       if(saveSettings && !projectView)
2183       {
2184          ideSettings.ideFileDialogLocation = directory;
2185          settingsContainer.Save();
2186       }
2187
2188       ideFileDialog.currentDirectory = tempString;
2189       codeEditorFileDialog.currentDirectory = tempString;
2190       codeEditorFormFileDialog.currentDirectory = tempString;
2191    }
2192
2193    void ChangeProjectFileDialogDirectory(char * directory)
2194    {
2195       ideSettings.ideProjectFileDialogLocation = directory;
2196       settingsContainer.Save();
2197    }
2198
2199    Window FindWindow(char * filePath)
2200    {
2201       Window document = null;
2202
2203       // TOCHECK: Do we need to change slashes here?
2204       for(document = firstChild; document; document = document.next)
2205       {
2206          char * fileName = document.fileName;
2207          if(document.isDocument && fileName && !fstrcmp(fileName, filePath))
2208          {
2209             document.visible = true;
2210             document.Activate();
2211             return document;
2212          }
2213       }
2214       return null;
2215    }
2216
2217    bool DontTerminateDebugSession(char * title)
2218    {
2219       if(debugger.isActive)
2220       {
2221          if(MessageBox { type = yesNo, master = ide,
2222                            contents = $"Do you want to terminate the debugging session in progress?",
2223                            text = title }.Modal() == no)
2224             return true;
2225          /*
2226          MessageBox msg { type = yesNo, master = ide,
2227                            contents = "Do you want to terminate the debugging session in progress?",
2228                            text = title };
2229          if(msg.Modal() == no)
2230          {
2231             msg.Destroy(0);
2232             delete msg;
2233             return true;
2234          }
2235          msg.Destroy(0);
2236          delete msg;*/
2237       }
2238       return false;
2239    }
2240
2241    Window OpenFile(char * origFilePath, bool dontMaximize, bool visible, char * type, OpenCreateIfFails createIfFails, OpenMethod openMethod, bool noParsing)
2242    {
2243       char extension[MAX_EXTENSION] = "";
2244       Window document = null;
2245       bool isProject = false;
2246       bool needFileModified = true;
2247       char winFilePath[MAX_LOCATION];
2248       char * filePath = strstr(origFilePath, "http://") == origFilePath ? strcpy(winFilePath, origFilePath) : GetSystemPathBuffer(winFilePath, origFilePath);
2249       Window currentDoc = activeClient;
2250       bool maximizeDoc = !dontMaximize && ((currentDoc && currentDoc.state == maximized) || (!currentDoc && !projectView));
2251       if(!type)
2252       {
2253          GetExtension(filePath, extension);
2254          strlwr(extension);
2255       }
2256       else
2257          strcpy(extension, type);
2258
2259       if(strcmp(extension, ProjectExtension))
2260       {
2261          for(document = firstChild; document; document = document.next)
2262          {
2263             char * fileName = document.fileName;
2264             if(document.isDocument && fileName && !fstrcmp(fileName, filePath) && document.created)
2265             {
2266                document.visible = true;
2267                if(visible)
2268                   document.Activate();
2269                return document;
2270             }
2271          }
2272       }
2273
2274       if(createIfFails == whatever)
2275          ;
2276       else if(!strcmp(extension, ProjectExtension) || !strcmp(extension, WorkspaceExtension))
2277       {
2278          needFileModified = false;
2279          if(openMethod == normal)
2280          {
2281             if(DontTerminateDebugSession($"Open Project"))
2282                return null;
2283             isProject = true;
2284             if(ProjectClose())
2285             {
2286                if(!projectView)
2287                {
2288                   for(;;)
2289                   {
2290                      Project project;
2291                      Workspace workspace = null;
2292
2293                      if(FileExists(filePath))
2294                      {
2295                         if(!strcmp(extension, ProjectExtension))
2296                         {
2297                            char workspaceFile[MAX_LOCATION];
2298                            strcpy(workspaceFile, filePath);
2299                            ChangeExtension(workspaceFile, WorkspaceExtension, workspaceFile);
2300                            workspace = LoadWorkspace(workspaceFile, filePath);
2301                         }
2302                         else if(!strcmp(extension, WorkspaceExtension))
2303                            workspace = LoadWorkspace(filePath, null);
2304                         else
2305                            return null;
2306                         //project = LoadProject(filePath, null);
2307                      }
2308
2309                      if(workspace)
2310                      {
2311                         char absolutePath[MAX_LOCATION];
2312                         CreateProjectView(workspace, filePath);
2313                         document = projectView;
2314
2315                         toolBox.visible = true;
2316                         sheet.visible = true;
2317                         projectView.MakeActive();
2318
2319                         workspace.ParseLoadedBreakpoints();
2320                         workspace.DropInvalidBreakpoints(null);
2321                         workspace.Save();
2322
2323                         ide.projectView.ShowOutputBuildLog(true);
2324                         {
2325                            CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
2326                            ide.projectView.DisplayCompiler(compiler, false);
2327                            delete compiler;
2328                         }
2329                         UpdateCompilerConfigs(false);
2330                         UpdateMakefiles();
2331                         {
2332                            char newWorkingDir[MAX_LOCATION];
2333                            StripLastDirectory(filePath, newWorkingDir);
2334                            ChangeFileDialogsDirectory(newWorkingDir, false);
2335                         }
2336                         if(document)
2337                            document.fileName = filePath;
2338
2339                         ideMainFrame.SetText("%s - %s", filePath, titleECEREIDE);
2340
2341                         // this crashes on starting ide with epj file, solution please?
2342                         // app.UpdateDisplay();
2343
2344                         workspace.holdTracking = true;
2345                         for(ofi : workspace.openedFiles)
2346                         {
2347                            if(ofi.state != closed)
2348                            {
2349                               Window file = OpenFile(ofi.path, false, true, null, no, normal, noParsing);
2350                               if(file)
2351                               {
2352                                  char fileName[MAX_LOCATION];
2353                                  ProjectNode node;
2354                                  GetLastDirectory(ofi.path, fileName);
2355                                  node = projectView.project.topNode.Find(fileName, true);
2356                                  if(node)
2357                                     node.EnsureVisible();
2358                               }
2359                            }
2360                         }
2361                         ide.RepositionWindows(false);
2362                         workspace.holdTracking = false;
2363
2364                         workspace.timer.Start();
2365
2366 #if !defined(__WIN32__)
2367                         // Valgrind Debug menu updates
2368                         debugUseValgrindItem.checked = workspace.useValgrind;
2369
2370                         debugValgrindNoLeakCheckItem.checked      = workspace.vgLeakCheck == no;
2371                         debugValgrindSummaryLeakCheckItem.checked = workspace.vgLeakCheck == summary;
2372                         debugValgrindYesLeakCheckItem.checked     = workspace.vgLeakCheck == yes;
2373                         debugValgrindFullLeakCheckItem.checked    = workspace.vgLeakCheck == full;
2374
2375                         debugValgrindRSDefaultItem.checked = workspace.vgRedzoneSize == -1;
2376                         debugValgrindRS0Item.checked       = workspace.vgRedzoneSize == 0;
2377                         debugValgrindRS16Item.checked      = workspace.vgRedzoneSize == 16;
2378                         debugValgrindRS32Item.checked      = workspace.vgRedzoneSize == 32;
2379                         debugValgrindRS64Item.checked      = workspace.vgRedzoneSize == 64;
2380                         debugValgrindRS128Item.checked     = workspace.vgRedzoneSize == 128;
2381                         debugValgrindRS256Item.checked     = workspace.vgRedzoneSize == 256;
2382                         debugValgrindRS512Item.checked     = workspace.vgRedzoneSize == 512;
2383
2384                         debugValgrindTrackOriginsItem.checked = workspace.vgTrackOrigins;
2385 #endif
2386
2387                         findInFilesDialog.mode = FindInFilesMode::project;
2388                         findInFilesDialog.currentDirectory = ide.project.topNode.path;
2389
2390                         {
2391                            char location[MAX_LOCATION];
2392                            StripLastDirectory(ide.project.topNode.path, location);
2393                            ChangeProjectFileDialogDirectory(location);
2394                         }
2395
2396                         break;
2397                      }
2398                      else
2399                      {
2400                         if(MessageBox { type = yesNo, master = this, text = $"Error opening project", contents = $"Open a different project?" }.Modal() == yes)
2401                         {
2402                            ideProjectFileDialog.text = openProjectFileDialogTitle;
2403                            if(ideProjectFileDialog.Modal() == cancel)
2404                               return null;
2405                            filePath = ideProjectFileDialog.filePath;
2406                            GetExtension(filePath, extension);
2407                         }
2408                         else
2409                            return null;
2410                      }
2411                   }
2412                }
2413             }
2414             else
2415                return null;
2416          }
2417          else if(openMethod == add)
2418          {
2419             if(workspace)
2420             {
2421                Project prj = null;
2422                char slashFilePath[MAX_LOCATION];
2423                GetSlashPathBuffer(slashFilePath, filePath);
2424                for(p : workspace.projects)
2425                {
2426                   if(!fstrcmp(p.filePath, slashFilePath))
2427                   {
2428                      prj = p;
2429                      break;
2430                   }
2431                }
2432                if(prj)
2433                {
2434                   MessageBox { type = ok, parent = parent, master = this, text = $"Same Project",
2435                         contents = $"This project is already present in workspace." }.Modal();
2436                }
2437                else
2438                {
2439                   prj = LoadProject(filePath, null);
2440                   if(prj)
2441                   {
2442                      char * activeConfigName = null;
2443                      CompilerConfig compiler = ideSettings.GetCompilerConfig(workspace.compiler);
2444                      prj.StartMonitoring();
2445                      workspace.projects.Add(prj);
2446                      if(toolBar.activeConfig.currentRow && toolBar.activeConfig.currentRow != toolBar.activeConfig.firstRow &&
2447                            toolBar.activeConfig.currentRow.string && toolBar.activeConfig.currentRow.string[0])
2448                         activeConfigName = toolBar.activeConfig.currentRow.string;
2449                      if(activeConfigName)
2450                      {
2451                         for(cfg : prj.configurations)
2452                         {
2453                            if(cfg.name && !strcmp(cfg.name, activeConfigName))
2454                            {
2455                               prj.config = cfg;
2456                               break;
2457                            }
2458                         }
2459                      }
2460                      if(projectView)
2461                         projectView.AddNode(prj.topNode, null);
2462                      workspace.modified = true;
2463                      workspace.Save();
2464                      findInFilesDialog.AddProjectItem(prj);
2465                      projectView.ShowOutputBuildLog(true);
2466                      projectView.DisplayCompiler(compiler, false);
2467                      projectView.ProjectUpdateMakefileForAllConfigs(prj);
2468                      delete compiler;
2469
2470                      {
2471                         char location[MAX_LOCATION];
2472                         StripLastDirectory(prj.topNode.path, location);
2473                         ChangeProjectFileDialogDirectory(location);
2474                      }
2475
2476                      // projectView is associated with the main project and not with the one just added but
2477                      return projectView; // just to let the caller know something was opened
2478                   }
2479                }
2480             }
2481             else
2482                return null;
2483          }
2484       }
2485       else if(!strcmp(extension, "bmp") || !strcmp(extension, "pcx") ||
2486             !strcmp(extension, "jpg") || !strcmp(extension, "gif") ||
2487             !strcmp(extension, "jpeg") || !strcmp(extension, "png"))
2488       {
2489          if(FileExists(filePath))
2490             document = PictureEdit { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2491                                        hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2492                                        visible = visible, bitmapFile = filePath, OnClose = PictureEditOnClose/*why?--GenericDocumentOnClose*/;
2493                                     };
2494          if(!document)
2495             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2496       }
2497 #ifndef NO3D
2498       else if(!strcmp(extension, "3ds"))
2499       {
2500          if(FileExists(filePath))
2501             document = ModelView { hasMaximize = true, hasMinimize = true, hasClose = true, borderStyle = sizable,
2502                                     hasVertScroll = true, hasHorzScroll = true, parent = this, state = state,
2503                                     visible = visible, modelFile = filePath, OnClose = ModelViewOnClose/*why?--GenericDocumentOnClose*/
2504                                     };
2505
2506          if(!document)
2507             MessageBox { type = ok, master = this, text = filePath, contents = $"File doesn't exist." }.Modal();
2508       }
2509 #endif
2510       else if(!strcmp(extension, "txt") || !strcmp(extension, "text") ||
2511             !strcmp(extension, "nfo") || !strcmp(extension, "info") ||
2512             !strcmp(extension, "htm") || !strcmp(extension, "html") ||
2513             !strcmp(extension, "css") || !strcmp(extension, "php") ||
2514             !strcmp(extension, "js"))
2515       {
2516          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2517          editor.updatingCode = true;
2518          if(editor.LoadFile(filePath))
2519          {
2520             document = editor;
2521             editor.visible = true;
2522          }
2523          else
2524             delete editor;
2525          needFileModified = false;
2526       }
2527       else
2528       {
2529          CodeEditor editor { parent = this, state = state, visible = false, noParsing = noParsing };
2530          if(editor.LoadFile(filePath))
2531          {
2532             document = editor;
2533             editor.visible = true;
2534          }
2535          else
2536             delete editor;
2537          needFileModified = false;
2538       }
2539
2540       if(document && (document._class == class(PictureEdit) ||
2541             document._class == class(ModelView)))
2542       {
2543          document.Create();
2544          if(document)
2545          {
2546             document.fileName = filePath;
2547             if(workspace && !workspace.holdTracking)
2548                workspace.UpdateOpenedFileInfo(filePath, opened);
2549          }
2550       }
2551
2552       if(!document && createIfFails != no)
2553       {
2554          if(createIfFails != yes && !needFileModified &&
2555                MessageBox { type = yesNo, master = this, text = filePath, contents = $"File doesn't exist. Create?" }.Modal() == yes)
2556             createIfFails = yes;
2557          if(createIfFails == yes || createIfFails == whatever)
2558          {
2559             document = (Window)NewCodeEditor(this, maximizeDoc ? maximized : normal, true);
2560             if(document)
2561                document.fileName = filePath;
2562          }
2563       }
2564
2565       if(document)
2566       {
2567          if(projectView && document._class == class(CodeEditor) && workspace)
2568          {
2569             int lineNumber, position;
2570             Point scroll;
2571             CodeEditor editor = (CodeEditor)document;
2572             editor.openedFileInfo = workspace.UpdateOpenedFileInfo(filePath, opened);
2573             editor.openedFileInfo.holdTracking = true;
2574             lineNumber = Max(editor.openedFileInfo.lineNumber - 1, 0);
2575             position = Max(editor.openedFileInfo.position - 1, 0);
2576             if(editor.editBox.GoToLineNum(lineNumber))
2577                editor.editBox.GoToPosition(editor.editBox.line, lineNumber, position);
2578             scroll.x = Max(editor.openedFileInfo.scroll.x, 0);
2579             scroll.y = Max(editor.openedFileInfo.scroll.y, 0);
2580             editor.editBox.scroll = scroll;
2581             editor.openedFileInfo.holdTracking = false;
2582          }
2583
2584          if(needFileModified)
2585             document.OnFileModified = OnFileModified;
2586          document.NotifySaved = DocumentSaved;
2587          if(maximizeDoc && document.hasMaximize)
2588             document.state = maximized;
2589
2590          if(isProject)
2591             ideSettings.AddRecentProject(document.fileName);
2592          else
2593             ideSettings.AddRecentFile(document.fileName);
2594          ide.UpdateRecentMenus();
2595          ide.AdjustFileMenus();
2596          settingsContainer.Save();
2597
2598          return document;
2599       }
2600       else
2601          return null;
2602    }
2603
2604    // TOCHECK: I can't use a generic one for both ModelView and PictureEdit both derived from Window
2605    /*bool Window::GenericDocumentOnClose(bool parentClosing)
2606    {
2607       if(!parentClosing && ide.workspace)
2608          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2609       return true;
2610    }*/
2611    bool ModelView::ModelViewOnClose(bool parentClosing)
2612    {
2613       if(!parentClosing && ide.workspace)
2614          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2615       return true;
2616    }
2617    bool PictureEdit::PictureEditOnClose(bool parentClosing)
2618    {
2619       if(!parentClosing && ide.workspace)
2620          ide.workspace.UpdateOpenedFileInfo(fileName, unknown);
2621       return true;
2622    }
2623
2624    /*
2625    void OnUnloadGraphics(Window window)
2626    {
2627       display.ClearMaterials();
2628       display.ClearTextures();
2629       display.ClearMeshes();
2630    }
2631    */
2632
2633    void UpdateStateLight(StatusField fld, bool on)
2634    {
2635       fld.color = on ? lime : Color { 128,128,128 };
2636       fld.backColor = on ? dimGray : 0;
2637       fld.bold = on;
2638    }
2639
2640    bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
2641    {
2642       UpdateStateLight(caps, app.GetKeyState(capsState));
2643       UpdateStateLight(num, app.GetKeyState(numState));
2644       return true;
2645    }
2646
2647    bool OnKeyDown(Key key, unichar ch)
2648    {
2649       switch(key)
2650       {
2651          case b: projectView.Update(null); break;
2652          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2653          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2654       }
2655       return true;
2656    }
2657
2658    bool OnKeyUp(Key key, unichar ch)
2659    {
2660       switch(key)
2661       {
2662          case capsLock: UpdateStateLight(caps, app.GetKeyState(capsState)); break;
2663          case numLock:  UpdateStateLight(num, app.GetKeyState(numState)); break;
2664       }
2665       return true;
2666    }
2667
2668    void GoToError(const char * line, bool noParsing)
2669    {
2670       if(projectView)
2671          projectView.GoToError(line, noParsing);
2672    }
2673
2674    void CodeLocationParseAndGoTo(const char * text, Project project, const char * dir)
2675    {
2676       char *s = null;
2677       char *path = text;
2678       char *colon = strchr(text, ':');
2679       char filePath[MAX_LOCATION] = "";
2680       char completePath[MAX_LOCATION];
2681       int line = 0, col = 0;
2682       int len = strlen(text);
2683       Project prj = null;
2684       FileAttribs fileAttribs;
2685
2686       // support for valgrind output
2687       if((s = strstr(text, "==")) && (s = strstr(s+2, "==")) && (s = strstr(s+2, ":")) && (s = strstr(s+1, ":")))
2688       {
2689          colon = s;
2690          for(; s>text; s--)
2691          {
2692             if(*s == '(')
2693             {
2694                path = s+1;
2695                break;
2696             }
2697          }
2698          /*for(s=colon; *s; s++)
2699          {
2700             if(*s == ')')
2701             {
2702                *s = '\0';;
2703                break;
2704             }
2705          }*/
2706          //*colon = '\0';
2707          //line = atoi(colon+1);
2708       }
2709       // support for "Found n match(es) in "file/path";
2710       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)
2711       {
2712          path = s+1;
2713       }
2714       else
2715       {
2716          if(colon && (colon[1] == '/' || colon[1] == '\\'))
2717          {
2718             path = (colon - 1 > path) ? colon - 1 : path;
2719             colon = strstr(colon + 1, ":");
2720          }
2721          if(*path == '*' && (s = strchr(path+1, '*')))
2722             path = s+1;
2723          while(isspace(*path)) path++;
2724       }
2725       if(*path == '(')
2726       {
2727          char * close = strchr(path, ')');
2728          if(close)
2729          {
2730             char name[256];
2731             strncpy(name, path+1, close - path - 1);
2732             name[close - path - 1] = '\0';
2733             for(p : ide.workspace.projects)
2734             {
2735                if(!strcmp(p.name, name))
2736                {
2737                   path = close + 1;
2738                   prj = p;
2739                   break;
2740                }
2741             }
2742          }
2743       }
2744       if(!prj)
2745          prj = project ? project : (dir ? null : ide.project);
2746       if(colon)
2747       {
2748          strncpy(filePath, path, colon - path);
2749          filePath[colon - path] = '\0';
2750          line = atoi(colon + 1);
2751          colon = strstr(colon + 1, ":");
2752          if(colon)
2753             col = atoi(colon + 1);
2754       }
2755       else if(path - 1 >= text && *(path - 1) == '\"')
2756       {
2757          colon = strchr(path, '\"');
2758          if(colon)
2759          {
2760             strncpy(filePath, path, colon - path);
2761             filePath[colon - path] = '\0';
2762          }
2763       }
2764       else if(path && !colon)
2765       {
2766          strcpy(filePath, path);
2767       }
2768
2769       if(filePath[0])
2770       {
2771          if(prj)
2772             strcpy(completePath, prj.topNode.path);
2773          else if(dir && dir[0])
2774             strcpy(completePath, dir);
2775          else
2776             completePath[0] = '\0';
2777          PathCat(completePath, filePath);
2778
2779          if((fileAttribs = FileExists(completePath)))
2780             CodeLocationGoTo(completePath, fileAttribs, line, col);
2781          else if(ide.workspace)
2782          {
2783             bool done = false;
2784             for(p : ide.workspace.projects)
2785             {
2786                strcpy(completePath, p.topNode.path);
2787                PathCat(completePath, filePath);
2788                if((fileAttribs = FileExists(completePath)).isFile)
2789                {
2790                   CodeLocationGoTo(completePath, fileAttribs, line, col);
2791                   done = true;
2792                   break;
2793                }
2794             }
2795             if(!done)
2796             {
2797                for(p : ide.workspace.projects)
2798                {
2799                   ProjectNode node = p.topNode.Find(filePath, false);
2800                   if(node)
2801                   {
2802                      node.GetFullFilePath(completePath);
2803                      if((fileAttribs = FileExists(completePath)).isFile)
2804                      {
2805                         CodeLocationGoTo(completePath, fileAttribs, line, col);
2806                         break;
2807                      }
2808                   }
2809                }
2810             }
2811          }
2812       }
2813    }
2814
2815    void CodeLocationGoTo(const char * path, const FileAttribs fileAttribs, int line, int col)
2816    {
2817       if(fileAttribs.isFile)
2818       {
2819          char ext[MAX_EXTENSION];
2820          GetExtension(path, ext);
2821          strlwr(ext);
2822          if(binaryDocExt.Find(ext))
2823             ShellOpen(path);
2824          else if(!strcmp(ext, "a") || !strcmp(ext, "o") || !strcmp(ext, "lib") || !strcmp(ext, "dll") || !strcmp(ext, "exe"))
2825          {
2826             char dirPath[MAX_LOCATION];
2827             StripLastDirectory(path, dirPath);
2828             ShellOpen(dirPath);
2829          }
2830          else
2831          {
2832             CodeEditor codeEditor = (CodeEditor)OpenFile(path, false, true, ext, no, normal, false);
2833             if(codeEditor && line)
2834             {
2835                EditBox editBox = codeEditor.editBox;
2836                editBox.GoToLineNum(line - 1);
2837                editBox.GoToPosition(editBox.line, line - 1, col ? (col - 1) : 0);
2838             }
2839          }
2840       }
2841       else if(fileAttribs.isDirectory)
2842          ShellOpen(path);
2843    }
2844
2845    void OnRedraw(Surface surface)
2846    {
2847       Bitmap bitmap = back.bitmap;
2848       if(bitmap)
2849          surface.Blit(bitmap, (clientSize.w - bitmap.width) / 2, (clientSize.h - bitmap.height) / 2, 0, 0, bitmap.width, bitmap.height);
2850    }
2851
2852    void SheetSelected(SheetType sheetSelected)
2853    {
2854       if(activeChild == sheet)
2855       {
2856          if(sheetSelected == methods)
2857          {
2858             viewPropertiesItem.accelerator = f4;
2859             viewPropertiesItem.parent = viewMenu;
2860             viewMethodsItem.parent = null;
2861          }
2862          else
2863          {
2864             viewMethodsItem.accelerator = f4;
2865             viewMethodsItem.parent = viewMenu;
2866             viewPropertiesItem.parent = null;
2867          }
2868       }
2869       else
2870       {
2871          viewMethodsItem.parent = viewMenu;
2872          viewPropertiesItem.parent = viewMenu;
2873          if(sheetSelected == methods)
2874          {
2875             viewMethodsItem.accelerator = f4;
2876             viewPropertiesItem.accelerator = 0;
2877          }
2878          else
2879          {
2880             viewMethodsItem.accelerator = 0;
2881             viewPropertiesItem.accelerator = f4;
2882          }
2883       }
2884    }
2885
2886    void OnActivateClient(Window client, Window previous)
2887    {
2888       //if(!client || client != previous)
2889       {
2890          Class dataType;
2891          if(!client || client != previous)
2892          {
2893             if(previous)
2894                dataType = previous._class;
2895             if(previous && !strcmp(dataType.name, "CodeEditor"))
2896             {
2897                ((CodeEditor)previous).UpdateFormCode();
2898             }
2899             else if(previous && !strcmp(dataType.name, "Designer"))
2900             {
2901                ((Designer)previous).codeEditor.UpdateFormCode();
2902             }
2903          }
2904
2905          if(client)
2906             dataType = client._class;
2907          if(client && !strcmp(dataType.name, "CodeEditor"))
2908          {
2909             CodeEditor codeEditor = (CodeEditor)client;
2910             SetPrivateModule(codeEditor.privateModule);
2911             SetCurrentContext(codeEditor.globalContext);
2912             SetTopContext(codeEditor.globalContext);
2913             SetGlobalContext(codeEditor.globalContext);
2914
2915             SetDefines(&codeEditor.defines);
2916             SetImports(&codeEditor.imports);
2917
2918             SetActiveDesigner(codeEditor.designer);
2919
2920             sheet.codeEditor = codeEditor;
2921             toolBox.codeEditor = codeEditor;
2922
2923             viewDesignerItem.parent = viewMenu;
2924             if(activeChild != codeEditor)
2925             {
2926                viewCodeItem.parent = viewMenu;
2927                viewDesignerItem.accelerator = 0;
2928                viewCodeItem.accelerator = f8;
2929             }
2930             else
2931             {
2932                viewCodeItem.parent = null;
2933                viewDesignerItem.accelerator = f8;
2934             }
2935          }
2936          else if(client && !strcmp(dataType.name, "Designer"))
2937          {
2938             CodeEditor codeEditor = ((Designer)client).codeEditor;
2939             if(codeEditor)
2940             {
2941                SetPrivateModule(codeEditor.privateModule);
2942                SetCurrentContext(codeEditor.globalContext);
2943                SetTopContext(codeEditor.globalContext);
2944                SetGlobalContext(codeEditor.globalContext);
2945                SetDefines(&codeEditor.defines);
2946                SetImports(&codeEditor.imports);
2947             }
2948             else
2949             {
2950                SetPrivateModule(null);
2951                SetCurrentContext(null);
2952                SetTopContext(null);
2953                SetGlobalContext(null);
2954                SetDefines(null);
2955                SetImports(null);
2956             }
2957
2958             SetActiveDesigner((Designer)client);
2959
2960             sheet.codeEditor = codeEditor;
2961             toolBox.codeEditor = codeEditor;
2962
2963             viewCodeItem.parent = viewMenu;
2964             if(activeChild != client)
2965             {
2966                viewDesignerItem.parent = viewMenu;
2967                viewDesignerItem.accelerator = f8;
2968                viewCodeItem.accelerator = 0;
2969             }
2970             else
2971             {
2972                viewDesignerItem.parent = null;
2973                viewCodeItem.accelerator = f8;
2974             }
2975          }
2976          else
2977          {
2978             if(!client && !projectView && sheet.visible)
2979             {
2980                if(sheet)
2981                   sheet.visible = false;
2982                toolBox.visible = false;
2983             }
2984             if(sheet)
2985                sheet.codeEditor = null;
2986             toolBox.codeEditor = null;
2987             SetActiveDesigner(null);
2988
2989             viewDesignerItem.parent = null;
2990             viewCodeItem.parent = null;
2991          }
2992          if(sheet)
2993             SheetSelected(sheet.sheetSelected);
2994       }
2995
2996       projectCompileItem = null;
2997
2998       if(statusBar)
2999       {
3000          statusBar.Clear();
3001          if(client && client._class == eSystem_FindClass(__thisModule, "CodeEditor")) // !strcmp(client._class.name, "CodeEditor")
3002          {
3003             CodeEditor codeEditor = (CodeEditor)client;
3004             EditBox editBox = codeEditor.editBox;
3005
3006             statusBar.AddField(pos);
3007
3008             caps = { width = 40, text = $"CAPS" };
3009             statusBar.AddField(caps);
3010             UpdateStateLight(caps, app.GetKeyState(capsState));
3011
3012             ovr = { width = 36, text = $"OVR" };
3013             statusBar.AddField(ovr);
3014             UpdateStateLight(ovr, (editBox && editBox.overwrite));
3015
3016             num = { width = 36, text = $"NUM" };
3017             statusBar.AddField(num);
3018             UpdateStateLight(num, app.GetKeyState(numState));
3019
3020             //statusBar.text = "Ready";
3021
3022             if(projectView && projectView.project)
3023             {
3024                bool isCObject = false;
3025                ProjectNode node = projectView.GetNodeFromWindow(client, null, true, false, null);
3026                if(!node && (node = projectView.GetNodeFromWindow(client, null, true, true, null)))
3027                   isCObject = true;
3028                if(node)
3029                {
3030                   char nodeName[MAX_FILENAME];
3031                   char name[MAX_FILENAME+96];
3032                   if(isCObject)
3033                      ChangeExtension(node.name, "c", nodeName);
3034                   sprintf(name, $"Compile %s", isCObject ? nodeName : node.name);
3035                   projectCompileItem =
3036                   {
3037                      copyText = true, text = name, c, ctrlF7, disabled = projectView.buildInProgress;
3038
3039                      bool NotifySelect(MenuItem selection, Modifiers mods)
3040                      {
3041                         if(projectView)
3042                         {
3043                            bool isCObject = false;
3044                            bool isExcluded = false;
3045                            ProjectNode node = projectView.GetNodeForCompilationFromWindow(activeClient, true, &isExcluded, &isCObject);
3046                            if(node)
3047                            {
3048                               if(isExcluded)
3049                                  ide.outputView.buildBox.Logf($"%s %s is excluded from current build configuration.\n", isCObject ? "Object file" : "File", node.name);
3050                               else
3051                               {
3052                                  List<ProjectNode> nodes { };
3053                                  nodes.Add(node);
3054                                  projectView.Compile(node.project, nodes, mods.ctrl && mods.shift, isCObject ? cObject : normal);
3055                                  delete nodes;
3056                               }
3057                            }
3058                         }
3059                         return true;
3060                      }
3061                   };
3062                   projectMenu.AddDynamic(projectCompileItem, ide, false);
3063                }
3064             }
3065          }
3066          else
3067          {
3068             caps = ovr = num = null;
3069          }
3070       }
3071    }
3072
3073    bool OnClose(bool parentClosing)
3074    {
3075       //return !projectView.buildInProgress;
3076       if(projectView && projectView.buildInProgress)
3077          return false;
3078       if(DontTerminateDebugSession($"Close IDE"))
3079          return false;
3080       if(findInFilesDialog)
3081          findInFilesDialog.SearchStop();
3082       if(workspace)
3083       {
3084          workspace.timer.Stop();
3085          workspace.Save();
3086       }
3087       ideMainFrame.Destroy(0);
3088       return true;
3089    }
3090
3091    bool OnPostCreate()
3092    {
3093       int c;
3094       bool passThrough = false;
3095       bool debugStart = false;
3096       bool debugWorkDir = false;
3097       char * passDebugWorkDir = null;
3098       bool openAsText = false;
3099       DynamicString passArgs { };
3100       int ptArg = 0;
3101
3102       for(c = 1; c<app.argc; c++)
3103       {
3104          if(passThrough)
3105          {
3106             char * arg = app.argv[c];
3107             char * buf = new char[strlen(arg)*2+1];
3108             if(ptArg++ > 0)
3109                passArgs.concat(" ");
3110             PassArg(buf, arg);
3111             passArgs.concat(buf);
3112             delete buf;
3113          }
3114          else if(debugWorkDir)
3115          {
3116             passDebugWorkDir = CopyString(app.argv[c]);
3117             StripQuotes(passDebugWorkDir, passDebugWorkDir);
3118             debugWorkDir = false;
3119          }
3120          else if(!strcmp(app.argv[c], "-t"))
3121             openAsText = true;
3122          else if(!strcmp(app.argv[c], "-no-parsing"))
3123             ide.noParsing = true;
3124          else if(!strcmp(app.argv[c], "-debug-start"))
3125             debugStart = true;
3126          else if(!strcmp(app.argv[c], "-debug-work-dir"))
3127             debugWorkDir = true;
3128          else if(!strcmp(app.argv[c], "-@"))
3129             passThrough = true;
3130          else
3131          {
3132             char fullPath[MAX_LOCATION];
3133             char parentPath[MAX_LOCATION];
3134             char ext[MAX_EXTENSION];
3135             bool isProject;
3136             FileAttribs dirAttribs;
3137             GetWorkingDir(fullPath, MAX_LOCATION);
3138             PathCat(fullPath, app.argv[c]);
3139             StripLastDirectory(fullPath, parentPath);
3140             GetExtension(app.argv[c], ext);
3141             isProject = !openAsText && !strcmpi(ext, "epj");
3142
3143             if(isProject && c > (debugStart ? 2 : 1)) continue;
3144
3145             // Create directory for projects (only)
3146             if(((dirAttribs = FileExists(parentPath)) && dirAttribs.isDirectory) || isProject)
3147             {
3148                if(isProject && !FileExists(fullPath))
3149                {
3150                   char name[MAX_LOCATION];
3151                   NewProjectDialog newProjectDialog;
3152
3153                   if(projectView)
3154                   {
3155                      projectView.visible = false;
3156                      if(!projectView.Destroy(0))
3157                         return true;
3158                   }
3159
3160                   newProjectDialog = { master = this };
3161
3162                   strcpy(name, app.argv[c]);
3163                   StripExtension(name);
3164                   GetLastDirectory(name, name);
3165                   newProjectDialog.projectName.contents = name;
3166                   newProjectDialog.projectName.NotifyModified(newProjectDialog, newProjectDialog.projectName);
3167                   newProjectDialog.locationEditBox.path = parentPath;
3168                   newProjectDialog.NotifyModifiedLocation(newProjectDialog.locationEditBox);
3169
3170                   incref newProjectDialog;
3171                   newProjectDialog.Modal();
3172                   if(projectView)
3173                   {
3174                      ideSettings.AddRecentProject(projectView.fileName);
3175                      ide.UpdateRecentMenus();
3176                      settingsContainer.Save();
3177                   }
3178                   delete newProjectDialog;
3179                   // Open only one project
3180                   break;
3181                }
3182                else
3183                   ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3184             }
3185             else if(strstr(fullPath, "http://") == fullPath)
3186                ide.OpenFile(fullPath, app.argFilesCount > 1, true, openAsText ? "txt" : null, yes, normal, false);
3187          }
3188       }
3189       if(passThrough && projectView && projectView.project && workspace)
3190          workspace.commandLineArgs = passArgs;
3191       if(passDebugWorkDir && projectView && projectView.project && workspace)
3192       {
3193          workspace.debugDir = passDebugWorkDir;
3194          delete passDebugWorkDir;
3195       }
3196       if(debugStart)
3197          ;//MenuDebugStart(debugStartResumeItem, 0); // <-- how TODO this without getting into the app.Wait lock
3198
3199       UpdateToolBarActiveConfigs(false);
3200       UpdateToolBarActiveCompilers();
3201       delete passArgs;
3202       return true;
3203    }
3204
3205    void OnDestroy()
3206    {
3207       // IS THIS NEEDED? WASN'T HERE BEFORE...  Crashes on getting node's projectView otherwise
3208       if(projectView)
3209       {
3210          projectView.visible = false;
3211          projectView.Destroy(0);
3212          projectView = null;
3213       }
3214 #ifdef GDB_DEBUG_GUI
3215       gdbDialog.Destroy(0);
3216       delete gdbDialog;
3217 #endif
3218    }
3219
3220    void SetPath(bool projectsDirs, CompilerConfig compiler, ProjectConfig config, int bitDepth)
3221    {
3222       int c, len, count;
3223       char * newList;
3224       char * oldPaths[128];
3225       String oldList = new char[maxPathLen];
3226       Array<String> newExePaths { };
3227       //Map<String, bool> exePathExists { };
3228       bool found = false;
3229 #if defined(__unix__) || defined(__APPLE__)
3230       Array<String> newLibPaths { };
3231       Map<String, bool> libPathExists { };
3232 #endif
3233
3234       if(projectsDirs)
3235       {
3236          for(prj : workspace.projects)
3237          {
3238             DirExpression targetDirExp;
3239
3240             // SKIP FIRST PROJECT...
3241             if(prj == workspace.projects.firstIterator.data) continue;
3242
3243             // NOTE: Right now the additional project config dir will be
3244             //       obtained when the debugger is started, so toggling it
3245             //       while building will change which library gets used.
3246             //       To go with the initial state, e.g. when F5 was pressed,
3247             //       we nould need to keep a list of all project's active
3248             //       config upon startup.
3249             targetDirExp = prj.GetTargetDir(compiler, prj.config, bitDepth);
3250
3251             /*if(prj.config.targetType == sharedLibrary && prj.config.debug)
3252                cfg = prj.config;
3253             else
3254             {
3255                for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3256                   if(cfg.targetType == sharedLibrary && cfg.debug && !strcmpi(cfg.name, "Debug"))
3257                      break;
3258                if(!cfg)
3259                {
3260                   for(cfg = prj.configurations.first; cfg; cfg = cfg.next)
3261                      if(cfg.targetType == sharedLibrary && cfg.debug)
3262                         break;
3263                }
3264             }*/
3265             if(targetDirExp.dir)
3266             {
3267                char buffer[MAX_LOCATION];
3268 #if defined(__WIN32__)
3269                Array<String> paths = newExePaths;
3270 #else
3271                Array<String> paths = newLibPaths;
3272 #endif
3273                GetSystemPathBuffer(buffer, prj.topNode.path);
3274                PathCat(buffer, targetDirExp.dir);
3275                for(p : paths)
3276                {
3277                   if(!fstrcmp(p, buffer))
3278                   {
3279                      found = true;
3280                      break;
3281                   }
3282                }
3283                if(!found)
3284                   paths.Add(CopyString(buffer));
3285             }
3286             delete targetDirExp;
3287          }
3288       }
3289
3290       for(item : compiler.executableDirs)
3291       {
3292          found = false;
3293          for(p : newExePaths)
3294          {
3295             if(!fstrcmp(p, item))
3296             {
3297                found = true;
3298                break;
3299             }
3300          }
3301          if(!found)
3302             newExePaths.Add(CopySystemPath(item));
3303       }
3304
3305       GetEnvironment("PATH", oldList, maxPathLen);
3306 /*#ifdef _DEBUG
3307       printf("Old value of PATH: %s\n", oldList);
3308 #endif*/
3309       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3310       for(c = 0; c < count; c++)
3311       {
3312          found = false;
3313          for(p : newExePaths)
3314          {
3315             if(!fstrcmp(p, oldPaths[c]))
3316             {
3317                found = true;
3318                break;
3319             }
3320          }
3321          if(!found)
3322             newExePaths.Add(CopySystemPath(oldPaths[c]));
3323       }
3324
3325       len = 0;
3326       for(path : newExePaths)
3327          len += strlen(path) + 1;
3328       newList = new char[len + 1];
3329       newList[0] = '\0';
3330       for(path : newExePaths)
3331       {
3332          strcat(newList, path);
3333          strcat(newList, pathListSep);
3334       }
3335       newList[len - 1] = '\0';
3336       SetEnvironment("PATH", newList);
3337 /*#ifdef _DEBUG
3338       printf("New value of PATH: %s\n", newList);
3339 #endif*/
3340       delete newList;
3341
3342       newExePaths.Free();
3343       delete newExePaths;
3344
3345 #if defined(__unix__) || defined(__APPLE__)
3346
3347       for(item : compiler.libraryDirs)
3348       {
3349          if(!libPathExists[item])  // fstrcmp should be used
3350          {
3351             String s = CopyString(item);
3352             newLibPaths.Add(s);
3353             libPathExists[s] = true;
3354          }
3355       }
3356
3357 #if defined(__APPLE__)
3358       GetEnvironment("DYLD_LIBRARY_PATH", oldList, maxPathLen);
3359 #else
3360       GetEnvironment("LD_LIBRARY_PATH", oldList, maxPathLen);
3361 #endif
3362 /*#ifdef _DEBUG
3363       printf("Old value of [DY]LD_LIBRARY_PATH: %s\n", oldList);
3364 #endif*/
3365       count = TokenizeWith(oldList, sizeof(oldPaths) / sizeof(char *), oldPaths, pathListSep, false);
3366       for(c = 0; c < count; c++)
3367       {
3368          if(!libPathExists[oldPaths[c]])  // fstrcmp should be used
3369          {
3370             String s = CopyString(oldPaths[c]);
3371             newLibPaths.Add(s);
3372             libPathExists[s] = true;
3373          }
3374       }
3375
3376       len = 0;
3377       for(path : newLibPaths)
3378          len += strlen(path) + 1;
3379       newList = new char[len + 1];
3380       newList[0] = '\0';
3381       for(path : newLibPaths)
3382       {
3383          strcat(newList, path);
3384          strcat(newList, pathListSep);
3385       }
3386       newList[len - 1] = '\0';
3387 #if defined(__APPLE__)
3388       SetEnvironment("DYLD_LIBRARY_PATH", newList);
3389 #else
3390       SetEnvironment("LD_LIBRARY_PATH", newList);
3391 #endif
3392 /*#ifdef _DEBUG
3393       printf("New value of [DY]LD_LIBRARY_PATH: %s\n", newList);
3394 #endif*/
3395       delete newList;
3396
3397       newLibPaths.Free();
3398       delete newLibPaths;
3399       delete libPathExists;
3400 #endif
3401
3402       if(compiler.distccEnabled && compiler.distccHosts)
3403          SetEnvironment("DISTCC_HOSTS", compiler.distccHosts);
3404
3405       delete oldList;
3406    }
3407
3408    void DestroyTemporaryProjectDir()
3409    {
3410       if(tmpPrjDir && tmpPrjDir[0])
3411       {
3412          if(FileExists(tmpPrjDir).isDirectory)
3413             DestroyDir(tmpPrjDir);
3414          property::tmpPrjDir = null;
3415       }
3416    }
3417
3418    IDEWorkSpace()
3419    {
3420       // Graphics Driver Menu
3421       int c;
3422
3423       /*
3424       app.currentSkin.selectionColor = selectionColor;
3425       app.currentSkin.selectionText = selectionText;
3426       */
3427
3428 /*
3429       driverItems = new MenuItem[app.numDrivers];
3430       for(c = 0; c < app.numDrivers; c++)
3431       {
3432          driverItems[c] = MenuItem { driversMenu, app.drivers[c], NotifySelect = NotifySelectDisplayDriver };
3433          driverItems[c].id = c;
3434          driverItems[c].isRadio = true;
3435       }
3436 */
3437       driverItems = new MenuItem[2];
3438 #if defined(__unix__)
3439          driverItems[0] = MenuItem { driversMenu, "X", NotifySelect = NotifySelectDisplayDriver };
3440          driverItems[0].id = 0;
3441          driverItems[0].isRadio = true;
3442 #else
3443          driverItems[0] = MenuItem { driversMenu, "GDI", NotifySelect = NotifySelectDisplayDriver };
3444          driverItems[0].id = 0;
3445          driverItems[0].isRadio = true;
3446 #endif
3447          driverItems[1] = MenuItem { driversMenu, "OpenGL", NotifySelect = NotifySelectDisplayDriver };
3448          driverItems[1].id = 1;
3449          driverItems[1].isRadio = true;
3450
3451 /*      skinItems = new MenuItem[app.numSkins];
3452       for(c = 0; c < app.numSkins; c++)
3453       {
3454          skinItems[c] = MenuItem {skinsMenu, app.skins[c], NotifySelect = NotifySelectDisplaySkin };
3455          skinItems[c].id = c;
3456          skinItems[c].isRadio = true;
3457       }
3458 */
3459       ideFileDialog.master = this;
3460       ideProjectFileDialog.master = this;
3461
3462       //SetDriverAndSkin();
3463       return true;
3464    }
3465
3466    void UpdateRecentMenus()
3467    {
3468       int c;
3469       Menu fileMenu = menu.FindMenu($"File");
3470       Menu recentFiles = fileMenu.FindMenu($"Recent Files");
3471       Menu recentProjects = fileMenu.FindMenu($"Recent Projects");
3472       char * itemPath = new char[MAX_LOCATION];
3473       char * itemName = new char[MAX_LOCATION+4];
3474       MenuItem item;
3475
3476       recentFiles.Clear();
3477       c = 0;
3478
3479       for(recent : ideSettings.recentFiles)
3480       {
3481          strncpy(itemPath, recent, MAX_LOCATION); itemPath[MAX_LOCATION-1] = '\0';
3482          MakeSystemPath(itemPath);
3483          snprintf(itemName, MAX_LOCATION+4, "%d %s", 1 + c, itemPath); itemName[MAX_LOCATION+4-1] = '\0';
3484          recentFiles.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentFile }, ide, true);
3485          c++;
3486       }
3487
3488       recentProjects.Clear();
3489       c = 0;
3490       for(recent : ideSettings.recentProjects)
3491       {
3492          strncpy(itemPath, recent, MAX_LOCATION); itemPath[MAX_LOCATION-1] = '\0';
3493          MakeSystemPath(itemPath);
3494          snprintf(itemName, MAX_LOCATION+4, "%d %s", 1 + c, itemPath); itemName[MAX_LOCATION+4-1] = '\0';
3495          recentProjects.AddDynamic(MenuItem { copyText = true, text = itemName, (Key)k1 + c, id = c, NotifySelect = ide.FileRecentProject }, ide, true);
3496          c++;
3497       }
3498
3499       delete itemPath;
3500       delete itemName;
3501    }
3502
3503    ~IDEWorkSpace()
3504    {
3505       delete driverItems;
3506       delete skinItems;
3507       delete languageItems;
3508       delete ideSettings;
3509       if(documentor)
3510       {
3511          documentor.Puts("Quit\n");
3512          documentor.Wait();
3513          delete documentor;
3514       }
3515    }
3516 }
3517
3518 void DestroyDir(char * path)
3519 {
3520    RecursiveDeleteFolderFSI fsi { };
3521    fsi.Iterate(path);
3522    delete fsi;
3523 }
3524
3525 #if defined(__WIN32__)
3526 define sdkDirName = "Ecere SDK";
3527 #else
3528 define sdkDirName = "ecere";
3529 #endif
3530
3531 bool GetInstalledFileOrFolder(char * subDir, char * name, char * path, FileAttribs attribs)
3532 {
3533    bool found = false;
3534    char * v = new char[maxPathLen];
3535    v[0] = '\0';
3536    if(found)
3537    {
3538       strncpy(path, settingsContainer.moduleLocation, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3539       StripLastDirectory(path, path);
3540       PathCat(path, subDir);
3541       if(name) PathCat(path, name);
3542       if(FileExists(path) & attribs) found = true;
3543    }
3544 #if defined(__WIN32__)
3545    if(!found)
3546    {
3547       GetEnvironment("ECERE_SDK_SRC", v, maxPathLen);
3548       if(v[0])
3549       {
3550          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3551          PathCat(path, subDir);
3552          if(name) PathCat(path, name);
3553          if(FileExists(path) & attribs) found = true;
3554       }
3555    }
3556    if(!found)
3557    {
3558       GetEnvironment("AppData", v, maxPathLen);
3559       if(v[0])
3560       {
3561          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3562          PathCat(path, sdkDirName);
3563          PathCat(path, subDir);
3564          if(name) PathCat(path, name);
3565          if(FileExists(path) & attribs) found = true;
3566       }
3567    }
3568    if(!found)
3569    {
3570       GetEnvironment("ProgramData", v, maxPathLen);
3571       if(v[0])
3572       {
3573          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3574          PathCat(path, sdkDirName);
3575          PathCat(path, subDir);
3576          if(name) PathCat(path, name);
3577          if(FileExists(path) & attribs) found = true;
3578       }
3579    }
3580    if(!found)
3581    {
3582       GetEnvironment("ProgramFiles", v, maxPathLen);
3583       if(v[0])
3584       {
3585          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3586          PathCat(path, sdkDirName);
3587          PathCat(path, subDir);
3588          if(name) PathCat(path, name);
3589          if(FileExists(path) & attribs) found = true;
3590       }
3591    }
3592    if(!found)
3593    {
3594       GetEnvironment("ProgramFiles(x86)", v, maxPathLen);
3595       if(v[0])
3596       {
3597          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3598          PathCat(path, sdkDirName);
3599          PathCat(path, subDir);
3600          if(name) PathCat(path, name);
3601          if(FileExists(path) & attribs) found = true;
3602       }
3603    }
3604    if(!found)
3605    {
3606       GetEnvironment("SystemDrive", v, maxPathLen);
3607       if(v[0])
3608       {
3609          strncpy(path, v, MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3610          PathCat(path, "Program Files");
3611          PathCat(path, sdkDirName);
3612          PathCat(path, subDir);
3613          if(name) PathCat(path, name);
3614          if(FileExists(path) & attribs) found = true;
3615       }
3616    }
3617 #else
3618    if(!found)
3619    {
3620       byte * tokens[256];
3621       int c, numTokens;
3622
3623       GetEnvironment("XDG_DATA_DIRS", v, maxPathLen);
3624       numTokens = TokenizeWith(v, sizeof(tokens) / sizeof(byte *), tokens, ":", false);
3625       for(c=0; c<numTokens; c++)
3626       {
3627          strncpy(path, tokens[c], MAX_LOCATION); path[MAX_LOCATION-1] = '\0';
3628          PathCat(path, sdkDirName);
3629          PathCat(path, subDir);
3630          if(name) PathCat(path, name);
3631          if(FileExists(path) & attribs) found = true;
3632       }
3633    }
3634 #endif
3635    delete v;
3636    return found;
3637 }
3638
3639 void FindAndShellOpenInstalledFolder(char * name)
3640 {
3641    char path[MAX_LOCATION];
3642    if(GetInstalledFileOrFolder(name, null, path, { isDirectory = true }))
3643       ShellOpen(path);
3644 }
3645
3646 void FindAndShellOpenInstalledFile(char * subdir, char * name)
3647 {
3648    char path[MAX_LOCATION];
3649    if(GetInstalledFileOrFolder(subdir, name, path, { isFile = true }))
3650       ShellOpen(path);
3651 }
3652
3653 class RecursiveDeleteFolderFSI : NormalFileSystemIterator
3654 {
3655    bool preserveRootFolder;
3656
3657    void OutFolder(char * folderPath, bool isRoot)
3658    {
3659       if(!(preserveRootFolder && isRoot))
3660          RemoveDir(folderPath);
3661    }
3662
3663    bool OnFile(char * filePath)
3664    {
3665       DeleteFile(filePath);
3666       return true;
3667    }
3668 }
3669
3670 class IDEApp : GuiApplication
3671 {
3672    //driver = "Win32Console";
3673    // driver = "OpenGL";
3674    // skin = "Aqua";
3675    //skin = "TVision";
3676
3677    TempFile includeFile { };
3678    int argFilesCount;
3679
3680    bool Init()
3681    {
3682       char ext[MAX_EXTENSION];
3683       SetLoggingMode(stdOut, null);
3684       //SetLoggingMode(debug, null);
3685
3686       settingsContainer.Load();
3687
3688       if(ideSettings.language)
3689       {
3690          String language = GetLanguageString();
3691          if(ideSettings.language.OnCompare(language))
3692          {
3693             LanguageRestart(ideSettings.language, app, null, null, null, null, true);
3694             return false;
3695          }
3696       }
3697
3698       // First count files arg to decide whether to maximize
3699       {
3700          bool passThrough = false, debugWorkDir = false;
3701          int c;
3702          argFilesCount = 0;
3703          for(c = 1; c<app.argc; c++)
3704          {
3705             if(passThrough);
3706             else if(debugWorkDir)
3707                debugWorkDir = false;
3708             else if(!strcmp(app.argv[c], "-t"));
3709             else if(!strcmp(app.argv[c], "-no-parsing"));
3710             else if(!strcmp(app.argv[c], "-debug-start"));
3711             else if(!strcmp(app.argv[c], "-debug-work-dir"))
3712                debugWorkDir = true;
3713             else if(!strcmp(app.argv[c], "-@"))
3714                passThrough = true;
3715             else
3716                argFilesCount++;
3717          }
3718       }
3719
3720       if(app.argFilesCount > 0 && !strcmpi(GetExtension(argv[1], ext), "3ds"))
3721       {
3722          app.driver = "OpenGL";
3723          ide.driverItems[1].checked = true;
3724       }
3725       else
3726       {
3727 #if defined(__unix__) || defined(__APPLE__)
3728          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "X";
3729 #else
3730          app.driver = (ideSettings.displayDriver && !strcmp(ideSettings.displayDriver, "OpenGL")) ? ideSettings.displayDriver : "GDI";
3731 #endif
3732          ide.driverItems[ideSettings.displayDriver && !strcmp(ideSettings.displayDriver,"OpenGL")].checked = true;
3733       }
3734
3735       {
3736          char model[MAX_LOCATION];
3737          if(GetInstalledFileOrFolder("samples", "3D/ModelViewer/models/duck/duck.3DS", model, { isFile = true }))
3738          {
3739             ide.duck.modelFile = model;
3740             ide.duck.parent = ideMainFrame;
3741          }
3742       }
3743       if(ide.duck.modelFile && !strcmpi(app.driver, "OpenGL"))
3744          ide.debugRubberDuck.disabled = false;
3745
3746       SetInIDE(true);
3747
3748       desktop.caption = titleECEREIDE;
3749       /*
3750       int c;
3751       for(c = 1; c<app.argc; c++)
3752       {
3753          char fullPath[MAX_LOCATION];
3754          GetWorkingDir(fullPath, MAX_LOCATION);
3755          PathCat(fullPath, app.argv[c]);
3756          ide.OpenFile(fullPath, app.argFilesCount > 1, true, null, yes, normal, false);
3757       }
3758       */
3759
3760       // Default to language specified by environment if no language selected
3761       if(!ideSettings.language)
3762       {
3763          ideSettings.language = GetLanguageString();
3764          settingsContainer.Save();
3765       }
3766
3767       // Default to home directory if no directory yet set up
3768       if(!ideSettings.ideProjectFileDialogLocation[0])
3769       {
3770          bool found = false;
3771          char location[MAX_LOCATION];
3772          char * home = getenv("HOME");
3773          char * homeDrive = getenv("HOMEDRIVE");
3774          char * homePath = getenv("HOMEPATH");
3775          char * userProfile = getenv("USERPROFILE");
3776          char * systemDrive = getenv("SystemDrive");
3777          if(home && FileExists(home).isDirectory)
3778          {
3779             strcpy(location, home);
3780             found = true;
3781          }
3782          if(!found && homeDrive && homePath)
3783          {
3784             strcpy(location, homeDrive);
3785             PathCat(location, homePath);
3786             if(FileExists(location).isDirectory)
3787                found = true;
3788          }
3789          if(!found && FileExists(userProfile).isDirectory)
3790          {
3791             strcpy(location, userProfile);
3792             found = true;
3793          }
3794          if(!found && FileExists(systemDrive).isDirectory)
3795          {
3796             strcpy(location, systemDrive);
3797             found = true;
3798          }
3799          if(found)
3800          {
3801             ideSettings.ideProjectFileDialogLocation = location;
3802             if(!ideSettings.ideFileDialogLocation[0])
3803                ideSettings.ideFileDialogLocation = location;
3804          }
3805       }
3806
3807       if(!LoadIncludeFile())
3808          PrintLn("error: unable to load :crossplatform.mk file inside ide binary.");
3809
3810       // Create language menu
3811       {
3812          String language = ideSettings.language;
3813          int i = 0;
3814          bool found = false;
3815
3816          ide.languageItems = new MenuItem[languages.count];
3817          for(l : languages)
3818          {
3819             ide.languageItems[i] =
3820             {
3821                ide.languageMenu, l.name;
3822                bitmap = { l.bitmap };
3823                id = i;
3824                isRadio = true;
3825
3826                bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3827                {
3828                   if(!LanguageRestart(languages[(int)selection.id].code, app, ideSettings, settingsContainer, ide, ide.projectView, false))
3829                   {
3830                      // Re-select previous selected language if aborted
3831                      String language = ideSettings.language;
3832                      int i = 0;
3833                      for(l : languages)
3834                      {
3835                         if(((!language || !language[0]) && i == 0) ||
3836                            (language && !strcmpi(l.code, language)))
3837                         {
3838                            ide.languageItems[i].checked = true;
3839                            break;
3840                         }
3841                         i++;
3842                      }
3843                   }
3844                   return true;
3845                }
3846             };
3847             i++;
3848          }
3849
3850          // Try to find country-specific language first
3851          if(language)
3852          {
3853             i = 0;
3854             for(l : languages)
3855             {
3856                if(!strcmpi(l.code, language) || (i == 0 && !strcmpi("en", language)))
3857                {
3858                   ide.languageItems[i].checked = true;
3859                   found = true;
3860                   break;
3861                }
3862                i++;
3863             }
3864          }
3865
3866          // Try generalizing locale
3867          if(!found && language)
3868          {
3869             char * under;
3870             char genericLocale[256];
3871             i = 0;
3872             strncpy(genericLocale, language, sizeof(genericLocale));
3873             genericLocale[sizeof(genericLocale)] = 0;
3874
3875             under = strchr(genericLocale, '_');
3876             if(under)
3877                *under = 0;
3878             if(!strcmpi(genericLocale, "zh"))
3879                strcpy(genericLocale, "zh_CN");
3880             if(strcmp(genericLocale, language))
3881             {
3882                for(l : languages)
3883                {
3884                   if(!strcmpi(l.code, genericLocale) || (i == 0 && !strcmpi("en", genericLocale)))
3885                   {
3886                      ide.languageItems[i].checked = true;
3887                      found = true;
3888                      break;
3889                   }
3890                   i++;
3891                }
3892             }
3893          }
3894
3895          if(!found)
3896             ide.languageItems[0].checked = true;
3897
3898          MenuDivider { ide.languageMenu };
3899          MenuItem
3900          {
3901             ide.languageMenu, "Help Translate";
3902
3903             bool Window::NotifySelect(MenuItem selection, Modifiers mods)
3904             {
3905                ShellOpen("http://translations.launchpad.net/ecere");
3906                return true;
3907             }
3908          };
3909       }
3910
3911       ideMainFrame.Create();
3912       if(app.argFilesCount > 1)
3913          ide.MenuWindowTileVert(null, 0);
3914       return true;
3915    }
3916
3917    bool Cycle(bool idle)
3918    {
3919       if(ide.documentor)
3920       {
3921          if(ide.documentor.Peek())
3922          {
3923             char line[1024];
3924             ide.documentor.GetLine(line, sizeof(line));
3925             if(!strcmpi(line, "Exited"))
3926             {
3927                ide.documentor.CloseInput();
3928                ide.documentor.CloseOutput();
3929                ide.documentor.Wait();
3930                delete ide.documentor;
3931             }
3932          }
3933          if(ide.documentor && ide.documentor.eof)
3934          {
3935             ide.documentor.CloseInput();
3936             ide.documentor.CloseOutput();
3937             ide.documentor.Wait();
3938             delete ide.documentor;
3939          }
3940       }
3941       return true;
3942    }
3943
3944    bool LoadIncludeFile()
3945    {
3946       bool result = false;
3947       File include = FileOpen(":crossplatform.mk", read);
3948       if(include)
3949       {
3950          File f = includeFile;
3951          if(f)
3952          {
3953             for(; !include.Eof(); )
3954             {
3955                char buffer[4096];
3956                int count = include.Read(buffer, 1, 4096);
3957                f.Write(buffer, 1, count);
3958             }
3959             result = true;
3960          }
3961          delete include;
3962       }
3963       return result;
3964    }
3965 }
3966
3967 IDEMainFrame ideMainFrame { };
3968
3969 define app = ((IDEApp)__thisModule);
3970 #ifdef _DEBUG
3971 define titleECEREIDE = $"Ecere IDE (Debug)";
3972 #else
3973 define titleECEREIDE = $"Ecere IDE";
3974 #endif