-
Wuttke, Joachim authoredWuttke, Joachim authored
ProjectManager.cpp 15.14 KiB
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/View/Project/ProjectManager.cpp
//! @brief Implements class ProjectManager
//!
//! @homepage http://www.bornagainproject.org
//! @license GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
// ************************************************************************************************
#include "GUI/View/Project/ProjectManager.h"
#include "Base/Util/Assert.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/IO/ProjectUtils.h"
#include "GUI/Model/State/SessionData.h"
#include "GUI/Util/Error.h"
#include "GUI/Util/MessageService.h"
#include "GUI/View/Info/MessageBox.h"
#include "GUI/View/Info/ProjectLoadProblemDialog.h"
#include "GUI/View/Global/Globals.h"
#include "GUI/View/Project/NewProjectDialog.h"
#include "GUI/View/Project/SaveService.h"
#include "GUI/View/Tool/mainwindow_constants.h"
#include <QApplication>
#include <QDateTime>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
namespace {
const QString S_PROJECTMANAGER = "ProjectManager";
const QString S_AUTOSAVE = "EnableAutosave";
const QString S_DEFAULTPROJECTPATH = "DefaultProjectPath";
const QString S_RECENTPROJECTS = "RecentProjects";
const QString S_LASTUSEDIMPORTDIR = "LastUsedImportDir";
const QString S_LASTUSEDIMPORFILTER1D = "LastUsedImportFilter1D";
const QString S_LASTUSEDIMPORFILTER2D = "LastUsedImportFilter2D";
} // namespace
ProjectManager* ProjectManager::s_instance = nullptr;
ProjectManager::ProjectManager(MainWindow*)
: m_saveService(new SaveService(this))
{
if (s_instance != nullptr)
throw Error("ProjectManager::ProjectManager -> Error. Attempt to create "
"ProjectManager twice.");
s_instance = this;
}
ProjectManager::~ProjectManager()
{
s_instance = nullptr;
delete gSessionData->projectDocument;
}
ProjectManager* ProjectManager::instance()
{
if (!s_instance)
throw Error("ProjectManager::instance() -> Error. Attempt to access "
"non existing ProjectManager.");
return s_instance;
}
//! Reads settings of ProjectManager from global settings.
void ProjectManager::readSettings()
{
QSettings settings;
m_workingDirectory = QDir::homePath();
if (settings.childGroups().contains(S_PROJECTMANAGER)) {
settings.beginGroup(S_PROJECTMANAGER);
if (!settings.contains(S_AUTOSAVE))
settings.setValue(S_AUTOSAVE, true);
m_workingDirectory = settings.value(S_DEFAULTPROJECTPATH).toString();
m_recentProjects = settings.value(S_RECENTPROJECTS).toStringList();
if (settings.contains(S_LASTUSEDIMPORTDIR))
m_importDirectory = settings.value(S_LASTUSEDIMPORTDIR, QString()).toString();
m_importFilter1D = settings.value(S_LASTUSEDIMPORFILTER1D, m_importFilter1D).toString();
m_importFilter2D = settings.value(S_LASTUSEDIMPORFILTER2D, m_importFilter2D).toString();
setAutosaveEnabled(settings.value(S_AUTOSAVE).toBool());
settings.endGroup();
}
}
//! Saves settings of ProjectManager in global settings.
void ProjectManager::writeSettings()
{
QSettings settings;
settings.beginGroup(S_PROJECTMANAGER);
settings.setValue(S_DEFAULTPROJECTPATH, m_workingDirectory);
settings.setValue(S_RECENTPROJECTS, m_recentProjects);
if (!m_importDirectory.isEmpty())
settings.setValue(S_LASTUSEDIMPORTDIR, m_importDirectory);
settings.setValue(S_LASTUSEDIMPORFILTER1D, m_importFilter1D);
settings.setValue(S_LASTUSEDIMPORFILTER2D, m_importFilter2D);
settings.endGroup();
}
//! Returns list of recent projects, validates if projects still exists on disk.
QStringList ProjectManager::recentProjects()
{
QStringList updatedList;
for (const QString& fileName : m_recentProjects)
if (QFile fin(fileName); fin.exists())
updatedList.append(fileName);
m_recentProjects = updatedList;
return m_recentProjects;
}
//! Returns name of the current project directory.
QString ProjectManager::projectDir() const
{
if (auto* const pd = gSessionData->projectDocument)
return pd->validProjectDir();
return "";
}
//! Returns directory name which was used by the user to import files.
QString ProjectManager::userImportDir() const
{
if (m_importDirectory.isEmpty()) {
if (auto* const pd = gSessionData->projectDocument)
return pd->userExportDir();
return "";
}
return m_importDirectory;
}
QString ProjectManager::recentlyUsedImportFilter1D() const
{
return m_importFilter1D;
}
QString ProjectManager::recentlyUsedImportFilter2D() const
{
return m_importFilter2D;
}
//! Sets user import directory in system settings.
void ProjectManager::setImportDir(const QString& dirname)
{
m_importDirectory = dirname;
}
//! Sets user import directory in system settings.
void ProjectManager::setImportDirFromFilePath(const QString& filePath)
{
m_importDirectory = QFileInfo(filePath).absolutePath();
}
void ProjectManager::setRecentlyUsedImportFilter1D(const QString& filter)
{
m_importFilter1D = filter;
}
void ProjectManager::setRecentlyUsedImportFilter2D(const QString& filter)
{
m_importFilter2D = filter;
}
bool ProjectManager::isAutosaveEnabled() const
{
return m_saveService->isAutosaveEnabled();
}
void ProjectManager::setAutosaveEnabled(bool value)
{
m_saveService->setAutosaveEnabled(value);
QSettings settings;
settings.setValue(S_PROJECTMANAGER + "/" + S_AUTOSAVE, value);
}
//! Clears list of recent projects.
void ProjectManager::clearRecentProjects()
{
m_recentProjects.clear();
emit recentListModified();
}
//! Processes new project request (close old project, rise dialog for project name, create project).
void ProjectManager::newProject()
{
if (!closeCurrentProject())
return;
createNewProject();
emit documentOpenedOrClosed(true);
}
//! Processes close current project request. Call save/discard/cancel dialog, if necessary.
//! Returns false if saving was canceled.
bool ProjectManager::closeCurrentProject()
{
if (!gSessionData->projectDocument)
return true;
if (gSessionData->projectDocument->isModified()) {
QMessageBox msgBox;
msgBox.setText("The project has been modified.");
msgBox.setInformativeText("Do you want to save your changes?");
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
switch (msgBox.exec()) {
case QMessageBox::Save:
if (!saveProject())
return false;
break;
case QMessageBox::Discard:
break;
case QMessageBox::Cancel:
return false;
default:
break;
}
}
deleteCurrentProject();
emit documentOpenedOrClosed(false);
return true;
}
//! Processes save project request.
bool ProjectManager::saveProject(QString projectFileName)
{
if (projectFileName.isEmpty()) {
if (gSessionData->projectDocument->hasValidNameAndPath())
projectFileName = gSessionData->projectDocument->projectFileName();
else
projectFileName = acquireProjectFileName();
}
if (projectFileName.isEmpty())
return false;
try {
m_saveService->save(projectFileName);
} catch (const std::exception& ex) {
QString message = QString("Failed to save project under '%1'. \n\n").arg(projectFileName);
message.append("Exception was thrown.\n\n");
message.append(ex.what());
QMessageBox::warning(GUI::Global::mainWindow, "Error while saving project", message);
return false;
}
addToRecentProjects();
return true;
}
//! Processes 'save project as' request.
bool ProjectManager::saveProjectAs()
{
QString projectFileName = acquireProjectFileName();
if (projectFileName.isEmpty())
return false;
return saveProject(projectFileName);
}
//! Opens existing project. If fileName is empty, will popup file selection dialog.
void ProjectManager::openProject(QString fileName)
{
if (!closeCurrentProject())
return;
if (fileName.isEmpty()) {
fileName = QFileDialog::getOpenFileName(
GUI::Global::mainWindow, "Open project file", workingDirectory(),
"BornAgain project Files (*.pro)", nullptr,
appSettings->useNativeFileDialog() ? QFileDialog::Options()
: QFileDialog::DontUseNativeDialog);
if (fileName.isEmpty())
return;
}
createNewProject();
MessageService messageService;
const auto readResult = loadProject(fileName, messageService);
if (readResult == ProjectDocument::ReadResult::ok)
addToRecentProjects();
else if (readResult == ProjectDocument::ReadResult::error) {
riseProjectLoadFailedDialog(messageService);
deleteCurrentProject();
createNewProject();
} else if (readResult == ProjectDocument::ReadResult::warning) {
riseProjectLoadProblemDialog(messageService);
addToRecentProjects();
}
if (gSessionData->projectDocument != nullptr)
emit documentOpenedOrClosed(true);
}
//! Calls dialog window to define project path and name.
void ProjectManager::createNewProject()
{
if (gSessionData->projectDocument)
throw Error("ProjectManager::createNewProject() -> Project already exists");
gSessionData->projectDocument = new ProjectDocument();
const QVariant defFunctionalities =
appSettings->defaultFunctionalities(toVariant(ProjectDocument::All));
gSessionData->projectDocument->setFunctionalities(toFunctionalities(defFunctionalities));
gSessionData->projectDocument->setSingleInstrumentMode(
appSettings->defaultIsSingleInstrumentMode());
gSessionData->projectDocument->setSingleSampleMode(appSettings->defaultIsSingleSampleMode());
gSessionData->projectDocument->setProjectName("Untitled");
gSessionData->projectDocument->clearModified();
m_saveService->setDocument(gSessionData->projectDocument);
connect(gSessionData->projectDocument, &ProjectDocument::modified,
[this]() { emit documentModified(); });
}
void ProjectManager::deleteCurrentProject()
{
emit aboutToCloseDocument();
m_saveService->stopService();
delete gSessionData->projectDocument;
gSessionData->projectDocument = nullptr;
}
//! Load project data from file name. If autosave info exists, opens dialog for project restore.
ProjectDocument::ReadResult ProjectManager::loadProject(const QString& projectFileName,
MessageService& messageService)
{
auto readResult = ProjectDocument::ReadResult::ok;
const bool useAutosave =
m_saveService && GUI::Project::Utils::hasAutosavedData(projectFileName);
const QString autosaveName = GUI::Project::Utils::autosaveName(projectFileName);
if (useAutosave && restoreProjectDialog(projectFileName, autosaveName)) {
QApplication::setOverrideCursor(Qt::WaitCursor);
readResult = gSessionData->projectDocument->loadProjectFile(autosaveName, messageService);
gSessionData->projectDocument->setProjectFileName(projectFileName);
gSessionData->projectDocument->setModified();
} else {
QApplication::setOverrideCursor(Qt::WaitCursor);
readResult =
gSessionData->projectDocument->loadProjectFile(projectFileName, messageService);
}
QApplication::restoreOverrideCursor();
return readResult;
}
//! Returns project file name from dialog. Returns empty string if dialog was canceled.
QString ProjectManager::acquireProjectFileName()
{
NewProjectDialog dialog(GUI::Global::mainWindow, workingDirectory(), untitledProjectName());
if (dialog.exec() != QDialog::Accepted)
return "";
m_workingDirectory = dialog.getWorkingDirectory();
return dialog.getProjectFileName();
}
//! Add name of the current project to the name of recent projects
void ProjectManager::addToRecentProjects()
{
QString fileName = gSessionData->projectDocument->projectFileName();
m_recentProjects.removeAll(fileName);
m_recentProjects.prepend(fileName);
while (m_recentProjects.size() > GUI::Constants::MAX_RECENT_PROJECTS)
m_recentProjects.removeLast();
emit recentListModified();
}
//! Returns default project path.
QString ProjectManager::workingDirectory()
{
return m_workingDirectory;
}
//! Will return 'Untitled' if the directory with such name doesn't exist in project
//! path. Otherwise will return Untitled1, Untitled2 etc.
QString ProjectManager::untitledProjectName()
{
QString result = "Untitled";
QDir projectDir = workingDirectory() + "/" + result;
if (projectDir.exists()) {
for (size_t i = 1; i < 99; ++i) {
result = QString("Untitled") + QString::number(i);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
projectDir.setPath(workingDirectory() + "/" + result);
#else
projectDir = workingDirectory() + "/" + result;
#endif
if (!projectDir.exists())
break;
}
}
return result;
}
void ProjectManager::riseProjectLoadFailedDialog(const MessageService& messageService)
{
QString message = QString("Failed to load the project '%1' \n\n")
.arg(gSessionData->projectDocument->projectFileName());
for (const auto& details : messageService.errors())
message.append(details + "\n");
QMessageBox::warning(GUI::Global::mainWindow, "Error while opening project file", message);
}
void ProjectManager::riseProjectLoadProblemDialog(const MessageService& messageService)
{
ASSERT(gSessionData->projectDocument);
auto* problemDialog =
new ProjectLoadProblemDialog(GUI::Global::mainWindow, messageService.warnings(true),
gSessionData->projectDocument->documentVersion());
problemDialog->show();
problemDialog->raise();
}
//! Rises dialog if the project should be restored from autosave. Returns true, if yes.
bool ProjectManager::restoreProjectDialog(const QString& projectFileName,
const QString autosaveName)
{
const QString title("Recover project");
const QString lmProject =
QFileInfo(projectFileName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");
const QString lmAutoSave =
QFileInfo(autosaveName).lastModified().toString("hh:mm:ss, MMMM d, yyyy");
QString message = QString("Project '%1' contains autosaved data.\n\n"
"Project saved at %2\nAutosave from %3")
.arg(GUI::Project::Utils::projectName(projectFileName))
.arg(lmProject)
.arg(lmAutoSave);
return GUI::View::Helpers::question(GUI::Global::mainWindow, title, message,
"\nDo you want to restore from autosave?\n",
"Yes, please restore.", "No, keep loading original");
}