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