ide; added support for compiling whole folders. improvement over existing support...
[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(ProjectNode node, bool justPrint)
994    {
995       bool result = true;
996       char fileName[MAX_LOCATION];
997       Window document;
998       Project prj = node.project;
999       ProjectConfig config = prj.config;
1000
1001       stopBuild = false;
1002
1003       for(document = ide.firstChild; document; document = document.next)
1004       {
1005          if(document.modifiedDocument)
1006          {
1007             ProjectNode n = GetNodeFromWindow(document, prj);
1008             if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1009             {
1010                result = false;
1011                break;
1012             }
1013          }
1014       }
1015
1016       if(result)
1017       {
1018          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1019          result = false;
1020          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
1021          {
1022             if(!node.GetIsExcluded(config))
1023             {
1024                node.DeleteIntermediateFiles(compiler, config);
1025
1026                buildInProgress = compilingFile;
1027                ide.AdjustBuildMenus();
1028
1029                //ide.outputView.ShowClearSelectTab(build);
1030                // this stuff doesn't even appear
1031                //ide.outputView.buildBox.Logf($"%s Compiler\n", compiler.name);
1032                if(config)
1033                   ide.outputView.buildBox.Logf($"Compiling %s %s in project %s using the %s configuration...\n",
1034                         node.type == file ? $"single file" : $"folder", node.name, prj.name, config.name);
1035                else
1036                   ide.outputView.buildBox.Logf($"Compiling %s %s in project %s...\n",
1037                         node.type == file ? $"single file" : $"folder", node.name, prj.name);
1038
1039                prj.Compile(node, compiler, config, justPrint);
1040                buildInProgress = none;
1041                ide.AdjustBuildMenus();
1042
1043                result = true;
1044             }
1045          }
1046          delete compiler;
1047       }
1048       return result;
1049    }
1050
1051    bool Clean(ProjectNode node, bool justPrint)
1052    {
1053       bool result = true;
1054       char fileName[MAX_LOCATION];
1055       Window document;
1056       Project prj = node.project;
1057       ProjectConfig config = prj.config;
1058
1059       stopBuild = false;
1060
1061       for(document = ide.firstChild; document; document = document.next)
1062       {
1063          if(document.modifiedDocument)
1064          {
1065             ProjectNode n = GetNodeFromWindow(document, prj);
1066             if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1067             {
1068                result = false;
1069                break;
1070             }
1071          }
1072       }
1073
1074       if(result)
1075       {
1076          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1077          result = false;
1078          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
1079          {
1080             if(!node.GetIsExcluded(config))
1081             {
1082                node.DeleteIntermediateFiles(compiler, config);
1083
1084                if(config)
1085                   ide.outputView.buildBox.Logf($"Delete Intermediate Objects for %s %s in project %s using the %s configuration...\n",
1086                         node.type == file ? $"single file" : $"folder", node.name, prj.name, config.name);
1087                else
1088                   ide.outputView.buildBox.Logf($"Delete Intermediate Objects for %s %s in project %s...\n",
1089                         node.type == file ? $"single file" : $"folder", node.name, prj.name);
1090
1091                result = true;
1092             }
1093          }
1094          delete compiler;
1095       }
1096       return result;
1097    }
1098
1099    bool ProjectNewFile(MenuItem selection, Modifiers mods)
1100    {
1101       DataRow row = fileList.currentRow;
1102       if(row)
1103       {
1104          char fileName[1024];
1105          char filePath[MAX_LOCATION];
1106          ProjectNode parentNode = (ProjectNode)row.tag;
1107          ProjectNode n, fileNode;
1108          parentNode.GetFileSysMatchingPath(filePath);
1109          MakePathRelative(filePath, parentNode.project.topNode.path, filePath);
1110          for(n = parentNode; n && n != parentNode.project.resNode; n = n.parent);
1111          sprintf(fileName, $"Untitled %d", documentID);
1112          fileNode = AddFile(parentNode, fileName, (bool)n, true);
1113          fileNode.path = CopyUnixPath(filePath);
1114          if(fileNode)
1115          {
1116             NodeProperties nodeProperties
1117             {
1118                parent, this;
1119                position = { position.x + 100, position.y + 100 };
1120                mode = newFile;
1121                node = fileNode;
1122             };
1123             nodeProperties.Create(); // not modal?
1124          }
1125       }
1126       return true;
1127    }
1128
1129    bool ProjectNewFolder(MenuItem selection, Modifiers mods)
1130    {
1131       DataRow row = fileList.currentRow;
1132       if(row)
1133       {
1134          ProjectNode parentNode = (ProjectNode)row.tag;
1135          NewFolder(parentNode, null, true);
1136       }
1137       return true;
1138    }
1139
1140    bool ResourcesAddFiles(MenuItem selection, Modifiers mods)
1141    {
1142       AddFiles(true);
1143       return true;
1144    }
1145
1146    bool ProjectAddFiles(MenuItem selection, Modifiers mods)
1147    {
1148       AddFiles(false);
1149       return true;
1150    }
1151
1152    bool ProjectImportFolder(MenuItem selection, Modifiers mods)
1153    {
1154       DataRow row = fileList.currentRow;
1155       if(row)
1156       {
1157          ProjectNode toNode = (ProjectNode)row.tag;
1158          ImportFolder(toNode);
1159       }
1160       return true;
1161    }
1162
1163    bool ProjectAddNewForm(MenuItem selection, Modifiers mods)
1164    {
1165       CodeEditor codeEditor = CreateNew("Form", "form", "Window", null);
1166       codeEditor.EnsureUpToDate();
1167       return true;
1168    }
1169
1170    bool ProjectAddNewGraph(MenuItem selection, Modifiers mods)
1171    {
1172       CodeEditor codeEditor = CreateNew("Graph", "graph", "Block", null);
1173       if(codeEditor)
1174          codeEditor.EnsureUpToDate();
1175       return true;
1176    }
1177
1178    bool ProjectRemove(MenuItem selection, Modifiers mods)
1179    {
1180       DataRow row = fileList.currentRow;
1181       if(row)
1182       {
1183          ProjectNode node = (ProjectNode)row.tag;
1184          if(node.type == project)
1185             RemoveSelectedNodes();
1186       }
1187       return true;
1188    }
1189
1190    bool ProjectUpdateMakefileForAllConfigs(Project project)
1191    {
1192       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1193
1194       for(config : project.configurations)
1195          ProjectPrepareMakefile(project, forceExists, compiler, config);
1196
1197       ide.Update(null);
1198       delete compiler;
1199       return true;
1200    }
1201
1202    bool MenuConfig(MenuItem selection, Modifiers mods)
1203    {
1204       if(ProjectActiveConfig { master = parent, project = project }.Modal() == ok)
1205          ide.AdjustMenus();
1206       return true;
1207    }
1208
1209    bool MenuCompiler(MenuItem selection, Modifiers mods)
1210    {
1211       ActiveCompilerDialog compilerDialog
1212       {
1213          master = parent;
1214          ideSettings = ideSettings, workspaceActiveCompiler = ide.workspace.compiler;
1215       };
1216       incref compilerDialog;
1217       if(compilerDialog.Modal() == ok && strcmp(compilerDialog.workspaceActiveCompiler, ide.workspace.compiler))
1218       {
1219          CompilerConfig compiler = ideSettings.GetCompilerConfig(compilerDialog.workspaceActiveCompiler);
1220          ide.workspace.compiler = compilerDialog.workspaceActiveCompiler;
1221          ide.projectView.ShowOutputBuildLog(true);
1222          ide.projectView.DisplayCompiler(compiler, false);
1223
1224          for(prj : ide.workspace.projects)
1225             ide.projectView.ProjectPrepareCompiler(prj, compiler);
1226          delete compiler;
1227       }
1228       delete compilerDialog;
1229       return true;
1230    }
1231
1232    bool MenuSettings(MenuItem selection, Modifiers mods)
1233    {
1234       ProjectNode node = GetSelectedNode(true);
1235       Project prj = node ? node.project : project;
1236       projectSettingsDialog = ProjectSettings { master = parent, project = prj, projectNode = node };
1237       projectSettingsDialog.Modal();
1238
1239       Update(null);
1240       ide.AdjustMenus();
1241       return true;
1242    }
1243
1244    bool FileProperties(MenuItem selection, Modifiers mods)
1245    {
1246       DataRow row = fileList.currentRow;
1247       if(row)
1248       {
1249          ProjectNode node = (ProjectNode)row.tag;
1250          NodeProperties { parent = parent, master = this, node = node, 
1251                position = { position.x + 100, position.y + 100 } }.Create();
1252       }
1253       return true;
1254    }
1255
1256    bool FileOpenFile(MenuItem selection, Modifiers mods)
1257    {
1258       OpenSelectedNodes();
1259       return true;
1260    }
1261
1262    bool FileRemoveFile(MenuItem selection, Modifiers mods)
1263    {
1264       RemoveSelectedNodes();
1265       return true;
1266    }
1267
1268    bool FileCompile(MenuItem selection, Modifiers mods)
1269    {
1270       DataRow row = fileList.currentRow;
1271       if(row)
1272       {
1273          ProjectNode node = (ProjectNode)row.tag;
1274          if(!Compile(node, mods.ctrl && mods.shift))
1275             ide.outputView.buildBox.Logf($"File %s is excluded from current build configuration.\n", node.name);
1276       }
1277       return true;
1278    }
1279
1280    bool FileClean(MenuItem selection, Modifiers mods)
1281    {
1282       DataRow row = fileList.currentRow;
1283       if(row)
1284       {
1285          ProjectNode node = (ProjectNode)row.tag;
1286          if(!Clean(node, mods.ctrl && mods.shift))
1287             ide.outputView.buildBox.Logf($"File %s is excluded from current build configuration.\n", node.name);
1288       }
1289       return true;
1290    }
1291
1292    Project GetSelectedProject(bool useSelection)
1293    {
1294       Project prj = project;
1295       if(useSelection)
1296       {
1297          DataRow row = fileList.currentRow;
1298          ProjectNode node = row ? (ProjectNode)row.tag : null;
1299          if(node)
1300             prj = node.project;
1301       }
1302       return prj;
1303    }
1304    
1305    void SelectNextProject(bool backwards)
1306    {
1307       DataRow row = fileList.currentRow;
1308       DataRow currentRow = row;
1309       ProjectNode node = (ProjectNode)row.tag;
1310       if(node.type != project)
1311          row = node.project.topNode.row;
1312       else if(backwards)
1313          row = row.previous ? row.previous : fileList.lastRow;
1314       if(!backwards)
1315          row = row.next ? row.next : fileList.firstRow;
1316       if(row && row != currentRow)
1317       {
1318          fileList.SelectRow(row);
1319          fileList.currentRow = row;
1320       }
1321    }
1322
1323    ProjectNode GetSelectedNode(bool useSelection)
1324    {
1325       ProjectNode node = null;
1326       if(useSelection)
1327       {
1328          DataRow row = fileList.currentRow;
1329          if(row)
1330             node = (ProjectNode)row.tag;
1331       }
1332       return node;
1333    }
1334
1335    bool MenuBrowseFolder(MenuItem selection, Modifiers mods)
1336    {
1337       char folder[MAX_LOCATION];
1338       Project prj;
1339       ProjectNode node = GetSelectedNode(true);
1340       if(!node)
1341          node = project.topNode;
1342       prj = node.project;
1343
1344       strcpy(folder, prj.topNode.path);
1345       if(node != prj.topNode)
1346          PathCatSlash(folder, node.path);
1347       ShellOpen(folder);
1348       return true;
1349    }
1350
1351    bool Run(MenuItem selection, Modifiers mods)
1352    {
1353       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1354       ProjectConfig config = project.config;
1355       String args = new char[maxPathLen];
1356       args[0] = '\0';
1357       if(ide.workspace.commandLineArgs)
1358          //ide.debugger.GetCommandLineArgs(args);
1359          strcpy(args, ide.workspace.commandLineArgs);
1360       if(ide.debugger.isActive)
1361          project.Run(args, compiler, config);
1362       /*else if(config.targetType == sharedLibrary || config.targetType == staticLibrary)
1363          MessageBox { master = ide, type = ok, text = "Run", contents = "Shared and static libraries cannot be run like executables." }.Modal();*/
1364       else if(BuildInterrim(project, run, compiler, config, false))
1365          project.Run(args, compiler, config);
1366       delete args;
1367       delete compiler;
1368       return true;
1369    }
1370
1371    bool DebugStart()
1372    {
1373       bool result = false;
1374       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1375       ProjectConfig config = project.config;
1376       TargetTypes targetType = project.GetTargetType(config);
1377       if(targetType == sharedLibrary || targetType == staticLibrary)
1378          MessageBox { master = ide, type = ok, text = $"Run", contents = $"Shared and static libraries cannot be run like executables." }.Modal();
1379       else if(project.GetCompress(config))
1380          MessageBox { master = ide, text = $"Starting Debug", contents = $"Debugging compressed applications is not supported\n" }.Modal();
1381       else if(project.GetDebug(config) ||
1382          MessageBox { master = ide, type = okCancel, text = $"Starting Debug", contents = $"Attempting to debug non-debug configuration\nProceed anyways?" }.Modal() == ok)
1383       {
1384          if(/*!IsProjectModified() ||*/ BuildInterrim(project, start, compiler, config, false))
1385          {
1386             if(compiler.type.isVC)
1387             {
1388                //bool result = false;
1389                char oldwd[MAX_LOCATION];
1390                PathBackup pathBackup { };
1391                char command[MAX_LOCATION];
1392
1393                ide.SetPath(false, compiler, config);
1394                
1395                GetWorkingDir(oldwd, sizeof(oldwd));
1396                ChangeWorkingDir(project.topNode.path);
1397
1398                sprintf(command, "%s /useenv %s.sln /projectconfig \"%s|Win32\" /command \"%s\"" , "devenv", project.name, config.name, "Debug.Start");
1399                Execute(command);
1400                ChangeWorkingDir(oldwd);
1401
1402                delete pathBackup;
1403             }
1404             else
1405             {
1406                ide.debugger.Start(compiler, config);
1407                result = true;
1408             }
1409          }
1410       }
1411       delete compiler;
1412       return result;      
1413    }
1414
1415    void GoToError(const char * line)
1416    {
1417       char * colon;
1418       
1419       while(isspace(*line)) line++;
1420       colon = strstr(line, ":");
1421
1422       {
1423          int lineNumber = 0;
1424          int col = 1;
1425          bool lookForLineNumber = true;
1426
1427          // deal with linking error
1428          if(colon && colon[1] == ' ')
1429          {
1430             colon = strstr(colon + 1, ":");
1431             if(colon && !colon[1])
1432             {
1433                colon = strstr(line, ":");
1434                lookForLineNumber = false;
1435             }
1436             else if(colon && !isdigit(colon[1]))
1437             {
1438                line = colon + 1;
1439                colon = strstr(line, ":");
1440             }
1441          }
1442          // Don't be mistaken by the drive letter colon
1443          if(colon && (colon[1] == '/' || colon[1] == '\\'))
1444             colon = strstr(colon + 1, ":");
1445          if(colon && lookForLineNumber)
1446          {
1447             char * comma;
1448             // MSVS Errors
1449             char * par = RSearchString(line, "(", colon - line, true, false);
1450             if(par && strstr(par, ")"))
1451                colon = par;
1452             else if((colon+1)[0] == ' ')
1453             {
1454                // NOTE: This is the same thing as saying 'colon = line'
1455                for( ; colon != line; colon--)
1456                   /*if(*colon == '(')
1457                      break*/;
1458             }
1459             lineNumber = atoi(colon + 1);
1460             /*
1461             comma = strchr(colon, ',');
1462             if(comma)
1463                col = atoi(comma+1);
1464             */
1465             comma = strchr(colon+1, ':');
1466             if(comma)
1467                col = atoi(comma+1);
1468          }
1469          
1470          {
1471             char moduleName[MAX_LOCATION], filePath[MAX_LOCATION];
1472             char * bracket;
1473             if(colon)
1474             {
1475                char * inFileIncludedFrom = strstr(line, stringInFileIncludedFrom);
1476                char * start = inFileIncludedFrom ? line + strlen(stringInFileIncludedFrom) : line;
1477                int len = (int)(colon - start);
1478                len = Min(len, MAX_LOCATION-1);
1479                // Cut module name
1480                strncpy(moduleName, start, len);
1481                moduleName[len] = '\0';
1482             }
1483             else
1484                strcpy(moduleName, line);
1485
1486             // Remove stuff in brackets
1487             /*
1488             bracket = strstr(moduleName, "(");
1489             if(bracket) *bracket = '\0';
1490             */
1491             MakeSlashPath(moduleName);
1492
1493             if(!colon)
1494             {
1495                // Check if it's one of our modules
1496                ProjectNode node = project.topNode.Find(moduleName, false);
1497                if(node)
1498                {
1499                   strcpy(moduleName, node.path);
1500                   PathCatSlash(moduleName, node.name);
1501                }
1502                else
1503                   moduleName[0] = '\0';
1504             }
1505             if(moduleName[0])
1506             {
1507                CodeEditor codeEditor;
1508                strcpy(filePath, project.topNode.path);
1509                PathCatSlash(filePath, moduleName);
1510       
1511                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal);
1512                if(!codeEditor)
1513                {
1514                   char name[MAX_LOCATION];
1515                   // TOFIX: Improve on this, don't use only filename, make a function
1516                   if(ide && ide.workspace)
1517                   {
1518                      for(prj : ide.workspace.projects)
1519                      {
1520                         if(prj.topNode.FindWithPath(moduleName, false))
1521                         {
1522                            strcpy(filePath, prj.topNode.path);
1523                            PathCatSlash(filePath, moduleName);
1524                            codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal);
1525                            if(codeEditor)
1526                               break;
1527                         }
1528                      }
1529                   }
1530                }
1531                if(codeEditor && lineNumber)
1532                {
1533                   EditBox editBox = codeEditor.editBox;
1534                   editBox.GoToLineNum(lineNumber - 1);
1535                   editBox.GoToPosition(editBox.line, lineNumber - 1, col ? (col - 1) : 0);
1536                }
1537             }
1538          }
1539       }
1540    }
1541
1542    bool OpenNode(ProjectNode node)
1543    {
1544       char filePath[MAX_LOCATION];
1545       node.GetFullFilePath(filePath);
1546       return ide.OpenFile(filePath, normal, true/*false Why was it opening hidden?*/, null, something, normal) ? true : false;
1547    }
1548
1549    void AddNode(ProjectNode node, DataRow addTo)
1550    {
1551       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1552
1553       row.tag = (int)node;
1554       node.row = row;
1555
1556       if(node.type == resources)
1557          resourceRow = row;
1558
1559       row.SetData(null, node);
1560
1561       if(node.files && node.files.first && node.parent && 
1562             !(!node.parent.parent && 
1563                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") || 
1564                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1565          row.collapsed = true;
1566       else if(node.type == folder)
1567          node.icon = openFolder;
1568
1569       if(node.files)
1570       {
1571          for(child : node.files)
1572             AddNode(child, row);
1573       }
1574    }
1575
1576    void DeleteNode(ProjectNode projectNode)
1577    {
1578       if(projectNode.files)
1579       {
1580          ProjectNode child;
1581          while(child = projectNode.files.first)
1582             DeleteNode(child);
1583       }
1584       fileList.DeleteRow(projectNode.row);
1585       projectNode.Delete();
1586    }
1587
1588    bool ProjectSave(MenuItem selection, Modifiers mods)
1589    {
1590       DataRow row = fileList.currentRow;
1591       ProjectNode node = row ? (ProjectNode)row.tag : null;
1592       Project prj = node ? node.project : null;
1593       if(prj)
1594       {
1595          prj.StopMonitoring();
1596          if(prj.Save(prj.filePath))
1597          {
1598             Project modPrj = null;
1599             prj.topNode.modified = false;
1600             for(p : ide.workspace.projects)
1601             {
1602                if(p.topNode.modified)
1603                { 
1604                   modPrj = p;
1605                   break;
1606                }
1607             }
1608             if(!modPrj)
1609                modifiedDocument = false;
1610             Update(null);
1611          }
1612          prj.StartMonitoring();
1613       }
1614       return true;
1615    }
1616
1617    bool ShowOutputBuildLog(bool cleanLog)
1618    {
1619       OutputView output = ide.outputView;
1620       if(cleanLog)
1621          output.ShowClearSelectTab(build);
1622       else
1623       {
1624          output.SelectTab(build);
1625          output.Show();
1626       }
1627       return true;
1628    }
1629
1630    bool DebugRestart()
1631    {
1632       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1633       ProjectConfig config = project.config;
1634
1635       bool result = false;
1636       if(/*!IsProjectModified() ||*/ BuildInterrim(project, restart, compiler, config, false))
1637       {
1638          // For Restart, compiler and config will only be used if for
1639          // whatever reason (if at all possible) the Debugger is in a
1640          // 'terminated' or 'none' state
1641          ide.debugger.Restart(compiler, config);
1642          result = true;
1643       }
1644
1645       delete compiler;
1646       return result;
1647    }
1648
1649    bool DebugResume()
1650    {
1651       ide.debugger.Resume();
1652       return true;
1653    }
1654
1655    bool DebugBreak()
1656    {
1657       ide.debugger.Break();
1658       return true;
1659    }
1660
1661    bool DebugStop()
1662    {
1663       ide.debugger.Stop();
1664       return true;
1665    }
1666
1667    bool DebugStepInto()
1668    {
1669       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1670       ProjectConfig config = project.config;
1671
1672       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, false)))
1673          ide.debugger.StepInto(compiler, config);
1674       delete compiler;
1675       return true;
1676    }
1677
1678    bool DebugStepOver(bool skip)
1679    {
1680       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1681       ProjectConfig config = project.config;
1682
1683       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, false)))
1684          ide.debugger.StepOver(compiler, config, skip);
1685
1686       delete compiler;
1687       return true;
1688    }
1689
1690    bool DebugStepOut(bool skip)
1691    {
1692       ide.debugger.StepOut(skip);
1693       return true;
1694    }
1695
1696    void ImportFolder(ProjectNode toNode)
1697    {
1698       if(toNode)
1699       {
1700          //bool isFolder = toNode.type == folder;
1701          //bool isRes = toNode.isInResources;
1702          
1703          FileDialog fileDialog = importFileDialog;
1704          fileDialog.master = parent;
1705          if(fileDialog.Modal() == ok)
1706          {
1707             ImportFolderFSI fsi { projectView = this };
1708             fsi.stack.Add(toNode);
1709             fsi.Iterate(fileDialog.filePath);
1710             delete fsi;
1711          }
1712       }
1713    }
1714
1715    ProjectNode NewFolder(ProjectNode parentNode, char * name, bool showProperties)
1716    {
1717       if(parentNode)
1718       {
1719          ProjectNode folderNode;
1720          Project prj = parentNode.project;
1721          int c;
1722          ProjectNode after = null;
1723          for(node : parentNode.files)
1724          {
1725             if(node.type != folder)
1726                break;
1727             after = node;
1728          }
1729          
1730          if(name && name[0])
1731             folderNode = parentNode.Add(prj, name, after, folder, folder, true);
1732          else
1733          {
1734             for(c = 0; c < 100; c++)
1735             {
1736                char string[16];
1737                sprintf(string, c ? "New Folder (%d)" : "New Folder", c);
1738                if((folderNode = parentNode.Add(prj, string, after, folder, folder, true)))
1739                   break;
1740             }
1741          }
1742
1743          if(folderNode)
1744          {
1745             NodeProperties nodeProperties;
1746             if(!showProperties)
1747             {
1748                modifiedDocument = true;
1749                prj.topNode.modified = true;
1750             }
1751             Update(null);
1752             folderNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
1753             folderNode.row.tag = (int)folderNode;
1754                
1755             folderNode.row.SetData(null, folderNode);
1756             fileList.currentRow = folderNode.row;
1757             
1758             if(showProperties)
1759             {
1760                nodeProperties = NodeProperties
1761                {
1762                   parent, this, mode = newFolder, node = folderNode;
1763                   position = { position.x + 100, position.y + 100 };
1764                };
1765                nodeProperties.Create();   // Modal?
1766             }
1767             return folderNode;
1768          }
1769       }
1770       return null;
1771    }
1772
1773    void AddFiles(bool resources)
1774    {
1775       FileDialog fileDialog = (!resources) ? this.fileDialog : resourceFileDialog;
1776       fileDialog.type = multiOpen;
1777       fileDialog.text = !resources ? $"Add Files to Project" : $"Add Resources to Project";
1778       fileDialog.master = parent;
1779
1780       if(fileDialog.Modal() == ok)
1781       {
1782          int c;
1783          DataRow row = fileList.currentRow;
1784          ProjectNode parentNode = (ProjectNode)row.tag;
1785          bool addFailed = false;
1786          int numSelections = fileDialog.numSelections;
1787          char ** multiFilePaths = fileDialog.multiFilePaths;
1788
1789          Array<String> nameConflictFiles { };
1790
1791          for(c = 0; c < numSelections; c++)
1792          {
1793             char * filePath = multiFilePaths[c];
1794             FileAttribs exists = FileExists(filePath);
1795             bool addThisFile = true;
1796
1797             if(exists.isDirectory)
1798                addThisFile = false;
1799             else if(!exists)
1800             {
1801                if(MessageBox { master = ide, type = yesNo, text = filePath, 
1802                      contents = $"File doesn't exist. Create?" }.Modal() == yes)
1803                {
1804                   File f = FileOpen(filePath, write);
1805                   if(f)
1806                   {
1807                      addThisFile = true;
1808                      delete f;
1809                   }
1810                   else
1811                   {
1812                      MessageBox { master = ide, type = ok, text = filePath, 
1813                            contents = $"Couldn't create file."}.Modal();
1814                      addThisFile = false;
1815                   }
1816                }
1817             }
1818
1819             if(addThisFile)
1820             {
1821                /*addFailed = */if(!AddFile(parentNode, filePath, resources, false))//;
1822                {
1823                   nameConflictFiles.Add(CopyString(filePath));
1824                   addFailed = true;
1825                }
1826             }
1827          }
1828          if(addFailed)
1829          {
1830             int len = 0;
1831             char * part1 = $"The following file";
1832             char * opt1 = $" was ";
1833             char * opt2 = $"s were ";
1834             char * part2 = $"not added because of identical file name conflict within the project.\n\n";
1835             char * message;
1836             len += strlen(part1);
1837             len += strlen(part2);
1838             len += nameConflictFiles.count > 1 ? strlen(opt2) : strlen(opt1);
1839             for(s : nameConflictFiles)
1840                len += strlen(s) + 1;
1841             message = new char[len + 1];
1842             strcpy(message, part1);
1843             strcat(message, nameConflictFiles.count > 1 ? opt2 : opt1);
1844             strcat(message, part2);
1845             for(s : nameConflictFiles)
1846             {
1847                strcat(message, s);
1848                strcat(message, "\n");
1849             }
1850             MessageBox { master = ide, type = ok, text = $"Name Conflict", 
1851                   contents = message }.Modal();
1852             delete message;
1853          }
1854          nameConflictFiles.Free();
1855          delete nameConflictFiles;
1856       }
1857    }
1858
1859    ProjectNode AddFile(ProjectNode parentNode, char * filePath, bool resources, bool isTemporary)
1860    {
1861       ProjectNode result = null;
1862       ProjectNode after = null;
1863       for(node : parentNode.files)
1864       {
1865          if(node.type != folder && node.type != file && node.type)
1866             break;
1867          after = node;
1868       }
1869
1870       result = parentNode.Add(parentNode.project, filePath, after, file, NodeIcons::SelectFileIcon(filePath), !resources);
1871
1872       if(result)
1873       {
1874          if(!isTemporary)
1875          {
1876             modifiedDocument = true;
1877             parentNode.project.topNode.modified = true;
1878             parentNode.project.ModifiedAllConfigs(true, false, true, true);
1879          }
1880          Update(null);
1881          result.row = parentNode.row.AddRowAfter(after ? after.row : null);
1882          result.row.tag = (int)result;
1883          result.row.SetData(null, result);
1884       }
1885       return result;
1886    }
1887
1888    CodeEditor CreateNew(char * upper, char * lower, char * base, char * className)
1889    {
1890       CodeEditor codeEditor = null;
1891       ProjectNode projectNode;
1892       ProjectNode after = null;
1893       DataRow row = fileList.currentRow;
1894       ProjectNode parentNode;
1895       int c;
1896
1897       if(!row) row = project.topNode.row;
1898
1899       parentNode = (ProjectNode)row.tag;
1900
1901       for(node : parentNode.files)
1902       {
1903          if(node.type != folder && node.type != file && node.type)
1904             break;
1905          after = node;
1906       }
1907       for(c = 1; c < 100; c++)
1908       {
1909          char string[16];
1910          sprintf(string, c ? "%s%d.ec" : "%s.ec", lower, c);
1911          if((projectNode = parentNode.Add(project, string, after, file, genFile, true)))
1912             break;
1913       }
1914       if(projectNode)
1915       {
1916          char name[256];
1917          char filePath[MAX_LOCATION];
1918
1919          modifiedDocument = true;
1920          project.topNode.modified = true;
1921          Update(null);
1922          project.ModifiedAllConfigs(true, false, false, true);
1923          projectNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
1924          projectNode.row.tag =(int)projectNode;
1925             
1926          projectNode.row.SetData(null, projectNode);
1927          fileList.currentRow = projectNode.row;
1928
1929          strcpy(filePath, project.topNode.path);
1930          PathCat(filePath, projectNode.path);
1931          PathCat(filePath, projectNode.name);
1932
1933          codeEditor = (CodeEditor)ide.FindWindow(filePath);
1934          if(!codeEditor)
1935          {
1936             Class baseClass = eSystem_FindClass(__thisModule, base);
1937             subclass(ClassDesignerBase) designerClass = eClass_GetDesigner(baseClass);
1938             if(designerClass)
1939             {
1940                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal);
1941                strcpy(name, projectNode.name);
1942                sprintf(name, "%s%d", upper, c);
1943                if(className)
1944                   strcpy(className, name);
1945
1946                designerClass.CreateNew(codeEditor.editBox, codeEditor.clientSize, name, base);
1947
1948                //codeEditor.modifiedDocument = false;
1949                //codeEditor.designer.modifiedDocument = false;
1950
1951                //Code_EnsureUpToDate(codeEditor);
1952             }
1953          }
1954          else // TODO: fix no symbols generated when ommiting {} for following else
1955          {
1956             codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal);
1957          }
1958          if(codeEditor)
1959          {
1960             codeEditor.ViewDesigner();
1961             codeEditor.codeModified = true;
1962          }
1963       }
1964       visible = false;
1965       return codeEditor;   
1966    }
1967
1968    // Returns true if we opened something
1969    bool OpenSelectedNodes()
1970    {
1971       bool result = false;
1972       OldList selection;
1973       OldLink item;
1974
1975       fileList.GetMultiSelection(selection);
1976       for(item = selection.first; item; item = item.next)
1977       {
1978          DataRow row = item.data;
1979          ProjectNode node = (ProjectNode)row.tag;
1980          if(node.type == file)
1981          {
1982             OpenNode(node);
1983             result = true;
1984             break;
1985          }
1986       }
1987       selection.Free(null);
1988       return result;
1989    }
1990
1991    void RemoveSelectedNodes()
1992    {
1993       OldList selection;
1994       OldLink item, next;
1995       
1996       fileList.GetMultiSelection(selection);
1997
1998       // Remove children of parents we're deleting
1999       for(item = selection.first; item; item = next)
2000       {
2001          OldLink i;
2002          DataRow row = item.data;
2003          ProjectNode n, node = (ProjectNode)row.tag;
2004          bool remove = false;
2005
2006          next = item.next;
2007          for(i = selection.first; i && !remove; i = i.next)
2008          {
2009             ProjectNode iNode = (ProjectNode)((DataRow)i.data).tag;
2010             for(n = node.parent; n; n = n.parent)
2011             {
2012                if(iNode == n)
2013                {
2014                   remove = true;
2015                   break;
2016                }
2017             }
2018          }
2019          if(remove)
2020             selection.Delete(item);
2021       }
2022
2023       for(item = selection.first; item; item = item.next)
2024       {
2025          DataRow row = item.data;
2026          ProjectNode node = (ProjectNode)row.tag;
2027          ProjectNode resNode;
2028          for(resNode = node.parent; resNode; resNode = resNode.parent)
2029             if(resNode.type == resources)
2030                break;
2031          if(node.type == file)
2032          {
2033             Project prj = node.project;
2034             DeleteNode(node);
2035             modifiedDocument = true;
2036             prj.topNode.modified = true;
2037             Update(null);
2038             prj.ModifiedAllConfigs(true, false, true, true);
2039          }
2040          else if(node.type == folder)
2041          {
2042             char message[1024];
2043             sprintf(message, $"Are you sure you want to remove the folder \"%s\"\n"
2044                   "and all of its contents from the project?", node.name);
2045             if(MessageBox { master = ide, type = yesNo, text = $"Delete Folder", contents = message }.Modal() == yes)
2046             {
2047                Project prj = node.project;
2048                if(node.containsFile)
2049                   prj.ModifiedAllConfigs(true, false, true, true);
2050                DeleteNode(node);
2051                modifiedDocument = true;
2052                prj.topNode.modified = true;
2053             }
2054          }
2055          else if(node.type == project && node != project.topNode && !buildInProgress)
2056          {
2057             char message[1024];
2058             Project prj = null;
2059             for(p : workspace.projects)
2060             {
2061                if(p.topNode == node)
2062                {
2063                   prj = p;
2064                   break;
2065                }
2066             }
2067             sprintf(message, $"Are you sure you want to remove the \"%s\" project\n" "from this workspace?", node.name);
2068             if(MessageBox { master = ide, type = yesNo, text = $"Remove Project", contents = message }.Modal() == yes)
2069             {
2070                // THIS GOES FIRST!
2071                DeleteNode(node);
2072                if(prj)
2073                   workspace.RemoveProject(prj);
2074                //modifiedDocument = true; // when project view is a workspace view
2075             }
2076          }
2077       }
2078       selection.Free(null);
2079    }
2080 }