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