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