diff --git a/installers/sasview.spec b/installers/sasview.spec
index fca9705b5f..01582c444a 100644
--- a/installers/sasview.spec
+++ b/installers/sasview.spec
@@ -15,6 +15,7 @@ datas = [
('../src/sas/example_data', 'example_data'),
('../src/sas/qtgui/Utilities/Reports/report_style.css', 'sas/qtgui/Utilities/Reports'),
('../src/sas/qtgui/Perspectives/Fitting/plugin_models', 'plugin_models'),
+ ('../src/sas/qtgui/Utilities/WhatsNew/messages', 'sas/qtgui/Utilities/WhatsNew/messages'),
('../src/sas/system/log.ini', 'sas/system/'),
('../../sasmodels/sasmodels','sasmodels'),
('../docs/sphinx-docs/build/html','doc')
diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py
index 2ebc711792..008baa0011 100644
--- a/src/sas/qtgui/MainWindow/GuiManager.py
+++ b/src/sas/qtgui/MainWindow/GuiManager.py
@@ -69,6 +69,7 @@
from sas.qtgui.Utilities.AddMultEditor import AddMultEditor
from sas.qtgui.Utilities.ImageViewer import ImageViewer
from sas.qtgui.Utilities.FileConverter import FileConverterWidget
+from sas.qtgui.Utilities.WhatsNew.WhatsNew import WhatsNew
import sas
from sas import config
@@ -134,6 +135,9 @@ def __init__(self, parent=None):
"_downloads",
"Tutorial.pdf"))
+ if self.WhatsNew.has_new_messages():
+ self.actionWhatsNew()
+
def info(self, type, value, tb):
logger.error("".join(traceback.format_exception(type, value, tb)))
@@ -199,6 +203,7 @@ def addWidgets(self):
self.ResolutionCalculator = ResolutionCalculatorPanel(self)
self.DataOperation = DataOperationUtilityPanel(self)
self.FileConverter = FileConverterWidget(self)
+ self.WhatsNew = WhatsNew(self)
def loadAllPerspectives(self):
# Close any existing perspectives to prevent multiple open instances
@@ -619,6 +624,9 @@ def actionWelcome(self):
self._workspace.workspace.addSubWindow(self.welcomePanel)
self.welcomePanel.show()
+ def actionWhatsNew(self):
+ self.WhatsNew.show()
+
def showWelcomeMessage(self):
""" Show the Welcome panel, when required """
# Assure the welcome screen is requested
@@ -734,7 +742,8 @@ def addTriggers(self):
self._workspace.actionAbout.triggered.connect(self.actionAbout)
self._workspace.actionWelcomeWidget.triggered.connect(self.actionWelcome)
self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update)
-
+ self._workspace.actionWhat_s_New.triggered.connect(self.actionWhatsNew)
+
self.communicate.sendDataToGridSignal.connect(self.showBatchOutput)
self.communicate.resultPlotUpdateSignal.connect(self.showFitResults)
diff --git a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
index 44b176e8aa..07b358fdc6 100755
--- a/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
+++ b/src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
@@ -158,6 +158,7 @@
+
@@ -619,6 +620,11 @@
Preferences...
+
+
+ What's New
+
+
diff --git a/src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py b/src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py
index c3bebe9533..98b33833c5 100644
--- a/src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py
+++ b/src/sas/qtgui/MainWindow/UnitTesting/GuiManagerTest.py
@@ -21,10 +21,15 @@
from sas.qtgui.UnitTesting.TestUtils import QtSignalSpy
from sas.qtgui.Utilities.HidableDialog import HidableDialog
+from sas.system import config
class GuiManagerTest:
'''Test the Main Window functionality'''
+ def __init__(self):
+ config.override_with_defaults() # Disable saving of test file
+ config.LAST_WHATS_NEW_HIDDEN_VERSION = "999.999.999" # Give a very large version number
+
@pytest.fixture(autouse=True)
def manager(self, qapp):
'''Create/Destroy the GUI Manager'''
diff --git a/src/sas/qtgui/MainWindow/UnitTesting/MainWindowTest.py b/src/sas/qtgui/MainWindow/UnitTesting/MainWindowTest.py
index 4571eee6a5..4d78026778 100644
--- a/src/sas/qtgui/MainWindow/UnitTesting/MainWindowTest.py
+++ b/src/sas/qtgui/MainWindow/UnitTesting/MainWindowTest.py
@@ -13,8 +13,14 @@
from sas.qtgui.Perspectives.Fitting import FittingPerspective
from sas.qtgui.Utilities.HidableDialog import HidableDialog, ShowAgainResult
+from sas.system import config
class MainWindowTest:
"""Test the Main Window GUI"""
+
+ def __init__(self):
+ config.override_with_defaults() # Disable saving of test file
+ config.LAST_WHATS_NEW_HIDDEN_VERSION = "999.999.999" # Give a very large version number
+
@pytest.fixture(autouse=True)
def widget(self, qapp):
'''Create/Destroy the GUI'''
diff --git a/src/sas/qtgui/Utilities/WhatsNew/WhatsNew.py b/src/sas/qtgui/Utilities/WhatsNew/WhatsNew.py
new file mode 100644
index 0000000000..4ead84f1bb
--- /dev/null
+++ b/src/sas/qtgui/Utilities/WhatsNew/WhatsNew.py
@@ -0,0 +1,152 @@
+from collections import defaultdict
+
+from PySide6 import QtWidgets
+from PySide6.QtWidgets import QDialog, QWidget, QTextBrowser, QVBoxLayout, QHBoxLayout, QPushButton, QCheckBox
+
+from sas.system.version import __version__ as sasview_version
+import importlib.resources as resources
+
+from sas.system import config
+
+
+from sas.qtgui.Utilities.WhatsNew.newer import strictly_newer_than, reduced_version, newest
+
+def whats_new_messages():
+ """ Accumulate all files that are newer than the value in the config"""
+
+ out = defaultdict(list)
+ message_dir = resources.files("sas.qtgui.Utilities.WhatsNew.messages")
+ for message_dir in message_dir.iterdir():
+ # Get short filename
+ if message_dir.is_dir():
+
+ newer = False
+
+ try:
+ newer = strictly_newer_than(message_dir.name, config.LAST_WHATS_NEW_HIDDEN_VERSION)
+
+ except ValueError:
+ pass
+
+ if newer:
+ for file in message_dir.iterdir():
+ if file.name.endswith(".html"):
+ out[message_dir.name].append(file)
+
+
+ return out
+
+
+class WhatsNew(QDialog):
+ """ What's New window: displays messages about what is new in this version of SasView
+
+ It will find all files in messages.[version] if [version] is newer than the last time
+ the "don't show me again" option was chosen
+
+ To add new messages, just dump a (self-contained) html file into the appropriate folder
+
+ """
+ def __init__(self, parent=None):
+ super().__init__()
+
+ self.setWindowTitle(f"What's New in SasView {sasview_version}")
+
+ self.browser = QTextBrowser()
+
+ # Layout stuff
+ self.mainLayout = QVBoxLayout()
+ self.buttonBar = QWidget()
+ self.buttonLayout = QHBoxLayout()
+
+
+ # Buttons
+ self.buttonBar.setLayout(self.buttonLayout)
+
+ self.closeButton = QPushButton("Close")
+ self.nextButton = QPushButton("Next")
+
+ self.showAgain = QCheckBox("Show on Startup")
+ self.showAgain.setChecked(True)
+
+ self.buttonLayout.addWidget(self.showAgain)
+ self.buttonLayout.addWidget(self.closeButton)
+ self.buttonLayout.addWidget(self.nextButton)
+
+ # Viewer
+ self.setLayout(self.mainLayout)
+ self.mainLayout.addWidget(self.browser)
+ self.mainLayout.addWidget(self.buttonBar)
+
+ # Callbacks
+ self.closeButton.clicked.connect(self.close_me)
+ self.nextButton.clicked.connect(self.next_file)
+
+ # # Gather new files
+ new_messages = whats_new_messages()
+ new_message_directories = [key for key in new_messages.keys()]
+ new_message_directories.sort(key=reduced_version)
+
+ self.all_messages = []
+
+ for version in new_messages:
+ self.all_messages += new_messages[version]
+
+ self.max_index = len(self.all_messages)
+ self.current_index = 0
+
+ self.show_file()
+
+ self.setModal(True)
+
+ def next_file(self):
+ self.current_index += 1
+ self.current_index %= self.max_index
+ self.show_file()
+
+ def show_file(self):
+ if len(self.all_messages) > 0:
+ filename = self.all_messages[self.current_index]
+ with open(filename, 'r') as fid:
+ data = fid.read()
+ self.browser.setText(data)
+ else:
+ self.browser.setText("
You should not see this!!!
")
+
+ def close_me(self):
+ if not self.showAgain.isChecked():
+ # We choose the newest, for backwards compatability, i.e. we never reduce the last version
+ config.LAST_WHATS_NEW_HIDDEN_VERSION = newest(sasview_version, config.LAST_WHATS_NEW_HIDDEN_VERSION)
+
+ self.close()
+
+ def has_new_messages(self) -> bool:
+ """ Should the window be shown? """
+ return bool(self.all_messages)
+
+
+
+def maybe_show_whats_new():
+ global whats_new_window
+ """ Show the What's New dialogue if it is wanted """
+
+ if whats_new_messages():
+ whats_new_window = WhatsNew()
+ whats_new_window.show()
+
+
+def main():
+ """ Demo/testing window"""
+
+ from sas.qtgui.convertUI import main
+
+ main()
+
+ app = QtWidgets.QApplication([])
+
+ maybe_show_whats_new()
+
+ app.exec_()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/sas/qtgui/Utilities/WhatsNew/__init__.py b/src/sas/qtgui/Utilities/WhatsNew/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/1.html b/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/1.html
new file mode 100644
index 0000000000..83c62844f8
--- /dev/null
+++ b/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/1.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Some Handy Tips
+
+
+Have you tried randomly pressing buttons?
+
+
\ No newline at end of file
diff --git a/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/2.html b/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/2.html
new file mode 100644
index 0000000000..b9f937665f
--- /dev/null
+++ b/src/sas/qtgui/Utilities/WhatsNew/messages/6.0.0/2.html
@@ -0,0 +1,10 @@
+
+
+
+ Welcome to SasView - What's New
+
+
+Lot's of things
+
+
\ No newline at end of file
diff --git a/src/sas/qtgui/Utilities/WhatsNew/messages/__init__.py b/src/sas/qtgui/Utilities/WhatsNew/messages/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/sas/qtgui/Utilities/WhatsNew/newer.py b/src/sas/qtgui/Utilities/WhatsNew/newer.py
new file mode 100644
index 0000000000..0adff7cd15
--- /dev/null
+++ b/src/sas/qtgui/Utilities/WhatsNew/newer.py
@@ -0,0 +1,43 @@
+from typing import Tuple
+import re
+
+def reduced_version(version_string: str) -> Tuple[int, int, int]:
+ """ Convert a version string into the three numbers we care about for the purposes
+ of the WhatsNew dialog (i.e. strip a,b suffixes etc, make into three ints"""
+
+ version_string = re.sub(r"[^\.0-9]+.*", "", version_string)
+
+ parts = version_string.split(".")
+
+ if len(parts) > 3:
+ raise ValueError(f"{version_string} not a valid version string")
+
+
+ parts = [int(part) for part in parts]
+
+ return tuple(parts + [0]*(3-len(parts)))
+
+
+def strictly_newer_than(version_a: str, version_b: str) -> bool:
+ """ Is the version string "version_a" string strictly newer than "version_b" """
+
+ numeric_a = reduced_version(version_a)
+ numeric_b = reduced_version(version_b)
+
+ for i in range(3):
+ if numeric_a[i] > numeric_b[i]:
+ return True
+ elif numeric_a[i] < numeric_b[i]:
+ return False
+
+ return False
+
+def newest(version_a: str, version_b: str) -> str:
+ """Return the newest of two versions by the comparison used in the what's new box,
+ if they are equally new, return the first one.
+ """
+
+ if strictly_newer_than(version_b, version_a):
+ return version_b
+
+ return version_a
\ No newline at end of file
diff --git a/src/sas/system/config/config.py b/src/sas/system/config/config.py
index d3fd556955..6edf0c2a25 100644
--- a/src/sas/system/config/config.py
+++ b/src/sas/system/config/config.py
@@ -208,6 +208,9 @@ def __init__(self):
# Default fitting optimizer
self.FITTING_DEFAULT_OPTIMIZER = 'lm'
+ # What's New variables
+ self.LAST_WHATS_NEW_HIDDEN_VERSION = "5.0.0"
+
#
# Lock the class down, this is necessary both for
# securing the class, and for setting up reading/writing files