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