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