c66acd35716826eae6cbb0bcb99e180712c1b4f5
[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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.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 = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
1613       ProjectConfig config = project.config;
1614       int bitDepth = ide.workspace.bitDepth;
1615       bool shellOpen = compiler.hasDocumentOutput;
1616       String args = new char[maxPathLen];
1617       args[0] = '\0';
1618       if(ide.workspace.commandLineArgs)
1619          //ide.debugger.GetCommandLineArgs(args);
1620          strcpy(args, ide.workspace.commandLineArgs);
1621       if(ide.debugger.isActive)
1622          project.Run(args, compiler, config, bitDepth, shellOpen);
1623       /*else if(config.targetType == sharedLibrary || config.targetType == staticLibrary)
1624          MessageBox { master = ide, type = ok, text = "Run", contents = "Shared and static libraries cannot be run like executables." }.Modal();*/
1625       else if(BuildInterrim(project, run, compiler, config, bitDepth, normal))
1626          project.Run(args, compiler, config, bitDepth, shellOpen);
1627       delete args;
1628       delete compiler;
1629       return true;
1630    }
1631
1632    bool DebugStart()
1633    {
1634       bool result = false;
1635       CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
1636       ProjectConfig config = project.config;
1637       int bitDepth = ide.workspace.bitDepth;
1638       bool useValgrind = ide.workspace.useValgrind;
1639       TargetTypes targetType = project.GetTargetType(config);
1640       if(targetType == sharedLibrary || targetType == staticLibrary)
1641          MessageBox { master = ide, type = ok, text = $"Run", contents = $"Shared and static libraries cannot be run like executables." }.Modal();
1642       else if(project.GetCompress(config))
1643          MessageBox { master = ide, text = $"Starting Debug", contents = $"Debugging compressed applications is not supported\n" }.Modal();
1644       else if(project.GetDebug(config) ||
1645          MessageBox { master = ide, type = okCancel, text = $"Starting Debug", contents = $"Attempting to debug non-debug configuration\nProceed anyways?" }.Modal() == ok)
1646       {
1647          if(/*!IsProjectModified() ||*/ BuildInterrim(project, start, compiler, config, bitDepth, normal))
1648          {
1649             if(compiler.type.isVC)
1650             {
1651                //bool result = false;
1652                char oldwd[MAX_LOCATION];
1653                PathBackup pathBackup { };
1654                char command[MAX_LOCATION];
1655
1656                ide.SetPath(false, compiler, config, bitDepth);
1657
1658                GetWorkingDir(oldwd, sizeof(oldwd));
1659                ChangeWorkingDir(project.topNode.path);
1660
1661                sprintf(command, "%s /useenv %s.sln /projectconfig \"%s|Win32\" /command \"%s\"" , "devenv", project.name, config.name, "Debug.Start");
1662                Execute(command);
1663                ChangeWorkingDir(oldwd);
1664
1665                delete pathBackup;
1666             }
1667             else if(compiler.hasDocumentOutput)
1668             {
1669                project.Run("", compiler, config, bitDepth, true);
1670             }
1671             else
1672             {
1673                ide.debugger.Start(compiler, config, bitDepth, useValgrind);
1674                result = true;
1675             }
1676          }
1677       }
1678       delete compiler;
1679       return result;
1680    }
1681
1682    void GoToError(const char * line, const bool noParsing, const char * objectFileExt)
1683    {
1684       char * colon;
1685
1686       while(isspace(*line)) line++;
1687       colon = strstr(line, ":");
1688
1689       {
1690          int lineNumber = 0;
1691          int col = 1;
1692          bool lookForLineNumber = true;
1693
1694          // deal with linking error
1695          if(colon && colon[1] == ' ')
1696          {
1697             colon = strstr(colon + 1, ":");
1698             if(colon && !colon[1])
1699             {
1700                colon = strstr(line, ":");
1701                lookForLineNumber = false;
1702             }
1703             else if(colon && !isdigit(colon[1]))
1704             {
1705                line = colon + 1;
1706                colon = strstr(line, ":");
1707             }
1708          }
1709          // Don't be mistaken by the drive letter colon
1710          if(colon && (colon[1] == '/' || colon[1] == '\\'))
1711             colon = strstr(colon + 1, ":");
1712          if(colon && lookForLineNumber)
1713          {
1714             char * comma;
1715 #if 0 // MSVS Errors -- todo fix this later
1716             char * par = RSearchString(line, "(", colon - line, true, false);
1717             if(par && strstr(par, ")"))
1718                colon = par;
1719             else if((colon+1)[0] == ' ')
1720             {
1721                // NOTE: This is the same thing as saying 'colon = line'
1722                for( ; colon != line; colon--)
1723                   /*if(*colon == '(')
1724                      break*/;
1725             }
1726 #endif
1727             lineNumber = atoi(colon + 1);
1728             /*
1729             comma = strchr(colon, ',');
1730             if(comma)
1731                col = atoi(comma+1);
1732             */
1733             comma = strchr(colon+1, ':');
1734             if(comma)
1735                col = atoi(comma+1);
1736          }
1737
1738          {
1739             char moduleName[MAX_LOCATION], filePath[MAX_LOCATION] = "";
1740             char ext[MAX_EXTENSION] = "";
1741             ProjectNode node = null;
1742             if(colon)
1743             {
1744                const char * inFileIncludedFrom = strstr(line, stringInFileIncludedFrom);
1745                const char * from = strstr(line, "from ");
1746                const char * start = inFileIncludedFrom ? inFileIncludedFrom + strlen(stringInFileIncludedFrom) : from ? from + strlen("from ") : line;
1747                int len;
1748                if(colon < start)
1749                   start = line;
1750                len = Min((int)(colon - start), MAX_LOCATION-1);
1751                // Cut module name
1752                strncpy(moduleName, start, len);
1753                moduleName[len] = '\0';
1754             }
1755             else
1756                strcpy(moduleName, line);
1757
1758             if(!colon)
1759             {
1760                char * msg;
1761                if((msg = strstr(moduleName, " - ")))
1762                {
1763                   bool found = false;
1764                   msg[0] = '\0';
1765                   for(prj : ide.workspace.projects)
1766                   {
1767                      strcpy(filePath, prj.topNode.path);
1768                      PathCatSlash(filePath, moduleName);
1769                      if(FileExists(filePath).isFile)
1770                      {
1771                         found = true;
1772                         break;
1773                      }
1774                   }
1775                   if(!found)
1776                   {
1777                      msg[0] = ' ';
1778                      filePath[0] = '\0';
1779                   }
1780                }
1781                else if((msg = strstr(moduleName, "...")) && (colon = strchr(moduleName, ' ')) && (++colon)[0])
1782                {
1783                   bool found = false;
1784                   msg[0] = '\0';
1785                   for(prj : ide.workspace.projects)
1786                   {
1787                      if((node = prj.resNode.Find(colon, true)))
1788                      {
1789                         strcpy(filePath, prj.topNode.path);
1790                         PathCatSlash(filePath, node.path);
1791                         PathCatSlash(filePath, node.name);
1792
1793                         found = true;
1794                         break;
1795                      }
1796                   }
1797                   if(!found)
1798                   {
1799                      msg[0] = '.';
1800                      filePath[0] = '\0';
1801                   }
1802                }
1803                colon = null;
1804             }
1805             // Remove stuff in brackets
1806             /*
1807             bracket = strstr(moduleName, "(");
1808             if(bracket) *bracket = '\0';
1809             */
1810             MakeSlashPath(moduleName);
1811             GetExtension(moduleName, ext);
1812
1813             if(!colon && !filePath[0])
1814             {
1815                // Check if it's one of our modules
1816                node = project.topNode.Find(moduleName, false);
1817                if(node)
1818                {
1819                   strcpy(filePath, node.path);
1820                   PathCatSlash(filePath, node.name);
1821                }
1822                else
1823                {
1824                   char ext[MAX_EXTENSION];
1825                   GetExtension(moduleName, ext);
1826                   {
1827                      DotMain dotMain = DotMain::FromFileName(moduleName);
1828                      IntermediateFileType type = IntermediateFileType::FromExtension(ext);
1829                      ProjectConfig config = null;
1830                      if(type)
1831                      {
1832                         for(prj : ide.workspace.projects; prj.lastBuildConfigName)
1833                         {
1834                            if((config = prj.GetConfig(prj.lastBuildConfigName)))
1835                               node = prj.FindNodeByObjectFileName(moduleName, type, dotMain, config, objectFileExt);
1836                            if(node)
1837                               break;
1838                         }
1839                      }
1840                      if(node)
1841                      {
1842                         char name[MAX_FILENAME];
1843                         Project project = node.project;
1844                         CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(project.lastBuildCompilerName);
1845                         if(compiler)
1846                         {
1847                            int bitDepth = ide.workspace.bitDepth;
1848                            const char * objectFileExt = compiler ? compiler.objectFileExt : objectDefaultFileExt;
1849                            DirExpression objDir = project.GetObjDir(compiler, config, bitDepth);
1850                            strcpy(filePath, project.topNode.path);
1851                            PathCatSlash(filePath, objDir.dir);
1852                            node.GetObjectFileName(name, project.configsNameCollisions[config ? config.name : ""], type, dotMain, objectFileExt);
1853                            PathCatSlash(filePath, name);
1854                            delete objDir;
1855                         }
1856                         delete compiler;
1857                      }
1858                   }
1859                }
1860                if(!node)
1861                   filePath[0] = '\0';
1862             }
1863             if(!strcmp(ext, "a") || !strcmp(ext, "o") || !strcmp(ext, "lib") ||
1864                   !strcmp(ext, "dll") || !strcmp(ext, "exe") || !strcmp(ext, "mo"))
1865                moduleName[0] = 0;    // Avoid opening binary files
1866             if(moduleName[0])
1867             {
1868                CodeEditor codeEditor = null;
1869
1870                if(ide.GoToCodeSelectFile(moduleName, null, project, null, filePath, objectFileExt))
1871                {
1872                   codeEditor = (CodeEditor)ide.OpenFile(filePath, false, true, null, no, normal, noParsing);
1873                }
1874
1875                if(!codeEditor)
1876                {
1877                   if(!filePath[0])
1878                   {
1879                      strcpy(filePath, project.topNode.path);
1880                      PathCatSlash(filePath, moduleName);
1881                   }
1882
1883                   codeEditor = (CodeEditor)ide.OpenFile(filePath, false, true, null, no, normal, noParsing);
1884                   if(!codeEditor && !strcmp(ext, "c"))
1885                   {
1886                      char ecName[MAX_LOCATION];
1887                      ChangeExtension(filePath, "ec", ecName);
1888                      codeEditor = (CodeEditor)ide.OpenFile(ecName, false, true, null, no, normal, noParsing);
1889                   }
1890                   if(!codeEditor)
1891                   {
1892                      char path[MAX_LOCATION];
1893                      // TOFIX: Improve on this, don't use only filename, make a function
1894                      if(ide && ide.workspace)
1895                      {
1896                         for(prj : ide.workspace.projects)
1897                         {
1898                            ProjectNode node;
1899                            MakePathRelative(filePath, prj.topNode.path, path);
1900
1901                            if((node = prj.topNode.FindWithPath(path, false)))
1902                            {
1903                               strcpy(filePath, prj.topNode.path);
1904                               PathCatSlash(filePath, node.path);
1905                               PathCatSlash(filePath, node.name);
1906                               codeEditor = (CodeEditor)ide.OpenFile(filePath, false, true, null, no, normal, noParsing);
1907                               if(codeEditor)
1908                                  break;
1909                            }
1910                         }
1911                         if(!codeEditor && (strchr(moduleName, '/') || strchr(moduleName, '\\')))
1912                         {
1913                            for(prj : ide.workspace.projects)
1914                            {
1915                               ProjectNode node;
1916                               if((node = prj.topNode.FindWithPath(moduleName, false)))
1917                               {
1918                                  strcpy(filePath, prj.topNode.path);
1919                                  PathCatSlash(filePath, node.path);
1920                                  PathCatSlash(filePath, node.name);
1921                                  codeEditor = (CodeEditor)ide.OpenFile(filePath, false, true, null, no, normal, noParsing);
1922                                  if(codeEditor)
1923                                     break;
1924                               }
1925                            }
1926                         }
1927                         if(!codeEditor)
1928                         {
1929                            GetLastDirectory(moduleName, moduleName);
1930                            for(prj : ide.workspace.projects)
1931                            {
1932                               ProjectNode node;
1933                               if((node = prj.topNode.Find(moduleName, false)))
1934                               {
1935                                  strcpy(filePath, prj.topNode.path);
1936                                  PathCatSlash(filePath, node.path);
1937                                  PathCatSlash(filePath, node.name);
1938                                  codeEditor = (CodeEditor)ide.OpenFile(filePath, false, true, null, no, normal, noParsing);
1939                                  if(codeEditor)
1940                                     break;
1941                               }
1942                            }
1943                         }
1944                      }
1945                   }
1946                }
1947                if(codeEditor && lineNumber)
1948                {
1949                   EditBox editBox = codeEditor.editBox;
1950                   if(editBox.GoToLineNum(lineNumber - 1))
1951                      editBox.GoToPosition(editBox.line, lineNumber - 1, col ? (col - 1) : 0);
1952                }
1953             }
1954          }
1955       }
1956    }
1957
1958    bool OpenNode(ProjectNode node, bool noParsing)
1959    {
1960       char filePath[MAX_LOCATION];
1961       char ext[MAX_EXTENSION];
1962       node.GetFullFilePath(filePath, true);
1963
1964       GetExtension(filePath, ext);
1965       if(binaryDocExt.Find(ext))
1966       {
1967          ShellOpen(filePath);
1968          return true;
1969       }
1970       else
1971          return ide.OpenFile(filePath, false, true/*false Why was it opening hidden?*/, null, something, normal, noParsing) ? true : false;
1972    }
1973
1974    void AddNode(ProjectNode node, DataRow addTo)
1975    {
1976       DataRow row = addTo ? addTo.AddRow() : fileList.AddRow();
1977
1978       row.tag = (int64)(intptr)node;
1979       node.row = row;
1980
1981       if(node.type == resources)
1982          resourceRow = row;
1983
1984       row.SetData(null, node);
1985
1986       if(node.files && node.files.first && node.parent &&
1987             !(!node.parent.parent &&
1988                (!strcmpi(node.name, "notes") || !strcmpi(node.name, "sources") ||
1989                   !strcmpi(node.name, "src") || !strcmpi(node.name, "tools"))))
1990          row.collapsed = true;
1991       else if(node.type == folder)
1992          node.icon = openFolder;
1993
1994       if(node.files)
1995       {
1996          for(child : node.files)
1997             AddNode(child, row);
1998       }
1999    }
2000
2001    void DeleteNode(ProjectNode projectNode)
2002    {
2003       if(projectNode.files)
2004       {
2005          ProjectNode child;
2006          while((child = projectNode.files.first))
2007             DeleteNode(child);
2008       }
2009       fileList.DeleteRow(projectNode.row);
2010       projectNode.Delete();
2011    }
2012
2013    bool ProjectSave(MenuItem selection, Modifiers mods)
2014    {
2015       DataRow row = fileList.currentRow;
2016       ProjectNode node = row ? (ProjectNode)(intptr)row.tag : null;
2017       Project prj = node ? node.project : null;
2018       if(prj)
2019       {
2020          prj.StopMonitoring();
2021          if(prj.Save(prj.filePath))
2022          {
2023             Project modPrj = null;
2024             prj.topNode.modified = false;
2025             for(p : ide.workspace.projects)
2026             {
2027                if(p.topNode.modified)
2028                {
2029                   modPrj = p;
2030                   break;
2031                }
2032             }
2033             if(!modPrj)
2034                modifiedDocument = false;
2035             Update(null);
2036          }
2037          prj.StartMonitoring();
2038       }
2039       return true;
2040    }
2041
2042    bool ShowOutputBuildLog(bool cleanLog)
2043    {
2044       OutputView output = ide.outputView;
2045       if(cleanLog)
2046          output.ShowClearSelectTab(build);
2047       else
2048       {
2049          output.SelectTab(build);
2050          output.Show();
2051       }
2052       return true;
2053    }
2054
2055    bool DebugRestart()
2056    {
2057       CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
2058       ProjectConfig config = project.config;
2059       int bitDepth = ide.workspace.bitDepth;
2060       bool useValgrind = ide.workspace.useValgrind;
2061
2062       bool result = false;
2063       if(/*!IsProjectModified() ||*/ BuildInterrim(project, restart, compiler, config, bitDepth, normal))
2064       {
2065          // For Restart, compiler and config will only be used if for
2066          // whatever reason (if at all possible) the Debugger is in a
2067          // 'terminated' or 'none' state
2068          ide.debugger.Restart(compiler, config, bitDepth, useValgrind);
2069          result = true;
2070       }
2071
2072       delete compiler;
2073       return result;
2074    }
2075
2076    bool DebugResume()
2077    {
2078       ide.debugger.Resume();
2079       return true;
2080    }
2081
2082    bool DebugBreak()
2083    {
2084       ide.debugger.Break();
2085       return true;
2086    }
2087
2088    bool DebugStop()
2089    {
2090       ide.debugger.Stop();
2091       return true;
2092    }
2093
2094    bool DebugStepInto()
2095    {
2096       CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
2097       ProjectConfig config = project.config;
2098       int bitDepth = ide.workspace.bitDepth;
2099       bool useValgrind = ide.workspace.useValgrind;
2100
2101       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, normal)))
2102          ide.debugger.StepInto(compiler, config, bitDepth, useValgrind);
2103       delete compiler;
2104       return true;
2105    }
2106
2107    bool DebugStepOver(bool skip)
2108    {
2109       CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
2110       ProjectConfig config = project.config;
2111       int bitDepth = ide.workspace.bitDepth;
2112       bool useValgrind = ide.workspace.useValgrind;
2113
2114       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, normal)))
2115          ide.debugger.StepOver(compiler, config, bitDepth, useValgrind, skip);
2116
2117       delete compiler;
2118       return true;
2119    }
2120
2121    bool DebugStepUntil(bool skip)
2122    {
2123       CompilerConfig compiler = ideConfig.compilers.GetCompilerConfig(ide.workspace.activeCompiler);
2124       ProjectConfig config = project.config;
2125       int bitDepth = ide.workspace.bitDepth;
2126       bool useValgrind = ide.workspace.useValgrind;
2127
2128       if((ide.debugger.isActive) || (!buildInProgress && BuildInterrim(project, start, compiler, config, bitDepth, normal)))
2129          ide.debugger.StepUntil(compiler, config, bitDepth, useValgrind, skip);
2130
2131       delete compiler;
2132       return true;
2133    }
2134
2135    bool DebugStepOut(bool skip)
2136    {
2137       ide.debugger.StepOut(skip);
2138       return true;
2139    }
2140
2141    void ImportFolder(ProjectNode toNode)
2142    {
2143       if(toNode)
2144       {
2145          char path[MAX_LOCATION];
2146          char currentDir[MAX_LOCATION];
2147          ProjectNode node = toNode;
2148          FileDialog fileDialog = importFileDialog;
2149          fileDialog.master = parent;
2150          while(node)
2151          {
2152             node.GetFullFilePath(path, true);
2153             while(path[0])
2154             {
2155                StripLastDirectory(path, path);
2156                if(FileExists(path).isDirectory) break;
2157             }
2158             if(path[0] || node == toNode.project.topNode)
2159                node = null;
2160             else
2161                node = toNode.project.topNode;
2162          }
2163          MakeSystemPath(path);
2164          StripLastDirectory(path, currentDir);
2165          fileDialog.currentDirectory = currentDir[0] ? currentDir : path;
2166          fileDialog.filePath = path;
2167          if(fileDialog.Modal() == ok)
2168          {
2169             ImportFolderFSI fsi { projectView = this };
2170             fsi.stack.Add(toNode);
2171             fsi.Iterate(fileDialog.filePath);
2172             delete fsi;
2173          }
2174       }
2175    }
2176
2177    ProjectNode NewFolder(ProjectNode parentNode, char * name, bool showProperties)
2178    {
2179       if(parentNode)
2180       {
2181          ProjectNode folderNode;
2182          Project prj = parentNode.project;
2183          int c;
2184          ProjectNode after = null;
2185          for(node : parentNode.files)
2186          {
2187             if(node.type != folder)
2188                break;
2189             after = node;
2190          }
2191
2192          if(name && name[0])
2193             folderNode = parentNode.Add(prj, name, after, folder, folder, true);
2194          else
2195          {
2196             for(c = 0; c < 100; c++)
2197             {
2198                char string[16];
2199                sprintf(string, c ? "New Folder (%d)" : "New Folder", c);
2200                if((folderNode = parentNode.Add(prj, string, after, folder, folder, true)))
2201                   break;
2202             }
2203          }
2204
2205          if(folderNode)
2206          {
2207             NodeProperties nodeProperties;
2208             if(!showProperties)
2209             {
2210                modifiedDocument = true;
2211                prj.topNode.modified = true;
2212             }
2213             Update(null);
2214             folderNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
2215             folderNode.row.tag = (int64)(intptr)folderNode;
2216
2217             folderNode.row.SetData(null, folderNode);
2218             fileList.currentRow = folderNode.row;
2219
2220             if(showProperties)
2221             {
2222                nodeProperties = NodeProperties
2223                {
2224                   parent, this, mode = newFolder, node = folderNode;
2225                   position = { position.x + 100, position.y + 100 };
2226                };
2227                nodeProperties.Create();   // Modal?
2228             }
2229             return folderNode;
2230          }
2231       }
2232       return null;
2233    }
2234
2235    void AddFiles(bool resources)
2236    {
2237       FileDialog fileDialog = (!resources) ? this.fileDialog : resourceFileDialog;
2238       fileDialog.type = multiOpen;
2239       fileDialog.text = !resources ? $"Add Files to Project" : $"Add Resources to Project";
2240       fileDialog.master = parent;
2241
2242       if(fileDialog.Modal() == ok)
2243       {
2244          int c;
2245          DataRow row = fileList.currentRow;
2246          ProjectNode parentNode = (ProjectNode)(intptr)row.tag;
2247          bool addFailed = false;
2248          int numSelections = fileDialog.numSelections;
2249          const char * const * multiFilePaths = fileDialog.multiFilePaths;
2250
2251          Array<String> nameConflictFiles { };
2252
2253          for(c = 0; c < numSelections; c++)
2254          {
2255             const char * filePath = multiFilePaths[c];
2256             FileAttribs exists = FileExists(filePath);
2257             bool addThisFile = true;
2258
2259             if(exists.isDirectory)
2260                addThisFile = false;
2261             else if(!exists)
2262             {
2263                if(MessageBox { master = ide, type = yesNo, text = filePath,
2264                      contents = $"File doesn't exist. Create?" }.Modal() == yes)
2265                {
2266                   File f = FileOpen(filePath, write);
2267                   if(f)
2268                   {
2269                      addThisFile = true;
2270                      delete f;
2271                   }
2272                   else
2273                   {
2274                      MessageBox { master = ide, type = ok, text = filePath,
2275                            contents = $"Couldn't create file."}.Modal();
2276                      addThisFile = false;
2277                   }
2278                }
2279             }
2280
2281             if(addThisFile)
2282             {
2283                /*addFailed = */if(!AddFile(parentNode, filePath, resources, false))//;
2284                {
2285                   nameConflictFiles.Add(CopyString(filePath));
2286                   addFailed = true;
2287                }
2288             }
2289          }
2290          if(addFailed)
2291          {
2292             int len = 0;
2293             const char * part1 = $"The following file";
2294             const char * opt1 = $" was ";
2295             const char * opt2 = $"s were ";
2296             const char * part2 = $"not added because of identical file name conflict within the project.\n\n";
2297             char * message;
2298             len += strlen(part1);
2299             len += strlen(part2);
2300             len += nameConflictFiles.count > 1 ? strlen(opt2) : strlen(opt1);
2301             for(s : nameConflictFiles)
2302                len += strlen(s) + 1;
2303             message = new char[len + 1];
2304             strcpy(message, part1);
2305             strcat(message, nameConflictFiles.count > 1 ? opt2 : opt1);
2306             strcat(message, part2);
2307             for(s : nameConflictFiles)
2308             {
2309                strcat(message, s);
2310                strcat(message, "\n");
2311             }
2312             MessageBox { master = ide, type = ok, text = $"Name Conflict",
2313                   contents = message }.Modal();
2314             delete message;
2315          }
2316          nameConflictFiles.Free();
2317          delete nameConflictFiles;
2318       }
2319    }
2320
2321    ProjectNode AddFile(ProjectNode parentNode, const char * filePath, bool resources, bool isTemporary)
2322    {
2323       ProjectNode result = null;
2324       ProjectNode after = null;
2325       for(node : parentNode.files)
2326       {
2327          if(node.type != folder && node.type != file && node.type)
2328             break;
2329          after = node;
2330       }
2331
2332       result = parentNode.Add(parentNode.project, filePath, after, file, NodeIcons::SelectFileIcon(filePath), !resources);
2333
2334       if(result)
2335       {
2336          if(!isTemporary)
2337          {
2338             modifiedDocument = true;
2339             parentNode.project.topNode.modified = true;
2340             parentNode.project.ModifiedAllConfigs(true, false, true, true);
2341          }
2342          Update(null);
2343          result.row = parentNode.row.AddRowAfter(after ? after.row : null);
2344          result.row.tag = (int64)(intptr)result;
2345          result.row.SetData(null, result);
2346       }
2347       return result;
2348    }
2349
2350    CodeEditor CreateNew(const char * upper, const char * lower, const char * base, char * className)
2351    {
2352       CodeEditor codeEditor = null;
2353       ProjectNode projectNode;
2354       ProjectNode after = null;
2355       DataRow row = fileList.currentRow;
2356       ProjectNode parentNode;
2357       int c;
2358
2359       if(!row) row = project.topNode.row;
2360
2361       parentNode = (ProjectNode)(intptr)row.tag;
2362
2363       for(node : parentNode.files)
2364       {
2365          if(node.type != folder && node.type != file && node.type)
2366             break;
2367          after = node;
2368       }
2369       for(c = 1; c < 100; c++)
2370       {
2371          char string[16];
2372          sprintf(string, "%s%d.ec", lower, c);
2373          if((projectNode = parentNode.Add(project, string, after, file, genFile, true)))
2374             break;
2375       }
2376       if(projectNode)
2377       {
2378          char name[256];
2379          char filePath[MAX_LOCATION];
2380
2381          modifiedDocument = true;
2382          project.topNode.modified = true;
2383          Update(null);
2384          project.ModifiedAllConfigs(true, false, false, true);
2385          projectNode.row = parentNode.row.AddRowAfter(after ? after.row : null);
2386          projectNode.row.tag = (int64)(intptr)projectNode;
2387
2388          projectNode.row.SetData(null, projectNode);
2389          fileList.currentRow = projectNode.row;
2390
2391          strcpy(filePath, project.topNode.path);
2392          PathCat(filePath, projectNode.path);
2393          PathCat(filePath, projectNode.name);
2394
2395          codeEditor = (CodeEditor)ide.FindWindow(filePath);
2396          if(!codeEditor)
2397          {
2398             Class baseClass = eSystem_FindClass(__thisModule, base);
2399             subclass(ClassDesignerBase) designerClass = eClass_GetDesigner(baseClass);
2400             if(designerClass)
2401             {
2402                codeEditor = (CodeEditor)ide.OpenFile(filePath, false, false, null, whatever, normal, false);
2403                strcpy(name, projectNode.name);
2404                sprintf(name, "%s%d", upper, c);
2405                if(className)
2406                   strcpy(className, name);
2407
2408                designerClass.CreateNew(codeEditor.editBox, codeEditor.clientSize, name, base);
2409
2410                //codeEditor.modifiedDocument = false;
2411                //codeEditor.designer.modifiedDocument = false;
2412
2413                //Code_EnsureUpToDate(codeEditor);
2414             }
2415          }
2416          else // TODO: fix no symbols generated when ommiting {} for following else
2417          {
2418             codeEditor = (CodeEditor)ide.OpenFile(filePath, false, false, null, whatever, normal, false);
2419          }
2420          ide.sheet.visible = true;
2421          ide.sheet.Activate();
2422          if(codeEditor)
2423          {
2424             codeEditor.ViewDesigner();
2425             codeEditor.codeModified = true;
2426          }
2427       }
2428       //visible = false;
2429       return codeEditor;
2430    }
2431
2432    // Returns true if we opened something
2433    bool OpenSelectedNodes(bool noParsing)
2434    {
2435       bool result = false;
2436       OldList selection;
2437       OldLink item;
2438
2439       fileList.GetMultiSelection(selection);
2440       for(item = selection.first; item; item = item.next)
2441       {
2442          DataRow row = item.data;
2443          ProjectNode node = (ProjectNode)(intptr)row.tag;
2444          if(node.type == file)
2445          {
2446             OpenNode(node, noParsing);
2447             result = true;
2448          }
2449       }
2450       selection.Free(null);
2451       ide.RepositionWindows(false);
2452       return result;
2453    }
2454
2455    void RemoveSelectedNodes()
2456    {
2457       OldList selection;
2458       OldLink item, next;
2459
2460       fileList.GetMultiSelection(selection);
2461
2462       // Remove children of parents we're deleting
2463       for(item = selection.first; item; item = next)
2464       {
2465          OldLink i;
2466          DataRow row = item.data;
2467          ProjectNode n, node = (ProjectNode)(intptr)row.tag;
2468          bool remove = false;
2469
2470          next = item.next;
2471          for(i = selection.first; i && !remove; i = i.next)
2472          {
2473             ProjectNode iNode = (ProjectNode)(intptr)((DataRow)i.data).tag;
2474             for(n = node.parent; n; n = n.parent)
2475             {
2476                if(iNode == n)
2477                {
2478                   remove = true;
2479                   break;
2480                }
2481             }
2482          }
2483          if(remove)
2484             selection.Delete(item);
2485       }
2486
2487       for(item = selection.first; item; item = item.next)
2488       {
2489          DataRow row = item.data;
2490          ProjectNode node = (ProjectNode)(intptr)row.tag;
2491          ProjectNode resNode;
2492          for(resNode = node.parent; resNode; resNode = resNode.parent)
2493             if(resNode.type == resources)
2494                break;
2495          if(node.type == file)
2496          {
2497             Project prj = node.project;
2498             DeleteNode(node);
2499             modifiedDocument = true;
2500             prj.topNode.modified = true;
2501             Update(null);
2502             prj.ModifiedAllConfigs(true, false, true, true);
2503          }
2504          else if(node.type == folder)
2505          {
2506             char message[1024];
2507             sprintf(message, $"Are you sure you want to remove the folder \"%s\"\n"
2508                   "and all of its contents from the project?", node.name);
2509             if(MessageBox { master = ide, type = yesNo, text = $"Delete Folder", contents = message }.Modal() == yes)
2510             {
2511                Project prj = node.project;
2512                if(node.containsFile)
2513                   prj.ModifiedAllConfigs(true, false, true, true);
2514                DeleteNode(node);
2515                modifiedDocument = true;
2516                prj.topNode.modified = true;
2517             }
2518          }
2519          else if(node.type == project && node != project.topNode && !buildInProgress)
2520          {
2521             char message[1024];
2522             Project prj = null;
2523             for(p : workspace.projects)
2524             {
2525                if(p.topNode == node)
2526                {
2527                   prj = p;
2528                   break;
2529                }
2530             }
2531             sprintf(message, $"Are you sure you want to remove the \"%s\" project\n" "from this workspace?", node.name);
2532             if(MessageBox { master = ide, type = yesNo, text = $"Remove Project", contents = message }.Modal() == yes)
2533             {
2534                // THIS GOES FIRST!
2535                DeleteNode(node);
2536                if(prj)
2537                   workspace.RemoveProject(prj);
2538                //modifiedDocument = true; // when project view is a workspace view
2539             }
2540          }
2541       }
2542       selection.Free(null);
2543    }
2544 }