ide/ProjectSettings: Fixed crash on reloading Modified Project while Project Settings...
[sdk] / ide / src / project / ProjectView.ec
1 import "ide"
2
3 import "FileSystemIterator"
4
5 class ImportFolderFSI : NormalFileSystemIterator
6 {
7    ProjectView projectView;
8    Array<ProjectNode> stack { };
9
10    bool OnFolder(char * folderPath)
11    {
12       char name[MAX_LOCATION];
13       ProjectNode parentNode = stack.lastIterator.data;
14       ProjectNode folder;
15       GetLastDirectory(folderPath, name);
16       folder = parentNode.FindSpecial(name, false, true, true);
17       if(!folder)
18          folder = projectView.NewFolder(parentNode, name, false);
19       stack.Add(folder);
20       return true;
21    }
22
23    void OutFolder(char * folderPath, bool isRoot)
24    {
25       stack.lastIterator.Remove(); //stack.Remove();
26    }
27
28    bool OnFile(char * filePath)
29    {
30       ProjectNode parentNode = stack.lastIterator.data;
31       projectView.AddFile(parentNode, filePath, parentNode.isInResources, false);
32       return true;
33    }
34 }
35
36 static Array<FileFilter> fileFilters
37 { [
38    { $"eC/C/C++ Files (*.ec, *.eh, *.c, *.cpp, *.cc, *.cxx, *.h, *.hpp, *.hh, *.hxx)", "ec, eh, c, cpp, cc, cxx, h, hpp, hh, hxx" },
39    { $"eC/C/C++ Source Files (*.ec, *.c, *.cpp, *.cc, *.cxx)", "ec, eh, c, cpp, cc, cxx" },
40    { $"Header Files for eC/C/C++ (*.eh, *.h, *.hpp, *.hh, *.hxx)", "eh, h, hpp, hh, hxx" },
41    { $"All files", null }
42 ] };
43
44 static Array<FileFilter> resourceFilters
45 { [
46    { $"Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png,*.gif)", "jpg, jpeg, bmp, pcx, png, gif" },
47    { $"3D Studio Model Files (*.3ds)", "3ds" },
48    { $"Translations (*.mo)", "mo" },
49    { $"All files", null }
50 ] };
51
52 static Array<FileFilter> projectFilters
53 { [
54    { $"Project Files (*.epj)", ProjectExtension },
55    { $"Workspace Files (*.ews)", WorkspaceExtension }
56 ] };
57
58 static Array<FileType> projectTypes
59 { [
60    { $"Ecere IDE Project", ProjectExtension },
61    { $"Ecere IDE Workspace", WorkspaceExtension }
62 ] };
63
64 static char * iconNames[] = 
65 {
66    "<:ecere>mimeTypes/file.png",                   /*genFile*/
67    "<:ecere>mimeTypes/textEcereWorkspace.png",     /*ewsFile*/
68    "<:ecere>mimeTypes/textEcereProject.png",       /*epjFile*/
69    "<:ecere>places/folder.png",                    /*folder*/
70    "<:ecere>status/folderOpen.png",                /*openFolder*/
71    "<:ecere>mimeTypes/textEcereSource.png",        /*ecFile*/
72    "<:ecere>mimeTypes/textEcereHeader.png",        /*ehFile*/
73    "<:ecere>mimeTypes/textCSource.png",            /*sFile*/ // TODO: change sFile icon to differentiate from cFile icon
74    "<:ecere>mimeTypes/textCSource.png",            /*cFile*/
75    "<:ecere>mimeTypes/textCHeader.png",            /*hFile*/
76    "<:ecere>mimeTypes/textC++Source.png",          /*cppFile*/
77    "<:ecere>mimeTypes/textC++Header.png",          /*hppFile*/
78    "<:ecere>mimeTypes/text.png",                   /*textFile*/
79    "<:ecere>mimeTypes/textHyperTextMarkup.png",    /*webFile*/
80    "<:ecere>mimeTypes/image.png",                  /*pictureFile*/
81    "<:ecere>status/audioVolumeHigh.png",           /*soundFile*/
82    "<:ecere>mimeTypes/package.png",                /*archiveFile*/
83    "<:ecere>mimeTypes/packageSoftware.png",        /*packageFile*/
84    "<:ecere>mimeTypes/packageOpticalDisc.png",     /*opticalMediaImageFile*/
85    "<:ecere>mimeTypes/file.png"                    /*mFile*/ //TODO: create icon for mfile
86 };
87
88 enum PrepareMakefileMethod { normal, force, forceExists };
89
90 enum CleanType { clean, realClean, cleanTarget };
91 enum BuildType { build, rebuild, relink, run, start, restart, clean };
92 enum BuildState
93 {
94    none, buildingMainProject, buildingSecondaryProject, compilingFile;
95
96    property bool { get { return this != none; } }
97    //property bool actualBuild { get { return this == buildingMainProject || this == buildingSecondaryProject;  } }
98 };
99
100 class ProjectView : Window
101 {
102    isDocument = true;
103    //hasMinimize = true;
104    hasClose = true;
105    borderStyle = sizable;
106    hasHorzScroll = true;
107    hasVertScroll = true;
108    background = white;
109    size = { 300 };
110    anchor = Anchor { left = 0, top = 0, bottom = 0 };
111    menu = Menu { };
112    
113    //hasMinimize = true;
114    saveDialog = projectFileDialog;
115    
116    DataRow resourceRow;
117    BuildState buildInProgress;
118    BitmapResource icons[NodeIcons];
119    Project project;
120    Workspace workspace;
121    property Workspace workspace
122    {
123       set
124       {
125          if(workspace)
126          {
127             for(prj : workspace.projects)
128             {
129                DeleteNode(prj.topNode);
130             }
131             workspace.projects.Free();
132             ide.debugger.CleanUp();
133          }
134          workspace = value;
135          if(workspace)
136          {
137             fileDialog.currentDirectory = workspace.workspaceDir;
138             resourceFileDialog.currentDirectory = workspace.workspaceDir;
139             for(prj : workspace.projects)
140                AddNode(prj.topNode, null);
141             ide.statusBar.text = $"Generating Makefile & Dependencies...";
142             app.UpdateDisplay();
143             for(prj : workspace.projects)
144                prj.ModifiedAllConfigs(true, false, false, false);
145             ide.statusBar.text = $"Initializing Debugger"; app.UpdateDisplay();
146             ide.statusBar.text = null;
147             app.UpdateDisplay();
148          }
149       }
150       get { return workspace; }
151    }
152    
153    bool drawingInProjectSettingsDialog;
154    bool drawingInProjectSettingsDialogHeader;
155    ProjectSettings projectSettingsDialog;
156
157    bool stopBuild;
158    PopupMenu popupMenu;
159
160    ProjectView()
161    {
162       NodeIcons c;
163       for(c = 0; c < NodeIcons::enumSize; c++)
164       {
165          icons[c] = BitmapResource { iconNames[c], alphaBlend = true };
166          AddResource(icons[c]);
167       }
168       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false, userData = this });
169    }
170
171    ~ProjectView()
172    {
173       DebugStop();
174       ide.DestroyTemporaryProjectDir();
175       if(project)
176       {
177          workspace.Free();
178          delete workspace;
179       }
180    }
181
182    ListBox fileList
183    {
184       multiSelect = true, fullRowSelect = false, hasVertScroll = true, hasHorzScroll = true;
185       borderStyle = deep, parent = this, collapseControl = true, treeBranches = true;
186       anchor = Anchor { left = 0, right = 0, top = 0 , bottom = 0 };
187
188       background = projectViewBackground;
189       foreground = projectViewText;
190       selectionColor = selectionColor, selectionText = selectionText;
191       stippleColor = skyBlue;
192
193       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
194       {
195          if(!active) Update(null);
196          return ListBox::OnActivate(active, previous, goOnWithActivation, direct);
197       }
198       
199       bool NotifyDoubleClick(ListBox listBox, int x, int y, Modifiers mods)
200       {
201          // Prevent the double click from reactivating the project view (returns false if we opened something)
202          return !OpenSelectedNodes();
203       }
204
205       bool NotifyRightClick(ListBox listBox, int x, int y, Modifiers mods)
206       {
207          DataRow row = listBox.currentRow;
208          if(row)
209          {
210             ProjectNode node = (ProjectNode)row.tag;
211             if(node.type == NodeTypes::project || node.type == resources || node.type == file || node.type == folder)
212             {
213                bool buildMenuUnavailable = buildInProgress;
214                Menu popupContent { };
215                
216                if(node.type == NodeTypes::project)
217                {
218                   //if(node == ((Project)workspace.projects.first).topNode)
219                   {
220                      MenuItem { popupContent, $"Build", b, f7, NotifySelect = ProjectBuild }.disabled = buildMenuUnavailable;
221                      MenuItem { popupContent, $"Relink", l, NotifySelect = ProjectLink }.disabled = buildMenuUnavailable;
222                      MenuItem { popupContent, $"Rebuild", r, shiftF7, NotifySelect = ProjectRebuild }.disabled = buildMenuUnavailable;
223                      MenuItem { popupContent, $"Clean", c, NotifySelect = ProjectClean }.disabled = buildMenuUnavailable;
224                      MenuItem { popupContent, $"Real Clean", d, NotifySelect = ProjectRealClean }.disabled = buildMenuUnavailable;
225                      MenuItem { popupContent, $"Regenerate Makefile", m, NotifySelect = ProjectRegenerate }.disabled = buildMenuUnavailable;
226                      MenuDivider { popupContent };
227                   }
228                   MenuItem { popupContent, $"Debug Generate Symbols", l, NotifySelect = FileDebugGenerateSymbols }.disabled = buildMenuUnavailable;
229                   MenuDivider { popupContent };
230                   MenuItem { popupContent, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
231                   MenuItem { popupContent, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
232                   MenuItem { popupContent, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
233                   MenuItem { popupContent, $"Add Files to Project...", f, NotifySelect = ProjectAddFiles };
234                   MenuDivider { popupContent };
235                   MenuItem { popupContent, $"Add New Form...", o, NotifySelect = ProjectAddNewForm };
236                   // MenuItem { popupContent, "Add New Behavior Graph...", g, NotifySelect = ProjectAddNewGraph };
237                   MenuDivider { popupContent };
238                   if(node != ((Project)workspace.projects.first).topNode)
239                   {
240                      MenuItem { popupContent, $"Remove project from workspace", r, NotifySelect = ProjectRemove }.disabled = buildMenuUnavailable;
241                      MenuDivider { popupContent };
242                   }
243                   MenuItem { popupContent, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
244                   MenuDivider { popupContent };
245                   MenuItem { popupContent, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
246                   MenuDivider { popupContent };
247                   MenuItem { popupContent, $"Save", v, Key { s, ctrl = true }, NotifySelect = ProjectSave }.disabled = !node.modified;
248                   MenuDivider { popupContent };
249                   MenuItem { popupContent, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
250                }
251                else if(node.type == resources)
252                {
253                   MenuItem { popupContent, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
254                   MenuItem { popupContent, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
255                   MenuItem { popupContent, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
256                   MenuItem { popupContent, $"Add Resources to Project...", f, NotifySelect = ResourcesAddFiles };
257                   MenuItem { popupContent, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
258                   MenuDivider { popupContent };
259                   MenuItem { popupContent, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
260                   MenuItem { popupContent, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
261                }
262                else if(node.type == file)
263                {
264                   MenuItem { popupContent, $"Open", o, NotifySelect = FileOpenFile };
265                   MenuDivider { popupContent };
266                   MenuItem { popupContent, $"Clean", l, NotifySelect = FileClean }.disabled = buildMenuUnavailable;
267                   MenuItem { popupContent, $"Compile", c, Key { f7, ctrl = true}, NotifySelect = FileCompile }.disabled = buildMenuUnavailable;
268                   MenuDivider { popupContent };
269                   MenuItem { popupContent, $"Debug Precompile", l, NotifySelect = FileDebugPrecompile }.disabled = buildMenuUnavailable;
270                   MenuItem { popupContent, $"Debug Compile", l, NotifySelect = FileDebugCompile }.disabled = buildMenuUnavailable;
271                   MenuDivider { popupContent };
272                   MenuItem { popupContent, $"Remove", r, NotifySelect = FileRemoveFile };
273                   MenuDivider { popupContent };
274                   MenuItem { popupContent, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
275                   MenuDivider { popupContent };
276                   MenuItem { popupContent, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
277                   MenuItem { popupContent, $"Properties..", p, Key { enter, alt = true }, NotifySelect = FileProperties };
278                }
279                else if(node.type == folder)
280                {
281                   bool isInResources = node.isInResources;
282
283                   MenuItem { popupContent, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
284                   MenuItem { popupContent, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
285                   MenuItem { popupContent, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
286                   if(isInResources)
287                   {
288                      MenuItem { popupContent, $"Add Resources to Folder...", f, NotifySelect = ResourcesAddFiles };
289                   }
290                   else
291                   {
292                      MenuItem { popupContent, $"Add Files to Folder...", f, NotifySelect = ProjectAddFiles };
293                   }
294                   if(!isInResources)
295                   {
296                      MenuDivider { popupContent };
297                      MenuItem { popupContent, $"Add New Form...", o, NotifySelect = ProjectAddNewForm };
298                      MenuItem { popupContent, $"Add New Behavior Graph...", g, NotifySelect = ProjectAddNewGraph };
299                   }
300                   MenuDivider { popupContent };
301                   MenuItem { popupContent, $"Clean", l, NotifySelect = FileClean }.disabled = buildMenuUnavailable;
302                   MenuItem { popupContent, $"Compile", c, Key { f7, ctrl = true}, NotifySelect = FileCompile }.disabled = buildMenuUnavailable;
303                   MenuDivider { popupContent };
304                   MenuItem { popupContent, $"Remove", r, NotifySelect = FileRemoveFile };
305                   MenuDivider { popupContent };
306                   MenuItem { popupContent, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
307                   MenuDivider { popupContent };
308                   MenuItem { popupContent, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
309                   MenuItem { popupContent, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
310                }
311
312                popupMenu = 
313                {
314                   master = this, menu = popupContent;
315                   position = {
316                      x + clientStart.x + absPosition.x - app.desktop.position.x, 
317                      y + clientStart.y + absPosition.y - app.desktop.position.y };
318
319                   void NotifyDestroyed(Window window, DialogResult result)
320                   {
321                      popupMenu = null;
322                   }
323                };
324                popupMenu.Create();
325                ide.AdjustPopupBuildMenus();
326             }
327          }
328          return true;
329       }
330
331       bool NotifyKeyHit(ListBox listBox, DataRow row, Key key, unichar ch)
332       {
333          if(key == altUp || key == altDown)
334          {
335             SelectNextProject(key == altUp);
336             return false;
337          }
338          return true;
339       }
340
341       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
342       {
343          if(row)
344          {
345             ProjectNode node = (ProjectNode)row.tag;
346             switch(key)
347             {
348                case altEnter: case Key { keyPadEnter, alt = true }:
349                {
350                   NodeProperties { parent = parent, master = this, 
351                      position = { position.x + 100, position.y + 100 }, node = node }.Create();
352                   return false;
353                }
354                case enter: case keyPadEnter:
355                {
356                   ProjectNode resNode;
357                   for(resNode = node.parent; resNode; resNode = resNode.parent)
358                      if(resNode.type == resources)
359                         break;
360                   if(node.type == project || (node.type == folder && !resNode))
361                   {
362                      AddFiles(false);
363                      return false;
364                   }
365                   else if(node.type == resources || node.type == folder)
366                   {
367                      AddFiles(true);
368                      return false;
369                   }
370                   break;
371                }
372                case ctrlI:
373                {
374                   if(node.type == project || node.type == folder || node.type == resources)
375                   {
376                      ImportFolder(node);
377                      return false;
378                   }
379                   break;
380                }
381                case ctrlF:
382                {
383                   if(node.type == project || node.type == folder || node.type == resources)
384                   {
385                      NewFolder(node, null, true);
386                      return false;
387                   }
388                   break;
389                }
390                case ctrlF7:
391                {
392                   if(node.type == file)
393                   {
394                      FileCompile(null, (Modifiers)key);
395                      return false;
396                   }
397                   break;
398                }
399                case Key { space, shift = true }:
400                case space:
401                {
402                   if(node.type == NodeTypes::project)
403                   {
404                      Project prj = null;
405                      for(p : workspace.projects)
406                      {
407                         if(p.topNode == node)
408                         {
409                            prj = p;
410                            break;
411                         }
412                      }
413                      prj.RotateActiveConfig(!key.shift);
414                      if(prj == project)
415                         ide.AdjustMenus();
416                      return false;
417                   }
418                   break;
419                }
420             }
421          }
422          switch(key)
423          {
424             case enter: case keyPadEnter:  OpenSelectedNodes();   break;
425             case del:                      RemoveSelectedNodes(); break;
426             case escape:                      
427             {
428                Window activeClient = ide.activeClient;
429                if(activeClient)
430                   activeClient.Activate();
431                else
432                   ide.RepositionWindows(true);
433                break;
434             }
435          }
436          return true;
437       }
438
439       bool NotifyCollapse(ListBox listBox, DataRow row, bool collapsed)
440       {
441          ProjectNode node = (ProjectNode)row.tag;
442          if(node.type == folder)
443             node.icon = collapsed ? folder : openFolder;
444          return true;
445       }
446    };
447
448    FileDialog importFileDialog { autoCreate = false, type = selectDir, text = $"Import Folder" };
449    FileDialog projectFileDialog
450    {
451       autoCreate = false, filters = projectFilters.array, sizeFilters = projectFilters.count * sizeof(FileFilter);
452       types = projectTypes.array, sizeTypes = projectTypes.count * sizeof(FileType);
453    };
454    FileDialog fileDialog
455    {
456       autoCreate = false, mayNotExist = true, filters = fileFilters.array, sizeFilters = fileFilters.count * sizeof(FileFilter);
457    };
458    FileDialog resourceFileDialog
459    {
460       autoCreate = false, mayNotExist = true, filters = resourceFilters.array, sizeFilters = resourceFilters.count * sizeof(FileFilter);
461    };
462
463    Menu fileMenu { menu, $"File", f };
464    MenuItem { fileMenu, $"Save", s, Key { s, ctrl = true }, NotifySelect = MenuFileSave };
465    // MenuItem { fileMenu, "Save As...", a, NotifySelect = MenuFileSaveAs };
466
467    bool OnClose(bool parentClosing)
468    {
469       if(!parentClosing && visible)
470       {
471          visible = false;
472          return false;
473       }
474       if(buildInProgress)
475          return false;
476
477       if(modifiedDocument)
478       {
479          DialogResult dialogRes;
480          char msg[2048];
481          bool first = true;
482          strcpy(msg, $"You have modified projects.\nSave changes to ");
483          for(p : ide.workspace.projects)
484          {
485             if(p.topNode.modified)
486             {
487                if(!first) strcat(msg, ", ");
488                strcat(msg, p.name);
489                first = false;
490             }
491          }
492          strcat(msg, "?");
493
494          dialogRes = MessageBox { master = master, type = yesNoCancel, text = parent.caption ? parent.caption : rootWindow.caption, contents = msg }.Modal();
495
496          if(dialogRes == yes)
497          {
498             // TOFIX: Precomp error if brackets are taken out
499             return (DialogResult)MenuFileSave(null, 0) != cancel;
500          }
501          else if(dialogRes == cancel)
502             return false;
503          modifiedDocument = false;
504       }
505       return true;
506    }
507
508    void OnDestroy(void)
509    {
510       //if(ide.findInFilesDialog && ide.findInFilesDialog.created && ide.findInFilesDialog.mode != directory)
511       //   ide.findInFilesDialog.SearchStop();
512
513       ide.outputView.buildBox.Clear();
514       ide.outputView.debugBox.Clear();
515       //ide.outputView.findBox.Clear();
516       ide.callStackView.Clear();
517       ide.watchesView.Clear();
518       ide.threadsView.Clear();
519       ide.breakpointsView.Clear();
520       ide.outputView.ShowClearSelectTab(find); // why this? 
521    }
522
523    bool OnSaveFile(char * fileName)
524    {
525       for(prj : ide.workspace.projects)
526       {
527          if(prj.topNode.modified)
528          {
529             prj.StopMonitoring();
530             if(prj.Save(prj.filePath))
531                prj.topNode.modified = false;
532             prj.StartMonitoring();
533          }
534       }
535       modifiedDocument = false;
536       Update(null);
537       return true;
538    }
539
540    bool IsModuleInProject(char * filePath)
541    {
542       char moduleName[MAX_FILENAME]; //, modulePath[MAX_LOCATION];
543       GetLastDirectory(filePath, moduleName);
544       return project.topNode.Find(moduleName, false) != null;
545    }
546
547    bool GetRelativePath(char * filePath, char * relativePath)
548    {
549       return project.GetRelativePath(filePath, relativePath);
550    }
551
552    ProjectNode GetNodeFromWindow(Window document, Project project, bool isCObject)
553    {
554       if(document.fileName)
555       {
556          char winFileName[MAX_LOCATION];
557          char * documentFileName = GetSlashPathBuffer(winFileName, document.fileName);
558          ProjectNode node;
559          Project prj;
560          if(isCObject)
561          {
562             char name[MAX_FILENAME];
563             GetLastDirectory(documentFileName, name);
564             ChangeExtension(name, "ec", name);
565             for(p : ide.workspace.projects)
566             {
567                prj = project ? project : p;
568                if((node = prj.topNode.Find(name, false)))
569                   return node;
570                if(project) break;
571             }
572          }
573          else
574          {
575             for(p : ide.workspace.projects)
576             {
577                prj = project ? project : p;
578                if((node = prj.topNode.FindByFullPath(documentFileName, false)))
579                   return node;
580                if(project) break;
581             }
582          }
583       }
584       return null;
585    }
586
587    //                          ((( UTILITY FUNCTIONS )))
588    //
589    //  ************************************************************************
590    //  *** These methods below are part of a sequence of events, and as     ***
591    //  *** such they must be passed the current compiler and project config ***
592    //  ************************************************************************
593    bool DisplayCompiler(CompilerConfig compiler, bool cleanLog)
594    {
595       ide.outputView.buildBox.Logf($"%s Compiler\n", compiler ? compiler.name : $"{problem with compiler selection}");
596       return true;
597    }
598
599    bool ProjectPrepareForToolchain(Project project, PrepareMakefileMethod method, bool cleanLog, bool displayCompiler,
600       CompilerConfig compiler, ProjectConfig config)
601    {
602       bool isReady = true;
603       char message[MAX_F_STRING];
604       LogBox logBox = ide.outputView.buildBox;
605
606       ShowOutputBuildLog(cleanLog);
607
608       if(displayCompiler)
609          DisplayCompiler(compiler, false);
610
611       ProjectPrepareCompiler(project, compiler, false);
612       ProjectPrepareMakefile(project, method, compiler, config);
613       return true;
614    }
615
616    bool ProjectPrepareCompiler(Project project, CompilerConfig compiler, bool silent)
617    {
618       if((!project.GenerateCrossPlatformMk(app.includeFile) || !project.GenerateCompilerCf(compiler)) && !silent)
619          ide.outputView.buildBox.Logf($"Error generating compiler configuration (Is the project/config directory writable?)\n");
620       return true;
621    }
622
623    // Note: Compiler is only passed in to for VisualStudio support
624    bool ProjectPrepareMakefile(Project project, PrepareMakefileMethod method, CompilerConfig compiler, ProjectConfig config)
625    {
626       if(compiler.type.isVC)
627       {
628          // I'm guessing we'll want to support generating VS files on Linux as well...
629          ide.statusBar.text = $"Generating Visual Studio Solution...";
630          app.UpdateDisplay();
631          //GenerateVSSolutionFile(project, compiler);
632          ide.statusBar.text = $"Generating Visual Studio Project...";
633          app.UpdateDisplay();
634          //GenerateVCProjectFile(project, compiler, bitDepth);
635          ide.statusBar.text = null;
636          app.UpdateDisplay();
637          return true;
638       }
639       else
640       {
641          char makefilePath[MAX_LOCATION];
642          char makefileName[MAX_LOCATION];
643          bool exists;
644          LogBox logBox = ide.outputView.buildBox;
645          
646          strcpy(makefilePath, project.topNode.path);
647          project.CatMakeFileName(makefileName, config);
648          PathCatSlash(makefilePath, makefileName);
649
650          exists = FileExists(makefilePath);
651          if(method == force ||
652            (method == forceExists && exists) ||
653            (method == normal && (!exists || (config && config.makingModified))))
654          {
655             char * reason;
656             char * action;
657             ide.statusBar.text = $"Generating Makefile & Dependencies..."; // Dependencies?
658             app.UpdateDisplay();
659
660             if((method == normal && !exists) || (method == force && !exists))
661                action = $"Generating ";
662             else if(method == force)
663                action = $"Regenerating ";
664             else if(method == normal || method == forceExists)
665                action = $"Updating ";
666             else
667                action = "";
668             if(!exists)
669                reason = $"Makefile doesn't exist. ";
670             else if(project.topNode.modified)
671                reason = $"Project has been modified. ";
672             else
673                reason = "";
674
675             //logBox.Logf("%s\n", makefileName);
676             logBox.Logf($"%s - %s%smakefile for %s config...\n", makefileName, reason, action, GetConfigName(config));
677
678             if(!project.GenerateMakefile(null, false, null, config))
679                ide.outputView.buildBox.Logf($"Error generating makefile (Is the project directory writable?)\n");
680
681             ide.statusBar.text = null;
682             app.UpdateDisplay();
683             return true;
684          }
685       }
686       return false;
687    }
688    
689    bool BuildInterrim(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config, int bitDepth, bool justPrint)
690    {
691       if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
692       {
693          ide.outputView.buildBox.Logf($"Building project %s using the %s configuration...\n", prj.name, GetConfigName(config));
694          return Build(prj, buildType, compiler, config, bitDepth, justPrint);
695       }
696       return false;
697    }
698
699    bool DebugStopForMake(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config)
700    {
701       bool result = false;
702       // TOFIX: DebugStop is being abused and backfiring on us.
703       //        It's supposed to be the 'Debug/Stop' item, not unloading executable or anything else
704
705       //        configIsInDebugSession seems to be used for two OPPOSITE things:
706       //        If we're debugging another config, we need to unload the executable!
707       //        In building, we want to stop if we're debugging the 'same' executable
708       if(buildType != run) ///* && prj == project*/ && prj.configIsInDebugSession)
709       {
710          if(buildType == start || buildType == restart)
711          {
712             if(ide.debugger && ide.debugger.isPrepared)
713                result = DebugStop();
714          }
715          else
716          {
717             if(ide.project == prj && ide.debugger && ide.debugger.prjConfig == config && ide.debugger.isPrepared)
718                result = DebugStop();
719          }
720       }
721       return result;
722    }
723
724    bool Build(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config, int bitDepth, bool justPrint)
725    {
726       bool result = true;
727       Window document;
728
729       stopBuild = false;
730       for(document = master.firstChild; document; document = document.next)
731       {
732          if(document.modifiedDocument)
733          {
734             ProjectNode node = GetNodeFromWindow(document, prj, false);
735             if(node && !document.MenuFileSave(null, 0))
736             {
737                result = false;
738                break;
739             }
740          }
741       }
742       if(result)
743       {
744          DirExpression targetDir = prj.GetTargetDir(compiler, config, bitDepth);
745
746          DebugStopForMake(prj, buildType, compiler, config);
747
748          // TODO: Disabled until problems fixed... is it fixed?
749          if(buildType == rebuild || (config && config.compilingModified))
750             prj.Clean(compiler, config, bitDepth, clean, justPrint);
751          else
752          {
753             if(buildType == relink || (config && config.linkingModified))
754                prj.Clean(compiler, config, bitDepth, cleanTarget, false);
755             if(config && config.symbolGenModified)
756             {
757                DirExpression objDir = prj.GetObjDir(compiler, config, bitDepth);
758                char fileName[MAX_LOCATION];
759                char moduleName[MAX_FILENAME];
760                strcpy(fileName, prj.topNode.path);
761                PathCatSlash(fileName, objDir.dir);
762                strcpy(moduleName, prj.moduleName);
763                strcat(moduleName, ".main.ec");
764                PathCatSlash(fileName, moduleName);
765                if(FileExists(fileName))
766                   DeleteFile(fileName);
767                ChangeExtension(fileName, "c", fileName);
768                if(FileExists(fileName))
769                   DeleteFile(fileName);
770                ChangeExtension(fileName, "o", fileName);
771                if(FileExists(fileName))
772                   DeleteFile(fileName);
773
774                delete objDir;
775             }
776          }
777          buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
778          ide.AdjustBuildMenus();
779          ide.AdjustDebugMenus();
780
781          result = prj.Build(buildType == run, null, compiler, config, bitDepth, justPrint, normal);
782
783          if(config)
784          {
785             config.compilingModified = false;
786             if(!ide.ShouldStopBuild())
787                config.linkingModified = false;
788
789             config.symbolGenModified = false;
790          }
791          buildInProgress = none;
792          ide.AdjustBuildMenus();
793          ide.AdjustDebugMenus();
794
795          ide.workspace.modified = true;
796
797          delete targetDir;
798       }
799       return result;
800    }
801
802    //                          ((( USER ACTIONS )))
803    //
804    //  ************************************************************************
805    //  *** Methods below should atomically start a process, and as such     ***
806    //  *** they can query compiler and config directly from ide and project ***
807    //  *** but ONLY ONCE!!!                                                 ***
808    //  ************************************************************************
809
810    bool ProjectBuild(MenuItem selection, Modifiers mods)
811    {
812       if(buildInProgress == none)
813       {
814          Project prj = project;
815          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
816          int bitDepth = ide.workspace.bitDepth;
817          ProjectConfig config;
818          if(selection || !ide.activeClient)
819          {
820             DataRow row = fileList.currentRow;
821             ProjectNode node = row ? (ProjectNode)row.tag : null;
822             if(node) prj = node.project;
823          }
824          else
825          {
826             ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
827             if(node)
828                prj = node.project;
829          }
830          config = prj.config;
831          if(/*prj != project || */!prj.GetConfigIsInDebugSession(config) || !ide.DontTerminateDebugSession($"Project Build"))
832          {
833             BuildInterrim(prj, build, compiler, config, bitDepth, mods.ctrl && mods.shift);
834          }
835          delete compiler;
836       }
837       else
838          stopBuild = true;
839       return true;
840    }
841
842    bool ProjectLink(MenuItem selection, Modifiers mods)
843    {
844       Project prj = project;
845       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
846       int bitDepth = ide.workspace.bitDepth;
847       ProjectConfig config;
848       if(selection || !ide.activeClient)
849       {
850          DataRow row = fileList.currentRow;
851          ProjectNode node = row ? (ProjectNode)row.tag : null;
852          if(node) prj = node.project;
853       }
854       else
855       {
856          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
857          if(node)
858             prj = node.project;
859       }
860       config = prj.config;
861       if(!prj.GetConfigIsInDebugSession(config) ||
862             (!ide.DontTerminateDebugSession($"Project Link") && DebugStopForMake(prj, relink, compiler, config)))
863       {
864          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
865          {
866             ide.outputView.buildBox.Logf($"Relinking project %s using the %s configuration...\n", prj.name, GetConfigName(config));
867             if(config)
868                config.linkingModified = true;
869             Build(prj, relink, compiler, config, bitDepth, mods.ctrl && mods.shift);
870          }
871       }
872       delete compiler;
873       return true;
874    }
875
876    bool ProjectRebuild(MenuItem selection, Modifiers mods)
877    {
878       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
879       int bitDepth = ide.workspace.bitDepth;
880       Project prj = project;
881       ProjectConfig config;
882       if(selection || !ide.activeClient)
883       {
884          DataRow row = fileList.currentRow;
885          ProjectNode node = row ? (ProjectNode)row.tag : null;
886          if(node) prj = node.project;
887       }
888       else
889       {
890          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
891          if(node)
892             prj = node.project;
893       }
894       config = prj.config;
895       if(!prj.GetConfigIsInDebugSession(config) ||
896             (!ide.DontTerminateDebugSession($"Project Rebuild") && DebugStopForMake(prj, rebuild, compiler, config)))
897       {
898          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
899          {
900             ide.outputView.buildBox.Logf($"Rebuilding project %s using the %s configuration...\n", prj.name, GetConfigName(config));
901             /*if(config)
902             {
903                config.compilingModified = true;
904                config.makingModified = true;
905             }*/ // -- should this still be used depite the new solution of BuildType?
906             Build(prj, rebuild, compiler, config, bitDepth, mods.ctrl && mods.shift);
907          }
908       }
909       delete compiler;
910       return true;
911    }
912
913 #if 0
914    bool ProjectCleanTarget(MenuItem selection, Modifiers mods)
915    {
916       Project prj = project;
917       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
918       ProjectConfig config;
919       if(selection || !ide.activeClient)
920       {
921          DataRow row = fileList.currentRow;
922          ProjectNode node = row ? (ProjectNode)row.tag : null;
923          if(node) prj = node.project;
924       }
925       else
926       {
927          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
928          if(node)
929             prj = node.project;
930       }
931       config = prj.config;
932       if(!prj.GetConfigIsInDebugSession(config) ||
933             (!ide.DontTerminateDebugSession($"Project Clean Target") && DebugStopForMake(prj, clean, compiler, config)))
934       {
935          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
936          {
937             ide.outputView.buildBox.Logf($"Cleaning target for project %s using the %s configuration...\n", prj.name, GetConfigName(config));
938
939             buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
940             ide.AdjustBuildMenus();
941             ide.AdjustDebugMenus();
942
943             prj.Clean(compiler, config, cleanTarget, mods.ctrl && mods.shift);
944             buildInProgress = none;
945             ide.AdjustBuildMenus();
946             ide.AdjustDebugMenus();
947          }
948       }
949       delete compiler;
950       return true;
951    }
952 #endif
953
954    bool ProjectClean(MenuItem selection, Modifiers mods)
955    {
956       Project prj = project;
957       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
958       ProjectConfig config;
959       int bitDepth = ide.workspace.bitDepth;
960       if(selection || !ide.activeClient)
961       {
962          DataRow row = fileList.currentRow;
963          ProjectNode node = row ? (ProjectNode)row.tag : null;
964          if(node) prj = node.project;
965       }
966       else
967       {
968          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
969          if(node)
970             prj = node.project;
971       }
972       config = prj.config;
973       if(!prj.GetConfigIsInDebugSession(config) ||
974             (!ide.DontTerminateDebugSession($"Project Clean") && DebugStopForMake(prj, clean, compiler, config)))
975       {
976          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
977          {
978             ide.outputView.buildBox.Logf($"Cleaning project %s using the %s configuration...\n", prj.name, GetConfigName(config));
979
980             buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
981             ide.AdjustBuildMenus();
982             ide.AdjustDebugMenus();
983
984             prj.Clean(compiler, config, bitDepth, clean, mods.ctrl && mods.shift);
985             buildInProgress = none;
986             ide.AdjustBuildMenus();
987             ide.AdjustDebugMenus();
988          }
989       }
990       delete compiler;
991       return true;
992    }
993
994    bool ProjectRealClean(MenuItem selection, Modifiers mods)
995    {
996       Project prj = project;
997       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
998       ProjectConfig config;
999       int bitDepth = ide.workspace.bitDepth;
1000       if(selection || !ide.activeClient)
1001       {
1002          DataRow row = fileList.currentRow;
1003          ProjectNode node = row ? (ProjectNode)row.tag : null;
1004          if(node) prj = node.project;
1005       }
1006       else
1007       {
1008          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
1009          if(node)
1010             prj = node.project;
1011       }
1012       config = prj.config;
1013       if(!prj.GetConfigIsInDebugSession(config) ||
1014             (!ide.DontTerminateDebugSession($"Project Real Clean") && DebugStopForMake(prj, clean, compiler, config)))
1015       {
1016          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
1017          {
1018             ide.outputView.buildBox.Logf($"Removing intermediate objects directory for project %s using the %s configuration...\n", prj.name, GetConfigName(config));
1019
1020             buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
1021             ide.AdjustBuildMenus();
1022             ide.AdjustDebugMenus();
1023
1024             prj.Clean(compiler, config, bitDepth, realClean, mods.ctrl && mods.shift);
1025             buildInProgress = none;
1026             ide.AdjustBuildMenus();
1027             ide.AdjustDebugMenus();
1028          }
1029       }
1030       delete compiler;
1031       return true;
1032    }
1033
1034    bool ProjectRegenerate(MenuItem selection, Modifiers mods)
1035    {
1036       Project prj = project;
1037       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1038       ShowOutputBuildLog(true);
1039       if(selection || !ide.activeClient)
1040       {
1041          DataRow row = fileList.currentRow;
1042          ProjectNode node = row ? (ProjectNode)row.tag : null;
1043          if(node)
1044             prj = node.project;
1045       }
1046       else
1047       {
1048          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, false);
1049          if(node)
1050             prj = node.project;
1051       }
1052
1053       DisplayCompiler(compiler, false);
1054       ProjectPrepareCompiler(project, compiler, false);
1055       ProjectPrepareMakefile(prj, force, compiler, prj.config);
1056       delete compiler;
1057       return true;
1058    }
1059
1060    bool Compile(Project project, List<ProjectNode> nodes, bool justPrint, SingleFileCompileMode mode)
1061    {
1062       bool result = true;
1063       char fileName[MAX_LOCATION];
1064       Window document;
1065       ProjectConfig config = project.config;
1066
1067       stopBuild = false;
1068
1069       for(document = ide.firstChild; document; document = document.next)
1070       {
1071          if(document.modifiedDocument)
1072          {
1073             ProjectNode n = GetNodeFromWindow(document, project, mode == cObject ? true : false);
1074             for(node : nodes)
1075             {
1076                if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1077                {
1078                   ide.outputView.buildBox.Logf($"Unable to save %s file.\n", node.name);
1079                   result = false;
1080                   break;
1081                }
1082             }
1083          }
1084       }
1085
1086       if(result)
1087       {
1088          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1089          int bitDepth = ide.workspace.bitDepth;
1090          result = false;
1091          if(ProjectPrepareForToolchain(project, normal, true, true, compiler, config))
1092          {
1093             if(config)
1094                ide.outputView.buildBox.Logf($"%s specific file(s) in project %s using the %s configuration...\n",
1095                      mode == normal ? $"Compiling" : $"Debug compiling", project.name, config.name);
1096             else
1097                ide.outputView.buildBox.Logf($"%s specific file(s) in project %s...\n",
1098                      mode == normal ? $"Compiling" : $"Debug compiling", project.name);
1099
1100             buildInProgress = compilingFile;
1101             ide.AdjustBuildMenus();
1102             project.Compile(nodes, compiler, config, bitDepth, justPrint, mode);
1103             buildInProgress = none;
1104             ide.AdjustBuildMenus();
1105
1106             result = true;
1107          }
1108          delete compiler;
1109       }
1110       return result;
1111    }
1112
1113    bool Clean(Project project, List<ProjectNode> nodes, bool justPrint)
1114    {
1115       bool result = true;
1116       char fileName[MAX_LOCATION];
1117       Window document;
1118       ProjectConfig config = project.config;
1119
1120       stopBuild = false;
1121
1122       for(document = ide.firstChild; document; document = document.next)
1123       {
1124          if(document.modifiedDocument)
1125          {
1126             ProjectNode n = GetNodeFromWindow(document, project, false);
1127             for(node : nodes)
1128             {
1129                if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1130                {
1131                   ide.outputView.buildBox.Logf($"Unable to save %s file.\n", node.name);
1132                   result = false;
1133                   break;
1134                }
1135             }
1136          }
1137       }
1138
1139       if(result)
1140       {
1141          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1142          int bitDepth = ide.workspace.bitDepth;
1143          result = false;
1144          if(ProjectPrepareForToolchain(project, normal, true, true, compiler, config))
1145          {
1146             Map<String, NameCollisionInfo> namesInfo { };
1147             project.topNode.GenMakefileGetNameCollisionInfo(namesInfo, config);
1148             for(node : nodes)
1149             {
1150                if(node.GetIsExcluded(config))
1151                   ide.outputView.buildBox.Logf($"File %s is excluded from current build configuration.\n", node.name);
1152                else
1153                {
1154                   if(config)
1155                      ide.outputView.buildBox.Logf($"Deleteing intermediate objects for %s %s in project %s using the %s configuration...\n",
1156                            node.type == file ? $"single file" : $"folder", node.name, project.name, config.name);
1157                   else
1158                      ide.outputView.buildBox.Logf($"Deleteing intermediate objects for %s %s in project %s...\n",
1159                            node.type == file ? $"single file" : $"folder", node.name, project.name);
1160
1161                   node.DeleteIntermediateFiles(compiler, config, bitDepth, namesInfo, false);
1162                   result = true;
1163                }
1164             }
1165             namesInfo.Free();
1166             delete namesInfo;
1167          }
1168          delete compiler;
1169       }
1170       return result;
1171    }
1172
1173    bool ProjectNewFile(MenuItem selection, Modifiers mods)
1174    {
1175       DataRow row = fileList.currentRow;
1176       if(row)
1177       {
1178          char fileName[1024];
1179          char filePath[MAX_LOCATION];
1180          ProjectNode parentNode = (ProjectNode)row.tag;
1181          ProjectNode n, fileNode;
1182          parentNode.GetFileSysMatchingPath(filePath);
1183          MakePathRelative(filePath, parentNode.project.topNode.path, filePath);
1184          for(n = parentNode; n && n != parentNode.project.resNode; n = n.parent);
1185          sprintf(fileName, $"Untitled %d", documentID);
1186          fileNode = AddFile(parentNode, fileName, (bool)n, true);
1187          fileNode.path = CopyUnixPath(filePath);
1188          if(fileNode)
1189          {
1190             NodeProperties nodeProperties
1191             {
1192                parent, this;
1193                position = { position.x + 100, position.y + 100 };
1194                mode = newFile;
1195                node = fileNode;
1196             };
1197             nodeProperties.Create(); // not modal?
1198          }
1199       }
1200       return true;
1201    }
1202
1203    bool ProjectNewFolder(MenuItem selection, Modifiers mods)
1204    {
1205       DataRow row = fileList.currentRow;
1206       if(row)
1207       {
1208          ProjectNode parentNode = (ProjectNode)row.tag;
1209          NewFolder(parentNode, null, true);
1210       }
1211       return true;
1212    }
1213
1214    bool ResourcesAddFiles(MenuItem selection, Modifiers mods)
1215    {
1216       AddFiles(true);
1217       return true;
1218    }
1219
1220    bool ProjectAddFiles(MenuItem selection, Modifiers mods)
1221    {
1222       AddFiles(false);
1223       return true;
1224    }
1225
1226    bool ProjectImportFolder(MenuItem selection, Modifiers mods)
1227    {
1228       DataRow row = fileList.currentRow;
1229       if(row)
1230       {
1231          ProjectNode toNode = (ProjectNode)row.tag;
1232          ImportFolder(toNode);
1233       }
1234       return true;
1235    }
1236
1237    bool ProjectAddNewForm(MenuItem selection, Modifiers mods)
1238    {
1239       CodeEditor codeEditor = CreateNew("Form", "form", "Window", null);
1240       codeEditor.EnsureUpToDate();
1241       return true;
1242    }
1243
1244    bool ProjectAddNewGraph(MenuItem selection, Modifiers mods)
1245    {
1246       CodeEditor codeEditor = CreateNew("Graph", "graph", "Block", null);
1247       if(codeEditor)
1248          codeEditor.EnsureUpToDate();
1249       return true;
1250    }
1251
1252    bool ProjectRemove(MenuItem selection, Modifiers mods)
1253    {
1254       DataRow row = fileList.currentRow;
1255       if(row)
1256       {
1257          ProjectNode node = (ProjectNode)row.tag;
1258          if(node.type == project)
1259             RemoveSelectedNodes();
1260       }
1261       return true;
1262    }
1263
1264    bool ProjectUpdateMakefileForAllConfigs(Project project)
1265    {
1266       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1267
1268       // This call really does not belong here:
1269       ide.UpdateToolBarActiveConfigs(false);
1270       for(config : project.configurations)
1271          ProjectPrepareMakefile(project, forceExists, compiler, config);
1272
1273       ide.Update(null);
1274       delete compiler;
1275       return true;
1276    }
1277
1278    bool MenuSettings(MenuItem selection, Modifiers mods)
1279    {
1280       ProjectNode node = GetSelectedNode(true);
1281       Project prj = node ? node.project : project;
1282       projectSettingsDialog = ProjectSettings { master = parent, project = prj, projectNode = node };
1283       incref projectSettingsDialog;
1284       projectSettingsDialog.Modal();
1285       delete projectSettingsDialog;
1286       ide.UpdateToolBarActiveConfigs(false);
1287       Update(null);
1288       ide.AdjustMenus();
1289       return true;
1290    }
1291
1292    bool FileProperties(MenuItem selection, Modifiers mods)
1293    {
1294       DataRow row = fileList.currentRow;
1295       if(row)
1296       {
1297          ProjectNode node = (ProjectNode)row.tag;
1298          NodeProperties { parent = parent, master = this, node = node, 
1299                position = { position.x + 100, position.y + 100 } }.Create();
1300       }
1301       return true;
1302    }
1303
1304    bool FileOpenFile(MenuItem selection, Modifiers mods)
1305    {
1306       OpenSelectedNodes();
1307       return true;
1308    }
1309
1310    bool FileRemoveFile(MenuItem selection, Modifiers mods)
1311    {
1312       RemoveSelectedNodes();
1313       return true;
1314    }
1315
1316    bool FileCompile(MenuItem selection, Modifiers mods)
1317    {
1318       OldLink item;
1319       OldList selectedRows;
1320       Project project = null;
1321       List<ProjectNode> nodes { };
1322       fileList.GetMultiSelection(selectedRows);
1323       for(item = selectedRows.first; item; item = item.next)
1324       {
1325          OldLink i;
1326          DataRow row = item.data;
1327          ProjectNode node = (ProjectNode)row.tag;
1328          if(!project)
1329             project = node.project;
1330          else if(node.project != project)
1331          {
1332             project = null;
1333             break;
1334          }
1335          nodes.Add(node);
1336       }
1337       selectedRows.Free(null);
1338       if(project)
1339          Compile(project, nodes, mods.ctrl && mods.shift, normal);
1340       else
1341          ide.outputView.buildBox.Logf($"Please select files from a single project.\n");
1342       delete nodes;
1343       return true;
1344    }
1345
1346    bool FileClean(MenuItem selection, Modifiers mods)
1347    {
1348       OldLink item;
1349       OldList selectedRows;
1350       Project project = null;
1351       List<ProjectNode> nodes { };
1352       fileList.GetMultiSelection(selectedRows);
1353       for(item = selectedRows.first; item; item = item.next)
1354       {
1355          OldLink i;
1356          DataRow row = item.data;
1357          ProjectNode node = (ProjectNode)row.tag;
1358          if(!project)
1359             project = node.project;
1360          else if(node.project != project)
1361          {
1362             project = null;
1363             break;
1364          }
1365          nodes.Add(node);
1366       }
1367       selectedRows.Free(null);
1368       if(project)
1369          Clean(project, nodes, mods.ctrl && mods.shift);
1370       else
1371          ide.outputView.buildBox.Logf($"Please select files from a single project.\n");
1372       delete nodes;
1373       return true;
1374    }
1375
1376    bool FileDebugPrecompile(MenuItem selection, Modifiers mods)
1377    {
1378       DataRow row = fileList.currentRow;
1379       ProjectNode node = row ? (ProjectNode)row.tag : null;
1380       if(node)
1381       {
1382          List<ProjectNode> nodes { };
1383          nodes.Add(node);
1384          Compile(node.project, nodes, mods.ctrl && mods.shift, debugPrecompile);
1385          delete nodes;
1386       }
1387    }
1388
1389    bool FileDebugCompile(MenuItem selection, Modifiers mods)
1390    {
1391       DataRow row = fileList.currentRow;
1392       ProjectNode node = row ? (ProjectNode)row.tag : null;
1393       if(node)
1394       {
1395          List<ProjectNode> nodes { };
1396          nodes.Add(node);
1397          Compile(node.project, nodes, mods.ctrl && mods.shift, debugCompile);
1398          delete nodes;
1399       }
1400    }
1401
1402    bool FileDebugGenerateSymbols(MenuItem selection, Modifiers mods)
1403    {
1404       DataRow row = fileList.currentRow;
1405       ProjectNode node = row ? (ProjectNode)row.tag : null;
1406       if(node)
1407       {
1408          List<ProjectNode> nodes { };
1409          nodes.Add(node);
1410          Compile(node.project, nodes, mods.ctrl && mods.shift, debugGenerateSymbols);
1411          delete nodes;
1412       }
1413    }
1414
1415    Project GetSelectedProject(bool useSelection)
1416    {
1417       Project prj = project;
1418       if(useSelection)
1419       {
1420          DataRow row = fileList.currentRow;
1421          ProjectNode node = row ? (ProjectNode)row.tag : null;
1422          if(node)
1423             prj = node.project;
1424       }
1425       return prj;
1426    }
1427    
1428    void SelectNextProject(bool backwards)
1429    {
1430       DataRow row = fileList.currentRow;
1431       DataRow currentRow = row;
1432       ProjectNode node = (ProjectNode)row.tag;
1433       if(node.type != project)
1434          row = node.project.topNode.row;
1435       else if(backwards)
1436          row = row.previous ? row.previous : fileList.lastRow;
1437       if(!backwards)
1438          row = row.next ? row.next : fileList.firstRow;
1439       if(row && row != currentRow)
1440       {
1441          fileList.SelectRow(row);
1442          fileList.currentRow = row;
1443       }
1444    }
1445
1446    ProjectNode GetSelectedNode(bool useSelection)
1447    {
1448       ProjectNode node = null;
1449       if(useSelection)
1450       {
1451          DataRow row = fileList.currentRow;
1452          if(row)
1453             node = (ProjectNode)row.tag;
1454       }
1455       return node;
1456    }
1457
1458    bool MenuBrowseFolder(MenuItem selection, Modifiers mods)
1459    {
1460       char folder[MAX_LOCATION];
1461       Project prj;
1462       ProjectNode node = GetSelectedNode(true);
1463       if(!node)
1464          node = project.topNode;
1465       prj = node.project;
1466
1467       strcpy(folder, prj.topNode.path);
1468       if(node != prj.topNode)
1469          PathCatSlash(folder, node.path);
1470       ShellOpen(folder);
1471       return true;
1472    }
1473
1474    bool Run(MenuItem selection, Modifiers mods)
1475    {
1476       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1477       ProjectConfig config = project.config;
1478       int bitDepth = ide.workspace.bitDepth;
1479       String args = new char[maxPathLen];
1480       args[0] = '\0';
1481       if(ide.workspace.commandLineArgs)
1482          //ide.debugger.GetCommandLineArgs(args);
1483          strcpy(args, ide.workspace.commandLineArgs);
1484       if(ide.debugger.isActive)
1485          project.Run(args, compiler, config, bitDepth);
1486       /*else if(config.targetType == sharedLibrary || config.targetType == staticLibrary)
1487          MessageBox { master = ide, type = ok, text = "Run", contents = "Shared and static libraries cannot be run like executables." }.Modal();*/
1488       else if(BuildInterrim(project, run, compiler, config, bitDepth, false))
1489          project.Run(args, compiler, config, bitDepth);
1490       delete args;
1491       delete compiler;
1492       return true;
1493    }
1494
1495    bool DebugStart()
1496    {
1497       bool result = false;
1498       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1499       ProjectConfig config = project.config;
1500       int bitDepth = ide.workspace.bitDepth;
1501       TargetTypes targetType = project.GetTargetType(config);
1502       if(targetType == sharedLibrary || targetType == staticLibrary)
1503          MessageBox { master = ide, type = ok, text = $"Run", contents = $"Shared and static libraries cannot be run like executables." }.Modal();
1504       else if(project.GetCompress(config))
1505          MessageBox { master = ide, text = $"Starting Debug", contents = $"Debugging compressed applications is not supported\n" }.Modal();
1506       else if(project.GetDebug(config) ||
1507          MessageBox { master = ide, type = okCancel, text = $"Starting Debug", contents = $"Attempting to debug non-debug configuration\nProceed anyways?" }.Modal() == ok)
1508       {
1509          if(/*!IsProjectModified() ||*/ BuildInterrim(project, start, compiler, config, bitDepth, false))
1510          {
1511             if(compiler.type.isVC)
1512             {
1513                //bool result = false;
1514                char oldwd[MAX_LOCATION];
1515                PathBackup pathBackup { };
1516                char command[MAX_LOCATION];
1517
1518                ide.SetPath(false, compiler, config, bitDepth);
1519                
1520                GetWorkingDir(oldwd, sizeof(oldwd));
1521                ChangeWorkingDir(project.topNode.path);
1522
1523                sprintf(command, "%s /useenv %s.sln /projectconfig \"%s|Win32\" /command \"%s\"" , "devenv", project.name, config.name, "Debug.Start");
1524                Execute(command);
1525                ChangeWorkingDir(oldwd);
1526
1527                delete pathBackup;
1528             }
1529             else
1530             {
1531                ide.debugger.Start(compiler, config, bitDepth);
1532                result = true;
1533             }
1534          }
1535       }
1536       delete compiler;
1537       return result;      
1538    }
1539
1540    void GoToError(const char * line)
1541    {
1542       char * colon;
1543       
1544       while(isspace(*line)) line++;
1545       colon = strstr(line, ":");
1546
1547       {
1548          int lineNumber = 0;
1549          int col = 1;
1550          bool lookForLineNumber = true;
1551
1552          // deal with linking error
1553          if(colon && colon[1] == ' ')
1554          {
1555             colon = strstr(colon + 1, ":");
1556             if(colon && !colon[1])
1557             {
1558                colon = strstr(line, ":");
1559                lookForLineNumber = false;
1560             }
1561             else if(colon && !isdigit(colon[1]))
1562             {
1563                line = colon + 1;
1564                colon = strstr(line, ":");
1565             }
1566          }
1567          // Don't be mistaken by the drive letter colon
1568          if(colon && (colon[1] == '/' || colon[1] == '\\'))
1569             colon = strstr(colon + 1, ":");
1570          if(colon && lookForLineNumber)
1571          {
1572             char * comma;
1573             // MSVS Errors
1574             char * par = RSearchString(line, "(", colon - line, true, false);
1575             if(par && strstr(par, ")"))
1576                colon = par;
1577             else if((colon+1)[0] == ' ')
1578             {
1579                // NOTE: This is the same thing as saying 'colon = line'
1580                for( ; colon != line; colon--)
1581                   /*if(*colon == '(')
1582                      break*/;
1583             }
1584             lineNumber = atoi(colon + 1);
1585             /*
1586             comma = strchr(colon, ',');
1587             if(comma)
1588                col = atoi(comma+1);
1589             */
1590             comma = strchr(colon+1, ':');
1591             if(comma)
1592                col = atoi(comma+1);
1593          }
1594          
1595          {
1596             char moduleName[MAX_LOCATION], filePath[MAX_LOCATION];
1597             char * bracket;
1598             if(colon)
1599             {
1600                char * inFileIncludedFrom = strstr(line, stringInFileIncludedFrom);
1601                char * start = inFileIncludedFrom ? line + strlen(stringInFileIncludedFrom) : line;
1602                int len = (int)(colon - start);
1603                len = Min(len, MAX_LOCATION-1);
1604                // Cut module name
1605                strncpy(moduleName, start, len);
1606                moduleName[len] = '\0';
1607             }
1608             else
1609                strcpy(moduleName, line);
1610
1611             // Remove stuff in brackets
1612             /*
1613             bracket = strstr(moduleName, "(");
1614             if(bracket) *bracket = '\0';
1615             */
1616             MakeSlashPath(moduleName);
1617
1618             if(!colon)
1619             {
1620                // Check if it's one of our modules
1621                ProjectNode node = project.topNode.Find(moduleName, false);
1622                if(node)
1623                {
1624                   strcpy(moduleName, node.path);
1625                   PathCatSlash(moduleName, node.name);
1626                }
1627                else
1628                   moduleName[0] = '\0';
1629             }
1630             if(moduleName[0])
1631             {
1632                CodeEditor codeEditor;
1633                strcpy(filePath, project.topNode.path);
1634                PathCatSlash(filePath, moduleName);
1635       
1636                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal);
1637                if(!codeEditor)
1638                {
1639                   char name[MAX_LOCATION];
1640                   // TOFIX: Improve on this, don't use only filename, make a function
1641                   if(ide && ide.workspace)
1642                   {
1643                      for(prj : ide.workspace.projects)
1644                      {
1645                         if(prj.topNode.FindWithPath(moduleName, false))
1646                         {
1647                            strcpy(filePath, prj.topNode.path);
1648                            PathCatSlash(filePath, moduleName);
1649                            codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal);
1650                            if(codeEditor)
1651                               break;
1652                         }
1653                      }
1654                   }
1655                }
1656                if(codeEditor && lineNumber)
1657                {
1658                   EditBox editBox = codeEditor.editBox;
1659                   editBox.GoToLineNum(lineNumber - 1);
1660                   editBox.GoToPosition(editBox.line, lineNumber - 1, col ? (col - 1) : 0);
1661                }
1662             }
1663          }
1664       }
1665    }
1666
1667    bool OpenNode(ProjectNode node)
1668    {
1669       char filePath[MAX_LOCATION];
1670       node.GetFullFilePath(filePath);
1671       return ide.OpenFile(filePath, normal, true/*false Why was it opening hidden?*/, null, something, normal) ? true : false;
1672    }
1673
1674    void AddNode(ProjectNode node, DataRow addTo)
1675    {
1676       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1677
1678       row.tag = (int64)node;
1679       node.row = row;
1680
1681       if(node.type == resources)
1682          resourceRow = row;
1683
1684       row.SetData(null, node);
1685
1686       if(node.files && node.files.first && node.parent && 
1687             !(!node.parent.parent && 
1688                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") || 
1689                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1690          row.collapsed = true;
1691       else if(node.type == folder)
1692          node.icon = openFolder;
1693
1694       if(node.files)
1695       {
1696          for(child : node.files)
1697             AddNode(child, row);
1698       }
1699    }
1700
1701    void DeleteNode(ProjectNode projectNode)
1702    {
1703       if(projectNode.files)
1704       {
1705          ProjectNode child;
1706          while(child = projectNode.files.first)
1707             DeleteNode(child);
1708       }
1709       fileList.DeleteRow(projectNode.row);
1710       projectNode.Delete();
1711    }
1712
1713    bool ProjectSave(MenuItem selection, Modifiers mods)
1714    {
1715       DataRow row = fileList.currentRow;
1716       ProjectNode node = row ? (ProjectNode)row.tag : null;
1717       Project prj = node ? node.project : null;
1718       if(prj)
1719       {
1720          prj.StopMonitoring();
1721          if(prj.Save(prj.filePath))
1722          {
1723             Project modPrj = null;
1724             prj.topNode.modified = false;
1725             for(p : ide.workspace.projects)
1726             {
1727                if(p.topNode.modified)
1728                { 
1729                   modPrj = p;
1730                   break;
1731                }
1732             }
1733             if(!modPrj)
1734                modifiedDocument = false;
1735             Update(null);
1736          }
1737          prj.StartMonitoring();
1738       }
1739       return true;
1740    }
1741
1742    bool ShowOutputBuildLog(bool cleanLog)
1743    {
1744       OutputView output = ide.outputView;
1745       if(cleanLog)
1746          output.ShowClearSelectTab(build);
1747       else
1748       {
1749          output.SelectTab(build);
1750          output.Show();
1751       }
1752       return true;
1753    }
1754
1755    bool DebugRestart()
1756    {
1757       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1758       ProjectConfig config = project.config;
1759       int bitDepth = ide.workspace.bitDepth;
1760
1761       bool result = false;
1762       if(/*!IsProjectModified() ||*/ BuildInterrim(project, restart, compiler, config, bitDepth, false))
1763       {
1764          // For Restart, compiler and config will only be used if for
1765          // whatever reason (if at all possible) the Debugger is in a
1766          // 'terminated' or 'none' state
1767          ide.debugger.Restart(compiler, config, bitDepth);
1768          result = true;
1769       }
1770
1771       delete compiler;
1772       return result;
1773    }
1774
1775    bool DebugResume()
1776    {
1777       ide.debugger.Resume();
1778       return true;
1779    }
1780
1781    bool DebugBreak()
1782    {
1783       ide.debugger.Break();
1784       return true;
1785    }
1786
1787    bool DebugStop()
1788    {
1789       ide.debugger.Stop();
1790       return true;
1791    }
1792
1793    bool DebugStepInto()
1794    {
1795       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1796       ProjectConfig config = project.config;
1797       int bitDepth = ide.workspace.bitDepth;
1798
1799       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, false)))
1800          ide.debugger.StepInto(compiler, config, bitDepth);
1801       delete compiler;
1802       return true;
1803    }
1804
1805    bool DebugStepOver(bool skip)
1806    {
1807       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1808       ProjectConfig config = project.config;
1809       int bitDepth = ide.workspace.bitDepth;
1810
1811       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, false)))
1812          ide.debugger.StepOver(compiler, config, skip, bitDepth);
1813
1814       delete compiler;
1815       return true;
1816    }
1817
1818    bool DebugStepOut(bool skip)
1819    {
1820       ide.debugger.StepOut(skip);
1821       return true;
1822    }
1823
1824    void ImportFolder(ProjectNode toNode)
1825    {
1826       if(toNode)
1827       {
1828          //bool isFolder = toNode.type == folder;
1829          //bool isRes = toNode.isInResources;
1830          
1831          FileDialog fileDialog = importFileDialog;
1832          fileDialog.master = parent;
1833          if(fileDialog.Modal() == ok)
1834          {
1835             ImportFolderFSI fsi { projectView = this };
1836             fsi.stack.Add(toNode);
1837             fsi.Iterate(fileDialog.filePath);
1838             delete fsi;
1839          }
1840       }
1841    }
1842
1843    ProjectNode NewFolder(ProjectNode parentNode, char * name, bool showProperties)
1844    {
1845       if(parentNode)
1846       {
1847          ProjectNode folderNode;
1848          Project prj = parentNode.project;
1849          int c;
1850          ProjectNode after = null;
1851          for(node : parentNode.files)
1852          {
1853             if(node.type != folder)
1854                break;
1855             after = node;
1856          }
1857          
1858          if(name && name[0])
1859             folderNode = parentNode.Add(prj, name, after, folder, folder, true);
1860          else
1861          {
1862             for(c = 0; c < 100; c++)
1863             {
1864                char string[16];
1865                sprintf(string, c ? "New Folder (%d)" : "New Folder", c);
1866                if((folderNode = parentNode.Add(prj, string, after, folder, folder, true)))
1867                   break;
1868             }
1869          }
1870
1871          if(folderNode)
1872          {
1873             NodeProperties nodeProperties;
1874             if(!showProperties)
1875             {
1876                modifiedDocument = true;
1877                prj.topNode.modified = true;
1878             }
1879             Update(null);
1880             folderNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
1881             folderNode.row.tag = (int64)folderNode;
1882                
1883             folderNode.row.SetData(null, folderNode);
1884             fileList.currentRow = folderNode.row;
1885             
1886             if(showProperties)
1887             {
1888                nodeProperties = NodeProperties
1889                {
1890                   parent, this, mode = newFolder, node = folderNode;
1891                   position = { position.x + 100, position.y + 100 };
1892                };
1893                nodeProperties.Create();   // Modal?
1894             }
1895             return folderNode;
1896          }
1897       }
1898       return null;
1899    }
1900
1901    void AddFiles(bool resources)
1902    {
1903       FileDialog fileDialog = (!resources) ? this.fileDialog : resourceFileDialog;
1904       fileDialog.type = multiOpen;
1905       fileDialog.text = !resources ? $"Add Files to Project" : $"Add Resources to Project";
1906       fileDialog.master = parent;
1907
1908       if(fileDialog.Modal() == ok)
1909       {
1910          int c;
1911          DataRow row = fileList.currentRow;
1912          ProjectNode parentNode = (ProjectNode)row.tag;
1913          bool addFailed = false;
1914          int numSelections = fileDialog.numSelections;
1915          char ** multiFilePaths = fileDialog.multiFilePaths;
1916
1917          Array<String> nameConflictFiles { };
1918
1919          for(c = 0; c < numSelections; c++)
1920          {
1921             char * filePath = multiFilePaths[c];
1922             FileAttribs exists = FileExists(filePath);
1923             bool addThisFile = true;
1924
1925             if(exists.isDirectory)
1926                addThisFile = false;
1927             else if(!exists)
1928             {
1929                if(MessageBox { master = ide, type = yesNo, text = filePath, 
1930                      contents = $"File doesn't exist. Create?" }.Modal() == yes)
1931                {
1932                   File f = FileOpen(filePath, write);
1933                   if(f)
1934                   {
1935                      addThisFile = true;
1936                      delete f;
1937                   }
1938                   else
1939                   {
1940                      MessageBox { master = ide, type = ok, text = filePath, 
1941                            contents = $"Couldn't create file."}.Modal();
1942                      addThisFile = false;
1943                   }
1944                }
1945             }
1946
1947             if(addThisFile)
1948             {
1949                /*addFailed = */if(!AddFile(parentNode, filePath, resources, false))//;
1950                {
1951                   nameConflictFiles.Add(CopyString(filePath));
1952                   addFailed = true;
1953                }
1954             }
1955          }
1956          if(addFailed)
1957          {
1958             int len = 0;
1959             char * part1 = $"The following file";
1960             char * opt1 = $" was ";
1961             char * opt2 = $"s were ";
1962             char * part2 = $"not added because of identical file name conflict within the project.\n\n";
1963             char * message;
1964             len += strlen(part1);
1965             len += strlen(part2);
1966             len += nameConflictFiles.count > 1 ? strlen(opt2) : strlen(opt1);
1967             for(s : nameConflictFiles)
1968                len += strlen(s) + 1;
1969             message = new char[len + 1];
1970             strcpy(message, part1);
1971             strcat(message, nameConflictFiles.count > 1 ? opt2 : opt1);
1972             strcat(message, part2);
1973             for(s : nameConflictFiles)
1974             {
1975                strcat(message, s);
1976                strcat(message, "\n");
1977             }
1978             MessageBox { master = ide, type = ok, text = $"Name Conflict", 
1979                   contents = message }.Modal();
1980             delete message;
1981          }
1982          nameConflictFiles.Free();
1983          delete nameConflictFiles;
1984       }
1985    }
1986
1987    ProjectNode AddFile(ProjectNode parentNode, char * filePath, bool resources, bool isTemporary)
1988    {
1989       ProjectNode result = null;
1990       ProjectNode after = null;
1991       for(node : parentNode.files)
1992       {
1993          if(node.type != folder && node.type != file && node.type)
1994             break;
1995          after = node;
1996       }
1997
1998       result = parentNode.Add(parentNode.project, filePath, after, file, NodeIcons::SelectFileIcon(filePath), !resources);
1999
2000       if(result)
2001       {
2002          if(!isTemporary)
2003          {
2004             modifiedDocument = true;
2005             parentNode.project.topNode.modified = true;
2006             parentNode.project.ModifiedAllConfigs(true, false, true, true);
2007          }
2008          Update(null);
2009          result.row = parentNode.row.AddRowAfter(after ? after.row : null);
2010          result.row.tag = (int64)result;
2011          result.row.SetData(null, result);
2012       }
2013       return result;
2014    }
2015
2016    CodeEditor CreateNew(char * upper, char * lower, char * base, char * className)
2017    {
2018       CodeEditor codeEditor = null;
2019       ProjectNode projectNode;
2020       ProjectNode after = null;
2021       DataRow row = fileList.currentRow;
2022       ProjectNode parentNode;
2023       int c;
2024
2025       if(!row) row = project.topNode.row;
2026
2027       parentNode = (ProjectNode)row.tag;
2028
2029       for(node : parentNode.files)
2030       {
2031          if(node.type != folder && node.type != file && node.type)
2032             break;
2033          after = node;
2034       }
2035       for(c = 1; c < 100; c++)
2036       {
2037          char string[16];
2038          sprintf(string, c ? "%s%d.ec" : "%s.ec", lower, c);
2039          if((projectNode = parentNode.Add(project, string, after, file, genFile, true)))
2040             break;
2041       }
2042       if(projectNode)
2043       {
2044          char name[256];
2045          char filePath[MAX_LOCATION];
2046
2047          modifiedDocument = true;
2048          project.topNode.modified = true;
2049          Update(null);
2050          project.ModifiedAllConfigs(true, false, false, true);
2051          projectNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
2052          projectNode.row.tag =(int64)projectNode;
2053             
2054          projectNode.row.SetData(null, projectNode);
2055          fileList.currentRow = projectNode.row;
2056
2057          strcpy(filePath, project.topNode.path);
2058          PathCat(filePath, projectNode.path);
2059          PathCat(filePath, projectNode.name);
2060
2061          codeEditor = (CodeEditor)ide.FindWindow(filePath);
2062          if(!codeEditor)
2063          {
2064             Class baseClass = eSystem_FindClass(__thisModule, base);
2065             subclass(ClassDesignerBase) designerClass = eClass_GetDesigner(baseClass);
2066             if(designerClass)
2067             {
2068                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal);
2069                strcpy(name, projectNode.name);
2070                sprintf(name, "%s%d", upper, c);
2071                if(className)
2072                   strcpy(className, name);
2073
2074                designerClass.CreateNew(codeEditor.editBox, codeEditor.clientSize, name, base);
2075
2076                //codeEditor.modifiedDocument = false;
2077                //codeEditor.designer.modifiedDocument = false;
2078
2079                //Code_EnsureUpToDate(codeEditor);
2080             }
2081          }
2082          else // TODO: fix no symbols generated when ommiting {} for following else
2083          {
2084             codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal);
2085          }
2086          if(codeEditor)
2087          {
2088             codeEditor.ViewDesigner();
2089             codeEditor.codeModified = true;
2090          }
2091       }
2092       visible = false;
2093       return codeEditor;   
2094    }
2095
2096    // Returns true if we opened something
2097    bool OpenSelectedNodes()
2098    {
2099       bool result = false;
2100       OldList selection;
2101       OldLink item;
2102
2103       fileList.GetMultiSelection(selection);
2104       for(item = selection.first; item; item = item.next)
2105       {
2106          DataRow row = item.data;
2107          ProjectNode node = (ProjectNode)row.tag;
2108          if(node.type == file)
2109          {
2110             OpenNode(node);
2111             result = true;
2112             break;
2113          }
2114       }
2115       selection.Free(null);
2116       return result;
2117    }
2118
2119    void RemoveSelectedNodes()
2120    {
2121       OldList selection;
2122       OldLink item, next;
2123       
2124       fileList.GetMultiSelection(selection);
2125
2126       // Remove children of parents we're deleting
2127       for(item = selection.first; item; item = next)
2128       {
2129          OldLink i;
2130          DataRow row = item.data;
2131          ProjectNode n, node = (ProjectNode)row.tag;
2132          bool remove = false;
2133
2134          next = item.next;
2135          for(i = selection.first; i && !remove; i = i.next)
2136          {
2137             ProjectNode iNode = (ProjectNode)((DataRow)i.data).tag;
2138             for(n = node.parent; n; n = n.parent)
2139             {
2140                if(iNode == n)
2141                {
2142                   remove = true;
2143                   break;
2144                }
2145             }
2146          }
2147          if(remove)
2148             selection.Delete(item);
2149       }
2150
2151       for(item = selection.first; item; item = item.next)
2152       {
2153          DataRow row = item.data;
2154          ProjectNode node = (ProjectNode)row.tag;
2155          ProjectNode resNode;
2156          for(resNode = node.parent; resNode; resNode = resNode.parent)
2157             if(resNode.type == resources)
2158                break;
2159          if(node.type == file)
2160          {
2161             Project prj = node.project;
2162             DeleteNode(node);
2163             modifiedDocument = true;
2164             prj.topNode.modified = true;
2165             Update(null);
2166             prj.ModifiedAllConfigs(true, false, true, true);
2167          }
2168          else if(node.type == folder)
2169          {
2170             char message[1024];
2171             sprintf(message, $"Are you sure you want to remove the folder \"%s\"\n"
2172                   "and all of its contents from the project?", node.name);
2173             if(MessageBox { master = ide, type = yesNo, text = $"Delete Folder", contents = message }.Modal() == yes)
2174             {
2175                Project prj = node.project;
2176                if(node.containsFile)
2177                   prj.ModifiedAllConfigs(true, false, true, true);
2178                DeleteNode(node);
2179                modifiedDocument = true;
2180                prj.topNode.modified = true;
2181             }
2182          }
2183          else if(node.type == project && node != project.topNode && !buildInProgress)
2184          {
2185             char message[1024];
2186             Project prj = null;
2187             for(p : workspace.projects)
2188             {
2189                if(p.topNode == node)
2190                {
2191                   prj = p;
2192                   break;
2193                }
2194             }
2195             sprintf(message, $"Are you sure you want to remove the \"%s\" project\n" "from this workspace?", node.name);
2196             if(MessageBox { master = ide, type = yesNo, text = $"Remove Project", contents = message }.Modal() == yes)
2197             {
2198                // THIS GOES FIRST!
2199                DeleteNode(node);
2200                if(prj)
2201                   workspace.RemoveProject(prj);
2202                //modifiedDocument = true; // when project view is a workspace view
2203             }
2204          }
2205       }
2206       selection.Free(null);
2207    }
2208 }