diff --git a/rascal2/config.py b/rascal2/config.py index 71e504c..9b71fb9 100644 --- a/rascal2/config.py +++ b/rascal2/config.py @@ -1,9 +1,10 @@ import logging import pathlib import platform +import sys from os import PathLike -from rascal2.core import Settings +from rascal2.core import Settings, get_global_settings SOURCE_PATH = pathlib.Path(__file__).parent STATIC_PATH = SOURCE_PATH / "static" @@ -72,8 +73,9 @@ def setup_logging(log_path: str | PathLike, level: int = logging.INFO) -> loggin """ path = pathlib.Path(log_path) - logger = logging.getLogger(path.stem) + logger = logging.getLogger("rascal_log") logger.setLevel(level) + logger.handlers.clear() # TODO add console print handler when console is added # https://github.com/RascalSoftware/RasCAL-2/issues/5 @@ -81,3 +83,16 @@ def setup_logging(log_path: str | PathLike, level: int = logging.INFO) -> loggin logger.addHandler(log_filehandler) return logger + + +def log_uncaught_exceptions(exc_type, exc_value, exc_traceback): + """Qt slots swallows exceptions but this ensures exceptions are logged""" + logger = logging.getLogger("rascal_log") + if not logger.handlers: + # Backup in case the crash happens before the local logger setup + path = pathlib.Path(get_global_settings().fileName()).parent + path.mkdir(parents=True, exist_ok=True) + logger.addHandler(logging.FileHandler(path / "crash.log")) + logger.critical("An unhandled exception occurred!", exc_info=(exc_type, exc_value, exc_traceback)) + logging.shutdown() + sys.exit(1) diff --git a/rascal2/core/__init__.py b/rascal2/core/__init__.py index 8b02ddf..47592e4 100644 --- a/rascal2/core/__init__.py +++ b/rascal2/core/__init__.py @@ -1,3 +1,3 @@ -from rascal2.core.settings import Settings +from rascal2.core.settings import Settings, get_global_settings -__all__ = ["Settings"] +__all__ = ["Settings", "get_global_settings"] diff --git a/rascal2/main.py b/rascal2/main.py index 36b1087..3334ba7 100644 --- a/rascal2/main.py +++ b/rascal2/main.py @@ -3,7 +3,7 @@ from PyQt6 import QtGui, QtWidgets -from rascal2.config import handle_scaling, path_for +from rascal2.config import handle_scaling, log_uncaught_exceptions, path_for from rascal2.ui.view import MainWindowView @@ -28,7 +28,7 @@ def ui_execute(): def main(): multiprocessing.freeze_support() - + sys.excepthook = log_uncaught_exceptions exit_code = ui_execute() sys.exit(exit_code) diff --git a/rascal2/ui/presenter.py b/rascal2/ui/presenter.py index 1c09906..ee9260e 100644 --- a/rascal2/ui/presenter.py +++ b/rascal2/ui/presenter.py @@ -19,7 +19,6 @@ def __init__(self, view): self.view = view self.model = MainWindowModel() self.title = self.view.windowTitle() - self.undo_stack = self.view.undo_stack def create_project(self, name: str, save_path: str): """Creates a new RAT project and controls object then initialise UI. @@ -39,6 +38,7 @@ def create_project(self, name: str, save_path: str): # https://github.com/RascalSoftware/RasCAL-2/issues/15 self.view.init_settings_and_log(save_path) self.view.setup_mdi() + self.view.undo_stack.clear() def edit_controls(self, setting: str, value: Any): """Edit a setting in the Controls object. diff --git a/rascal2/ui/view.py b/rascal2/ui/view.py index aacbc5d..30c7842 100644 --- a/rascal2/ui/view.py +++ b/rascal2/ui/view.py @@ -82,16 +82,20 @@ def create_actions(self): self.save_project_action.setIcon(QtGui.QIcon(path_for("save-project.png"))) self.save_project_action.setShortcut(QtGui.QKeySequence.StandardKey.Save) - self.undo_action = QtGui.QAction("&Undo", self) - self.undo_action.setStatusTip("Undo") + self.undo_action = self.undo_stack.createUndoAction(self, "&Undo") + self.undo_action.setStatusTip("Undo the last action") self.undo_action.setIcon(QtGui.QIcon(path_for("undo.png"))) self.undo_action.setShortcut(QtGui.QKeySequence.StandardKey.Undo) - self.redo_action = QtGui.QAction("&Redo", self) - self.redo_action.setStatusTip("Redo") + self.redo_action = self.undo_stack.createRedoAction(self, "&Redo") + self.redo_action.setStatusTip("Redo the last undone action") self.redo_action.setIcon(QtGui.QIcon(path_for("redo.png"))) self.redo_action.setShortcut(QtGui.QKeySequence.StandardKey.Redo) + self.undo_view_action = QtGui.QAction("Undo &History", self) + self.undo_view_action.setStatusTip("View undo history") + self.undo_view_action.triggered.connect(self.undo_view.show) + self.export_plots_action = QtGui.QAction("Export", self) self.export_plots_action.setStatusTip("Export Plots") self.export_plots_action.setIcon(QtGui.QIcon(path_for("export-plots.png"))) @@ -128,11 +132,12 @@ def create_menus(self): file_menu.addSeparator() file_menu.addAction(self.exit_action) - # edit_menu = main_menu.addMenu("&Edit") + edit_menu = main_menu.addMenu("&Edit") + edit_menu.addAction(self.undo_action) + edit_menu.addAction(self.redo_action) + edit_menu.addAction(self.undo_view_action) - tools_menu = main_menu.addMenu("&Tools") - tools_menu.addAction(self.undo_action) - tools_menu.addAction(self.redo_action) + # tools_menu = main_menu.addMenu("&Tools") windows_menu = main_menu.addMenu("&Windows") windows_menu.addAction(self.tile_windows_action)