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