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