From ad2892f9846ddce450646e7eea5b104fdf1186e4 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Wed, 4 Feb 2026 12:04:47 +0000 Subject: [PATCH] Fix custom file crash --- pyproject.toml | 1 + rascal2/widgets/delegates.py | 7 +++++-- rascal2/widgets/inputs.py | 18 ++++++++++++++++-- rascal2/widgets/project/project.py | 10 +++++++++- rascal2/widgets/project/tables.py | 10 ++++++---- tests/widgets/project/test_models.py | 13 ++++++++++--- tests/widgets/test_inputs.py | 18 ++++++++++++++++++ 7 files changed, 65 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0ddb837c..3baa7120 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ extend-ignore-names = ['allKeys', 'setEditorData', 'setModel', 'setModelData', + 'setText', 'setValue', 'showEvent', 'sizeHint', diff --git a/rascal2/widgets/delegates.py b/rascal2/widgets/delegates.py index 0fafe2d8..205322cf 100644 --- a/rascal2/widgets/delegates.py +++ b/rascal2/widgets/delegates.py @@ -45,6 +45,9 @@ def commit_and_close_editor(self): def setEditorData(self, _editor: QtWidgets.QWidget, index): data = index.data(QtCore.Qt.ItemDataRole.DisplayRole) + extra = index.data(QtCore.Qt.ItemDataRole.UserRole) + if extra: + self.widget.editor.path = extra self.widget.set_data(data) def setModelData(self, _editor, model, index): @@ -60,9 +63,9 @@ def __init__(self, parent): self.widget = parent def createEditor(self, parent, option, index): - func_names = self.widget.model.func_names[ + func_names = self.widget.model.func_names.get( index.siblingAtColumn(index.column() - 1).data(QtCore.Qt.ItemDataRole.DisplayRole) - ] + ) # we define the methods set_data and get_data # so that setEditorData and setModelData don't need # to know what kind of widget the editor is diff --git a/rascal2/widgets/inputs.py b/rascal2/widgets/inputs.py index 93e90163..f5aeeaa6 100644 --- a/rascal2/widgets/inputs.py +++ b/rascal2/widgets/inputs.py @@ -176,6 +176,7 @@ def __init__(self, parent): self.open_on_show = False self.setAutoFillBackground(True) + self.path = "" def mouseDoubleClickEvent(self, event): self.open() @@ -186,12 +187,25 @@ def showEvent(self, event): self.open() self.open_on_show = False + def text(self): + if self.path: + return (Path(self.path) / super().text()).as_posix() + return super().text() + + def setText(self, value): + # if value is a path, set text to the base name only. + if isinstance(value, Path): + self.path = value.parent.as_posix() + super().setText(value.name) + else: + super().setText(value) + def open(self): """Open file dialog and get the selected filename.""" file_dialog = QtWidgets.QFileDialog(parent=self) - file = file_dialog.getOpenFileName()[0] + file = file_dialog.getOpenFileName(directory=self.path)[0] if file: - self.setText(file) + self.setText(Path(file)) self.text_changed.emit() diff --git a/rascal2/widgets/project/project.py b/rascal2/widgets/project/project.py index 56839857..038f4478 100644 --- a/rascal2/widgets/project/project.py +++ b/rascal2/widgets/project/project.py @@ -233,7 +233,15 @@ def create_edit_view(self) -> QtWidgets.QWidget: self.edit_absorption_checkbox.checkStateChanged.connect( lambda s: self.edit_tabs["Layers"].tables["layers"].set_absorption(s == QtCore.Qt.CheckState.Checked) ) - for tab in ["Experimental Parameters", "Layers", "Backgrounds", "Resolutions", "Data", "Domains"]: + for tab in [ + "Experimental Parameters", + "Layers", + "Backgrounds", + "Resolutions", + "Data", + "Domains", + "Custom Files", + ]: for table in self.edit_tabs[tab].tables.values(): table.edited.connect(lambda: self.edit_tabs["Contrasts"].tables["contrasts"].update_item_view()) diff --git a/rascal2/widgets/project/tables.py b/rascal2/widgets/project/tables.py index 0d9439c7..0050c6a1 100644 --- a/rascal2/widgets/project/tables.py +++ b/rascal2/widgets/project/tables.py @@ -588,11 +588,13 @@ def flags(self, index): def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): data = super().data(index, role) - if role == QtCore.Qt.ItemDataRole.DisplayRole and self.index_header(index) == "filename" and self.edit_mode: - if data == "" or data == "Browse...": + if self.index_header(index) == "filename": + if role == QtCore.Qt.ItemDataRole.DisplayRole and self.edit_mode and (data == "" or data == "Browse..."): return "Browse..." - return str(self.classlist[index.row()].path / data) - + elif role in [QtCore.Qt.ItemDataRole.ToolTipRole, QtCore.Qt.ItemDataRole.UserRole]: + display = super().data(index, QtCore.Qt.ItemDataRole.DisplayRole) + if display != "" and display != "Browse...": + return self.classlist[index.row()].path.as_posix() return data def setData(self, index, value, role=QtCore.Qt.ItemDataRole.DisplayRole): diff --git a/tests/widgets/project/test_models.py b/tests/widgets/project/test_models.py index 828ae058..f1a1eeca 100644 --- a/tests/widgets/project/test_models.py +++ b/tests/widgets/project/test_models.py @@ -420,14 +420,21 @@ def test_file_model_filename_data(): model = CustomFileModel(init_list, parent) filename_col = model.headers.index("filename") + model.col_offset - + working_path = Path(".").resolve().as_posix() assert model.data(model.index(0, filename_col)) == "myfile.m" assert model.data(model.index(1, filename_col)) == "" + assert model.data(model.index(0, filename_col), QtCore.Qt.ItemDataRole.UserRole) == working_path + assert model.data(model.index(1, filename_col), QtCore.Qt.ItemDataRole.UserRole) is None + assert model.data(model.index(0, filename_col), QtCore.Qt.ItemDataRole.ToolTipRole) == working_path + assert model.data(model.index(1, filename_col), QtCore.Qt.ItemDataRole.ToolTipRole) is None model.edit_mode = True - - assert Path(model.data(model.index(0, filename_col))) == (Path(".") / "myfile.m").resolve() + assert model.data(model.index(0, filename_col)) == "myfile.m" assert model.data(model.index(1, filename_col)) == "Browse..." + assert model.data(model.index(0, filename_col), QtCore.Qt.ItemDataRole.UserRole) == working_path + assert model.data(model.index(1, filename_col), QtCore.Qt.ItemDataRole.UserRole) is None + assert model.data(model.index(0, filename_col), QtCore.Qt.ItemDataRole.ToolTipRole) == working_path + assert model.data(model.index(1, filename_col), QtCore.Qt.ItemDataRole.ToolTipRole) is None @pytest.mark.parametrize( diff --git a/tests/widgets/test_inputs.py b/tests/widgets/test_inputs.py index 7ce861de..a33f9490 100644 --- a/tests/widgets/test_inputs.py +++ b/tests/widgets/test_inputs.py @@ -5,11 +5,14 @@ except ImportError: from strenum import StrEnum +from pathlib import Path + import pytest from pydantic.fields import FieldInfo from PyQt6 import QtWidgets from rascal2.widgets import AdaptiveDoubleSpinBox, MultiSelectComboBox, MultiSelectList, get_validated_input +from rascal2.widgets.inputs import PathWidget class MyEnum(StrEnum): @@ -26,6 +29,7 @@ class MyEnum(StrEnum): (FieldInfo(annotation=int), QtWidgets.QSpinBox, 15), (FieldInfo(annotation=MyEnum), QtWidgets.QComboBox, "value 2"), (FieldInfo(annotation=str), QtWidgets.QLineEdit, "Test string"), + (FieldInfo(annotation=Path), PathWidget, str(Path(".").resolve())), ], ) def test_editor_type(field_info, expected_type, example_data): @@ -71,3 +75,17 @@ def test_multi_select_list_update(selected): buttons = msl.findChildren(QtWidgets.QToolButton) buttons[1].click() assert msl.list.count() == 0 + + +def test_path_widget(): + widget = PathWidget(None) + assert widget.path == "" + assert widget.text() == "" + + widget.setText("Browse...") + assert widget.text() == "Browse..." + + path = Path(".") / "file.m" + widget.setText(path) + assert widget.path == path.parent.as_posix() + assert widget.text() == path.name