BornAgain  1.19.79
Simulate and fit neutron and x-ray scattering at grazing incidence
ProjectManager.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/View/Project/ProjectManager.cpp
6 //! @brief Implements class ProjectManager
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
16 #include "Base/Util/Assert.h"
20 #include "GUI/Util/Error.h"
28 #include <QApplication>
29 #include <QDateTime>
30 #include <QFileDialog>
31 #include <QMessageBox>
32 #include <QSettings>
33 
34 namespace {
35 
36 const QString S_PROJECTMANAGER = "ProjectManager";
37 const QString S_AUTOSAVE = "EnableAutosave";
38 const QString S_DEFAULTPROJECTPATH = "DefaultProjectPath";
39 const QString S_RECENTPROJECTS = "RecentProjects";
40 const QString S_LASTUSEDIMPORTDIR = "LastUsedImportDir";
41 const QString S_LASTUSEDIMPORFILTER1D = "LastUsedImportFilter1D";
42 const QString S_LASTUSEDIMPORFILTER2D = "LastUsedImportFilter2D";
43 
44 } // namespace
45 
46 
48 
50  : QObject(parent)
51  , m_saveService(new SaveService(this))
52 
53 {
54  if (s_instance != nullptr)
55  throw Error("ProjectManager::ProjectManager -> Error. Attempt to create "
56  "ProjectManager twice.");
57 
58  s_instance = this;
59 }
60 
62 {
63  s_instance = nullptr;
65 }
66 
68 {
69  if (!s_instance)
70  throw Error("ProjectManager::instance() -> Error. Attempt to access "
71  "non existing ProjectManager.");
72 
73  return s_instance;
74 }
75 
76 //! Reads settings of ProjectManager from global settings.
77 
79 {
80  QSettings settings;
81  m_workingDirectory = QDir::homePath();
82  if (settings.childGroups().contains(S_PROJECTMANAGER)) {
83  settings.beginGroup(S_PROJECTMANAGER);
84 
85  if (!settings.contains(S_AUTOSAVE))
86  settings.setValue(S_AUTOSAVE, true);
87 
88  m_workingDirectory = settings.value(S_DEFAULTPROJECTPATH).toString();
89  m_recentProjects = settings.value(S_RECENTPROJECTS).toStringList();
90 
91  if (settings.contains(S_LASTUSEDIMPORTDIR))
92  m_importDirectory = settings.value(S_LASTUSEDIMPORTDIR, QString()).toString();
93 
94  m_importFilter1D = settings.value(S_LASTUSEDIMPORFILTER1D, m_importFilter1D).toString();
95  m_importFilter2D = settings.value(S_LASTUSEDIMPORFILTER2D, m_importFilter2D).toString();
96 
97  setAutosaveEnabled(settings.value(S_AUTOSAVE).toBool());
98 
99  settings.endGroup();
100  }
101 }
102 
103 //! Saves settings of ProjectManager in global settings.
104 
106 {
107  QSettings settings;
108  settings.beginGroup(S_PROJECTMANAGER);
109  settings.setValue(S_DEFAULTPROJECTPATH, m_workingDirectory);
110  settings.setValue(S_RECENTPROJECTS, m_recentProjects);
111 
112  if (!m_importDirectory.isEmpty())
113  settings.setValue(S_LASTUSEDIMPORTDIR, m_importDirectory);
114  settings.setValue(S_LASTUSEDIMPORFILTER1D, m_importFilter1D);
115  settings.setValue(S_LASTUSEDIMPORFILTER2D, m_importFilter2D);
116 
117  settings.endGroup();
118 }
119 
120 //! Returns list of recent projects, validates if projects still exists on disk.
121 
123 {
124  QStringList updatedList;
125  for (const QString& fileName : m_recentProjects)
126  if (QFile fin(fileName); fin.exists())
127  updatedList.append(fileName);
128  m_recentProjects = updatedList;
129  return m_recentProjects;
130 }
131 
132 //! Returns name of the current project directory.
133 
135 {
136  if (gSessionData->projectDocument.has_value())
137  return gSessionData->projectDocument.value()->validProjectDir();
138  return "";
139 }
140 
141 //! Returns directory name which was used by the user to import files.
142 
144 {
145  if (m_importDirectory.isEmpty()) {
146  if (gSessionData->projectDocument.has_value())
147  return gSessionData->projectDocument.value()->userExportDir();
148  return "";
149  }
150  return m_importDirectory;
151 }
152 
154 {
155  return m_importFilter1D;
156 }
157 
159 {
160  return m_importFilter2D;
161 }
162 
163 //! Sets user import directory in system settings.
164 
165 void ProjectManager::setImportDir(const QString& dirname)
166 {
167  m_importDirectory = dirname;
168 }
169 
170 //! Sets user import directory in system settings.
171 
172 void ProjectManager::setImportDirFromFilePath(const QString& filePath)
173 {
174  m_importDirectory = QFileInfo(filePath).absolutePath();
175 }
176 
178 {
179  m_importFilter1D = filter;
180 }
181 
183 {
184  m_importFilter2D = filter;
185 }
186 
188 {
190 }
191 
193 {
195  QSettings settings;
196  settings.setValue(S_PROJECTMANAGER + "/" + S_AUTOSAVE, value);
197 }
198 
199 //! Clears list of recent projects.
200 
202 {
203  m_recentProjects.clear();
204  emit recentListModified();
205 }
206 
207 //! Processes new project request (close old project, rise dialog for project name, create project).
208 
210 {
211  if (!closeCurrentProject())
212  return;
214  emit documentOpenedOrClosed(true);
215 }
216 
217 //! Processes close current project request. Call save/discard/cancel dialog, if necessary.
218 //! Returns false if saving was canceled.
219 
221 {
222  if (!gSessionData->projectDocument.has_value())
223  return true;
224 
225  if (gSessionData->projectDocument.value()->isModified()) {
226  QMessageBox msgBox;
227  msgBox.setText("The project has been modified.");
228  msgBox.setInformativeText("Do you want to save your changes?");
229  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
230  msgBox.setDefaultButton(QMessageBox::Save);
231 
232  switch (msgBox.exec()) {
233  case QMessageBox::Save:
234  if (!saveProject())
235  return false;
236  break;
237  case QMessageBox::Discard:
238  break;
239  case QMessageBox::Cancel:
240  return false;
241  default:
242  break;
243  }
244  }
245 
247  emit documentOpenedOrClosed(false);
248  return true;
249 }
250 
251 //! Processes save project request.
252 
253 bool ProjectManager::saveProject(QString projectFileName)
254 {
255  if (projectFileName.isEmpty()) {
256  if (gSessionData->projectDocument.value()->hasValidNameAndPath())
257  projectFileName = gSessionData->projectDocument.value()->projectFileName();
258  else
259  projectFileName = acquireProjectFileName();
260  }
261 
262  if (projectFileName.isEmpty())
263  return false;
264 
265  try {
266  m_saveService->save(projectFileName);
267  } catch (const std::exception& ex) {
268  QString message = QString("Failed to save project under '%1'. \n\n").arg(projectFileName);
269  message.append("Exception was thrown.\n\n");
270  message.append(ex.what());
271 
272  QMessageBox::warning(GUI::Global::mainWindow, "Error while saving project", message);
273  return false;
274  }
275 
277 
278  return true;
279 }
280 
281 //! Processes 'save project as' request.
282 
284 {
285  QString projectFileName = acquireProjectFileName();
286 
287  if (projectFileName.isEmpty())
288  return false;
289 
290  return saveProject(projectFileName);
291 }
292 
293 //! Opens existing project. If fileName is empty, will popup file selection dialog.
294 
295 void ProjectManager::openProject(QString fileName)
296 {
297  if (!closeCurrentProject())
298  return;
299 
300  if (fileName.isEmpty()) {
301  fileName = QFileDialog::getOpenFileName(
302  GUI::Global::mainWindow, "Open project file", workingDirectory(),
303  "BornAgain project Files (*.pro)", nullptr,
304  appSettings->useNativeFileDialog() ? QFileDialog::Options()
305  : QFileDialog::DontUseNativeDialog);
306  if (fileName.isEmpty())
307  return;
308  }
309 
311  MessageService messageService;
312  const auto readResult = loadProject(fileName, messageService);
313 
314  if (readResult == ProjectDocument::ReadResult::ok)
316  else if (readResult == ProjectDocument::ReadResult::error) {
317  riseProjectLoadFailedDialog(messageService);
319  } else if (readResult == ProjectDocument::ReadResult::warning) {
320  riseProjectLoadProblemDialog(messageService);
322  }
323  if (gSessionData->projectDocument.has_value())
324  emit documentOpenedOrClosed(true);
325 }
326 
327 //! Calls dialog window to define project path and name.
328 
330 {
331  if (gSessionData->projectDocument.has_value())
332  throw Error("ProjectManager::createNewProject() -> Project already exists");
333 
335 
336  const QVariant defFunctionalities =
338  gSessionData->projectDocument.value()->setFunctionalities(
339  toFunctionalities(defFunctionalities));
340  gSessionData->projectDocument.value()->setSingleInstrumentMode(
342  gSessionData->projectDocument.value()->setSingleSampleMode(
344 
345  gSessionData->projectDocument.value()->setProjectName("Untitled");
346  gSessionData->projectDocument.value()->clearModified();
347 
349 
350  connect(gSessionData->projectDocument.value(), &ProjectDocument::modified, this,
352 }
353 
355 {
356  emit aboutToCloseDocument();
358 
359  gSessionData->projectDocument.reset();
360 }
361 
362 //! Load project data from file name. If autosave info exists, opens dialog for project restore.
363 
365  MessageService& messageService)
366 {
367  auto readResult = ProjectDocument::ReadResult::ok;
368 
369  const bool useAutosave =
371  const QString autosaveName = GUI::Project::Utils::autosaveName(projectFileName);
372  if (useAutosave && restoreProjectDialog(projectFileName, autosaveName)) {
373  QApplication::setOverrideCursor(Qt::WaitCursor);
374  readResult =
375  gSessionData->projectDocument.value()->loadProjectFile(autosaveName, messageService);
376  gSessionData->projectDocument.value()->setProjectFileName(projectFileName);
377  gSessionData->projectDocument.value()->setModified();
378  } else {
379  QApplication::setOverrideCursor(Qt::WaitCursor);
380  readResult =
381  gSessionData->projectDocument.value()->loadProjectFile(projectFileName, messageService);
382  }
383  QApplication::restoreOverrideCursor();
384  return readResult;
385 }
386 
387 //! Returns project file name from dialog. Returns empty string if dialog was canceled.
388 
390 {
392 
393  if (dialog.exec() != QDialog::Accepted)
394  return "";
395 
397 
398  return dialog.getProjectFileName();
399 }
400 
401 //! Add name of the current project to the name of recent projects
402 
404 {
405  QString fileName = gSessionData->projectDocument.value()->projectFileName();
406  m_recentProjects.removeAll(fileName);
407  m_recentProjects.prepend(fileName);
409  m_recentProjects.removeLast();
410 
411  emit recentListModified();
412 }
413 
414 //! Returns default project path.
415 
417 {
418  return m_workingDirectory;
419 }
420 
421 //! Will return 'Untitled' if the directory with such name doesn't exist in project
422 //! path. Otherwise will return Untitled1, Untitled2 etc.
423 
425 {
426  QString result = "Untitled";
427  QDir projectDir = workingDirectory() + "/" + result;
428  if (projectDir.exists()) {
429  for (size_t i = 1; i < 99; ++i) {
430  result = QString("Untitled") + QString::number(i);
431 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
432  projectDir.setPath(workingDirectory() + "/" + result);
433 #else
434  projectDir = workingDirectory() + "/" + result;
435 #endif
436  if (!projectDir.exists())
437  break;
438  }
439  }
440  return result;
441 }
442 
444 {
445  QString message = QString("Failed to load the project '%1' \n\n")
446  .arg(gSessionData->projectDocument.value()->projectFileName());
447 
448  for (const auto& details : messageService.errors())
449  message.append(details + "\n");
450 
451  QMessageBox::warning(GUI::Global::mainWindow, "Error while opening project file", message);
452 }
453 
455 {
456  ASSERT(gSessionData->projectDocument.has_value());
457  auto* problemDialog =
459  gSessionData->projectDocument.value()->documentVersion());
460 
461  problemDialog->show();
462  problemDialog->raise();
463 }
464 
465 //! Rises dialog if the project should be restored from autosave. Returns true, if yes.
466 
467 bool ProjectManager::restoreProjectDialog(const QString& projectFileName,
468  const QString autosaveName)
469 {
470  const QString title("Recover project");
471  const QString lmProject =
472  QFileInfo(projectFileName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");
473  const QString lmAutoSave =
474  QFileInfo(autosaveName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");
475 
476  QString message = QString("Project '%1' contains autosaved data.\n\n"
477  "Project saved at %2\nAutosave from %3")
478  .arg(GUI::Project::Utils::projectName(projectFileName))
479  .arg(lmProject)
480  .arg(lmAutoSave);
481 
483  "\nDo you want to restore from autosave?\n",
484  "Yes, please restore.", "No, keep loading original");
485 }
ApplicationSettings * appSettings
global pointer to the instance
Defines class ApplicationSettings.
Defines error class.
Defines global pointers.
Defines class GUIHelpers functions.
Defines MessageService class.
Defines class NewProjectDialog.
ProjectDocument::Functionalities toFunctionalities(const QVariant &v)
QVariant toVariant(const ProjectDocument::Functionalities &f)
Defines class ProjectLoadProblemDialog.
Defines class ProjectManager.
Defines namespace GUI::Project::Utils.
Defines class SaveService.
SessionData * gSessionData
global pointer to the single instance
Definition: SessionData.cpp:17
Defines struct SessionData.
bool defaultIsSingleSampleMode() const
bool defaultIsSingleInstrumentMode() const
bool useNativeFileDialog() const
QVariant defaultFunctionalities(const QVariant &absenceValue) const
The service to collect messages from different senders.
QStringList errors(bool includeSenders=false) const
QStringList warnings(bool includeSenders=false) const
new project dialog window
QString getWorkingDirectory() const
QString getProjectFileName() const
Project document class handles all data related to the opened project (sample, job,...
void modified()
Emitted for any modifications in the document.
The dialog to inform user about encountered problems during the loading of old project.
Handles activity related to opening/save projects.
bool saveProject(QString projectFileName="")
Processes save project request.
void setRecentlyUsedImportFilter1D(const QString &filter)
void createNewProject()
Calls dialog window to define project path and name.
void deleteCurrentProject()
void recentListModified()
QString untitledProjectName()
Will return 'Untitled' if the directory with such name doesn't exist in project path....
ProjectDocument::ReadResult loadProject(const QString &projectFileName, MessageService &messageService)
Load project data from file name. If autosave info exists, opens dialog for project restore.
void aboutToCloseDocument()
ProjectManager(QObject *parent)
static ProjectManager * instance()
QString m_importFilter2D
Recently used import filter for 2D files.
void riseProjectLoadProblemDialog(const MessageService &messageService)
QStringList m_recentProjects
void clearRecentProjects()
Clears list of recent projects.
void setAutosaveEnabled(bool value)
QString acquireProjectFileName()
Returns project file name from dialog. Returns empty string if dialog was canceled.
void setImportDirFromFilePath(const QString &filePath)
Sets user import directory in system settings.
void documentModified()
QString recentlyUsedImportFilter2D() const
SaveService * m_saveService
void addToRecentProjects()
Add name of the current project to the name of recent projects.
QString m_importDirectory
void setRecentlyUsedImportFilter2D(const QString &filter)
bool closeCurrentProject()
Processes close current project request. Call save/discard/cancel dialog, if necessary....
void readSettings()
Reads settings of ProjectManager from global settings.
QString m_workingDirectory
Name of directory from there user prefer to import files.
void openProject(QString fileName="")
Opens existing project. If fileName is empty, will popup file selection dialog.
void setImportDir(const QString &dirname)
Sets user import directory in system settings.
QString userImportDir() const
Returns directory name which was used by the user to import files.
~ProjectManager() override
bool isAutosaveEnabled() const
static ProjectManager * s_instance
void newProject()
Processes new project request (close old project, rise dialog for project name, create project).
bool saveProjectAs()
Processes 'save project as' request.
QStringList recentProjects()
Returns list of recent projects, validates if projects still exists on disk.
QString m_importFilter1D
Recently used import filter for 1D files.
QString projectDir() const
Returns name of the current project directory.
void documentOpenedOrClosed(bool opened)
QString workingDirectory()
Returns default project path.
QString recentlyUsedImportFilter1D() const
bool restoreProjectDialog(const QString &projectFileName, QString autosaveName)
Name of directory where project directory was created.
void writeSettings()
Saves settings of ProjectManager in global settings.
void riseProjectLoadFailedDialog(const MessageService &messageService)
Provides save/autosave of ProjectDocument in a thread.
Definition: SaveService.h:26
void setAutosaveEnabled(bool value)
Definition: SaveService.cpp:70
void stopService()
void save(const QString &project_file_name)
Definition: SaveService.cpp:62
bool isAutosaveEnabled() const
Definition: SaveService.cpp:84
void setDocument(ProjectDocument *document)
Definition: SaveService.cpp:52
Defines namespace GUI::Constants.
const int MAX_RECENT_PROJECTS
static QMainWindow * mainWindow
Definition: Globals.h:22
bool hasAutosavedData(const QString &projectFileName)
Returns true if project with given projectFileName contains autosaved data.
QString projectName(const QString &projectFileName)
Returns project name deduced from project file name.
QString autosaveName(const QString &projectFileName)
Returns name of project for autoSave from given project file name. E.g. from '/projects/Untitled2/Unt...
void warning(QWidget *parent, const QString &title, const QString &text, const QString &detailedText)
Definition: MessageBox.cpp:37
bool question(QWidget *parent, const QString &title, const QString &text, const QString &detailedText, const QString &yesText, const QString &noText)
Definition: MessageBox.cpp:52
std::optional< ProjectDocument * > projectDocument
Definition: SessionData.h:27