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