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