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