1 /*
2  * This file is part of the Code::Blocks IDE and licensed under the GNU Lesser General Public License, version 3
3  * http://www.gnu.org/licenses/lgpl-3.0.html
4  *
5  * $Revision: 11898 $
6  * $Id: projectmanager.cpp 11898 2019-11-04 19:35:16Z fuscated $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/projectmanager.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef CB_PRECOMP
13     #include <wx/datetime.h>
14     #include <wx/dir.h>
15     #include <wx/filename.h>
16 
17     #include "projectmanager.h" // class's header file
18     #include "sdk_events.h"
19     #include "manager.h"
20     #include "configmanager.h"
21     #include "cbproject.h"
22     #include "logmanager.h"
23     #include "pluginmanager.h"
24     #include "editormanager.h"
25     #include "uservarmanager.h"
26     #include "workspaceloader.h"
27     #include "cbworkspace.h"
28     #include "cbeditor.h"
29     #include "globals.h"
30     #include "cbexception.h"  // for cbassert
31 #endif
32 
33 #include <wx/progdlg.h>
34 
35 #include "cbauibook.h"
36 //#include "filefilters.h"
37 #include "filegroupsandmasks.h"
38 
39 template<> ProjectManager* Mgr<ProjectManager>::instance = nullptr;
40 template<> bool  Mgr<ProjectManager>::isShutdown = false;
41 
42 // static
43 bool ProjectManager::s_CanShutdown = true;
44 
45 
46 BEGIN_EVENT_TABLE(ProjectManager, wxEvtHandler)
47 END_EVENT_TABLE()
48 
49 class NullProjectManagerUI : public cbProjectManagerUI
50 {
51     public:
GetNotebook()52         cbAuiNotebook* GetNotebook() override { return nullptr; }
GetTree()53         cbTreeCtrl* GetTree() override { return nullptr; }
RebuildTree()54         void RebuildTree() override {}
FreezeTree()55         void FreezeTree() override {}
UnfreezeTree(bool force=false)56         void UnfreezeTree(bool force = false) override { (void)force; }
GetTreeSelection()57         wxTreeItemId GetTreeSelection() override { return wxTreeItemId(); }
UpdateActiveProject(cbProject * WXUNUSED (oldProject),cbProject * WXUNUSED (newProject),bool WXUNUSED (refresh))58         void UpdateActiveProject(cbProject* WXUNUSED(oldProject), cbProject* WXUNUSED(newProject), bool WXUNUSED(refresh)) override {}
RemoveProject(cbProject * WXUNUSED (project))59         void RemoveProject(cbProject* WXUNUSED(project)) override {}
BeginLoadingWorkspace()60         void BeginLoadingWorkspace() override {}
CloseWorkspace()61         void CloseWorkspace() override {}
FinishLoadingProject(cbProject * WXUNUSED (project),bool WXUNUSED (newAddition),FilesGroupsAndMasks * WXUNUSED (fileGroups))62         void FinishLoadingProject(cbProject* WXUNUSED(project), bool WXUNUSED(newAddition), FilesGroupsAndMasks* WXUNUSED(fileGroups)) override {}
FinishLoadingWorkspace(cbProject * WXUNUSED (activeProject),const wxString & WXUNUSED (workspaceTitle))63         void FinishLoadingWorkspace(cbProject* WXUNUSED(activeProject), const wxString& WXUNUSED(workspaceTitle)) override {}
ShowFileInTree(ProjectFile & WXUNUSED (projectFile))64         void ShowFileInTree(ProjectFile& WXUNUSED(projectFile)) override {}
QueryCloseAllProjects()65         bool QueryCloseAllProjects() override { return true; }
QueryCloseProject(cbProject * WXUNUSED (proj),bool dontsavefiles=false)66         bool QueryCloseProject(cbProject* WXUNUSED(proj), bool dontsavefiles = false) override  { (void)dontsavefiles; return true; }
QueryCloseWorkspace()67         bool QueryCloseWorkspace() override  { return true; }
AskForBuildTargetIndex(cbProject * project=nullptr)68         int AskForBuildTargetIndex(cbProject* project = nullptr) override { (void)project; return -1; }
AskForMultiBuildTargetIndex(cbProject * project=nullptr)69         wxArrayInt AskForMultiBuildTargetIndex(cbProject* project = nullptr) override { (void)project; return wxArrayInt(); }
ConfigureProjectDependencies(cb_unused cbProject * base,cb_unused wxWindow * parent)70         void ConfigureProjectDependencies(cb_unused cbProject* base,
71                                           cb_unused wxWindow *parent) override {}
SwitchToProjectsPage()72         void SwitchToProjectsPage() override {}
73 };
74 
75 // class constructor
ProjectManager()76 ProjectManager::ProjectManager() :
77     m_ui(new NullProjectManagerUI),
78     m_pWorkspace(nullptr),
79     m_IsLoadingProject(false),
80     m_IsLoadingWorkspace(false),
81     m_IsClosingProject(false),
82     m_IsClosingWorkspace(false),
83     m_InitialDir(_T("")),
84     m_CanSendWorkspaceChanged(false),
85     m_RunningPlugin(nullptr)
86 {
87     m_InitialDir = wxFileName::GetCwd();
88     m_pActiveProject = nullptr;
89     m_pProjectToActivate = nullptr;
90     m_pProjects = new ProjectsArray;
91     m_pProjects->Clear();
92 
93     m_pFileGroups = new FilesGroupsAndMasks;
94 
95     // register event sinks
96     Manager::Get()->RegisterEventSink(cbEVT_APP_STARTUP_DONE, new cbEventFunctor<ProjectManager, CodeBlocksEvent>(this, &ProjectManager::OnAppDoneStartup));
97 
98     // Event handling. This must be THE LAST THING activated on startup.
99     // Constructors and destructors must always follow the LIFO rule:
100     // Last in, first out.
101     Manager::Get()->GetAppWindow()->PushEventHandler(this);
102 }
103 
104 // class destructor
~ProjectManager()105 ProjectManager::~ProjectManager()
106 {
107     // this is a core manager, so it is removed when the app is shutting down.
108     // in this case, the app has already un-hooked us, so no need to do it ourselves...
109 //    Manager::Get()->GetAppWindow()->RemoveEventHandler(this);
110 
111     delete m_pWorkspace;
112     m_pWorkspace = nullptr;
113 
114     int count = m_pProjects->GetCount();
115     for (int i = 0; i < count; ++i)
116     {
117         cbProject* project = m_pProjects->Item(i);
118         if (project)
119             delete project;
120     }
121     m_pProjects->Clear();
122 
123     delete m_pProjects;
124     m_pProjects = nullptr;
125 
126     delete m_pFileGroups;
127     m_pFileGroups = nullptr;
128 
129     delete m_ui;
130     m_ui = nullptr;
131 }
132 
SetUI(cbProjectManagerUI * ui)133 void ProjectManager::SetUI(cbProjectManagerUI *ui)
134 {
135     delete m_ui;
136     m_ui = ui;
137 }
138 
GetDefaultPath()139 wxString ProjectManager::GetDefaultPath()
140 {
141     wxString path = Manager::Get()->GetConfigManager(_T("project_manager"))->Read(_T("default_path"), wxEmptyString);
142     if (!path.IsEmpty() && path.Last() != _T('/') && path.Last() != _T('\\'))
143         path.Append(wxFILE_SEP_PATH);
144     return path;
145 }
146 
SetDefaultPath(const wxString & path)147 void ProjectManager::SetDefaultPath(const wxString& path)
148 {
149     Manager::Get()->GetConfigManager(_T("project_manager"))->Write(_T("default_path"), path);
150 }
151 
IsProjectStillOpen(cbProject * project)152 bool ProjectManager::IsProjectStillOpen(cbProject* project)
153 {
154     int count = m_pProjects->GetCount();
155     for (int i = 0; i < count; ++i)
156     {
157         if (m_pProjects->Item(i) == project)
158             return true;
159     }
160     return false;
161 }
162 
SetProject(cbProject * project,bool refresh)163 void ProjectManager::SetProject(cbProject* project, bool refresh)
164 {
165     bool activeProjectChanged = false;
166     if (project != m_pActiveProject)
167     {
168         // Only set workspace as modified, if there was an active project before
169         if (m_pWorkspace && m_pActiveProject)
170             activeProjectChanged = true;
171     }
172     else
173         return; // already active
174 
175     wxStopWatch timer;
176 
177     cbProject *oldActiveProject = m_pActiveProject;
178     m_pActiveProject = project;
179 
180     m_ui->UpdateActiveProject(oldActiveProject, m_pActiveProject, refresh);
181 
182     if (activeProjectChanged)
183         m_pWorkspace->ActiveProjectChanged();
184 
185     CodeBlocksEvent event(cbEVT_PROJECT_ACTIVATE);
186     event.SetProject(m_pActiveProject);
187     Manager::Get()->GetPluginManager()->NotifyPlugins(event);
188 
189     long time = timer.Time();
190     if (time >= 50)
191         Manager::Get()->GetLogManager()->Log(F(wxT("ProjectManager::SetProject took: %.3f seconds."),
192                                                time / 1000.0f));
193 }
194 
IsOpen(const wxString & filename)195 cbProject* ProjectManager::IsOpen(const wxString& filename)
196 {
197     if (filename.IsEmpty())
198         return nullptr;
199     wxString realFile = realpath(filename);
200     int count = m_pProjects->GetCount();
201     for (int i = 0; i < count; ++i)
202     {
203         cbProject* project = m_pProjects->Item(i);
204         if (project)
205         {
206 #ifdef __WXMSW__
207             // MS Windows Filenames are case-insensitive, we have to
208             // avoid opening the same project if the files are only
209             // different in upper/lowercase.
210             if (project->GetFilename().Lower().Matches(realFile.Lower()))
211                 return project;
212 #else
213             if (project->GetFilename().Matches(realFile))
214                 return project;
215 #endif
216         }
217     }
218     return nullptr;
219 }
220 
LoadProject(const wxString & filename,bool activateIt)221 cbProject* ProjectManager::LoadProject(const wxString& filename, bool activateIt)
222 {
223     wxStopWatch timer;
224     cbProject* result = nullptr;
225     if (!wxFileExists(filename) || !BeginLoadingProject())
226     {
227         return nullptr;
228     }
229 
230     // "Try" block (loop which only gets executed once)
231     // These blocks are extremely useful in constructs that need
232     // premature exits. Instead of having multiple return points,
233     // multiple return values and/or gotos,
234     // you just break out of the loop (which only gets executed once) to exit.
235     do
236     {
237         cbProject* project = IsOpen(filename);
238         if (project)
239         {
240             // already open
241             result = project;
242             break;
243         }
244 
245         if (FileTypeOf(filename) == ftCodeBlocksProject)
246         {
247             project = new cbProject(filename);
248 
249             // We need to do this because creating cbProject allows the app to be
250             // closed in the middle of the operation. So the class destructor gets
251             // called in the middle of a method call.
252 
253             if (!project->IsLoaded())
254             {
255                 delete project;
256                 break;
257             }
258 
259             result = project;
260         }
261         else // !ftCodeBlocksProject
262         {
263             // the plugin handler should call begin/end on its own...
264             EndLoadingProject(nullptr);
265 
266             cbMimePlugin* plugin = Manager::Get()->GetPluginManager()->GetMIMEHandlerForFile(filename);
267             if (plugin)
268                 plugin->OpenFile(filename);
269         }
270 
271         break;
272     }  while (false);
273     // we 're done
274 
275     EndLoadingProject(result);
276     if (activateIt)
277     {
278         if (m_IsLoadingWorkspace)
279             // postpone the call to SetProject() until EndLoadingWorkspace() is called
280             // (we must call RebuildTree() before SetProject() is called)
281             m_pProjectToActivate = result;
282         else
283         {
284             // I don't think we need to refresh the tree one more time.
285             // This is already done in the EndLoadingProject call.
286             SetProject(result, false);
287         }
288     }
289 
290     long time = timer.Time();
291     if (time >= 100)
292     {
293         LogManager *log = Manager::Get()->GetLogManager();
294         log->Log(F(wxT("ProjectManager::LoadProject took: %.3f seconds."), time / 1000.0f));
295     }
296 
297     return result;
298 }
299 
ReloadProject(cbProject * project)300 void ProjectManager::ReloadProject(cbProject *project)
301 {
302     m_ui->FreezeTree();
303 
304     bool workspaceModified = m_pWorkspace ? m_pWorkspace->GetModified() : false;
305     wxString name = project->GetFilename();
306     wxString activeProjectName = m_pActiveProject ? m_pActiveProject->GetFilename() : wxString(wxEmptyString);
307     ProjectsArray projectDependencies; // all projects that the reloaded project depends on
308     ProjectsArray projectsDependingOnReloaded; // all projects that depend on the reloaded project
309 
310     for (DepsMap::iterator it = m_ProjectDeps.begin(); it != m_ProjectDeps.end(); ++it)
311     {
312         if (!it->second)
313             continue;
314 
315         if (it->first == project)
316             projectDependencies = *(it->second);
317         else
318         {
319             if (it->second->Index(project) != wxNOT_FOUND)
320                 projectsDependingOnReloaded.push_back(it->first);
321         }
322     }
323 
324 
325     int originalPosition = m_pProjects->Index(project);
326 
327     CloseProject(project);
328     cbProject *loadedProject = LoadProject(name);
329 
330     if (loadedProject)
331     {
332         if (!projectDependencies.empty())
333         {
334             for (ProjectsArray::iterator it = projectDependencies.begin(); it != projectDependencies.end(); ++it)
335                 AddProjectDependency(loadedProject, *it);
336         }
337         if (!projectsDependingOnReloaded.empty())
338         {
339             for (ProjectsArray::iterator it = projectsDependingOnReloaded.begin();
340                  it != projectsDependingOnReloaded.end(); ++it)
341             {
342                 AddProjectDependency(*it, loadedProject);
343             }
344         }
345 
346         int loadedPosition = -1;
347         int index = 0;
348         cbProject *projectToActivate = nullptr;
349         for (ProjectsArray::iterator it = m_pProjects->begin(); it != m_pProjects->end(); ++it, ++index)
350         {
351             if (*it == loadedProject)
352                 loadedPosition = index;
353 
354             if ((*it)->GetFilename() == activeProjectName)
355                 projectToActivate = *it;
356         }
357 
358         m_pProjects->RemoveAt(loadedPosition);
359         m_pProjects->Insert(loadedProject, originalPosition);
360 
361         if (projectToActivate)
362             m_pActiveProject = projectToActivate;
363 
364         m_ui->RebuildTree();
365 
366         if (m_pWorkspace)
367             m_pWorkspace->SetModified(workspaceModified);
368     }
369 
370     m_ui->UnfreezeTree();
371 }
372 
NewProject(const wxString & filename)373 cbProject* ProjectManager::NewProject(const wxString& filename)
374 {
375     if (!filename.IsEmpty() && wxFileExists(filename))
376     {
377         if (cbMessageBox(_("Project file already exists.\nAre you really sure you want to OVERWRITE it?"),
378                          _("Confirmation"), wxYES_NO | wxICON_QUESTION) == wxID_YES)
379         {
380             if (!wxRemoveFile(filename))
381             {
382                 cbMessageBox(_("Couldn't remove the old project file to create the new one.\nThe file might be read-only?!"),
383                              _("Error"), wxICON_WARNING);
384                 return nullptr;
385             }
386         }
387         else
388             return nullptr;
389     }
390 
391     cbProject* prj = IsOpen(filename);
392     if (!prj && BeginLoadingProject())
393     {
394         prj = new cbProject(filename);
395         EndLoadingProject(prj);
396         SetProject(prj, !m_IsLoadingWorkspace);
397     }
398     return prj;
399 }
400 
CloseAllProjects(bool dontsave)401 bool ProjectManager::CloseAllProjects(bool dontsave)
402 {
403     wxStopWatch timer;
404 
405     if (!dontsave)
406     {
407         if (!m_ui->QueryCloseAllProjects())
408             return false;
409     }
410 
411     m_ui->FreezeTree();
412     m_IsClosingProject = true;
413     while (m_pProjects->GetCount() != 0)
414     {
415 // Commented it by Heromyth
416 //        if (!CloseActiveProject(true))
417         if (!CloseProject(m_pProjects->Item(0), true, false))
418         {
419             m_ui->UnfreezeTree(true);
420             m_IsClosingProject = false;
421             return false;
422         }
423     }
424 
425     if (!Manager::IsAppShuttingDown())
426         m_ui->RebuildTree();
427     m_ui->UnfreezeTree(true);
428 
429     if (!m_InitialDir.IsEmpty())
430         wxFileName::SetCwd(m_InitialDir);
431     m_IsClosingProject = false;
432     WorkspaceChanged();
433 
434     long time = timer.Time();
435     if (time >= 100)
436     {
437         LogManager *log = Manager::Get()->GetLogManager();
438         log->Log(F(wxT("ProjectManager::CloseAllProjects took: %.3f seconds."), time / 1000.0f));
439     }
440 
441     return true;
442 }
443 
CloseProject(cbProject * project,bool dontsave,bool refresh)444 bool ProjectManager::CloseProject(cbProject* project, bool dontsave, bool refresh)
445 {
446     if (!project)
447         return true;
448     if (project->GetCurrentlyCompilingTarget())
449         return false;
450     if (!dontsave)
451     {
452          if (!m_ui->QueryCloseProject(project))
453             return false;
454     }
455 
456     bool wasActive = project == m_pActiveProject;
457     if (wasActive)
458         m_pActiveProject = nullptr;
459 
460     int index = m_pProjects->Index(project);
461     if (index == wxNOT_FOUND)
462         return false;
463 
464     // CloseProject is also called by CloseAllProjects, so we need to save
465     // the state of m_IsClosingProject.
466     bool isClosingOtherProjects = m_IsClosingProject;
467     m_IsClosingProject = true;
468     Manager::Get()->GetEditorManager()->UpdateProjectFiles(project);
469     project->SaveLayout();
470 
471     if (m_pWorkspace)
472         m_pWorkspace->SetModified(true);
473 
474     RemoveProjectFromAllDependencies(project);
475     ClearProjectDependencies(project);
476     m_pProjects->Remove(project);
477 
478     // moved here from cbProject's destructor, because by then
479     // the list of project files was already emptied...
480     CodeBlocksEvent event(cbEVT_PROJECT_CLOSE);
481     event.SetProject(project);
482     Manager::Get()->GetPluginManager()->NotifyPlugins(event);
483 
484     project->CloseAllFiles(true);
485     if (refresh)
486         m_ui->RemoveProject(project);
487     if (wasActive && m_pProjects->GetCount())
488         SetProject(m_pProjects->Item(0), refresh);
489     delete project;
490     if (!m_InitialDir.IsEmpty()) // Restore the working directory
491         wxFileName::SetCwd(m_InitialDir);
492     m_IsClosingProject = isClosingOtherProjects;
493     WorkspaceChanged();
494     return true;
495 }
496 
CloseActiveProject(bool dontsave)497 bool ProjectManager::CloseActiveProject(bool dontsave)
498 {
499     if (!CloseProject(m_pActiveProject, dontsave))
500         return false;
501     if (m_pProjects->GetCount() > 0)
502         SetProject(m_pProjects->Item(0));
503     return true;
504 }
505 
SaveProject(cbProject * project)506 bool ProjectManager::SaveProject(cbProject* project)
507 {
508     if (!project)
509         return false;
510     if (project->Save())
511     {
512         m_ui->RebuildTree();
513         return true;
514     }
515     return false;
516 }
517 
SaveProjectAs(cbProject * project)518 bool ProjectManager::SaveProjectAs(cbProject* project)
519 {
520     if (!project)
521         return false;
522 
523     if (project->SaveAs())
524     {
525         m_ui->RebuildTree();
526         return true;
527     }
528     return false;
529 }
530 
SaveActiveProject()531 bool ProjectManager::SaveActiveProject()
532 {
533     return SaveProject(m_pActiveProject);
534 }
535 
SaveActiveProjectAs()536 bool ProjectManager::SaveActiveProjectAs()
537 {
538     return SaveProjectAs(m_pActiveProject);
539 }
540 
SaveAllProjects()541 bool ProjectManager::SaveAllProjects()
542 {
543     m_ui->FreezeTree();
544     int prjCount = m_pProjects->GetCount();
545     int count = 0;
546     for (int i = 0; i < prjCount; ++i)
547     {
548         cbProject* project = m_pProjects->Item(i);
549         if (project)
550         {
551             bool isModified = project->GetModified();
552             if (isModified && SaveProject(project))
553                 ++count;
554         }
555     }
556     m_ui->UnfreezeTree(true);
557     return count == prjCount;
558 }
559 
GetWorkspace()560 cbWorkspace* ProjectManager::GetWorkspace()
561 {
562     if (!m_pWorkspace)
563     {
564         m_pWorkspace = new cbWorkspace(_T(""));
565         m_pWorkspace->SetTitle(_("Workspace"));
566         m_pWorkspace->SetModified(false);
567     }
568     return m_pWorkspace;
569 }
570 
571 
LoadWorkspace(const wxString & filename)572 bool ProjectManager::LoadWorkspace(const wxString& filename)
573 {
574     if ( !BeginLoadingWorkspace() )
575         return false;
576 
577     cbWorkspace *temp = new cbWorkspace(filename);
578 
579     // Do this after the c-tor call, because the c-tor calls methods which use call GetWorkspace
580     // and if the pointer is equal to nullptr the GetWorkspace will create a new one and we'll have
581     // a leak.
582     delete m_pWorkspace;
583     m_pWorkspace = temp;
584 
585     EndLoadingWorkspace();
586 
587     if (m_pProjects->GetCount() > 0 && !m_pActiveProject)
588         SetProject(m_pProjects->Item(0), false);
589 
590     if ( m_pWorkspace && m_pWorkspace->IsOK() )
591     {
592         // Fire-up event here, where we're sure there's an active project
593         CodeBlocksEvent event(cbEVT_WORKSPACE_LOADING_COMPLETE);
594         Manager::Get()->GetPluginManager()->NotifyPlugins(event);
595 
596         return true;
597     }
598 
599     return false;
600 }
601 
SaveWorkspace()602 bool ProjectManager::SaveWorkspace()
603 {
604     return GetWorkspace()->Save();
605 }
606 
SaveWorkspaceAs(const wxString & filename)607 bool ProjectManager::SaveWorkspaceAs(const wxString& filename)
608 {
609     return GetWorkspace()->SaveAs(filename);
610 }
611 
CloseWorkspace()612 bool ProjectManager::CloseWorkspace()
613 {
614     bool result = false;
615     m_IsClosingWorkspace = true;
616 
617     CodeBlocksEvent eventBegin(cbEVT_WORKSPACE_CLOSING_BEGIN);
618     Manager::Get()->GetPluginManager()->NotifyPlugins(eventBegin);
619 
620     if (m_pWorkspace)
621     {
622         if (!Manager::IsBatchBuild() && !m_ui->QueryCloseWorkspace())
623         {
624             m_IsClosingWorkspace = false;
625             return false;
626         }
627         // m_ui->QueryCloseWorkspace asked for saving workspace AND projects, no need to do again
628         if (!CloseAllProjects(true))
629         {
630             m_IsClosingWorkspace = false;
631             return false;
632         }
633 
634         delete m_pWorkspace;
635         m_pWorkspace = nullptr;
636 
637         m_ui->CloseWorkspace();
638         result = true;
639     }
640     else
641         result = CloseAllProjects(false);
642     m_IsClosingWorkspace = false;
643 
644     CodeBlocksEvent eventComplete(cbEVT_WORKSPACE_CLOSING_COMPLETE);
645     Manager::Get()->GetPluginManager()->NotifyPlugins(eventComplete);
646 
647     WorkspaceChanged();
648     return result;
649 }
650 
651 // This function is static for your convenience :)
IsBusy()652 bool ProjectManager::IsBusy()
653 {
654     if (Manager::IsAppShuttingDown())
655         return true;
656 
657     ProjectManager* projman = Manager::Get()->GetProjectManager();
658     if (!projman)
659         return true;
660 
661     return projman->IsLoadingOrClosing();
662 }
663 
IsLoadingOrClosing()664 bool ProjectManager::IsLoadingOrClosing()
665 {
666     return (m_IsLoadingProject || m_IsLoadingWorkspace || m_IsClosingProject || m_IsClosingWorkspace);
667 }
668 
IsLoadingProject()669 bool ProjectManager::IsLoadingProject()
670 {
671     return m_IsLoadingProject;
672 }
673 
IsLoadingWorkspace()674 bool ProjectManager::IsLoadingWorkspace()
675 {
676     return m_IsLoadingWorkspace;
677 }
678 
IsLoading()679 bool ProjectManager::IsLoading()
680 {
681     return (m_IsLoadingProject || m_IsLoadingWorkspace);
682 }
683 
IsClosingProject()684 bool ProjectManager::IsClosingProject()
685 {
686     return m_IsClosingProject;
687 }
688 
IsClosingWorkspace()689 bool ProjectManager::IsClosingWorkspace()
690 {
691     return m_IsClosingWorkspace;
692 }
693 
694 
DoAddFileToProject(const wxString & filename,cbProject * project,wxArrayInt & targets)695 int ProjectManager::DoAddFileToProject(const wxString& filename, cbProject* project, wxArrayInt& targets)
696 {
697     if (!project)
698         return 0;
699 
700     // do we have to ask for target?
701     if (targets.GetCount() == 0)
702     {
703         // if project has only one target, use this
704         if (project->GetBuildTargetsCount() == 1)
705             targets.Add(0);
706         // else display multiple target selection dialog
707         else
708         {
709             targets = m_ui->AskForMultiBuildTargetIndex(project);
710             if (targets.GetCount() == 0)
711                 return 0;
712         }
713     }
714 
715     // make sure filename is relative to project path
716     wxFileName fname(filename);
717     fname.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE, project->GetBasePath());
718     fname.MakeRelativeTo(project->GetBasePath());
719 
720     // add the file to the project first
721     ProjectFile* pf = project->AddFile(-1, fname.GetFullPath());
722     if (pf)
723     {
724         // if the file was added successfully,
725         // add to this file the selected targets...
726         for (size_t i = 0; i < targets.GetCount(); ++i)
727         {
728             ProjectBuildTarget* target = project->GetBuildTarget(targets[i]);
729             if (target)
730                 pf->AddBuildTarget(target->GetTitle());
731         }
732     }
733     return targets.GetCount();
734 }
735 
AddFileToProject(const wxString & filename,cbProject * project,int target)736 int ProjectManager::AddFileToProject(const wxString& filename, cbProject* project, int target)
737 {
738     if (!project)
739         project = GetActiveProject();
740 
741     wxArrayInt targets;
742     targets.Add(target);
743     if (AddFileToProject(filename, project, targets) == 1)
744         return targets[0];
745     return -1;
746 }
747 
AddFileToProject(const wxString & filename,cbProject * project,wxArrayInt & targets)748 int ProjectManager::AddFileToProject(const wxString& filename, cbProject* project, wxArrayInt& targets)
749 {
750     if (!project)
751         project = GetActiveProject();
752 
753     int ret = DoAddFileToProject(filename, project, targets);
754     if (ret > 0)
755     {
756         CodeBlocksEvent event(cbEVT_PROJECT_FILE_ADDED);
757         event.SetProject(project);
758         event.SetString(filename);
759         Manager::Get()->GetPluginManager()->NotifyPlugins(event);
760     }
761     return ret;
762 }
763 
AddMultipleFilesToProject(const wxArrayString & filelist,cbProject * project,int target)764 int ProjectManager::AddMultipleFilesToProject(const wxArrayString& filelist, cbProject* project, int target)
765 {
766     if (!project)
767         project = GetActiveProject();
768 
769     wxArrayInt targets;
770     targets.Add(target);
771     if (AddMultipleFilesToProject(filelist, project, targets) == 1)
772         return targets[0];
773     return -1;
774 }
775 
AddMultipleFilesToProject(const wxArrayString & filelist,cbProject * project,wxArrayInt & targets)776 int ProjectManager::AddMultipleFilesToProject(const wxArrayString& filelist, cbProject* project, wxArrayInt& targets)
777 {
778     wxProgressDialog progress(_("Project Manager"), _("Please wait while adding files to project..."), filelist.GetCount(), Manager::Get()->GetAppFrame());
779 
780     if (!project)
781         project = GetActiveProject();
782 
783     if (project)
784     {
785         project->BeginAddFiles();
786 
787         wxArrayString addedFiles; // to know which files were added successfully
788         for (unsigned int i = 0; i < filelist.GetCount(); ++i)
789         {
790             if (DoAddFileToProject(filelist[i], project, targets) != 0)
791                 addedFiles.Add(filelist[i]);
792             progress.Update(i);
793         }
794 
795         if (addedFiles.GetCount() != 0)
796         {
797             for (unsigned int i = 0; i < addedFiles.GetCount(); ++i)
798             {
799                 CodeBlocksEvent event(cbEVT_PROJECT_FILE_ADDED);
800                 event.SetProject(project);
801                 event.SetString(addedFiles[i]);
802                 Manager::Get()->GetPluginManager()->NotifyPlugins(event);
803             }
804         }
805 
806         project->EndAddFiles();
807     }
808 
809     return targets.GetCount();
810 }
811 
CausesCircularDependency(cbProject * base,cbProject * dependsOn)812 bool ProjectManager::CausesCircularDependency(cbProject* base, cbProject* dependsOn)
813 {
814     if (!base || !dependsOn)
815         return false;
816 
817     // 1st check is both projects are the same one
818     if (base == dependsOn)
819         return true;
820 
821     const ProjectsArray* arr = GetDependenciesForProject(dependsOn);
822     if (arr)
823     {
824         // now check deeper
825         for (size_t i = 0; i < arr->GetCount(); ++i)
826         {
827             if (CausesCircularDependency(base, arr->Item(i)))
828                 return true;
829         }
830     }
831 
832     // if we reached here, no possibility of circular dependency :)
833     return false;
834 }
835 
AddProjectDependency(cbProject * base,cbProject * dependsOn)836 bool ProjectManager::AddProjectDependency(cbProject* base, cbProject* dependsOn)
837 {
838     if (!base || !dependsOn)
839         return false;
840 
841     // avoid circular dependencies
842     if ( CausesCircularDependency(base, dependsOn) )
843         return false;
844 
845     ProjectsArray* arr = nullptr;
846     DepsMap::iterator it = m_ProjectDeps.find(base);
847     if (it == m_ProjectDeps.end())
848     {
849         // create a ProjectsArray* to hold the dependencies for base
850         arr = new ProjectsArray;
851         m_ProjectDeps[base] = arr;
852     }
853     else
854         arr = it->second;
855 
856     // add dependency only if not already there
857     if (arr && arr->Index(dependsOn) == wxNOT_FOUND)
858     {
859         arr->Add(dependsOn);
860         if (m_pWorkspace)
861             m_pWorkspace->SetModified(true);
862         Manager::Get()->GetLogManager()->DebugLog(F(_T("%s now depends on %s (%lu deps)"), base->GetTitle().wx_str(), dependsOn->GetTitle().wx_str(), static_cast<unsigned long>(arr->GetCount())));
863     }
864     return true;
865 }
866 
RemoveProjectDependency(cbProject * base,cbProject * doesNotDependOn)867 void ProjectManager::RemoveProjectDependency(cbProject* base, cbProject* doesNotDependOn)
868 {
869     if (!base || !doesNotDependOn)
870         return;
871 
872     DepsMap::iterator it = m_ProjectDeps.find(base);
873     if (it == m_ProjectDeps.end())
874         return; // nothing to remove
875 
876     ProjectsArray* arr = it->second;
877     arr->Remove(doesNotDependOn);
878 
879     Manager::Get()->GetLogManager()->DebugLog(F(_T("%s now does not depend on %s (%lu deps)"), base->GetTitle().wx_str(), doesNotDependOn->GetTitle().wx_str(), static_cast<unsigned long>(arr->GetCount())));
880     // if it was the last dependency, delete the array
881     if (!arr->GetCount())
882     {
883         m_ProjectDeps.erase(it);
884         delete arr;
885     }
886     if (m_pWorkspace)
887         m_pWorkspace->SetModified(true);
888 }
889 
ClearProjectDependencies(cbProject * base)890 void ProjectManager::ClearProjectDependencies(cbProject* base)
891 {
892     if (!base)
893         return;
894     DepsMap::iterator it = m_ProjectDeps.find(base);
895     if (it == m_ProjectDeps.end())
896         return; // nothing to remove
897 
898     delete it->second;
899     m_ProjectDeps.erase(it);
900     if (m_pWorkspace)
901         m_pWorkspace->SetModified(true);
902 
903     Manager::Get()->GetLogManager()->DebugLog(_T("Removed all deps from ") + base->GetTitle());
904 }
905 
RemoveProjectFromAllDependencies(cbProject * base)906 void ProjectManager::RemoveProjectFromAllDependencies(cbProject* base)
907 {
908     if (!base)
909         return;
910     DepsMap::iterator it = m_ProjectDeps.begin();
911     while (it != m_ProjectDeps.end())
912     {
913         if (it->first == base)
914         {
915             ++it;
916             continue;
917         }
918 
919         ProjectsArray* arr = it->second;
920         // only check projects that do have a dependencies array
921         if (!arr)
922         {
923             ++it;
924             continue;
925         }
926 
927         int index = arr->Index(base);
928         if (index != wxNOT_FOUND)
929             arr->RemoveAt(index);
930 
931         if (m_pWorkspace)
932             m_pWorkspace->SetModified(true);
933 
934         // if it was the last dependency, delete the array
935         if (!arr->GetCount())
936         {
937             DepsMap::iterator it2 = it++;
938             m_ProjectDeps.erase(it2);
939             delete arr;
940         }
941         else
942             ++it;
943     }
944     Manager::Get()->GetLogManager()->DebugLog(F(_T("Removed %s from all deps"), base->GetTitle().wx_str()));
945 }
946 
GetDependenciesForProject(cbProject * base)947 const ProjectsArray* ProjectManager::GetDependenciesForProject(cbProject* base)
948 {
949     DepsMap::iterator it = m_ProjectDeps.find(base);
950     if (it != m_ProjectDeps.end())
951         return it->second;
952     return nullptr;
953 }
954 
955 // events
956 
OnAppDoneStartup(CodeBlocksEvent & event)957 void ProjectManager::OnAppDoneStartup(CodeBlocksEvent& event)
958 {
959     // we do not send the workspace loaded event yet because: a) We don't know
960     // if there's a workspace yet, and b) app.cpp hasn't finished init'ing yet.
961     // We'll let app.cpp send the workspace changed for us when it's done.
962     m_CanSendWorkspaceChanged = true;
963     event.Skip();
964 }
965 
WorkspaceChanged()966 void ProjectManager::WorkspaceChanged()
967 {
968     // We use IsBusy() to check *ALL* the conditions: If we're in the process of
969     // opening or closing a project, we cannot send the event yet.
970     // Specifically, *DO NOT* send the event if the application hasn't been
971     // initialized yet!!
972     if (!IsBusy() && m_CanSendWorkspaceChanged)
973     {
974         CodeBlocksEvent event(cbEVT_WORKSPACE_CHANGED);
975         Manager::Get()->GetPluginManager()->NotifyPlugins(event);
976         Manager::Get()->GetEditorManager()->GetNotebook()->MinimizeFreeSpace();
977     }
978 }
979 
RemoveFileFromProject(ProjectFile * pfile,cbProject * project)980 void ProjectManager::RemoveFileFromProject(ProjectFile *pfile, cbProject* project)
981 {
982     if (!pfile)
983     {
984         Manager::Get()->GetLogManager()->DebugLog(_T("Invalid project file!"));
985         return;
986     }
987 
988     if (pfile->AutoGeneratedBy())
989     {
990         cbMessageBox(_("Can't remove file because it is auto-generated..."), _("Error"));
991         return;
992     }
993 
994     if (!project)
995         project = pfile->GetParentProject(); // should actually not be necessary
996 
997     wxString filename = pfile->file.GetFullPath();
998     project->RemoveFile(pfile);
999 
1000     CodeBlocksEvent evt(cbEVT_PROJECT_FILE_REMOVED);
1001     evt.SetProject(project);
1002     evt.SetString(filename);
1003     Manager::Get()->GetPluginManager()->NotifyPlugins(evt);
1004 
1005     Manager::Get()->GetLogManager()->DebugLog(_T("Removed ") + filename + _T(" from ") + project->GetTitle());
1006 }
1007 
BeginLoadingProject()1008 bool ProjectManager::BeginLoadingProject()
1009 {
1010     if (m_IsLoadingProject)
1011         return false;
1012 
1013     if (!Manager::Get()->GetPluginManager()->FindPluginByName(_T("Compiler")))
1014     {
1015         cbMessageBox(_("Deactivating the compiler plugin is most unwise.\n\nIf you intend to open a project, you have to re-activate the compiler plugin first."), _("Error"));
1016         return false;
1017     }
1018 
1019     // disallow application shutdown while opening files
1020     s_CanShutdown = false;
1021     // flag project loading
1022     m_IsLoadingProject = true;
1023 
1024     return true;
1025 }
1026 
EndLoadingProject(cbProject * project)1027 void ProjectManager::EndLoadingProject(cbProject* project)
1028 {
1029     s_CanShutdown = true;
1030     if (!m_IsLoadingProject)
1031         return;
1032 
1033     if (project)
1034     {
1035         bool newAddition = m_pProjects->Index(project) == -1;
1036         if (newAddition)
1037         {
1038             m_pProjects->Add(project);
1039             project->LoadLayout();
1040         }
1041 
1042         if (!m_IsLoadingWorkspace)
1043             m_ui->FinishLoadingProject(project, newAddition, m_pFileGroups);
1044 
1045         if (m_pWorkspace)
1046             m_pWorkspace->SetModified(true);
1047 
1048         // if loading a workspace, avoid sending the event now
1049         // we 'll send them after all projects have been loaded
1050         // (look in LoadWorkspace)
1051         if (!m_IsLoadingWorkspace)
1052         {
1053             // notify plugins that the project is loaded
1054             // moved here from cbProject::Open() because code-completion
1055             // kicks in too early and the perceived loading time is long...
1056             CodeBlocksEvent event(cbEVT_PROJECT_OPEN);
1057             event.SetProject(project);
1058             Manager::Get()->ProcessEvent(event);
1059 
1060             // finally, display project notes (if appropriate)
1061             if (project->GetShowNotesOnLoad())
1062                 project->ShowNotes(true);
1063         }
1064     }
1065 
1066     /* While loading the project layout, the ProjectManager is still working.
1067        Thus it should be set to Not Busy only at the end.*/
1068     m_IsLoadingProject = false;
1069 
1070     // sort out any global user vars that need to be defined now (in a batch) :)
1071     // but only if not loading workspace (else LoadWorkspace() will handle this)
1072     if (!m_IsLoadingWorkspace)
1073         Manager::Get()->GetUserVariableManager()->Arrogate();
1074 
1075     WorkspaceChanged();
1076 }
1077 
BeginLoadingWorkspace()1078 bool ProjectManager::BeginLoadingWorkspace()
1079 {
1080     if (m_IsLoadingWorkspace)
1081         return false;
1082 
1083     m_IsLoadingWorkspace = true;
1084     if (!CloseWorkspace())
1085     {
1086         m_IsLoadingWorkspace = false;
1087         return false; // didn't close
1088     }
1089 
1090     m_ui->BeginLoadingWorkspace();
1091 
1092     return true;
1093 }
1094 
EndLoadingWorkspace()1095 void ProjectManager::EndLoadingWorkspace()
1096 {
1097     if (!m_IsLoadingWorkspace)
1098         return;
1099 
1100     m_IsLoadingWorkspace = false;
1101     if (!m_pWorkspace)
1102         return;
1103 
1104     if (m_pWorkspace->IsOK())
1105     {
1106         if (m_pProjectToActivate)
1107         {
1108             SetProject(m_pProjectToActivate, true);
1109             m_pProjectToActivate = nullptr;
1110         }
1111 
1112         m_ui->FinishLoadingWorkspace(m_pActiveProject, m_pWorkspace->GetTitle());
1113 
1114         // sort out any global user vars that need to be defined now (in a batch) :)
1115         Manager::Get()->GetUserVariableManager()->Arrogate();
1116 
1117         int numNotes = 0;
1118 
1119         // and now send the project loaded events
1120         // since we were loading a workspace, these events were not sent before
1121         for (size_t i = 0; i < m_pProjects->GetCount(); ++i)
1122         {
1123             cbProject* project = m_pProjects->Item(i);
1124 
1125             // notify plugins that the project is loaded
1126             // moved here from cbProject::Open() because code-completion
1127             // kicks in too early and the perceived loading time is long...
1128             CodeBlocksEvent event(cbEVT_PROJECT_OPEN);
1129             event.SetProject(project);
1130             Manager::Get()->GetPluginManager()->NotifyPlugins(event);
1131 
1132             // since we 're iterating anyway, let's count the project notes that should be displayed
1133             if (project->GetShowNotesOnLoad() && !project->GetNotes().IsEmpty())
1134                 ++numNotes;
1135         }
1136 
1137         // finally, display projects notes (if appropriate)
1138         if (numNotes)
1139         {
1140             if (numNotes == 1 || // if only one project has notes, don't bother asking
1141                 cbMessageBox(wxString::Format(_("%d projects contain notes that should be displayed on-load.\n"
1142                                                 "Do you want to display them now, one after the other?"),
1143                                                 numNotes),
1144                                                 _("Display project notes?"),
1145                                                 wxICON_QUESTION | wxYES_NO) == wxID_YES)
1146             {
1147                 for (size_t i = 0; i < m_pProjects->GetCount(); ++i)
1148                 {
1149                     cbProject* project = m_pProjects->Item(i);
1150                     if (project->GetShowNotesOnLoad())
1151                         project->ShowNotes(true);
1152                 }
1153             }
1154         }
1155 
1156         WorkspaceChanged();
1157     }
1158     else
1159         CloseWorkspace();
1160 }
1161 
SetIsRunning(cbPlugin * plugin)1162 void ProjectManager::SetIsRunning(cbPlugin *plugin)
1163 {
1164     m_RunningPlugin = plugin;
1165 }
1166 
GetIsRunning() const1167 cbPlugin* ProjectManager::GetIsRunning() const
1168 {
1169     return m_RunningPlugin;
1170 }
1171 
FindProjectForFile(const wxString & file,ProjectFile ** resultFile,bool isRelative,bool isUnixFilename)1172 cbProject* ProjectManager::FindProjectForFile(const wxString& file, ProjectFile **resultFile,
1173                                               bool isRelative, bool isUnixFilename)
1174 {
1175     for (size_t i = 0; i < m_pProjects->GetCount(); ++i)
1176     {
1177         cbProject* prj = m_pProjects->Item(i);
1178         ProjectFile *temp = prj->GetFileByFilename(file, isRelative, isUnixFilename);
1179         if (temp)
1180         {
1181             if (resultFile)
1182                 *resultFile = temp;
1183             return prj;
1184         }
1185     }
1186     if (resultFile)
1187         *resultFile = nullptr;
1188     return nullptr;
1189 }
1190