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