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