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