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: 11706 $
6  * $Id: workspaceloader.cpp 11706 2019-05-25 21:39:45Z bluehazzard $
7  * $HeadURL: svn://svn.code.sf.net/p/codeblocks/code/branches/release-20.xx/src/sdk/workspaceloader.cpp $
8  */
9 
10 #include "sdk_precomp.h"
11 
12 #ifndef CB_PRECOMP
13     #include <wx/confbase.h>
14     #include <wx/fileconf.h>
15     #include <wx/intl.h>
16     #include <wx/string.h>
17 
18     #include "workspaceloader.h"
19 
20     #include "manager.h"
21     #include "configmanager.h"
22     #include "projectmanager.h"
23     #include "logmanager.h"
24     #include "cbproject.h"
25     #include "globals.h"
26     #include "cbworkspace.h"
27     #include "editormanager.h"
28     #include "cbauibook.h"
29 #endif
30 
31 
32 
33 #include "annoyingdialog.h"
34 #include <tinyxml.h>
35 #include "tinywxuni.h"
36 
WorkspaceLoader()37 WorkspaceLoader::WorkspaceLoader()
38 {
39     //ctor
40 }
41 
~WorkspaceLoader()42 WorkspaceLoader::~WorkspaceLoader()
43 {
44     //dtor
45 }
46 
GetpMan()47 inline ProjectManager* GetpMan() { return Manager::Get()->GetProjectManager(); }
GetpMsg()48 inline LogManager* GetpMsg() { return Manager::Get()->GetLogManager(); }
49 
50 #include <wx/intl.h>
51 
Open(const wxString & filename,wxString & Title)52 bool WorkspaceLoader::Open(const wxString& filename, wxString& Title)
53 {
54     TiXmlDocument doc;
55     if (!TinyXML::LoadDocument(filename, &doc))
56         return false;
57 
58 //    ProjectManager* pMan = Manager::Get()->GetProjectManager();
59 //    LogManager* pMsg = Manager::Get()->GetLogManager();
60 
61     if (!GetpMan() || !GetpMsg())
62         return false;
63 
64     // BUG: Race condition. to be fixed by Rick.
65     // If I click close AFTER pMan and pMsg are calculated,
66     // I get a segfault.
67     // I modified classes projectmanager and logmanager,
68     // so that when self==NULL, they do nothing
69     // (constructors, destructors and static functions excempted from this)
70     // This way, we'll use the *manager::Get() functions to check for nulls.
71 
72     TiXmlElement* root = doc.FirstChildElement("CodeBlocks_workspace_file");
73     if (!root)
74     {
75         // old tag
76         root = doc.FirstChildElement("Code::Blocks_workspace_file");
77         if (!root)
78         {
79             GetpMsg()->DebugLog(_T("Not a valid Code::Blocks workspace file..."));
80             return false;
81         }
82     }
83     TiXmlElement* wksp = root->FirstChildElement("Workspace");
84     if (!wksp)
85     {
86         GetpMsg()->DebugLog(_T("No 'Workspace' element in file..."));
87         return false;
88     }
89 
90     Title = cbC2U(wksp->Attribute("title")); // Conversion to unicode is automatic (see wxString::operator= )
91 
92     TiXmlElement* proj = wksp->FirstChildElement("Project");
93     if (!proj)
94     {
95         GetpMsg()->DebugLog(_T("Workspace file contains no projects..."));
96         return false;
97     }
98 
99     int failedProjects = 0;
100     // first loop to load projects
101     while (proj)
102     {
103         if (Manager::IsAppShuttingDown() || !GetpMan() || !GetpMsg())
104             return false;
105         wxString projectFilename = UnixFilename(cbC2U(proj->Attribute("filename")));
106         if (projectFilename.IsEmpty())
107         {
108             GetpMsg()->DebugLog(_T("'Project' node exists, but no filename?!?"));
109         }
110         else
111         {
112             wxFileName fname(projectFilename);
113             wxFileName wfname(filename);
114             fname.MakeAbsolute(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
115             cbProject* pProject = GetpMan()->LoadProject(fname.GetFullPath(), false); // don't activate it
116             if (!pProject)
117             {
118                 GetpMsg()->LogError(wxString::Format(_("Unable to open \"%s\" during opening workspace \"%s\" "),
119                                                        projectFilename.c_str(),
120                                                        filename.c_str()));
121                 failedProjects++;
122             }
123         }
124         proj = proj->NextSiblingElement("Project");
125     }
126 
127     // second loop to setup dependencies
128     proj = wksp->FirstChildElement("Project");
129     while (proj)
130     {
131         cbProject* thisprj = nullptr;
132         wxString projectFilename = UnixFilename(cbC2U(proj->Attribute("filename")));
133         if (projectFilename.IsEmpty())
134         {
135             GetpMsg()->DebugLog(_T("'Project' node exists, but no filename?!?"));
136             thisprj = nullptr;
137         }
138         else
139         {
140             wxFileName fname(projectFilename);
141             wxFileName wfname(filename);
142             fname.MakeAbsolute(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
143             thisprj = Manager::Get()->GetProjectManager()->IsOpen(fname.GetFullPath());
144         }
145 
146         if (thisprj)
147         {
148             TiXmlElement* dep = proj->FirstChildElement("Depends");
149             while (dep)
150             {
151                 wxFileName fname( UnixFilename(cbC2U(dep->Attribute("filename"))) );
152                 wxFileName wfname(filename);
153                 fname.MakeAbsolute(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
154                 cbProject* depprj = Manager::Get()->GetProjectManager()->IsOpen(fname.GetFullPath());
155                 if (depprj)
156                     Manager::Get()->GetProjectManager()->AddProjectDependency(thisprj, depprj);
157                 dep = dep->NextSiblingElement("Depends");
158             }
159         }
160         proj = proj->NextSiblingElement("Project");
161     }
162 
163     if (failedProjects > 0)
164     {
165         cbMessageBox(wxString::Format(_("%d projects could not be loaded.\nPlease see the Log window for details"), failedProjects),
166                      _("Opening WorkSpace"), wxICON_WARNING);
167     }
168 
169     return true;
170 }
171 
Save(const wxString & title,const wxString & filename)172 bool WorkspaceLoader::Save(const wxString& title, const wxString& filename)
173 {
174     const char* ROOT_TAG = "CodeBlocks_workspace_file";
175 
176     TiXmlDocument doc;
177     doc.SetCondenseWhiteSpace(false);
178     doc.InsertEndChild(TiXmlDeclaration("1.0", "UTF-8", "yes"));
179     TiXmlElement* rootnode = static_cast<TiXmlElement*>(doc.InsertEndChild(TiXmlElement(ROOT_TAG)));
180     if (!rootnode)
181         return false;
182 
183     TiXmlElement* wksp = static_cast<TiXmlElement*>(rootnode->InsertEndChild(TiXmlElement("Workspace")));
184     wksp->SetAttribute("title", cbU2C(title));
185 
186     ProjectsArray* arr = Manager::Get()->GetProjectManager()->GetProjects();
187     for (unsigned int i = 0; i < arr->GetCount(); ++i)
188     {
189         cbProject* prj = arr->Item(i);
190 
191         wxFileName wfname(filename);
192         wxFileName fname(prj->GetFilename());
193         fname.MakeRelativeTo(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
194 
195         TiXmlElement* node = static_cast<TiXmlElement*>(wksp->InsertEndChild(TiXmlElement("Project")));
196         node->SetAttribute("filename", cbU2C( UnixFilename(fname.GetFullPath(), wxPATH_UNIX) ) );
197 
198         const ProjectsArray* deps = Manager::Get()->GetProjectManager()->GetDependenciesForProject(prj);
199         if (deps && deps->GetCount())
200         {
201             for (size_t j = 0; j < deps->GetCount(); ++j)
202             {
203                 prj = deps->Item(j);
204                 fname.Assign(prj->GetFilename());
205                 fname.MakeRelativeTo(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
206                 TiXmlElement* dnode = static_cast<TiXmlElement*>(node->InsertEndChild(TiXmlElement("Depends")));
207                 dnode->SetAttribute("filename", cbU2C( UnixFilename(fname.GetFullPath(), wxPATH_UNIX) ) );
208             }
209         }
210     }
211     return cbSaveTinyXMLDocument(&doc, filename);
212 }
213 
SaveLayout(const wxString & filename)214 bool WorkspaceLoader::SaveLayout(const wxString& filename)
215 {
216     const char* ROOT_TAG = "CodeBlocks_workspace_layout_file";
217 
218     TiXmlDocument doc;
219     doc.SetCondenseWhiteSpace(false);
220     doc.InsertEndChild(TiXmlDeclaration("1.0", "UTF-8", "yes"));
221     TiXmlElement* rootnode = static_cast<TiXmlElement*>(doc.InsertEndChild(TiXmlElement(ROOT_TAG)));
222     if (!rootnode)
223         return false; // Failed creating the root node of the workspace layout XML file?!
224 
225     rootnode->InsertEndChild(TiXmlElement("FileVersion"));
226     rootnode->FirstChildElement("FileVersion")->SetAttribute("major", WORKSPACE_LAYOUT_FILE_VERSION_MAJOR);
227     rootnode->FirstChildElement("FileVersion")->SetAttribute("minor", WORKSPACE_LAYOUT_FILE_VERSION_MINOR);
228 
229     // active project
230     ProjectManager *pm = Manager::Get()->GetProjectManager();
231     if (!pm)
232         return false; // Could not access ProjectManager?!
233 
234     if (const cbProject *project = pm->GetActiveProject())
235     {
236         TiXmlElement *el =
237             static_cast<TiXmlElement*>(
238                 rootnode->InsertEndChild( TiXmlElement("ActiveProject") ) );
239         wxFileName wfname(filename);
240         wxFileName fname( project->GetFilename() );
241         fname.MakeRelativeTo(wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
242         el->SetAttribute("path", cbU2C( UnixFilename(fname.GetFullPath(), wxPATH_UNIX) ) );
243     }
244     // else Workspace has no active project?!
245 
246     // preferred build target
247     if (const cbWorkspace* wsp = pm->GetWorkspace() )
248     {
249         const wxString preferredTarget = wsp->GetPreferredTarget();
250         if ( ! preferredTarget.IsEmpty() )
251         {
252             TiXmlElement* el =
253                 static_cast<TiXmlElement*>(
254                     rootnode->InsertEndChild( TiXmlElement("PreferredTarget") ) );
255             el->SetAttribute("name", cbU2C(preferredTarget) );
256         }
257         // else Project has not preferred target.
258     }
259     // else No workspace present to save.
260 
261     if (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/enable_editor_layout"), false))
262     {
263         TiXmlElement *el =
264             static_cast<TiXmlElement*>(
265                 rootnode->InsertEndChild( TiXmlElement("EditorTabsLayout") ) );
266         el->SetAttribute("layout", cbU2C( Manager::Get()->GetEditorManager()->GetNotebook()->SavePerspective() ));
267     }
268     // else ?!
269 
270     return cbSaveTinyXMLDocument(&doc, filename);
271 }
272 
LoadLayout(const wxString & filename)273 bool WorkspaceLoader::LoadLayout(const wxString& filename)
274 {
275     TiXmlDocument doc;
276     if ( ! TinyXML::LoadDocument(filename, &doc) )
277         return false; // Can't load XML file?!
278 
279     if ( ! GetpMan() || ! GetpMsg() )
280         return false; // GetpMan or GetpMsg returns NULL?!
281 
282     TiXmlElement* root = doc.FirstChildElement("CodeBlocks_workspace_layout_file");
283     if (!root)
284     {
285         GetpMsg()->DebugLog(_T("Unable to load Code::Blocks workspace layout file: File is invalid."));
286         return false;
287     }
288 
289     int major = 0;
290     int minor = 0;
291 
292     TiXmlElement* version = root->FirstChildElement("FileVersion");
293 
294     // don't show messages if we 're running a batch build (i.e. no gui)
295     if (!Manager::IsBatchBuild() && version)
296     {
297         version->QueryIntAttribute("major", &major);
298         version->QueryIntAttribute("minor", &minor);
299 
300         if (major >= WORKSPACE_LAYOUT_FILE_VERSION_MAJOR && minor > WORKSPACE_LAYOUT_FILE_VERSION_MINOR)
301         {
302             GetpMsg()->DebugLog(F(_T("Workspace layout file version is > %d.%d. Trying to load..."), WORKSPACE_LAYOUT_FILE_VERSION_MAJOR, WORKSPACE_LAYOUT_FILE_VERSION_MINOR));
303             AnnoyingDialog dlg(_("Workspace layout file format is newer/unknown"),
304                                 F(_("This workspace layout file was saved with a newer version of Code::Blocks.\n"
305                                 "Will try to load, but you might see unexpected results.\n"
306                                 "In this case close the workspace, delete %s and reopen the workspace."),filename.wx_str()),
307                                 wxART_WARNING,
308                                 AnnoyingDialog::OK);
309             dlg.ShowModal();
310         }
311         else
312         {
313             // use one message for all changes
314             wxString msg;
315             wxString warn_msg;
316 
317             if (major == 0 && minor == 0)
318             {
319                 msg << _("0.0 (unversioned) to 1.0:\n");
320                 msg << _("  * save editor-pane layout and order.\n");
321                 msg << _("\n");
322             }
323 
324             if (!msg.IsEmpty())
325             {
326                 msg.Prepend(wxString::Format(_("Workspace layout file format is older (%d.%d) than the current format (%d.%d).\n"
327                                                 "The file will automatically be upgraded on close.\n"
328                                                 "But please read the following list of changes, as some of them\n"
329                                                 "might not automatically convert existing (old) settings.\n"
330                                                 "If you don't understand what a change means, you probably don't\n"
331                                                 "use that feature so you don't have to worry about it.\n\n"
332                                                 "List of changes:\n"),
333                                             major,
334                                             minor,
335                                             WORKSPACE_LAYOUT_FILE_VERSION_MAJOR,
336                                             WORKSPACE_LAYOUT_FILE_VERSION_MINOR));
337                 AnnoyingDialog dlg(_("Workspace layout file format changed"),
338                                     msg,
339                                     wxART_INFORMATION,
340                                     AnnoyingDialog::OK);
341                 dlg.ShowModal();
342             }
343 
344             if (!warn_msg.IsEmpty())
345             {
346                 warn_msg.Prepend(_("!!! WARNING !!!\n\n"));
347                 AnnoyingDialog dlg(_("Workspace layout file upgrade warning"),
348                                     warn_msg,
349                                     wxART_WARNING,
350                                     AnnoyingDialog::OK);
351                 dlg.ShowModal();
352             }
353         }
354     }
355 
356     // active project
357     if (TiXmlElement* el = root->FirstChildElement("ActiveProject"))
358     {
359         wxFileName fname = cbC2U( el->Attribute("path") );
360         wxFileName wfname(filename);
361         fname.MakeAbsolute( wfname.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) );
362         cbProject *project = GetpMan()->IsOpen( fname.GetFullPath() );
363         if (project)
364         {
365             GetpMan()->SetProject(project);
366             Manager::Get()->GetLogManager()->DebugLog(F(_T("Project %s has been activated."), fname.GetFullPath().wx_str()));
367         }
368         else
369             Manager::Get()->GetLogManager()->DebugLog(F(_T("Could not activate project: %s"), fname.GetFullPath().wx_str()));
370     }
371     // else XML element 'ActiveProject' not found?!
372 
373     // preferred build target
374     if (TiXmlElement* el = root->FirstChildElement("PreferredTarget"))
375     {
376         const wxString name = cbC2U(el->Attribute("name"));
377         cbWorkspace *wsp = GetpMan()->GetWorkspace();
378         if (wsp)
379             wsp->SetPreferredTarget(name);
380     }
381     // else XML element 'PreferredTarget' not found?!
382 
383     if (   (major >= 1)
384         && (Manager::Get()->GetConfigManager(_T("app"))->ReadBool(_T("/environment/enable_editor_layout"), false)) )
385     {
386         if (TiXmlElement* el = root->FirstChildElement("EditorTabsLayout"))
387         {
388             if (el->Attribute("layout"))
389                 Manager::Get()->GetEditorManager()->GetNotebook()->LoadPerspective(cbC2U(el->Attribute("layout")));
390         }
391     }
392 
393     return true;
394 }
395