diff --git a/docs/sphinx-docs/source/user/menu_bar.rst b/docs/sphinx-docs/source/user/menu_bar.rst
index 6cc104257b..0ab0c88d84 100644
--- a/docs/sphinx-docs/source/user/menu_bar.rst
+++ b/docs/sphinx-docs/source/user/menu_bar.rst
@@ -15,6 +15,8 @@ onto Data Explorer.
A *SasView* session can also be saved and reloaded as an 'Analysis' (an individual model fit or invariant
calculation, etc), or as a 'Project' (everything you have done since starting your *SasView* session).
+Finally, a session can be closed so a new project can be created. This will clear all plots, data and
+content in all the perspectives, even those which are not currently visible.
Edit
----
diff --git a/src/sas/qtgui/MainWindow/DataExplorer.py b/src/sas/qtgui/MainWindow/DataExplorer.py
index d3266304d9..0869b9d9a7 100644
--- a/src/sas/qtgui/MainWindow/DataExplorer.py
+++ b/src/sas/qtgui/MainWindow/DataExplorer.py
@@ -2150,3 +2150,12 @@ def setCheckItems(self, status=QtCore.Qt.Unchecked):
if item.isCheckable():
item.setCheckState(status)
model.blockSignals(False)
+
+ def reset(self):
+ """
+ Reset the data explorer to an empty state
+ """
+ self.closeAllPlots()
+ self.model.clear()
+ self.theory_model.clear()
+
diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py
index 04e34dcd70..ac4018174d 100644
--- a/src/sas/qtgui/MainWindow/GuiManager.py
+++ b/src/sas/qtgui/MainWindow/GuiManager.py
@@ -8,7 +8,7 @@
from packaging.version import Version
from PySide6.QtCore import QLocale, Qt
from PySide6.QtGui import QStandardItem
-from PySide6.QtWidgets import QDockWidget, QLabel, QProgressBar, QTextBrowser
+from PySide6.QtWidgets import QDockWidget, QLabel, QMessageBox, QProgressBar, QTextBrowser
from twisted.internet import reactor
import sas
@@ -706,6 +706,7 @@ def addTriggers(self):
self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis)
self._workspace.actionSave.triggered.connect(self.actionSave_Project)
self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis)
+ self._workspace.actionClose_Project.triggered.connect(self.actionClose_Project)
self._workspace.actionPreferences.triggered.connect(self.actionOpen_Preferences)
self._workspace.actionQuit.triggered.connect(self.actionQuit)
# Edit
@@ -809,13 +810,14 @@ def actionOpen_Analysis(self):
self.filesWidget.loadAnalysis()
- def actionSave_Project(self):
+ def actionSave_Project(self) -> bool:
"""
Menu Save Project
+ return: True if save was successful, False otherwise
"""
filename = self.filesWidget.saveProject()
if not filename:
- return
+ return False
# datasets
all_data = self.filesWidget.getSerializedData()
@@ -840,6 +842,7 @@ def actionSave_Project(self):
with open(filename, 'w') as outfile:
GuiUtils.saveData(outfile, final_data)
+ return True
def actionSave_Analysis(self):
"""
@@ -1319,6 +1322,24 @@ def actionAbout(self):
about = About()
about.exec()
+ def actionClose_Project(self):
+ """
+ Menu File/Close Project
+ """
+ # Make sure this is what the user really wants
+ reply = QMessageBox.question(self._parent, 'Close Project',
+ "Do you want to save the project before closing?\n"
+ "All unsaved changes will be lost if you don't save.",
+ QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
+ QMessageBox.Cancel)
+ if reply == QMessageBox.Save:
+ saved = self.actionSave_Project()
+ if saved:
+ self.resetProject()
+ elif reply == QMessageBox.Discard:
+ self.resetProject()
+ # else Cancel, do nothing
+
def actionCheck_for_update(self):
"""
Menu Help/Check for Update
@@ -1396,3 +1417,15 @@ def saveCustomConfig(self):
Save the config file based on current session values
"""
config.save()
+
+ def resetProject(self):
+ """
+ Reset the project to an empty state
+ """
+ # perspectives
+ for per in self.loadedPerspectives.values():
+ if hasattr(per, 'reset'):
+ per.reset()
+ # file manager
+ self.filesWidget.reset()
+
diff --git a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
index 3829434509..c374f50ef0 100755
--- a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
+++ b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
@@ -24,7 +24,7 @@
0
0
915
- 20
+ 26
diff --git a/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py b/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py
index a4833d6dcb..fd361b6258 100644
--- a/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py
+++ b/src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py
@@ -989,3 +989,10 @@ def getReport(self) -> ReportData | None:
report.add_plot(self.idf_figure)
return report.report_data
+
+ def reset(self):
+ """
+ Reset the corfunc perspective to an empty state
+ """
+ self.removeData([self._model_item] if self._model_item else None)
+
diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py b/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
index 8ed2013dd6..1f680a60c9 100644
--- a/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
+++ b/src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
@@ -624,6 +624,15 @@ def getTabByName(self, name):
return tab
return None
+ def reset(self):
+ """
+ Reset the fitting perspective to an empty state
+ """
+ while self.count() > 0:
+ self.closeTabByIndex(0)
+ # Add an empty fit tab
+ self.addFit(None)
+
@property
def supports_reports(self) -> bool:
return True
diff --git a/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py b/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py
index 341b7bfb61..fcaa507fcf 100644
--- a/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py
+++ b/src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py
@@ -1151,3 +1151,9 @@ def allowSwap(self):
Tell the caller that we can't swap data
"""
return False
+
+ def reset(self):
+ """
+ Reset the fitting perspective to an empty state
+ """
+ self.removeData([self._model_item] if self._model_item else None)
diff --git a/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py b/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
index e53e4f0fe8..578aefde66 100644
--- a/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
+++ b/src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
@@ -126,7 +126,6 @@ def closeTabByIndex(self, index):
# The tab might have already been deleted previously
pass
-
def closeTabByName(self, tab_name):
"""
Given name of the tab - close it
@@ -435,3 +434,12 @@ def updateFromParameters(self, params):
inversion_widget = self.currentWidget()
if isinstance(inversion_widget, InversionWidget):
inversion_widget.updateFromParameters(params)
+
+ def reset(self):
+ """
+ Reset the Inversion perspective to an empty state
+ """
+ self.tabs.clear()
+ self.clear()
+ self.maxIndex = 1
+ self.addData(None)
diff --git a/src/sas/qtgui/Perspectives/SizeDistribution/SizeDistributionPerspective.py b/src/sas/qtgui/Perspectives/SizeDistribution/SizeDistributionPerspective.py
index f8da4c8ba9..0c70220c1d 100644
--- a/src/sas/qtgui/Perspectives/SizeDistribution/SizeDistributionPerspective.py
+++ b/src/sas/qtgui/Perspectives/SizeDistribution/SizeDistributionPerspective.py
@@ -813,3 +813,10 @@ def clearStatistics(self):
self.txtDiameterMean.setText("")
self.txtDiameterMode.setText("")
self.txtDiameterMedian.setText("")
+
+ def reset(self):
+ """
+ Reset the size distribution perspective to an empty state
+ """
+ self.removeData([self._model_item] if self._model_item else None)
+ self.resetWindow()