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