40365272e73be486cb2c51b3c29af139417e5fad
[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       if(!projectView.AddFile(parentNode, filePath, parentNode.isInResources, false))
32       {
33          char * msg = PrintString($"This file can't be imported due to a conflict.\n\n", filePath,
34                "\n\nThis occurs with identical file paths and with conflicting file names.\n");
35          MessageBox { master = ide, type = ok, text = "Import File Conflict", contents = msg }.Modal();
36          delete msg;
37       }
38       return true;
39    }
40 }
41
42 static Array<FileFilter> fileFilters
43 { [
44    { $"eC/C/C++ Files (*.ec, *.eh, *.c, *.cpp, *.cc, *.cxx, *.h, *.hpp, *.hh, *.hxx)", "ec, eh, c, cpp, cc, cxx, h, hpp, hh, hxx" },
45    { $"eC/C/C++ Source Files (*.ec, *.c, *.cpp, *.cc, *.cxx)", "ec, eh, c, cpp, cc, cxx" },
46    { $"Header Files for eC/C/C++ (*.eh, *.h, *.hpp, *.hh, *.hxx)", "eh, h, hpp, hh, hxx" },
47    { $"All files", null }
48 ] };
49
50 static Array<FileFilter> resourceFilters
51 { [
52    { $"Image Files (*.jpg, *.jpeg, *.bmp, *.pcx, *.png,*.gif)", "jpg, jpeg, bmp, pcx, png, gif" },
53    { $"3D Studio Model Files (*.3ds)", "3ds" },
54    { $"Translations (*.mo)", "mo" },
55    { $"All files", null }
56 ] };
57
58 static Array<FileFilter> projectFilters
59 { [
60    { $"Project Files (*.epj)", ProjectExtension },
61    { $"Workspace Files (*.ews)", WorkspaceExtension }
62 ] };
63
64 static Array<FileType> projectTypes
65 { [
66    { $"Ecere IDE Project", ProjectExtension },
67    { $"Ecere IDE Workspace", WorkspaceExtension }
68 ] };
69
70 static char * iconNames[] =
71 {
72    "<:ecere>mimeTypes/file.png",                   /*genFile*/
73    "<:ecere>mimeTypes/textEcereWorkspace.png",     /*ewsFile*/
74    "<:ecere>mimeTypes/textEcereProject.png",       /*epjFile*/
75    "<:ecere>places/folder.png",                    /*folder*/
76    "<:ecere>status/folderOpen.png",                /*openFolder*/
77    "<:ecere>mimeTypes/textEcereSource.png",        /*ecFile*/
78    "<:ecere>mimeTypes/textEcereHeader.png",        /*ehFile*/
79    "<:ecere>mimeTypes/textCSource.png",            /*sFile*/ // TODO: change sFile icon to differentiate from cFile icon
80    "<:ecere>mimeTypes/textCSource.png",            /*cFile*/
81    "<:ecere>mimeTypes/textCHeader.png",            /*hFile*/
82    "<:ecere>mimeTypes/textC++Source.png",          /*cppFile*/
83    "<:ecere>mimeTypes/textC++Header.png",          /*hppFile*/
84    "<:ecere>mimeTypes/text.png",                   /*textFile*/
85    "<:ecere>mimeTypes/textHyperTextMarkup.png",    /*webFile*/
86    "<:ecere>mimeTypes/image.png",                  /*pictureFile*/
87    "<:ecere>status/audioVolumeHigh.png",           /*soundFile*/
88    "<:ecere>mimeTypes/package.png",                /*archiveFile*/
89    "<:ecere>mimeTypes/packageSoftware.png",        /*packageFile*/
90    "<:ecere>mimeTypes/packageOpticalDisc.png",     /*opticalMediaImageFile*/
91    "<:ecere>mimeTypes/file.png",                   /*mFile*/ //TODO: create icon for .m file
92    "<:ecere>mimeTypes/file.png"                    /*mmFile*/ //TODO: create icon for .mm file
93 };
94
95 enum PrepareMakefileMethod { normal, force, forceExists };
96
97 enum CleanType { clean, realClean, cleanTarget };
98 enum BuildType { build, rebuild, relink, run, start, restart, clean, install };
99 enum BuildState
100 {
101    none, buildingMainProject, buildingSecondaryProject, compilingFile;
102
103    property bool { get { return this != none; } }
104    //property bool actualBuild { get { return this == buildingMainProject || this == buildingSecondaryProject;  } }
105 };
106
107 class ProjectView : Window
108 {
109    isDocument = true;
110    //hasMinimize = true;
111    hasClose = true;
112    borderStyle = sizable;
113    hasHorzScroll = true;
114    hasVertScroll = true;
115    background = white;
116    size = { 300 };
117    anchor = Anchor { left = 0, top = 0, bottom = 0 };
118    menu = Menu { };
119
120    //hasMinimize = true;
121    saveDialog = projectFileDialog;
122
123    DataRow resourceRow;
124    BuildState buildInProgress;
125    BitmapResource icons[NodeIcons];
126    Project project;
127    Workspace workspace;
128    property Workspace workspace
129    {
130       set
131       {
132          if(workspace)
133          {
134             for(prj : workspace.projects)
135             {
136                DeleteNode(prj.topNode);
137             }
138             workspace.projects.Free();
139             ide.debugger.CleanUp();
140          }
141          workspace = value;
142          if(workspace)
143          {
144             fileDialog.currentDirectory = workspace.workspaceDir;
145             resourceFileDialog.currentDirectory = workspace.workspaceDir;
146             for(prj : workspace.projects)
147                AddNode(prj.topNode, null);
148             ide.statusBar.text = $"Generating Makefile & Dependencies...";
149             app.UpdateDisplay();
150             for(prj : workspace.projects)
151                prj.ModifiedAllConfigs(true, false, false, false);
152             ide.statusBar.text = $"Initializing Debugger"; app.UpdateDisplay();
153             ide.statusBar.text = null;
154             app.UpdateDisplay();
155          }
156       }
157       get { return workspace; }
158    }
159
160    bool drawingInProjectSettingsDialog;
161    bool drawingInProjectSettingsDialogHeader;
162    ProjectSettings projectSettingsDialog;
163
164    bool stopBuild;
165    PopupMenu popupMenu;
166
167    ProjectView()
168    {
169       NodeIcons c;
170       for(c = 0; c < NodeIcons::enumSize; c++)
171       {
172          icons[c] = BitmapResource { iconNames[c], alphaBlend = true };
173          AddResource(icons[c]);
174       }
175       fileList.AddField(DataField { dataType = class(ProjectNode), freeData = false, userData = this });
176    }
177
178    ~ProjectView()
179    {
180       DebugStop();
181       ide.DestroyTemporaryProjectDir();
182       if(project)
183       {
184          workspace.Free();
185          delete workspace;
186       }
187    }
188
189    ListBox fileList
190    {
191       multiSelect = true, fullRowSelect = false, hasVertScroll = true, hasHorzScroll = true;
192       borderStyle = deep, parent = this, collapseControl = true, treeBranches = true;
193       anchor = Anchor { left = 0, right = 0, top = 0 , bottom = 0 };
194
195       background = projectViewBackground;
196       foreground = projectViewText;
197       selectionColor = selectionColor, selectionText = selectionText;
198       stippleColor = skyBlue;
199
200       bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
201       {
202          if(!active) Update(null);
203          return ListBox::OnActivate(active, previous, goOnWithActivation, direct);
204       }
205
206       bool NotifyDoubleClick(ListBox listBox, int x, int y, Modifiers mods)
207       {
208          // Prevent the double click from reactivating the project view (returns false if we opened something)
209          return !OpenSelectedNodes(mods.ctrl && mods.shift);
210       }
211
212       bool NotifyRightClick(ListBox listBox, int x, int y, Modifiers mods)
213       {
214          DataRow row = listBox.currentRow;
215          if(row)
216          {
217             bool showDebuggingMenuItems = mods.ctrl && mods.shift;
218             bool showInstallMenuItem = mods.ctrl && mods.shift;
219             ProjectNode node = (ProjectNode)row.tag;
220 #ifdef IDE_SHOW_INSTALL_MENU_BUTTON
221             showInstallMenuItem = true;
222 #endif
223             if(node.type == NodeTypes::project || node.type == resources || node.type == file || node.type == folder)
224             {
225                bool na = buildInProgress; // N/A - buildMenuUnavailable
226                Menu pop { };
227
228                if(node.type == NodeTypes::project)
229                {
230                   MenuItem mi;
231                                                                                                                                              mi = ide.projectBuildItem;
232                   MenuItem { pop, $"Build"              , b, f7     , NotifySelect = ProjectBuild      , bitmap = mi.bitmap }.disabled = na; mi = ide.projectLinkItem;
233                   MenuItem { pop, $"Relink"             , l         , NotifySelect = ProjectLink       , bitmap = mi.bitmap }.disabled = na; mi = ide.projectRebuildItem;
234                   MenuItem { pop, $"Rebuild"            , r, shiftF7, NotifySelect = ProjectRebuild    , bitmap = mi.bitmap }.disabled = na; mi = ide.projectCleanTargetItem;
235                   MenuItem { pop, $"Clean Target"       , g         , NotifySelect = ProjectCleanTarget, bitmap = mi.bitmap }.disabled = na; mi = ide.projectCleanItem;
236                   MenuItem { pop, $"Clean"              , c         , NotifySelect = ProjectClean      , bitmap = mi.bitmap }.disabled = na; mi = ide.projectRealCleanItem;
237                   MenuItem { pop, $"Real Clean"                     , NotifySelect = ProjectRealClean  , bitmap = mi.bitmap }.disabled = na; mi = ide.projectRegenerateItem;
238                   MenuItem { pop, $"Regenerate Makefile", m         , NotifySelect = ProjectRegenerate , bitmap = mi.bitmap }.disabled = na;
239                   if(showInstallMenuItem)
240                   {
241                      mi = ide.projectInstallItem;
242                      MenuItem { pop, $"Install"            , t         , NotifySelect = ProjectInstall    , bitmap = mi.bitmap }.disabled = na;
243                   }
244                   if(showDebuggingMenuItems && node.ContainsFilesWithExtension("ec", node.project.config))
245                   {
246                      MenuDivider { pop };
247                      MenuItem { pop, $"Debug Generate Symbols", l, NotifySelect = FileDebugGenerateSymbols }.disabled = na;
248                      MenuItem { pop, $"Debug Precompile", l, NotifySelect = FileDebugPrecompile }.disabled = na;
249                      MenuItem { pop, $"Debug Compile", l, NotifySelect = FileDebugCompile }.disabled = na;
250                   }
251                   MenuDivider { pop };
252                   MenuItem { pop, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
253                   MenuItem { pop, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
254                   MenuItem { pop, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
255                   MenuItem { pop, $"Add Files to Project...", f, NotifySelect = ProjectAddFiles };
256                   MenuDivider { pop };
257                   MenuItem { pop, $"Add New Form...", o, NotifySelect = ProjectAddNewForm };
258                   // MenuItem { pop, "Add New Behavior Graph...", g, NotifySelect = ProjectAddNewGraph };
259                   MenuDivider { pop };
260                   if(node != ((Project)workspace.projects.first).topNode)
261                   {
262                      MenuItem { pop, $"Remove project from workspace", r, NotifySelect = ProjectRemove }.disabled = na;
263                      MenuDivider { pop };
264                   }
265                   MenuItem { pop, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
266                   MenuDivider { pop };
267                   MenuItem { pop, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
268                   MenuDivider { pop };
269                   MenuItem { pop, $"Save", v, Key { s, ctrl = true }, NotifySelect = ProjectSave }.disabled = !node.modified;
270                   MenuDivider { pop };
271                   MenuItem { pop, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
272                }
273                else if(node.type == resources)
274                {
275                   MenuItem { pop, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
276                   MenuItem { pop, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
277                   MenuItem { pop, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
278                   MenuItem { pop, $"Add Resources to Project...", f, NotifySelect = ResourcesAddFiles };
279                   MenuItem { pop, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
280                   MenuDivider { pop };
281                   MenuItem { pop, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
282                   MenuItem { pop, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
283                }
284                else if(node.type == file)
285                {
286                   MenuItem { pop, $"Open", o, NotifySelect = FileOpenFile };
287                   MenuDivider { pop };
288                   MenuItem { pop, $"Clean", l, NotifySelect = FileClean, bitmap = ide.projectCleanItem.bitmap }.disabled = na;
289                   MenuItem { pop, $"Compile", c, Key { f7, ctrl = true}, NotifySelect = FileCompile, bitmap = ide.projectBuildItem.bitmap }.disabled = na;
290                   if(showDebuggingMenuItems)
291                   {
292                      char extension[MAX_EXTENSION];
293                      GetExtension(node.name, extension);
294                      if(!strcmpi(extension, "ec"))
295                      {
296                         MenuDivider { pop };
297                         MenuItem { pop, $"Debug Precompile", l, NotifySelect = FileDebugPrecompile }.disabled = na;
298                         MenuItem { pop, $"Debug Compile", l, NotifySelect = FileDebugCompile }.disabled = na;
299                      }
300                   }
301                   MenuDivider { pop };
302                   MenuItem { pop, $"Remove", r, NotifySelect = FileRemoveFile };
303                   MenuDivider { pop };
304                   MenuItem { pop, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
305                   MenuDivider { pop };
306                   MenuItem { pop, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
307                   MenuItem { pop, $"Properties..", p, Key { enter, alt = true }, NotifySelect = FileProperties };
308                }
309                else if(node.type == folder)
310                {
311                   bool isInResources = node.isInResources;
312
313                   MenuItem { pop, $"New File...", l, Key { l, ctrl = true }, NotifySelect = ProjectNewFile };
314                   MenuItem { pop, $"New Folder...", n, Key { f, ctrl = true }, NotifySelect = ProjectNewFolder };
315                   MenuItem { pop, $"Import Folder...", i, NotifySelect = ProjectImportFolder };
316                   if(isInResources)
317                   {
318                      MenuItem { pop, $"Add Resources to Folder...", f, NotifySelect = ResourcesAddFiles };
319                   }
320                   else
321                   {
322                      MenuItem { pop, $"Add Files to Folder...", f, NotifySelect = ProjectAddFiles };
323                   }
324                   if(!isInResources)
325                   {
326                      MenuDivider { pop };
327                      MenuItem { pop, $"Add New Form...", o, NotifySelect = ProjectAddNewForm };
328                      // MenuItem { pop, $"Add New Behavior Graph...", g, NotifySelect = ProjectAddNewGraph };
329                   }
330                   MenuDivider { pop };
331                   MenuItem { pop, $"Clean", l, NotifySelect = FileClean, bitmap = ide.projectCleanItem.bitmap }.disabled = na;
332                   MenuItem { pop, $"Compile", c, Key { f7, ctrl = true}, NotifySelect = FileCompile, bitmap = ide.projectBuildItem.bitmap }.disabled = na;
333                   MenuDivider { pop };
334                   MenuItem { pop, $"Remove", r, NotifySelect = FileRemoveFile };
335                   MenuDivider { pop };
336                   MenuItem { pop, $"Browse Folder", w, NotifySelect = MenuBrowseFolder };
337                   MenuDivider { pop };
338                   MenuItem { pop, $"Settings...", s, Key { f7, alt = true } , NotifySelect = MenuSettings };
339                   MenuItem { pop, $"Properties...", p, Key { enter, alt = true }, NotifySelect = FileProperties };
340                }
341
342                popupMenu =
343                {
344                   master = this, menu = pop;
345                   position = {
346                      x + clientStart.x + absPosition.x - app.desktop.position.x,
347                      y + clientStart.y + absPosition.y - app.desktop.position.y };
348
349                   void NotifyDestroyed(Window window, DialogResult result)
350                   {
351                      popupMenu = null;
352                   }
353                };
354                popupMenu.Create();
355                ide.AdjustPopupBuildMenus();
356             }
357          }
358          return true;
359       }
360
361       bool NotifyKeyHit(ListBox listBox, DataRow row, Key key, unichar ch)
362       {
363          if(key == altUp || key == altDown)
364          {
365             SelectNextProject(key == altUp);
366             return false;
367          }
368          return true;
369       }
370
371       bool NotifyKeyDown(ListBox listBox, DataRow row, Key key, unichar ch)
372       {
373          if(row)
374          {
375             ProjectNode node = (ProjectNode)row.tag;
376             switch(key)
377             {
378                case altEnter: case Key { keyPadEnter, alt = true }:
379                {
380                   NodeProperties { parent = parent, master = this,
381                      position = { position.x + 100, position.y + 100 }, node = node }.Create();
382                   return false;
383                }
384                case enter: case keyPadEnter:
385                {
386                   ProjectNode resNode;
387                   for(resNode = node.parent; resNode; resNode = resNode.parent)
388                      if(resNode.type == resources)
389                         break;
390                   if(node.type == project || (node.type == folder && !resNode))
391                   {
392                      AddFiles(false);
393                      return false;
394                   }
395                   else if(node.type == resources || node.type == folder)
396                   {
397                      AddFiles(true);
398                      return false;
399                   }
400                   break;
401                }
402                case ctrlI:
403                {
404                   if(node.type == project || node.type == folder || node.type == resources)
405                   {
406                      ImportFolder(node);
407                      return false;
408                   }
409                   break;
410                }
411                case ctrlF:
412                {
413                   if(node.type == project || node.type == folder || node.type == resources)
414                   {
415                      NewFolder(node, null, true);
416                      return false;
417                   }
418                   break;
419                }
420                case ctrlF7:
421                {
422                   if(node.type == file)
423                   {
424                      FileCompile(null, (Modifiers)key);
425                      return false;
426                   }
427                   break;
428                }
429                case Key { space, false, true }:
430                case Key { space, true, true }:
431                case Key { space, true }:
432                case space:
433                {
434                   if(node.type == NodeTypes::project)
435                   {
436                      Project prj = null;
437                      for(p : workspace.projects)
438                      {
439                         if(p.topNode == node)
440                         {
441                            prj = p;
442                            break;
443                         }
444                      }
445                      prj.RotateActiveConfig(!key.shift, key.ctrl);
446                      if(prj == project)
447                         ide.AdjustMenus();
448                      return false;
449                   }
450                   break;
451                }
452             }
453          }
454          switch(key)
455          {
456             case Key { enter, true, true }:        OpenSelectedNodes(true);   break;
457             case Key { keyPadEnter, true, true }:  OpenSelectedNodes(true);   break;
458             case enter: case keyPadEnter:          OpenSelectedNodes(false);  break;
459             case del:                              RemoveSelectedNodes();     break;
460             case escape:
461             {
462                Window activeClient = ide.activeClient;
463                if(activeClient)
464                   activeClient.Activate();
465                else
466                   ide.RepositionWindows(true);
467                break;
468             }
469          }
470          return true;
471       }
472
473       bool NotifyCollapse(ListBox listBox, DataRow row, bool collapsed)
474       {
475          ProjectNode node = (ProjectNode)row.tag;
476          if(node.type == folder)
477             node.icon = collapsed ? folder : openFolder;
478          return true;
479       }
480    };
481
482    FileDialog importFileDialog { autoCreate = false, type = selectDir, text = $"Import Folder" };
483    FileDialog projectFileDialog
484    {
485       autoCreate = false, filters = projectFilters.array, sizeFilters = projectFilters.count * sizeof(FileFilter);
486       types = projectTypes.array, sizeTypes = projectTypes.count * sizeof(FileType);
487    };
488    FileDialog fileDialog
489    {
490       autoCreate = false, mayNotExist = true, filters = fileFilters.array, sizeFilters = fileFilters.count * sizeof(FileFilter);
491    };
492    FileDialog resourceFileDialog
493    {
494       autoCreate = false, mayNotExist = true, filters = resourceFilters.array, sizeFilters = resourceFilters.count * sizeof(FileFilter);
495    };
496
497    Menu fileMenu { menu, $"File", f };
498    MenuItem { fileMenu, $"Save", s, Key { s, ctrl = true }, NotifySelect = MenuFileSave };
499    // MenuItem { fileMenu, "Save As...", a, NotifySelect = MenuFileSaveAs };
500
501    bool OnClose(bool parentClosing)
502    {
503       if(!parentClosing && visible)
504       {
505          visible = false;
506          return false;
507       }
508       if(buildInProgress)
509          return false;
510
511       if(modifiedDocument)
512       {
513          DialogResult dialogRes;
514          char msg[2048];
515          bool first = true;
516          strcpy(msg, $"You have modified projects.\nSave changes to ");
517          for(p : ide.workspace.projects)
518          {
519             if(p.topNode.modified)
520             {
521                if(!first) strcat(msg, ", ");
522                strcat(msg, p.name);
523                first = false;
524             }
525          }
526          strcat(msg, "?");
527
528          dialogRes = MessageBox { master = master, type = yesNoCancel, text = parent.caption ? parent.caption : rootWindow.caption, contents = msg }.Modal();
529
530          if(dialogRes == yes)
531          {
532             // TOFIX: Precomp error if brackets are taken out
533             return (DialogResult)MenuFileSave(null, 0) != cancel;
534          }
535          else if(dialogRes == cancel)
536             return false;
537          modifiedDocument = false;
538       }
539       return true;
540    }
541
542    void OnDestroy(void)
543    {
544       //if(ide.findInFilesDialog && ide.findInFilesDialog.created && ide.findInFilesDialog.mode != directory)
545       //   ide.findInFilesDialog.SearchStop();
546
547       ide.outputView.buildBox.Clear();
548       ide.outputView.debugBox.Clear();
549       //ide.outputView.findBox.Clear();
550       ide.callStackView.Clear();
551       ide.watchesView.Clear();
552       ide.threadsView.Clear();
553       ide.breakpointsView.Clear();
554       ide.outputView.ShowClearSelectTab(find); // why this?
555    }
556
557    bool OnSaveFile(char * fileName)
558    {
559       for(prj : ide.workspace.projects)
560       {
561          if(prj.topNode.modified)
562          {
563             prj.StopMonitoring();
564             if(prj.Save(prj.filePath))
565                prj.topNode.modified = false;
566             prj.StartMonitoring();
567          }
568       }
569       modifiedDocument = false;
570       Update(null);
571       return true;
572    }
573
574    bool IsModuleInProject(char * filePath)
575    {
576       char moduleName[MAX_FILENAME]; //, modulePath[MAX_LOCATION];
577       GetLastDirectory(filePath, moduleName);
578       return project.topNode.Find(moduleName, false) != null;
579    }
580
581    ProjectNode GetNodeForCompilationFromWindow(Window document, bool nonExcludedFirst, bool * isExcluded, bool * isCObject)
582    {
583       ProjectNode node = null;
584       if(nonExcludedFirst)
585          node = GetNodeFromWindow(document, null, false, false, isExcluded);
586       if(!node)
587          node = GetNodeFromWindow(document, null, true, false, isExcluded);
588       if(!node && nonExcludedFirst)
589       {
590          node = GetNodeFromWindow(document, null, false, true, isExcluded);
591          if(isCObject && node) *isCObject = true;
592       }
593       if(!node)
594       {
595          node = GetNodeFromWindow(document, null, true, true, isExcluded);
596          if(isCObject && node) *isCObject = true;
597       }
598       return node;
599    }
600
601    ProjectNode GetNodeFromWindow(Window document, Project project, bool allNodes, bool isCObject, bool * isNodeExcluded)
602    {
603       ProjectNode node = null;
604       if(document.fileName)
605       {
606          bool excluded;
607          char winFileName[MAX_LOCATION];
608          char * documentFileName = GetSlashPathBuffer(winFileName, document.fileName);
609          Project prj;
610          ProjectNode n;
611          if(isCObject)
612          {
613             char name[MAX_FILENAME];
614             GetLastDirectory(documentFileName, name);
615             ChangeExtension(name, "ec", name);
616             for(p : ide.workspace.projects)
617             {
618                prj = project ? project : p;
619                if((n = prj.topNode.Find(name, false)))
620                {
621                   if(allNodes || !(excluded = n.GetIsExcluded(prj.config)))
622                   {
623                      node = n;
624                      break;
625                   }
626                }
627                if(project) break;
628             }
629          }
630          else
631          {
632             for(p : ide.workspace.projects)
633             {
634                Project pr = p;
635                prj = project ? project : p;
636                if((n = prj.topNode.FindByFullPath(documentFileName, false)))
637                {
638                   if(allNodes || !(excluded = n.GetIsExcluded(prj.config)))
639                   {
640                      node = n;
641                      break;
642                   }
643                }
644                if(project) break;
645             }
646          }
647          if(node && isNodeExcluded)
648             *isNodeExcluded = excluded;
649       }
650       return node;
651    }
652
653    //                          ((( UTILITY FUNCTIONS )))
654    //
655    //  ************************************************************************
656    //  *** These methods below are part of a sequence of events, and as     ***
657    //  *** such they must be passed the current compiler and project config ***
658    //  ************************************************************************
659    bool DisplayCompiler(CompilerConfig compiler, bool cleanLog)
660    {
661       ide.outputView.buildBox.Logf($"%s Compiler\n", compiler ? compiler.name : $"{problem with compiler selection}");
662       return true;
663    }
664
665    bool ProjectPrepareForToolchain(Project project, PrepareMakefileMethod method, bool cleanLog, bool displayCompiler,
666       CompilerConfig compiler, ProjectConfig config)
667    {
668       bool isReady = true;
669       char message[MAX_F_STRING];
670       LogBox logBox = ide.outputView.buildBox;
671
672       ShowOutputBuildLog(cleanLog);
673
674       if(displayCompiler)
675          DisplayCompiler(compiler, false);
676
677       ProjectPrepareCompiler(project, compiler, false);
678       ProjectPrepareMakefile(project, method, compiler, config);
679       return true;
680    }
681
682    bool ProjectPrepareCompiler(Project project, CompilerConfig compiler, bool silent)
683    {
684       if((!project.GenerateCrossPlatformMk(app.includeFile) ||
685             !project.GenerateCompilerCf(compiler, project.topNode.ContainsFilesWithExtension("ec", project.config))) && !silent)
686          ide.outputView.buildBox.Logf($"Error generating compiler configuration (Is the project/config directory writable?)\n");
687       return true;
688    }
689
690    // Note: Compiler is only passed in to for VisualStudio support
691    bool ProjectPrepareMakefile(Project project, PrepareMakefileMethod method, CompilerConfig compiler, ProjectConfig config)
692    {
693       if(compiler.type.isVC)
694       {
695          // I'm guessing we'll want to support generating VS files on Linux as well...
696          ide.statusBar.text = $"Generating Visual Studio Solution...";
697          app.UpdateDisplay();
698          //GenerateVSSolutionFile(project, compiler);
699          ide.statusBar.text = $"Generating Visual Studio Project...";
700          app.UpdateDisplay();
701          //GenerateVCProjectFile(project, compiler, bitDepth);
702          ide.statusBar.text = null;
703          app.UpdateDisplay();
704          return true;
705       }
706       else
707       {
708          char makefilePath[MAX_LOCATION];
709          char makefileName[MAX_LOCATION];
710          bool exists;
711          LogBox logBox = ide.outputView.buildBox;
712
713          strcpy(makefilePath, project.topNode.path);
714          project.CatMakeFileName(makefileName, config);
715          PathCatSlash(makefilePath, makefileName);
716
717          exists = FileExists(makefilePath);
718          if(method == force ||
719            (method == forceExists && exists) ||
720            (method == normal && (!exists || (config && config.makingModified))))
721          {
722             char * reason;
723             char * action;
724             ide.statusBar.text = $"Generating Makefile & Dependencies..."; // Dependencies?
725             app.UpdateDisplay();
726
727             if((method == normal && !exists) || (method == force && !exists))
728                action = $"Generating ";
729             else if(method == force)
730                action = $"Regenerating ";
731             else if(method == normal || method == forceExists)
732                action = $"Updating ";
733             else
734                action = "";
735             if(!exists)
736                reason = $"Makefile doesn't exist. ";
737             else if(project.topNode.modified)
738                reason = $"Project has been modified. ";
739             else
740                reason = "";
741
742             //logBox.Logf("%s\n", makefileName);
743             logBox.Logf($"%s - %s%smakefile for %s config...\n", makefileName, reason, action, GetConfigName(config));
744
745             if(!project.GenerateMakefile(null, false, null, config))
746                ide.outputView.buildBox.Logf($"Error generating makefile (Is the project directory writable?)\n");
747
748             ide.statusBar.text = null;
749             app.UpdateDisplay();
750             return true;
751          }
752       }
753       return false;
754    }
755
756    bool BuildInterrim(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config, int bitDepth, bool justPrint)
757    {
758       if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
759       {
760          ide.outputView.buildBox.Logf($"Building project %s using the %s configuration...\n", prj.name, GetConfigName(config));
761          return Build(prj, buildType, compiler, config, bitDepth, justPrint);
762       }
763       return false;
764    }
765
766    bool DebugStopForMake(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config)
767    {
768       bool result = false;
769       // TOFIX: DebugStop is being abused and backfiring on us.
770       //        It's supposed to be the 'Debug/Stop' item, not unloading executable or anything else
771
772       //        configIsInDebugSession seems to be used for two OPPOSITE things:
773       //        If we're debugging another config, we need to unload the executable!
774       //        In building, we want to stop if we're debugging the 'same' executable
775       if(buildType != run) ///* && prj == project*/ && prj.configIsInDebugSession)
776       {
777          if(buildType == start || buildType == restart)
778          {
779             if(ide.debugger && ide.debugger.isPrepared)
780                result = DebugStop();
781          }
782          else
783          {
784             if(ide.project == prj && ide.debugger && ide.debugger.prjConfig == config && ide.debugger.isPrepared)
785                result = DebugStop();
786          }
787       }
788       return result;
789    }
790
791    bool Build(Project prj, BuildType buildType, CompilerConfig compiler, ProjectConfig config, int bitDepth, bool justPrint)
792    {
793       bool result = true;
794       Window document;
795
796       stopBuild = false;
797       for(document = master.firstChild; document; document = document.next)
798       {
799          if(document.modifiedDocument)
800          {
801             ProjectNode node = GetNodeFromWindow(document, prj, true, false, null);
802             if(node && !document.MenuFileSave(null, 0))
803             {
804                result = false;
805                break;
806             }
807          }
808       }
809       if(result)
810       {
811          DirExpression targetDir = prj.GetTargetDir(compiler, config, bitDepth);
812
813          DebugStopForMake(prj, buildType, compiler, config);
814
815          // TODO: Disabled until problems fixed... is it fixed?
816          if(buildType == rebuild || (config && config.compilingModified))
817             prj.Clean(compiler, config, bitDepth, clean, justPrint);
818          else
819          {
820             if(buildType == relink || (config && config.linkingModified))
821                prj.Clean(compiler, config, bitDepth, cleanTarget, false);
822             if(config && config.symbolGenModified)
823             {
824                DirExpression objDir = prj.GetObjDir(compiler, config, bitDepth);
825                char fileName[MAX_LOCATION];
826                char moduleName[MAX_FILENAME];
827                strcpy(fileName, prj.topNode.path);
828                PathCatSlash(fileName, objDir.dir);
829                ReplaceSpaces(moduleName, prj.moduleName);
830                strcat(moduleName, ".main.ec");
831                PathCatSlash(fileName, moduleName);
832                if(FileExists(fileName))
833                   DeleteFile(fileName);
834                ChangeExtension(fileName, "c", fileName);
835                if(FileExists(fileName))
836                   DeleteFile(fileName);
837                ChangeExtension(fileName, "o", fileName);
838                if(FileExists(fileName))
839                   DeleteFile(fileName);
840
841                delete objDir;
842             }
843          }
844          buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
845          ide.AdjustBuildMenus();
846          ide.AdjustDebugMenus();
847
848          result = prj.Build(buildType, null, compiler, config, bitDepth, justPrint, normal);
849
850          if(config)
851          {
852             config.compilingModified = false;
853             if(!stopBuild)
854                config.linkingModified = false;
855
856             config.symbolGenModified = false;
857          }
858          buildInProgress = none;
859          ide.AdjustBuildMenus();
860          ide.AdjustDebugMenus();
861
862          ide.workspace.modified = true;
863
864          delete targetDir;
865       }
866       return result;
867    }
868
869    //                          ((( USER ACTIONS )))
870    //
871    //  ************************************************************************
872    //  *** Methods below should atomically start a process, and as such     ***
873    //  *** they can query compiler and config directly from ide and project ***
874    //  *** but ONLY ONCE!!!                                                 ***
875    //  ************************************************************************
876
877    bool ProjectBuild(MenuItem selection, Modifiers mods)
878    {
879       if(buildInProgress == none)
880       {
881          Project prj = project;
882          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
883          int bitDepth = ide.workspace.bitDepth;
884          ProjectConfig config;
885          if(selection || !ide.activeClient)
886          {
887             DataRow row = fileList.currentRow;
888             ProjectNode node = row ? (ProjectNode)row.tag : null;
889             if(node) prj = node.project;
890          }
891          else
892          {
893             ProjectNode node = GetNodeForCompilationFromWindow(ide.activeClient, true, null, null);
894             if(node)
895                prj = node.project;
896          }
897          config = prj.config;
898          if(/*prj != project || */!prj.GetConfigIsInDebugSession(config) || !ide.DontTerminateDebugSession($"Project Build"))
899          {
900             BuildInterrim(prj, build, compiler, config, bitDepth, mods.ctrl && mods.shift);
901          }
902          delete compiler;
903       }
904       else
905          stopBuild = true;
906       return true;
907    }
908
909    bool ProjectInstall(MenuItem selection, Modifiers mods)
910    {
911       Project prj = project;
912       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
913       int bitDepth = ide.workspace.bitDepth;
914       ProjectConfig config;
915       if(selection || !ide.activeClient)
916       {
917          DataRow row = fileList.currentRow;
918          ProjectNode node = row ? (ProjectNode)row.tag : null;
919          if(node) prj = node.project;
920       }
921       else
922       {
923          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, true, false, null);
924          if(node)
925             prj = node.project;
926       }
927       config = prj.config;
928       if(!prj.GetConfigIsInDebugSession(config) ||
929             (!ide.DontTerminateDebugSession($"Project Install") && DebugStopForMake(prj, relink, compiler, config)))
930       {
931          BuildInterrim(prj, build, compiler, config, bitDepth, mods.ctrl && mods.shift);
932          if(ProjectPrepareForToolchain(prj, normal, false, false, compiler, config))
933          {
934             ide.outputView.buildBox.Logf($"\nInstalling project %s using the %s configuration...\n", prj.name, GetConfigName(config));
935             Build(prj, install, compiler, config, bitDepth, mods.ctrl && mods.shift);
936          }
937       }
938       delete compiler;
939       return true;
940    }
941
942    bool ProjectLink(MenuItem selection, Modifiers mods)
943    {
944       Project prj = project;
945       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
946       int bitDepth = ide.workspace.bitDepth;
947       ProjectConfig config;
948       if(selection || !ide.activeClient)
949       {
950          DataRow row = fileList.currentRow;
951          ProjectNode node = row ? (ProjectNode)row.tag : null;
952          if(node) prj = node.project;
953       }
954       else
955       {
956          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, true, false, null);
957          if(node)
958             prj = node.project;
959       }
960       config = prj.config;
961       if(!prj.GetConfigIsInDebugSession(config) ||
962             (!ide.DontTerminateDebugSession($"Project Link") && DebugStopForMake(prj, relink, compiler, config)))
963       {
964          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
965          {
966             ide.outputView.buildBox.Logf($"Relinking project %s using the %s configuration...\n", prj.name, GetConfigName(config));
967             if(config)
968                config.linkingModified = true;
969             Build(prj, relink, compiler, config, bitDepth, mods.ctrl && mods.shift);
970          }
971       }
972       delete compiler;
973       return true;
974    }
975
976    bool ProjectRebuild(MenuItem selection, Modifiers mods)
977    {
978       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
979       int bitDepth = ide.workspace.bitDepth;
980       Project prj = project;
981       ProjectConfig config;
982       if(selection || !ide.activeClient)
983       {
984          DataRow row = fileList.currentRow;
985          ProjectNode node = row ? (ProjectNode)row.tag : null;
986          if(node) prj = node.project;
987       }
988       else
989       {
990          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, true, false, null);
991          if(node)
992             prj = node.project;
993       }
994       config = prj.config;
995       if(!prj.GetConfigIsInDebugSession(config) ||
996             (!ide.DontTerminateDebugSession($"Project Rebuild") && DebugStopForMake(prj, rebuild, compiler, config)))
997       {
998          if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
999          {
1000             ide.outputView.buildBox.Logf($"Rebuilding project %s using the %s configuration...\n", prj.name, GetConfigName(config));
1001             /*if(config)
1002             {
1003                config.compilingModified = true;
1004                config.makingModified = true;
1005             }*/ // -- should this still be used depite the new solution of BuildType?
1006             Build(prj, rebuild, compiler, config, bitDepth, mods.ctrl && mods.shift);
1007          }
1008       }
1009       delete compiler;
1010       return true;
1011    }
1012
1013    bool ProjectCleanTarget(MenuItem selection, Modifiers mods)
1014    {
1015       CleanProject($"Project Clean Target", $"Cleaning project %s target using the %s configuration...\n", selection, cleanTarget, mods.ctrl && mods.shift);
1016       return true;
1017    }
1018
1019    bool ProjectClean(MenuItem selection, Modifiers mods)
1020    {
1021       CleanProject($"Project Clean", $"Cleaning project %s using the %s configuration...\n", selection, clean, mods.ctrl && mods.shift);
1022       return true;
1023    }
1024
1025    bool ProjectRealClean(MenuItem selection, Modifiers mods)
1026    {
1027       CleanProject($"Project Real Clean", $"Removing intermediate objects directory for project %s using the %s configuration...\n", selection, realClean, mods.ctrl && mods.shift);
1028       return true;
1029    }
1030
1031    void CleanProject(char * terminateDebugSessionMessage, char * cleaningMessageLogFormat, MenuItem selection, CleanType cleanType, bool justPrint)
1032    {
1033       Project prj = project;
1034       Array<Project> projects { };
1035       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1036       int bitDepth = ide.workspace.bitDepth;
1037       if(selection)
1038       {
1039          OldLink item;
1040          OldList selectedRows;
1041          List<ProjectNode> nodes { };
1042          fileList.GetMultiSelection(selectedRows);
1043          for(item = selectedRows.first; item; item = item.next)
1044          {
1045             OldLink i;
1046             DataRow row = item.data;
1047             ProjectNode node = (ProjectNode)row.tag;
1048             if(node.type == project)
1049                projects.Add(node.project);
1050          }
1051          selectedRows.Free(null);
1052       }
1053       if(selection || !ide.activeClient)
1054       {
1055          DataRow row = fileList.currentRow;
1056          ProjectNode node = row ? (ProjectNode)row.tag : null;
1057          if(node) prj = node.project;
1058          if(projects.count == 0)
1059             projects.Add(prj);
1060       }
1061       else
1062       {
1063          // TODO: a file can belong to more than one project, ask which one to clean
1064          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, true, false, null);
1065          if(node) prj = node.project;
1066          if(projects.count == 0)
1067             projects.Add(prj);
1068       }
1069       for(prj : projects)
1070       {
1071          ProjectConfig config = prj.config;
1072          if(!prj.GetConfigIsInDebugSession(config) ||
1073                (!ide.DontTerminateDebugSession(terminateDebugSessionMessage) && DebugStopForMake(prj, clean, compiler, config)))
1074          {
1075             if(ProjectPrepareForToolchain(prj, normal, true, true, compiler, config))
1076             {
1077                ide.outputView.buildBox.Logf(cleaningMessageLogFormat, prj.name, GetConfigName(config));
1078
1079                buildInProgress = prj == project ? buildingMainProject : buildingSecondaryProject;
1080                ide.AdjustBuildMenus();
1081                ide.AdjustDebugMenus();
1082
1083                prj.Clean(compiler, config, bitDepth, cleanType, justPrint);
1084                buildInProgress = none;
1085                ide.AdjustBuildMenus();
1086                ide.AdjustDebugMenus();
1087             }
1088          }
1089       }
1090       delete compiler;
1091       delete projects;
1092    }
1093
1094    bool ProjectRegenerate(MenuItem selection, Modifiers mods)
1095    {
1096       Project prj = project;
1097       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1098       ShowOutputBuildLog(true);
1099       if(selection || !ide.activeClient)
1100       {
1101          DataRow row = fileList.currentRow;
1102          ProjectNode node = row ? (ProjectNode)row.tag : null;
1103          if(node)
1104             prj = node.project;
1105       }
1106       else
1107       {
1108          ProjectNode node = GetNodeFromWindow(ide.activeClient, null, true, false, null);
1109          if(node)
1110             prj = node.project;
1111       }
1112
1113       DisplayCompiler(compiler, false);
1114       ProjectPrepareCompiler(project, compiler, false);
1115       ProjectPrepareMakefile(prj, force, compiler, prj.config);
1116       delete compiler;
1117       return true;
1118    }
1119
1120    bool Compile(Project project, List<ProjectNode> nodes, bool justPrint, SingleFileCompileMode mode)
1121    {
1122       bool result = true;
1123       char fileName[MAX_LOCATION];
1124       Window document;
1125       ProjectConfig config = project.config;
1126
1127       stopBuild = false;
1128
1129       for(document = ide.firstChild; document; document = document.next)
1130       {
1131          if(document.modifiedDocument)
1132          {
1133             ProjectNode n = GetNodeFromWindow(document, project, true, mode == cObject ? true : false, null);
1134             for(node : nodes)
1135             {
1136                if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1137                {
1138                   ide.outputView.buildBox.Logf($"Unable to save %s file.\n", node.name);
1139                   result = false;
1140                   break;
1141                }
1142             }
1143          }
1144       }
1145
1146       if(result)
1147       {
1148          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1149          int bitDepth = ide.workspace.bitDepth;
1150          result = false;
1151          if(ProjectPrepareForToolchain(project, normal, true, true, compiler, config))
1152          {
1153             if(config)
1154                ide.outputView.buildBox.Logf($"%s specific file(s) in project %s using the %s configuration...\n",
1155                      (mode == normal || mode == cObject) ? $"Compiling" : $"Debug compiling", project.name, config.name);
1156             else
1157                ide.outputView.buildBox.Logf($"%s specific file(s) in project %s...\n",
1158                      (mode == normal || mode == cObject) ? $"Compiling" : $"Debug compiling", project.name);
1159
1160             buildInProgress = compilingFile;
1161             ide.AdjustBuildMenus();
1162             result = project.Compile(nodes, compiler, config, bitDepth, justPrint, mode);
1163             buildInProgress = none;
1164             ide.AdjustBuildMenus();
1165          }
1166          delete compiler;
1167       }
1168       return result;
1169    }
1170
1171    bool Clean(Project project, List<ProjectNode> nodes, bool justPrint)
1172    {
1173       bool result = true;
1174       char fileName[MAX_LOCATION];
1175       Window document;
1176       ProjectConfig config = project.config;
1177
1178       stopBuild = false;
1179
1180       for(document = ide.firstChild; document; document = document.next)
1181       {
1182          if(document.modifiedDocument)
1183          {
1184             ProjectNode n = GetNodeFromWindow(document, project, true, false, null);
1185             for(node : nodes)
1186             {
1187                if(n && n.IsInNode(node) && !document.MenuFileSave(null, 0))
1188                {
1189                   ide.outputView.buildBox.Logf($"Unable to save %s file.\n", node.name);
1190                   result = false;
1191                   break;
1192                }
1193             }
1194          }
1195       }
1196
1197       if(result)
1198       {
1199          CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1200          int bitDepth = ide.workspace.bitDepth;
1201          result = false;
1202          if(ProjectPrepareForToolchain(project, normal, true, true, compiler, config))
1203          {
1204             Map<String, NameCollisionInfo> namesInfo { };
1205             project.topNode.GenMakefileGetNameCollisionInfo(namesInfo, config);
1206             for(node : nodes)
1207             {
1208                if(node.GetIsExcluded(config))
1209                   ide.outputView.buildBox.Logf($"File %s is excluded from current build configuration.\n", node.name);
1210                else
1211                {
1212                   if(config)
1213                      ide.outputView.buildBox.Logf($"Deleting intermediate objects for %s %s in project %s using the %s configuration...\n",
1214                            node.type == file ? $"single file" : $"folder", node.name, project.name, config.name);
1215                   else
1216                      ide.outputView.buildBox.Logf($"Deleting intermediate objects for %s %s in project %s...\n",
1217                            node.type == file ? $"single file" : $"folder", node.name, project.name);
1218
1219                   node.DeleteIntermediateFiles(compiler, config, bitDepth, namesInfo, false);
1220                   result = true;
1221                }
1222             }
1223             namesInfo.Free();
1224             delete namesInfo;
1225          }
1226          delete compiler;
1227       }
1228       return result;
1229    }
1230
1231    bool ProjectNewFile(MenuItem selection, Modifiers mods)
1232    {
1233       DataRow row = fileList.currentRow;
1234       if(row)
1235       {
1236          char fileName[1024];
1237          char filePath[MAX_LOCATION];
1238          ProjectNode parentNode = (ProjectNode)row.tag;
1239          ProjectNode n, fileNode;
1240          parentNode.GetFileSysMatchingPath(filePath);
1241          MakePathRelative(filePath, parentNode.project.topNode.path, filePath);
1242          for(n = parentNode; n && n != parentNode.project.resNode; n = n.parent);
1243          sprintf(fileName, $"Untitled %d", documentID);
1244          fileNode = AddFile(parentNode, fileName, (bool)n, true);
1245          fileNode.path = CopyUnixPath(filePath);
1246          if(fileNode)
1247          {
1248             NodeProperties nodeProperties
1249             {
1250                parent, this;
1251                position = { position.x + 100, position.y + 100 };
1252                mode = newFile;
1253                node = fileNode;
1254             };
1255             nodeProperties.Create(); // not modal?
1256          }
1257       }
1258       return true;
1259    }
1260
1261    bool ProjectNewFolder(MenuItem selection, Modifiers mods)
1262    {
1263       DataRow row = fileList.currentRow;
1264       if(row)
1265       {
1266          ProjectNode parentNode = (ProjectNode)row.tag;
1267          NewFolder(parentNode, null, true);
1268       }
1269       return true;
1270    }
1271
1272    bool ResourcesAddFiles(MenuItem selection, Modifiers mods)
1273    {
1274       AddFiles(true);
1275       return true;
1276    }
1277
1278    bool ProjectAddFiles(MenuItem selection, Modifiers mods)
1279    {
1280       AddFiles(false);
1281       return true;
1282    }
1283
1284    bool ProjectImportFolder(MenuItem selection, Modifiers mods)
1285    {
1286       DataRow row = fileList.currentRow;
1287       if(row)
1288       {
1289          ProjectNode toNode = (ProjectNode)row.tag;
1290          ImportFolder(toNode);
1291       }
1292       return true;
1293    }
1294
1295    bool ProjectAddNewForm(MenuItem selection, Modifiers mods)
1296    {
1297       CodeEditor codeEditor = CreateNew("Form", "form", "Window", null);
1298       codeEditor.EnsureUpToDate();
1299       return true;
1300    }
1301
1302    bool ProjectAddNewGraph(MenuItem selection, Modifiers mods)
1303    {
1304       CodeEditor codeEditor = CreateNew("Graph", "graph", "Block", null);
1305       if(codeEditor)
1306          codeEditor.EnsureUpToDate();
1307       return true;
1308    }
1309
1310    bool ProjectRemove(MenuItem selection, Modifiers mods)
1311    {
1312       DataRow row = fileList.currentRow;
1313       if(row)
1314       {
1315          ProjectNode node = (ProjectNode)row.tag;
1316          if(node.type == project)
1317             RemoveSelectedNodes();
1318       }
1319       return true;
1320    }
1321
1322    bool ProjectUpdateMakefileForAllConfigs(Project project)
1323    {
1324       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1325
1326       // This call really does not belong here:
1327       ide.UpdateToolBarActiveConfigs(false);
1328       for(config : project.configurations)
1329          ProjectPrepareMakefile(project, forceExists, compiler, config);
1330
1331       ide.Update(null);
1332       delete compiler;
1333       return true;
1334    }
1335
1336    bool MenuSettings(MenuItem selection, Modifiers mods)
1337    {
1338       ProjectNode node = GetSelectedNode(true);
1339       Project prj = node ? node.project : project;
1340       projectSettingsDialog = ProjectSettings { master = parent, project = prj, projectNode = node };
1341       incref projectSettingsDialog;
1342       projectSettingsDialog.Modal();
1343       delete projectSettingsDialog;
1344       ide.UpdateToolBarActiveConfigs(false);
1345       Update(null);
1346       ide.AdjustMenus();
1347       return true;
1348    }
1349
1350    bool FileProperties(MenuItem selection, Modifiers mods)
1351    {
1352       DataRow row = fileList.currentRow;
1353       if(row)
1354       {
1355          ProjectNode node = (ProjectNode)row.tag;
1356          NodeProperties { parent = parent, master = this, node = node,
1357                position = { position.x + 100, position.y + 100 } }.Create();
1358       }
1359       return true;
1360    }
1361
1362    bool FileOpenFile(MenuItem selection, Modifiers mods)
1363    {
1364       OpenSelectedNodes(mods.ctrl && mods.shift);
1365       return true;
1366    }
1367
1368    bool FileRemoveFile(MenuItem selection, Modifiers mods)
1369    {
1370       RemoveSelectedNodes();
1371       return true;
1372    }
1373
1374    bool FileCompile(MenuItem selection, Modifiers mods)
1375    {
1376       OldLink item;
1377       OldList selectedRows;
1378       Project project = null;
1379       List<ProjectNode> nodes { };
1380       fileList.GetMultiSelection(selectedRows);
1381       for(item = selectedRows.first; item; item = item.next)
1382       {
1383          OldLink i;
1384          DataRow row = item.data;
1385          ProjectNode node = (ProjectNode)row.tag;
1386          if(!project)
1387             project = node.project;
1388          else if(node.project != project)
1389          {
1390             project = null;
1391             break;
1392          }
1393          nodes.Add(node);
1394       }
1395       selectedRows.Free(null);
1396       if(project)
1397          Compile(project, nodes, mods.ctrl && mods.shift, normal);
1398       else
1399          ide.outputView.buildBox.Logf($"Please select files from a single project.\n");
1400       delete nodes;
1401       return true;
1402    }
1403
1404    bool FileClean(MenuItem selection, Modifiers mods)
1405    {
1406       OldLink item;
1407       OldList selectedRows;
1408       Project project = null;
1409       List<ProjectNode> nodes { };
1410       fileList.GetMultiSelection(selectedRows);
1411       for(item = selectedRows.first; item; item = item.next)
1412       {
1413          OldLink i;
1414          DataRow row = item.data;
1415          ProjectNode node = (ProjectNode)row.tag;
1416          if(!project)
1417             project = node.project;
1418          else if(node.project != project)
1419          {
1420             project = null;
1421             break;
1422          }
1423          nodes.Add(node);
1424       }
1425       selectedRows.Free(null);
1426       if(project)
1427          Clean(project, nodes, mods.ctrl && mods.shift);
1428       else
1429          ide.outputView.buildBox.Logf($"Please select files from a single project.\n");
1430       delete nodes;
1431       return true;
1432    }
1433
1434    bool FileDebugPrecompile(MenuItem selection, Modifiers mods)
1435    {
1436       DataRow row = fileList.currentRow;
1437       ProjectNode node = row ? (ProjectNode)row.tag : null;
1438       if(node)
1439       {
1440          List<ProjectNode> nodes { };
1441          nodes.Add(node);
1442          if(node.type == project)
1443             ProjectBuild(selection, mods);
1444          ide.Update(null);
1445          if(!stopBuild)
1446             Compile(node.project, nodes, mods.ctrl && mods.shift, debugPrecompile);
1447          delete nodes;
1448       }
1449       return true;
1450    }
1451
1452    bool FileDebugCompile(MenuItem selection, Modifiers mods)
1453    {
1454       DataRow row = fileList.currentRow;
1455       ProjectNode node = row ? (ProjectNode)row.tag : null;
1456       if(node)
1457       {
1458          List<ProjectNode> nodes { };
1459          nodes.Add(node);
1460          if(node.type == project)
1461             ProjectBuild(selection, mods);
1462          else
1463             Compile(node.project, nodes, mods.ctrl && mods.shift, normal);
1464          if(!stopBuild)
1465             Compile(node.project, nodes, mods.ctrl && mods.shift, debugCompile);
1466          delete nodes;
1467       }
1468       return true;
1469    }
1470
1471    bool FileDebugGenerateSymbols(MenuItem selection, Modifiers mods)
1472    {
1473       DataRow row = fileList.currentRow;
1474       ProjectNode node = row ? (ProjectNode)row.tag : null;
1475       if(node)
1476       {
1477          List<ProjectNode> nodes { };
1478          nodes.Add(node);
1479          if(node.type == project)
1480             ProjectBuild(selection, mods);
1481          else
1482             Compile(node.project, nodes, mods.ctrl && mods.shift, normal);
1483          if(!stopBuild)
1484             Compile(node.project, nodes, mods.ctrl && mods.shift, debugGenerateSymbols);
1485          delete nodes;
1486       }
1487       return true;
1488    }
1489
1490    Project GetSelectedProject(bool useSelection)
1491    {
1492       Project prj = project;
1493       if(useSelection)
1494       {
1495          DataRow row = fileList.currentRow;
1496          ProjectNode node = row ? (ProjectNode)row.tag : null;
1497          if(node)
1498             prj = node.project;
1499       }
1500       return prj;
1501    }
1502
1503    void SelectNextProject(bool backwards)
1504    {
1505       DataRow row = fileList.currentRow;
1506       DataRow currentRow = row;
1507       ProjectNode node = (ProjectNode)row.tag;
1508       if(node.type != project)
1509          row = node.project.topNode.row;
1510       else if(backwards)
1511          row = row.previous ? row.previous : fileList.lastRow;
1512       if(!backwards)
1513          row = row.next ? row.next : fileList.firstRow;
1514       if(row && row != currentRow)
1515       {
1516          fileList.SelectRow(row);
1517          fileList.currentRow = row;
1518       }
1519    }
1520
1521    ProjectNode GetSelectedNode(bool useSelection)
1522    {
1523       ProjectNode node = null;
1524       if(useSelection)
1525       {
1526          DataRow row = fileList.currentRow;
1527          if(row)
1528             node = (ProjectNode)row.tag;
1529       }
1530       return node;
1531    }
1532
1533    bool MenuBrowseFolder(MenuItem selection, Modifiers mods)
1534    {
1535       char folder[MAX_LOCATION];
1536       Project prj;
1537       ProjectNode node = GetSelectedNode(true);
1538       if(!node)
1539          node = project.topNode;
1540       prj = node.project;
1541
1542       strcpy(folder, prj.topNode.path);
1543       if(node != prj.topNode)
1544          PathCatSlash(folder, node.path);
1545       ShellOpen(folder);
1546       return true;
1547    }
1548
1549    bool Run(MenuItem selection, Modifiers mods)
1550    {
1551       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1552       ProjectConfig config = project.config;
1553       int bitDepth = ide.workspace.bitDepth;
1554       String args = new char[maxPathLen];
1555       args[0] = '\0';
1556       if(ide.workspace.commandLineArgs)
1557          //ide.debugger.GetCommandLineArgs(args);
1558          strcpy(args, ide.workspace.commandLineArgs);
1559       if(ide.debugger.isActive)
1560          project.Run(args, compiler, config, bitDepth);
1561       /*else if(config.targetType == sharedLibrary || config.targetType == staticLibrary)
1562          MessageBox { master = ide, type = ok, text = "Run", contents = "Shared and static libraries cannot be run like executables." }.Modal();*/
1563       else if(BuildInterrim(project, run, compiler, config, bitDepth, false))
1564          project.Run(args, compiler, config, bitDepth);
1565       delete args;
1566       delete compiler;
1567       return true;
1568    }
1569
1570    bool DebugStart()
1571    {
1572       bool result = false;
1573       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1574       ProjectConfig config = project.config;
1575       int bitDepth = ide.workspace.bitDepth;
1576       bool useValgrind = ide.workspace.useValgrind;
1577       TargetTypes targetType = project.GetTargetType(config);
1578       if(targetType == sharedLibrary || targetType == staticLibrary)
1579          MessageBox { master = ide, type = ok, text = $"Run", contents = $"Shared and static libraries cannot be run like executables." }.Modal();
1580       else if(project.GetCompress(config))
1581          MessageBox { master = ide, text = $"Starting Debug", contents = $"Debugging compressed applications is not supported\n" }.Modal();
1582       else if(project.GetDebug(config) ||
1583          MessageBox { master = ide, type = okCancel, text = $"Starting Debug", contents = $"Attempting to debug non-debug configuration\nProceed anyways?" }.Modal() == ok)
1584       {
1585          if(/*!IsProjectModified() ||*/ BuildInterrim(project, start, compiler, config, bitDepth, false))
1586          {
1587             if(compiler.type.isVC)
1588             {
1589                //bool result = false;
1590                char oldwd[MAX_LOCATION];
1591                PathBackup pathBackup { };
1592                char command[MAX_LOCATION];
1593
1594                ide.SetPath(false, compiler, config, bitDepth);
1595
1596                GetWorkingDir(oldwd, sizeof(oldwd));
1597                ChangeWorkingDir(project.topNode.path);
1598
1599                sprintf(command, "%s /useenv %s.sln /projectconfig \"%s|Win32\" /command \"%s\"" , "devenv", project.name, config.name, "Debug.Start");
1600                Execute(command);
1601                ChangeWorkingDir(oldwd);
1602
1603                delete pathBackup;
1604             }
1605             else
1606             {
1607                ide.debugger.Start(compiler, config, bitDepth, useValgrind);
1608                result = true;
1609             }
1610          }
1611       }
1612       delete compiler;
1613       return result;
1614    }
1615
1616    void GoToError(const char * line, const bool noParsing)
1617    {
1618       char * colon;
1619
1620       while(isspace(*line)) line++;
1621       colon = strstr(line, ":");
1622
1623       {
1624          int lineNumber = 0;
1625          int col = 1;
1626          bool lookForLineNumber = true;
1627
1628          // deal with linking error
1629          if(colon && colon[1] == ' ')
1630          {
1631             colon = strstr(colon + 1, ":");
1632             if(colon && !colon[1])
1633             {
1634                colon = strstr(line, ":");
1635                lookForLineNumber = false;
1636             }
1637             else if(colon && !isdigit(colon[1]))
1638             {
1639                line = colon + 1;
1640                colon = strstr(line, ":");
1641             }
1642          }
1643          // Don't be mistaken by the drive letter colon
1644          if(colon && (colon[1] == '/' || colon[1] == '\\'))
1645             colon = strstr(colon + 1, ":");
1646          if(colon && lookForLineNumber)
1647          {
1648             char * comma;
1649 #if 0 // MSVS Errors -- todo fix this later
1650             char * par = RSearchString(line, "(", colon - line, true, false);
1651             if(par && strstr(par, ")"))
1652                colon = par;
1653             else if((colon+1)[0] == ' ')
1654             {
1655                // NOTE: This is the same thing as saying 'colon = line'
1656                for( ; colon != line; colon--)
1657                   /*if(*colon == '(')
1658                      break*/;
1659             }
1660 #endif
1661             lineNumber = atoi(colon + 1);
1662             /*
1663             comma = strchr(colon, ',');
1664             if(comma)
1665                col = atoi(comma+1);
1666             */
1667             comma = strchr(colon+1, ':');
1668             if(comma)
1669                col = atoi(comma+1);
1670          }
1671
1672          {
1673             char moduleName[MAX_LOCATION], filePath[MAX_LOCATION] = "";
1674             char ext[MAX_EXTENSION] = "";
1675             char * bracket;
1676             ProjectNode node = null;
1677             if(colon)
1678             {
1679                char * inFileIncludedFrom = strstr(line, stringInFileIncludedFrom);
1680                char * from = strstr(line, "from ");
1681                char * start = inFileIncludedFrom ? inFileIncludedFrom + strlen(stringInFileIncludedFrom) : from ? from + strlen("from ") : line;
1682                int len;
1683                if(colon < start)
1684                   start = line;
1685                len = Min((int)(colon - start), MAX_LOCATION-1);
1686                // Cut module name
1687                strncpy(moduleName, start, len);
1688                moduleName[len] = '\0';
1689             }
1690             else
1691                strcpy(moduleName, line);
1692
1693             if(!colon)
1694             {
1695                char * msg;
1696                if((msg = strstr(moduleName, " - ")))
1697                {
1698                   bool found = false;
1699                   msg[0] = '\0';
1700                   for(prj : ide.workspace.projects)
1701                   {
1702                      strcpy(filePath, prj.topNode.path);
1703                      PathCatSlash(filePath, moduleName);
1704                      if(FileExists(filePath).isFile)
1705                      {
1706                         found = true;
1707                         break;
1708                      }
1709                   }
1710                   if(!found)
1711                   {
1712                      msg[0] = ' ';
1713                      filePath[0] = '\0';
1714                   }
1715                }
1716                else if((msg = strstr(moduleName, "...")) && (colon = strchr(moduleName, ' ')) && (++colon)[0])
1717                {
1718                   bool found = false;
1719                   msg[0] = '\0';
1720                   for(prj : ide.workspace.projects)
1721                   {
1722                      if((node = prj.resNode.Find(colon, true)))
1723                      {
1724                         strcpy(filePath, prj.topNode.path);
1725                         PathCatSlash(filePath, node.path);
1726                         PathCatSlash(filePath, node.name);
1727
1728                         found = true;
1729                         break;
1730                      }
1731                   }
1732                   if(!found)
1733                   {
1734                      msg[0] = '.';
1735                      filePath[0] = '\0';
1736                   }
1737                }
1738                colon = null;
1739             }
1740             // Remove stuff in brackets
1741             /*
1742             bracket = strstr(moduleName, "(");
1743             if(bracket) *bracket = '\0';
1744             */
1745             MakeSlashPath(moduleName);
1746             GetExtension(moduleName, ext);
1747
1748             if(!colon && !filePath[0])
1749             {
1750                // Check if it's one of our modules
1751                node = project.topNode.Find(moduleName, false);
1752                if(node)
1753                {
1754                   strcpy(filePath, node.path);
1755                   PathCatSlash(filePath, node.name);
1756                }
1757                else
1758                {
1759                   char ext[MAX_EXTENSION];
1760                   GetExtension(fileName, ext);
1761                   {
1762                      DotMain dotMain = DotMain::FromFileName(moduleName);
1763                      IntermediateFileType type = IntermediateFileType::FromExtension(ext);
1764                      ProjectConfig config = null;
1765                      if(type)
1766                      {
1767                         for(prj : ide.workspace.projects; prj.lastBuildConfigName)
1768                         {
1769                            if((config = prj.GetConfig(prj.lastBuildConfigName)))
1770                               node = prj.FindNodeByObjectFileName(moduleName, type, dotMain, config);
1771                            if(node)
1772                               break;
1773                         }
1774                      }
1775                      if(node)
1776                      {
1777                         char name[MAX_FILENAME];
1778                         Project project = node.project;
1779                         CompilerConfig compiler = ideSettings.GetCompilerConfig(project.lastBuildCompilerName);
1780                         if(compiler)
1781                         {
1782                            int bitDepth = ide.workspace.bitDepth;
1783                            DirExpression objDir = project.GetObjDir(compiler, config, bitDepth);
1784                            strcpy(filePath, project.topNode.path);
1785                            PathCatSlash(filePath, objDir.dir);
1786                            node.GetObjectFileName(name, project.configsNameCollisions[config ? config.name : ""], type, dotMain);
1787                            PathCatSlash(filePath, name);
1788                            delete objDir;
1789                         }
1790                         delete compiler;
1791                      }
1792                   }
1793                }
1794                if(!node)
1795                   filePath[0] = '\0';
1796             }
1797             if(!strcmp(ext, "a") || !strcmp(ext, "o") || !strcmp(ext, "lib") ||
1798                   !strcmp(ext, "dll") || !strcmp(ext, "exe") || !strcmp(ext, "mo"))
1799                moduleName[0] = 0;    // Avoid opening binary files
1800             if(moduleName[0])
1801             {
1802                CodeEditor codeEditor;
1803                if(!filePath[0])
1804                {
1805                   strcpy(filePath, project.topNode.path);
1806                   PathCatSlash(filePath, moduleName);
1807                }
1808
1809                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal, noParsing);
1810                if(!codeEditor && !strcmp(ext, "c"))
1811                {
1812                   char ecName[MAX_LOCATION];
1813                   ChangeExtension(filePath, "ec", ecName);
1814                   codeEditor = (CodeEditor)ide.OpenFile(ecName, normal, true, null, no, normal, noParsing);
1815                }
1816                if(!codeEditor)
1817                {
1818                   char path[MAX_LOCATION];
1819                   // TOFIX: Improve on this, don't use only filename, make a function
1820                   if(ide && ide.workspace)
1821                   {
1822                      for(prj : ide.workspace.projects)
1823                      {
1824                         ProjectNode node;
1825                         MakePathRelative(filePath, prj.topNode.path, path);
1826
1827                         if((node = prj.topNode.FindWithPath(path, false)))
1828                         {
1829                            strcpy(filePath, prj.topNode.path);
1830                            PathCatSlash(filePath, node.path);
1831                            PathCatSlash(filePath, node.name);
1832                            codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal, noParsing);
1833                            if(codeEditor)
1834                               break;
1835                         }
1836                      }
1837                      if(!codeEditor && !strchr(moduleName, '/') && !strchr(moduleName, '\\'))
1838                      {
1839                         for(prj : ide.workspace.projects)
1840                         {
1841                            ProjectNode node;
1842                            if((node = prj.topNode.Find(moduleName, false)))
1843                            {
1844                               strcpy(filePath, prj.topNode.path);
1845                               PathCatSlash(filePath, node.path);
1846                               PathCatSlash(filePath, node.name);
1847                               codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, true, null, no, normal, noParsing);
1848                               if(codeEditor)
1849                                  break;
1850                            }
1851                         }
1852                      }
1853                   }
1854                }
1855                if(codeEditor && lineNumber)
1856                {
1857                   EditBox editBox = codeEditor.editBox;
1858                   editBox.GoToLineNum(lineNumber - 1);
1859                   editBox.GoToPosition(editBox.line, lineNumber - 1, col ? (col - 1) : 0);
1860                }
1861             }
1862          }
1863       }
1864    }
1865
1866    bool OpenNode(ProjectNode node, bool noParsing)
1867    {
1868       char filePath[MAX_LOCATION];
1869       node.GetFullFilePath(filePath);
1870       return ide.OpenFile(filePath, normal, true/*false Why was it opening hidden?*/, null, something, normal, noParsing) ? true : false;
1871    }
1872
1873    void AddNode(ProjectNode node, DataRow addTo)
1874    {
1875       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1876
1877       row.tag = (int64)node;
1878       node.row = row;
1879
1880       if(node.type == resources)
1881          resourceRow = row;
1882
1883       row.SetData(null, node);
1884
1885       if(node.files && node.files.first && node.parent &&
1886             !(!node.parent.parent &&
1887                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") ||
1888                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1889          row.collapsed = true;
1890       else if(node.type == folder)
1891          node.icon = openFolder;
1892
1893       if(node.files)
1894       {
1895          for(child : node.files)
1896             AddNode(child, row);
1897       }
1898    }
1899
1900    void DeleteNode(ProjectNode projectNode)
1901    {
1902       if(projectNode.files)
1903       {
1904          ProjectNode child;
1905          while(child = projectNode.files.first)
1906             DeleteNode(child);
1907       }
1908       fileList.DeleteRow(projectNode.row);
1909       projectNode.Delete();
1910    }
1911
1912    bool ProjectSave(MenuItem selection, Modifiers mods)
1913    {
1914       DataRow row = fileList.currentRow;
1915       ProjectNode node = row ? (ProjectNode)row.tag : null;
1916       Project prj = node ? node.project : null;
1917       if(prj)
1918       {
1919          prj.StopMonitoring();
1920          if(prj.Save(prj.filePath))
1921          {
1922             Project modPrj = null;
1923             prj.topNode.modified = false;
1924             for(p : ide.workspace.projects)
1925             {
1926                if(p.topNode.modified)
1927                {
1928                   modPrj = p;
1929                   break;
1930                }
1931             }
1932             if(!modPrj)
1933                modifiedDocument = false;
1934             Update(null);
1935          }
1936          prj.StartMonitoring();
1937       }
1938       return true;
1939    }
1940
1941    bool ShowOutputBuildLog(bool cleanLog)
1942    {
1943       OutputView output = ide.outputView;
1944       if(cleanLog)
1945          output.ShowClearSelectTab(build);
1946       else
1947       {
1948          output.SelectTab(build);
1949          output.Show();
1950       }
1951       return true;
1952    }
1953
1954    bool DebugRestart()
1955    {
1956       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1957       ProjectConfig config = project.config;
1958       int bitDepth = ide.workspace.bitDepth;
1959       bool useValgrind = ide.workspace.useValgrind;
1960
1961       bool result = false;
1962       if(/*!IsProjectModified() ||*/ BuildInterrim(project, restart, compiler, config, bitDepth, false))
1963       {
1964          // For Restart, compiler and config will only be used if for
1965          // whatever reason (if at all possible) the Debugger is in a
1966          // 'terminated' or 'none' state
1967          ide.debugger.Restart(compiler, config, bitDepth, useValgrind);
1968          result = true;
1969       }
1970
1971       delete compiler;
1972       return result;
1973    }
1974
1975    bool DebugResume()
1976    {
1977       ide.debugger.Resume();
1978       return true;
1979    }
1980
1981    bool DebugBreak()
1982    {
1983       ide.debugger.Break();
1984       return true;
1985    }
1986
1987    bool DebugStop()
1988    {
1989       ide.debugger.Stop();
1990       return true;
1991    }
1992
1993    bool DebugStepInto()
1994    {
1995       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
1996       ProjectConfig config = project.config;
1997       int bitDepth = ide.workspace.bitDepth;
1998       bool useValgrind = ide.workspace.useValgrind;
1999
2000       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, false)))
2001          ide.debugger.StepInto(compiler, config, bitDepth, useValgrind);
2002       delete compiler;
2003       return true;
2004    }
2005
2006    bool DebugStepOver(bool skip)
2007    {
2008       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
2009       ProjectConfig config = project.config;
2010       int bitDepth = ide.workspace.bitDepth;
2011       bool useValgrind = ide.workspace.useValgrind;
2012
2013       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, false)))
2014          ide.debugger.StepOver(compiler, config, bitDepth, useValgrind, skip);
2015
2016       delete compiler;
2017       return true;
2018    }
2019
2020    bool DebugStepUntil(bool skip)
2021    {
2022       CompilerConfig compiler = ideSettings.GetCompilerConfig(ide.workspace.compiler);
2023       ProjectConfig config = project.config;
2024       int bitDepth = ide.workspace.bitDepth;
2025       bool useValgrind = ide.workspace.useValgrind;
2026
2027       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, false)))
2028          ide.debugger.StepUntil(compiler, config, bitDepth, useValgrind, skip);
2029
2030       delete compiler;
2031       return true;
2032    }
2033
2034    bool DebugStepOut(bool skip)
2035    {
2036       ide.debugger.StepOut(skip);
2037       return true;
2038    }
2039
2040    void ImportFolder(ProjectNode toNode)
2041    {
2042       if(toNode)
2043       {
2044          char path[MAX_LOCATION];
2045          char currentDir[MAX_LOCATION];
2046          ProjectNode node = toNode;
2047          FileDialog fileDialog = importFileDialog;
2048          fileDialog.master = parent;
2049          while(node)
2050          {
2051             node.GetFullFilePath(path);
2052             while(path[0])
2053             {
2054                StripLastDirectory(path, path);
2055                if(FileExists(path).isDirectory) break;
2056             }
2057             if(path[0] || node == toNode.project.topNode)
2058                node = null;
2059             else
2060                node = toNode.project.topNode;
2061          }
2062          MakeSystemPath(path);
2063          StripLastDirectory(path, currentDir);
2064          fileDialog.currentDirectory = currentDir[0] ? currentDir : path;
2065          fileDialog.filePath = path;
2066          if(fileDialog.Modal() == ok)
2067          {
2068             ImportFolderFSI fsi { projectView = this };
2069             fsi.stack.Add(toNode);
2070             fsi.Iterate(fileDialog.filePath);
2071             delete fsi;
2072          }
2073       }
2074    }
2075
2076    ProjectNode NewFolder(ProjectNode parentNode, char * name, bool showProperties)
2077    {
2078       if(parentNode)
2079       {
2080          ProjectNode folderNode;
2081          Project prj = parentNode.project;
2082          int c;
2083          ProjectNode after = null;
2084          for(node : parentNode.files)
2085          {
2086             if(node.type != folder)
2087                break;
2088             after = node;
2089          }
2090
2091          if(name && name[0])
2092             folderNode = parentNode.Add(prj, name, after, folder, folder, true);
2093          else
2094          {
2095             for(c = 0; c < 100; c++)
2096             {
2097                char string[16];
2098                sprintf(string, c ? "New Folder (%d)" : "New Folder", c);
2099                if((folderNode = parentNode.Add(prj, string, after, folder, folder, true)))
2100                   break;
2101             }
2102          }
2103
2104          if(folderNode)
2105          {
2106             NodeProperties nodeProperties;
2107             if(!showProperties)
2108             {
2109                modifiedDocument = true;
2110                prj.topNode.modified = true;
2111             }
2112             Update(null);
2113             folderNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
2114             folderNode.row.tag = (int64)folderNode;
2115
2116             folderNode.row.SetData(null, folderNode);
2117             fileList.currentRow = folderNode.row;
2118
2119             if(showProperties)
2120             {
2121                nodeProperties = NodeProperties
2122                {
2123                   parent, this, mode = newFolder, node = folderNode;
2124                   position = { position.x + 100, position.y + 100 };
2125                };
2126                nodeProperties.Create();   // Modal?
2127             }
2128             return folderNode;
2129          }
2130       }
2131       return null;
2132    }
2133
2134    void AddFiles(bool resources)
2135    {
2136       FileDialog fileDialog = (!resources) ? this.fileDialog : resourceFileDialog;
2137       fileDialog.type = multiOpen;
2138       fileDialog.text = !resources ? $"Add Files to Project" : $"Add Resources to Project";
2139       fileDialog.master = parent;
2140
2141       if(fileDialog.Modal() == ok)
2142       {
2143          int c;
2144          DataRow row = fileList.currentRow;
2145          ProjectNode parentNode = (ProjectNode)row.tag;
2146          bool addFailed = false;
2147          int numSelections = fileDialog.numSelections;
2148          char ** multiFilePaths = fileDialog.multiFilePaths;
2149
2150          Array<String> nameConflictFiles { };
2151
2152          for(c = 0; c < numSelections; c++)
2153          {
2154             char * filePath = multiFilePaths[c];
2155             FileAttribs exists = FileExists(filePath);
2156             bool addThisFile = true;
2157
2158             if(exists.isDirectory)
2159                addThisFile = false;
2160             else if(!exists)
2161             {
2162                if(MessageBox { master = ide, type = yesNo, text = filePath,
2163                      contents = $"File doesn't exist. Create?" }.Modal() == yes)
2164                {
2165                   File f = FileOpen(filePath, write);
2166                   if(f)
2167                   {
2168                      addThisFile = true;
2169                      delete f;
2170                   }
2171                   else
2172                   {
2173                      MessageBox { master = ide, type = ok, text = filePath,
2174                            contents = $"Couldn't create file."}.Modal();
2175                      addThisFile = false;
2176                   }
2177                }
2178             }
2179
2180             if(addThisFile)
2181             {
2182                /*addFailed = */if(!AddFile(parentNode, filePath, resources, false))//;
2183                {
2184                   nameConflictFiles.Add(CopyString(filePath));
2185                   addFailed = true;
2186                }
2187             }
2188          }
2189          if(addFailed)
2190          {
2191             int len = 0;
2192             char * part1 = $"The following file";
2193             char * opt1 = $" was ";
2194             char * opt2 = $"s were ";
2195             char * part2 = $"not added because of identical file name conflict within the project.\n\n";
2196             char * message;
2197             len += strlen(part1);
2198             len += strlen(part2);
2199             len += nameConflictFiles.count > 1 ? strlen(opt2) : strlen(opt1);
2200             for(s : nameConflictFiles)
2201                len += strlen(s) + 1;
2202             message = new char[len + 1];
2203             strcpy(message, part1);
2204             strcat(message, nameConflictFiles.count > 1 ? opt2 : opt1);
2205             strcat(message, part2);
2206             for(s : nameConflictFiles)
2207             {
2208                strcat(message, s);
2209                strcat(message, "\n");
2210             }
2211             MessageBox { master = ide, type = ok, text = $"Name Conflict",
2212                   contents = message }.Modal();
2213             delete message;
2214          }
2215          nameConflictFiles.Free();
2216          delete nameConflictFiles;
2217       }
2218    }
2219
2220    ProjectNode AddFile(ProjectNode parentNode, char * filePath, bool resources, bool isTemporary)
2221    {
2222       ProjectNode result = null;
2223       ProjectNode after = null;
2224       for(node : parentNode.files)
2225       {
2226          if(node.type != folder && node.type != file && node.type)
2227             break;
2228          after = node;
2229       }
2230
2231       result = parentNode.Add(parentNode.project, filePath, after, file, NodeIcons::SelectFileIcon(filePath), !resources);
2232
2233       if(result)
2234       {
2235          if(!isTemporary)
2236          {
2237             modifiedDocument = true;
2238             parentNode.project.topNode.modified = true;
2239             parentNode.project.ModifiedAllConfigs(true, false, true, true);
2240          }
2241          Update(null);
2242          result.row = parentNode.row.AddRowAfter(after ? after.row : null);
2243          result.row.tag = (int64)result;
2244          result.row.SetData(null, result);
2245       }
2246       return result;
2247    }
2248
2249    CodeEditor CreateNew(char * upper, char * lower, char * base, char * className)
2250    {
2251       CodeEditor codeEditor = null;
2252       ProjectNode projectNode;
2253       ProjectNode after = null;
2254       DataRow row = fileList.currentRow;
2255       ProjectNode parentNode;
2256       int c;
2257
2258       if(!row) row = project.topNode.row;
2259
2260       parentNode = (ProjectNode)row.tag;
2261
2262       for(node : parentNode.files)
2263       {
2264          if(node.type != folder && node.type != file && node.type)
2265             break;
2266          after = node;
2267       }
2268       for(c = 1; c < 100; c++)
2269       {
2270          char string[16];
2271          sprintf(string, c ? "%s%d.ec" : "%s.ec", lower, c);
2272          if((projectNode = parentNode.Add(project, string, after, file, genFile, true)))
2273             break;
2274       }
2275       if(projectNode)
2276       {
2277          char name[256];
2278          char filePath[MAX_LOCATION];
2279
2280          modifiedDocument = true;
2281          project.topNode.modified = true;
2282          Update(null);
2283          project.ModifiedAllConfigs(true, false, false, true);
2284          projectNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
2285          projectNode.row.tag =(int64)projectNode;
2286
2287          projectNode.row.SetData(null, projectNode);
2288          fileList.currentRow = projectNode.row;
2289
2290          strcpy(filePath, project.topNode.path);
2291          PathCat(filePath, projectNode.path);
2292          PathCat(filePath, projectNode.name);
2293
2294          codeEditor = (CodeEditor)ide.FindWindow(filePath);
2295          if(!codeEditor)
2296          {
2297             Class baseClass = eSystem_FindClass(__thisModule, base);
2298             subclass(ClassDesignerBase) designerClass = eClass_GetDesigner(baseClass);
2299             if(designerClass)
2300             {
2301                codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal, false);
2302                strcpy(name, projectNode.name);
2303                sprintf(name, "%s%d", upper, c);
2304                if(className)
2305                   strcpy(className, name);
2306
2307                designerClass.CreateNew(codeEditor.editBox, codeEditor.clientSize, name, base);
2308
2309                //codeEditor.modifiedDocument = false;
2310                //codeEditor.designer.modifiedDocument = false;
2311
2312                //Code_EnsureUpToDate(codeEditor);
2313             }
2314          }
2315          else // TODO: fix no symbols generated when ommiting {} for following else
2316          {
2317             codeEditor = (CodeEditor)ide.OpenFile(filePath, normal, false, null, whatever, normal, false);
2318          }
2319          ide.sheet.visible = true;
2320          ide.sheet.Activate();
2321          if(codeEditor)
2322          {
2323             codeEditor.ViewDesigner();
2324             codeEditor.codeModified = true;
2325          }
2326       }
2327       //visible = false;
2328       return codeEditor;
2329    }
2330
2331    // Returns true if we opened something
2332    bool OpenSelectedNodes(bool noParsing)
2333    {
2334       bool result = false;
2335       OldList selection;
2336       OldLink item;
2337
2338       fileList.GetMultiSelection(selection);
2339       for(item = selection.first; item; item = item.next)
2340       {
2341          DataRow row = item.data;
2342          ProjectNode node = (ProjectNode)row.tag;
2343          if(node.type == file)
2344          {
2345             OpenNode(node, noParsing);
2346             result = true;
2347          }
2348       }
2349       selection.Free(null);
2350       ide.RepositionWindows(false);
2351       return result;
2352    }
2353
2354    void RemoveSelectedNodes()
2355    {
2356       OldList selection;
2357       OldLink item, next;
2358
2359       fileList.GetMultiSelection(selection);
2360
2361       // Remove children of parents we're deleting
2362       for(item = selection.first; item; item = next)
2363       {
2364          OldLink i;
2365          DataRow row = item.data;
2366          ProjectNode n, node = (ProjectNode)row.tag;
2367          bool remove = false;
2368
2369          next = item.next;
2370          for(i = selection.first; i && !remove; i = i.next)
2371          {
2372             ProjectNode iNode = (ProjectNode)((DataRow)i.data).tag;
2373             for(n = node.parent; n; n = n.parent)
2374             {
2375                if(iNode == n)
2376                {
2377                   remove = true;
2378                   break;
2379                }
2380             }
2381          }
2382          if(remove)
2383             selection.Delete(item);
2384       }
2385
2386       for(item = selection.first; item; item = item.next)
2387       {
2388          DataRow row = item.data;
2389          ProjectNode node = (ProjectNode)row.tag;
2390          ProjectNode resNode;
2391          for(resNode = node.parent; resNode; resNode = resNode.parent)
2392             if(resNode.type == resources)
2393                break;
2394          if(node.type == file)
2395          {
2396             Project prj = node.project;
2397             DeleteNode(node);
2398             modifiedDocument = true;
2399             prj.topNode.modified = true;
2400             Update(null);
2401             prj.ModifiedAllConfigs(true, false, true, true);
2402          }
2403          else if(node.type == folder)
2404          {
2405             char message[1024];
2406             sprintf(message, $"Are you sure you want to remove the folder \"%s\"\n"
2407                   "and all of its contents from the project?", node.name);
2408             if(MessageBox { master = ide, type = yesNo, text = $"Delete Folder", contents = message }.Modal() == yes)
2409             {
2410                Project prj = node.project;
2411                if(node.containsFile)
2412                   prj.ModifiedAllConfigs(true, false, true, true);
2413                DeleteNode(node);
2414                modifiedDocument = true;
2415                prj.topNode.modified = true;
2416             }
2417          }
2418          else if(node.type == project && node != project.topNode && !buildInProgress)
2419          {
2420             char message[1024];
2421             Project prj = null;
2422             for(p : workspace.projects)
2423             {
2424                if(p.topNode == node)
2425                {
2426                   prj = p;
2427                   break;
2428                }
2429             }
2430             sprintf(message, $"Are you sure you want to remove the \"%s\" project\n" "from this workspace?", node.name);
2431             if(MessageBox { master = ide, type = yesNo, text = $"Remove Project", contents = message }.Modal() == yes)
2432             {
2433                // THIS GOES FIRST!
2434                DeleteNode(node);
2435                if(prj)
2436                   workspace.RemoveProject(prj);
2437                //modifiedDocument = true; // when project view is a workspace view
2438             }
2439          }
2440       }
2441       selection.Free(null);
2442    }
2443 }