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