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