From 47a56c2f19fd7b5dd6b781ab98f49eaa2e00c11e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 8 Mar 2026 11:30:08 -0500 Subject: [PATCH] Allow opening statepoint files when one is already open --- openmc_plotter/main_window.py | 18 ++-- tests/test_statepoint_open_prompt.py | 140 +++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 tests/test_statepoint_open_prompt.py diff --git a/openmc_plotter/main_window.py b/openmc_plotter/main_window.py index 08fca3a..799aca2 100755 --- a/openmc_plotter/main_window.py +++ b/openmc_plotter/main_window.py @@ -582,15 +582,18 @@ def openView(self): self.loadViewFile(filename) def openStatePoint(self): - # check for an alread-open statepoint + # confirm replacement when a statepoint is already open if self.model.statepoint: - msg_box = QMessageBox() - msg_box.setText("Please close the current statepoint file before " - "opening a new one.") + msg_box = QMessageBox(self) + msg_box.setText("A statepoint file is currently open. Continue to " + "close it and open a new one?") msg_box.setIcon(QMessageBox.Information) - msg_box.setStandardButtons(QMessageBox.Ok) - msg_box.exec() - return + msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel) + msg_box.setDefaultButton(QMessageBox.Cancel) + msg_box.button(QMessageBox.Yes).setText("Continue") + if msg_box.exec() != QMessageBox.Yes: + return + self.closeStatePoint() filename, ext = QFileDialog.getOpenFileName(self, "Open StatePoint", ".", "*.h5") if filename: @@ -635,6 +638,7 @@ def importProperties(self): def closeStatePoint(self): # remove the statepoint object and update the data menu filename = self.model.statepoint.filename + self.model.statepoint.close() self.model.statepoint = None self.model.currentView.selectedTally = None self.model.activeView.selectedTally = None diff --git a/tests/test_statepoint_open_prompt.py b/tests/test_statepoint_open_prompt.py new file mode 100644 index 0000000..51e71a0 --- /dev/null +++ b/tests/test_statepoint_open_prompt.py @@ -0,0 +1,140 @@ +from types import SimpleNamespace + +from openmc_plotter import main_window + + +class FakeButton: + def __init__(self): + self.text = None + + def setText(self, text): + self.text = text + + +class FakeMessageBox: + Yes = 1 + Cancel = 2 + Ok = 4 + Information = 8 + Warning = 16 + next_result = Cancel + instances = [] + + def __init__(self, *args, **kwargs): + self.text = None + self.icon = None + self.standard_buttons = None + self.default_button = None + self.buttons = {} + type(self).instances.append(self) + + def setText(self, text): + self.text = text + + def setIcon(self, icon): + self.icon = icon + + def setStandardButtons(self, buttons): + self.standard_buttons = buttons + + def setDefaultButton(self, button): + self.default_button = button + + def button(self, button): + return self.buttons.setdefault(button, FakeButton()) + + def exec(self): + return type(self).next_result + + +def make_window(events): + status_bar = SimpleNamespace( + showMessage=lambda message, timeout=None: events.append( + ("status", message, timeout) + ) + ) + statepoint = SimpleNamespace( + filename="current.h5", + close=lambda: events.append("close-current-statepoint"), + ) + model = SimpleNamespace( + statepoint=statepoint, + currentView=SimpleNamespace(selectedTally=17), + activeView=SimpleNamespace(selectedTally=17), + ) + + def open_statepoint(filename): + events.append(("open-statepoint", filename)) + model.statepoint = SimpleNamespace( + filename=filename, + close=lambda: events.append(("close-statepoint", filename)), + ) + + model.openStatePoint = open_statepoint + + window = SimpleNamespace( + model=model, + statusBar=lambda: status_bar, + updateDataMenu=lambda: events.append("update-data-menu"), + tallyPanel=SimpleNamespace( + selectTally=lambda: events.append("select-tally"), + update=lambda: events.append("update-tally-panel"), + ), + plotIm=SimpleNamespace(updatePixmap=lambda: events.append("update-pixmap")), + ) + window.closeStatePoint = lambda: main_window.MainWindow.closeStatePoint(window) + return window + + +def test_open_statepoint_cancel_keeps_current_file(monkeypatch): + events = [] + FakeMessageBox.instances = [] + FakeMessageBox.next_result = FakeMessageBox.Cancel + window = make_window(events) + + def fake_get_open_file_name(*args, **kwargs): + events.append("show-open-dialog") + return ("replacement.h5", "*.h5") + + monkeypatch.setattr(main_window, "QMessageBox", FakeMessageBox) + monkeypatch.setattr( + main_window.QFileDialog, "getOpenFileName", fake_get_open_file_name + ) + + main_window.MainWindow.openStatePoint(window) + + assert events == [] + assert window.model.statepoint.filename == "current.h5" + assert len(FakeMessageBox.instances) == 1 + assert FakeMessageBox.instances[0].text == ( + "A statepoint file is currently open. Continue to close it and open " + "a new one?" + ) + assert FakeMessageBox.instances[0].button(FakeMessageBox.Yes).text == "Continue" + + +def test_open_statepoint_continue_closes_current_file_before_reopening( + monkeypatch, +): + events = [] + FakeMessageBox.instances = [] + FakeMessageBox.next_result = FakeMessageBox.Yes + window = make_window(events) + + def fake_get_open_file_name(*args, **kwargs): + events.append("show-open-dialog") + return ("replacement.h5", "*.h5") + + monkeypatch.setattr(main_window, "QMessageBox", FakeMessageBox) + monkeypatch.setattr( + main_window.QFileDialog, "getOpenFileName", fake_get_open_file_name + ) + + main_window.MainWindow.openStatePoint(window) + + assert events.index("close-current-statepoint") < events.index("show-open-dialog") + assert ("open-statepoint", "replacement.h5") in events + assert ("status", "Opened statepoint file: replacement.h5", 5000) in events + assert window.model.currentView.selectedTally is None + assert window.model.activeView.selectedTally is None + assert window.model.statepoint.filename == "replacement.h5"