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