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