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