components.file_selector_widget

  1import os
  2
  3from PyQt6.QtGui import QIcon
  4from PyQt6.QtWidgets import (
  5    QWidget, QHBoxLayout, QVBoxLayout, QListWidget, QPushButton, QListWidgetItem
  6)
  7from PyQt6.QtCore import Qt, pyqtSignal, QCoreApplication
  8
  9from components.nifti_file_dialog import NiftiFileDialog
 10
 11
 12class FileSelectorWidget(QWidget):
 13    """
 14    Widget for selecting and displaying one or multiple NIfTI files.
 15    Provides a graphical list of selected files and buttons for choosing or clearing them.
 16    """
 17
 18    # Signal emitted when the widget has at least one file selected (True/False)
 19    has_file = pyqtSignal(bool)
 20
 21    def __init__(self, context, has_existing_function, label, allow_multiple,
 22                 processing=None, forced_filters=None, parent=None):
 23        """
 24        Initialize the FileSelectorWidget.
 25
 26        Args:
 27            context (dict): Shared context containing signals and shared UI functions.
 28            has_existing_function (callable): Function to check if a file already exists.
 29            label (str): Label describing the purpose of the file selection.
 30            allow_multiple (bool): Whether multiple file selection is allowed.
 31            processing (pyqtSignal, optional): Signal controlling whether buttons are active.
 32            forced_filters (list[str], optional): File type filters.
 33            parent (QWidget, optional): Parent widget.
 34        """
 35        super().__init__(parent)
 36
 37        self.context = context
 38        # Connects external signal for when files are selected from a shared dialog
 39        if self.context and "selected_files_signal" in self.context:
 40            self.context["selected_files_signal"].connect(self.set_selected_files)
 41
 42        self.label = label
 43        self.allow_multiple = allow_multiple
 44        self.has_existing_function = has_existing_function
 45        if processing:
 46            processing.connect(self.set_processing_mode)
 47        self.forced_filters = forced_filters
 48
 49        self.selected_files = None
 50
 51        # --- Main Layout ---
 52        file_selector_layout = QHBoxLayout(self)
 53        self.setLayout(file_selector_layout)
 54
 55        # --- File list display ---
 56        self.file_list_widget = QListWidget()
 57        self.file_list_widget.setSelectionMode(QListWidget.SelectionMode.NoSelection)
 58        self.file_list_widget.setFocusPolicy(Qt.FocusPolicy.NoFocus)
 59        self.file_list_widget.setMaximumHeight(100)
 60        file_selector_layout.addWidget(self.file_list_widget, stretch=1)
 61
 62        # --- Button container (Choose / Clear) ---
 63        button_container = QWidget()
 64        button_layout = QVBoxLayout(button_container)
 65        button_layout.addStretch()
 66
 67        # "Choose file(s)" button — opens NIfTI selection dialog
 68        self.file_button = QPushButton("Choose NIfTI File(s)")
 69        self.file_button.clicked.connect(self.open_tree_dialog)
 70        button_layout.addWidget(self.file_button, alignment=Qt.AlignmentFlag.AlignCenter)
 71
 72        # "Clear selection" button — removes all selected files
 73        self.clear_button = QPushButton("Clear Selection")
 74        self.clear_button.setEnabled(False)
 75        self.clear_button.clicked.connect(self.clear_selected_files)
 76        button_layout.addWidget(self.clear_button, alignment=Qt.AlignmentFlag.AlignCenter)
 77
 78        button_layout.addStretch()
 79        file_selector_layout.addWidget(button_container)
 80
 81        # --- Translation support ---
 82        self._translate_ui()
 83        if context and "language_changed" in context:
 84            context["language_changed"].connect(self._translate_ui)
 85
 86    # -------------------------------------------------------------------------
 87    # Dialog and file selection
 88    # -------------------------------------------------------------------------
 89    def open_tree_dialog(self):
 90        """
 91        Opens the NIfTI file selection dialog (tree-based).
 92        Updates the file list upon user confirmation.
 93        """
 94        results = NiftiFileDialog.get_files(
 95            context=self.context,
 96            allow_multiple=self.allow_multiple,
 97            has_existing_func=self.has_existing_function,
 98            label=self.label,
 99            forced_filters=self.forced_filters
100        )
101
102        if results:
103            if self.allow_multiple:
104                self.set_selected_files(results)
105            else:
106                # If single selection only, use the last selected file
107                self.set_selected_files([results[0]])
108
109    def clear_selected_files(self):
110        """
111        Clears all selected files and updates UI state.
112        """
113        self.selected_files = []
114        self.file_list_widget.clear()
115        self.clear_button.setEnabled(False)
116        self.has_file.emit(False)
117
118        # Notify the main UI to update its state
119        if self.context and "update_main_buttons" in self.context:
120            self.context["update_main_buttons"]()
121
122    def set_selected_files(self, file_paths):
123        """
124        Updates the internal file list and UI after user selection.
125
126        Args:
127            file_paths (list[str]): List of selected file paths.
128        """
129        if self.allow_multiple:
130            # Keep only valid NIfTI files (exclude folders and invalid extensions)
131            file_paths = [
132                file for file in file_paths
133                if os.path.exists(file)
134                and not os.path.isdir(file)
135                and (file.endswith('.nii.gz') or file.endswith('.nii'))
136            ]
137        else:
138            # If only one file is allowed, keep the last one
139            file_paths = [file_paths[-1]]
140
141        self.selected_files = file_paths
142        self.file_list_widget.clear()
143
144        # Populate the list widget with file names
145        for path in file_paths:
146            item = QListWidgetItem(QIcon.fromTheme("document"), os.path.basename(path))
147            item.setToolTip(path)
148            self.file_list_widget.addItem(item)
149
150        # Enable/disable buttons based on selection
151        self.clear_button.setEnabled(bool(file_paths))
152        self.has_file.emit(bool(file_paths))
153
154        # Notify main UI of state change
155        if self.context and "update_main_buttons" in self.context:
156            self.context["update_main_buttons"]()
157
158    # -------------------------------------------------------------------------
159    # Accessors and state management
160    # -------------------------------------------------------------------------
161    def get_selected_files(self):
162        """Return the list of selected file paths."""
163        return self.selected_files
164
165    def set_processing_mode(self, processing):
166        """
167        Enable/disable user interaction when the app is processing data.
168
169        Args:
170            processing (bool): True to disable file selection during processing.
171        """
172        self.file_button.setEnabled(not processing)
173        self.clear_button.setEnabled(not processing and bool(self.selected_files))
174        if processing:
175            self.context["selected_files_signal"].disconnect()
176        else:
177            self.context["selected_files_signal"].connect(self.set_selected_files)
178
179    # -------------------------------------------------------------------------
180    # Translation
181    # -------------------------------------------------------------------------
182    def _translate_ui(self):
183        """Apply dynamic UI translations (for multi-language support)."""
184        if self.allow_multiple:
185            self.file_button.setText(QCoreApplication.translate("Components", "Choose NIfTI File(s)"))
186        else:
187            self.file_button.setText(QCoreApplication.translate("Components", "Choose NIfTI File"))
188        self.clear_button.setText(QCoreApplication.translate("Components", "Clear Selection"))
class FileSelectorWidget(PyQt6.QtWidgets.QWidget):
 13class FileSelectorWidget(QWidget):
 14    """
 15    Widget for selecting and displaying one or multiple NIfTI files.
 16    Provides a graphical list of selected files and buttons for choosing or clearing them.
 17    """
 18
 19    # Signal emitted when the widget has at least one file selected (True/False)
 20    has_file = pyqtSignal(bool)
 21
 22    def __init__(self, context, has_existing_function, label, allow_multiple,
 23                 processing=None, forced_filters=None, parent=None):
 24        """
 25        Initialize the FileSelectorWidget.
 26
 27        Args:
 28            context (dict): Shared context containing signals and shared UI functions.
 29            has_existing_function (callable): Function to check if a file already exists.
 30            label (str): Label describing the purpose of the file selection.
 31            allow_multiple (bool): Whether multiple file selection is allowed.
 32            processing (pyqtSignal, optional): Signal controlling whether buttons are active.
 33            forced_filters (list[str], optional): File type filters.
 34            parent (QWidget, optional): Parent widget.
 35        """
 36        super().__init__(parent)
 37
 38        self.context = context
 39        # Connects external signal for when files are selected from a shared dialog
 40        if self.context and "selected_files_signal" in self.context:
 41            self.context["selected_files_signal"].connect(self.set_selected_files)
 42
 43        self.label = label
 44        self.allow_multiple = allow_multiple
 45        self.has_existing_function = has_existing_function
 46        if processing:
 47            processing.connect(self.set_processing_mode)
 48        self.forced_filters = forced_filters
 49
 50        self.selected_files = None
 51
 52        # --- Main Layout ---
 53        file_selector_layout = QHBoxLayout(self)
 54        self.setLayout(file_selector_layout)
 55
 56        # --- File list display ---
 57        self.file_list_widget = QListWidget()
 58        self.file_list_widget.setSelectionMode(QListWidget.SelectionMode.NoSelection)
 59        self.file_list_widget.setFocusPolicy(Qt.FocusPolicy.NoFocus)
 60        self.file_list_widget.setMaximumHeight(100)
 61        file_selector_layout.addWidget(self.file_list_widget, stretch=1)
 62
 63        # --- Button container (Choose / Clear) ---
 64        button_container = QWidget()
 65        button_layout = QVBoxLayout(button_container)
 66        button_layout.addStretch()
 67
 68        # "Choose file(s)" button — opens NIfTI selection dialog
 69        self.file_button = QPushButton("Choose NIfTI File(s)")
 70        self.file_button.clicked.connect(self.open_tree_dialog)
 71        button_layout.addWidget(self.file_button, alignment=Qt.AlignmentFlag.AlignCenter)
 72
 73        # "Clear selection" button — removes all selected files
 74        self.clear_button = QPushButton("Clear Selection")
 75        self.clear_button.setEnabled(False)
 76        self.clear_button.clicked.connect(self.clear_selected_files)
 77        button_layout.addWidget(self.clear_button, alignment=Qt.AlignmentFlag.AlignCenter)
 78
 79        button_layout.addStretch()
 80        file_selector_layout.addWidget(button_container)
 81
 82        # --- Translation support ---
 83        self._translate_ui()
 84        if context and "language_changed" in context:
 85            context["language_changed"].connect(self._translate_ui)
 86
 87    # -------------------------------------------------------------------------
 88    # Dialog and file selection
 89    # -------------------------------------------------------------------------
 90    def open_tree_dialog(self):
 91        """
 92        Opens the NIfTI file selection dialog (tree-based).
 93        Updates the file list upon user confirmation.
 94        """
 95        results = NiftiFileDialog.get_files(
 96            context=self.context,
 97            allow_multiple=self.allow_multiple,
 98            has_existing_func=self.has_existing_function,
 99            label=self.label,
100            forced_filters=self.forced_filters
101        )
102
103        if results:
104            if self.allow_multiple:
105                self.set_selected_files(results)
106            else:
107                # If single selection only, use the last selected file
108                self.set_selected_files([results[0]])
109
110    def clear_selected_files(self):
111        """
112        Clears all selected files and updates UI state.
113        """
114        self.selected_files = []
115        self.file_list_widget.clear()
116        self.clear_button.setEnabled(False)
117        self.has_file.emit(False)
118
119        # Notify the main UI to update its state
120        if self.context and "update_main_buttons" in self.context:
121            self.context["update_main_buttons"]()
122
123    def set_selected_files(self, file_paths):
124        """
125        Updates the internal file list and UI after user selection.
126
127        Args:
128            file_paths (list[str]): List of selected file paths.
129        """
130        if self.allow_multiple:
131            # Keep only valid NIfTI files (exclude folders and invalid extensions)
132            file_paths = [
133                file for file in file_paths
134                if os.path.exists(file)
135                and not os.path.isdir(file)
136                and (file.endswith('.nii.gz') or file.endswith('.nii'))
137            ]
138        else:
139            # If only one file is allowed, keep the last one
140            file_paths = [file_paths[-1]]
141
142        self.selected_files = file_paths
143        self.file_list_widget.clear()
144
145        # Populate the list widget with file names
146        for path in file_paths:
147            item = QListWidgetItem(QIcon.fromTheme("document"), os.path.basename(path))
148            item.setToolTip(path)
149            self.file_list_widget.addItem(item)
150
151        # Enable/disable buttons based on selection
152        self.clear_button.setEnabled(bool(file_paths))
153        self.has_file.emit(bool(file_paths))
154
155        # Notify main UI of state change
156        if self.context and "update_main_buttons" in self.context:
157            self.context["update_main_buttons"]()
158
159    # -------------------------------------------------------------------------
160    # Accessors and state management
161    # -------------------------------------------------------------------------
162    def get_selected_files(self):
163        """Return the list of selected file paths."""
164        return self.selected_files
165
166    def set_processing_mode(self, processing):
167        """
168        Enable/disable user interaction when the app is processing data.
169
170        Args:
171            processing (bool): True to disable file selection during processing.
172        """
173        self.file_button.setEnabled(not processing)
174        self.clear_button.setEnabled(not processing and bool(self.selected_files))
175        if processing:
176            self.context["selected_files_signal"].disconnect()
177        else:
178            self.context["selected_files_signal"].connect(self.set_selected_files)
179
180    # -------------------------------------------------------------------------
181    # Translation
182    # -------------------------------------------------------------------------
183    def _translate_ui(self):
184        """Apply dynamic UI translations (for multi-language support)."""
185        if self.allow_multiple:
186            self.file_button.setText(QCoreApplication.translate("Components", "Choose NIfTI File(s)"))
187        else:
188            self.file_button.setText(QCoreApplication.translate("Components", "Choose NIfTI File"))
189        self.clear_button.setText(QCoreApplication.translate("Components", "Clear Selection"))

Widget for selecting and displaying one or multiple NIfTI files. Provides a graphical list of selected files and buttons for choosing or clearing them.

FileSelectorWidget( context, has_existing_function, label, allow_multiple, processing=None, forced_filters=None, parent=None)
22    def __init__(self, context, has_existing_function, label, allow_multiple,
23                 processing=None, forced_filters=None, parent=None):
24        """
25        Initialize the FileSelectorWidget.
26
27        Args:
28            context (dict): Shared context containing signals and shared UI functions.
29            has_existing_function (callable): Function to check if a file already exists.
30            label (str): Label describing the purpose of the file selection.
31            allow_multiple (bool): Whether multiple file selection is allowed.
32            processing (pyqtSignal, optional): Signal controlling whether buttons are active.
33            forced_filters (list[str], optional): File type filters.
34            parent (QWidget, optional): Parent widget.
35        """
36        super().__init__(parent)
37
38        self.context = context
39        # Connects external signal for when files are selected from a shared dialog
40        if self.context and "selected_files_signal" in self.context:
41            self.context["selected_files_signal"].connect(self.set_selected_files)
42
43        self.label = label
44        self.allow_multiple = allow_multiple
45        self.has_existing_function = has_existing_function
46        if processing:
47            processing.connect(self.set_processing_mode)
48        self.forced_filters = forced_filters
49
50        self.selected_files = None
51
52        # --- Main Layout ---
53        file_selector_layout = QHBoxLayout(self)
54        self.setLayout(file_selector_layout)
55
56        # --- File list display ---
57        self.file_list_widget = QListWidget()
58        self.file_list_widget.setSelectionMode(QListWidget.SelectionMode.NoSelection)
59        self.file_list_widget.setFocusPolicy(Qt.FocusPolicy.NoFocus)
60        self.file_list_widget.setMaximumHeight(100)
61        file_selector_layout.addWidget(self.file_list_widget, stretch=1)
62
63        # --- Button container (Choose / Clear) ---
64        button_container = QWidget()
65        button_layout = QVBoxLayout(button_container)
66        button_layout.addStretch()
67
68        # "Choose file(s)" button — opens NIfTI selection dialog
69        self.file_button = QPushButton("Choose NIfTI File(s)")
70        self.file_button.clicked.connect(self.open_tree_dialog)
71        button_layout.addWidget(self.file_button, alignment=Qt.AlignmentFlag.AlignCenter)
72
73        # "Clear selection" button — removes all selected files
74        self.clear_button = QPushButton("Clear Selection")
75        self.clear_button.setEnabled(False)
76        self.clear_button.clicked.connect(self.clear_selected_files)
77        button_layout.addWidget(self.clear_button, alignment=Qt.AlignmentFlag.AlignCenter)
78
79        button_layout.addStretch()
80        file_selector_layout.addWidget(button_container)
81
82        # --- Translation support ---
83        self._translate_ui()
84        if context and "language_changed" in context:
85            context["language_changed"].connect(self._translate_ui)

Initialize the FileSelectorWidget.

Args: context (dict): Shared context containing signals and shared UI functions. has_existing_function (callable): Function to check if a file already exists. label (str): Label describing the purpose of the file selection. allow_multiple (bool): Whether multiple file selection is allowed. processing (pyqtSignal, optional): Signal controlling whether buttons are active. forced_filters (list[str], optional): File type filters. parent (QWidget, optional): Parent widget.

def has_file(unknown):

pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.

context
label
allow_multiple
has_existing_function
forced_filters
selected_files
file_list_widget
file_button
clear_button
def open_tree_dialog(self):
 90    def open_tree_dialog(self):
 91        """
 92        Opens the NIfTI file selection dialog (tree-based).
 93        Updates the file list upon user confirmation.
 94        """
 95        results = NiftiFileDialog.get_files(
 96            context=self.context,
 97            allow_multiple=self.allow_multiple,
 98            has_existing_func=self.has_existing_function,
 99            label=self.label,
100            forced_filters=self.forced_filters
101        )
102
103        if results:
104            if self.allow_multiple:
105                self.set_selected_files(results)
106            else:
107                # If single selection only, use the last selected file
108                self.set_selected_files([results[0]])

Opens the NIfTI file selection dialog (tree-based). Updates the file list upon user confirmation.

def clear_selected_files(self):
110    def clear_selected_files(self):
111        """
112        Clears all selected files and updates UI state.
113        """
114        self.selected_files = []
115        self.file_list_widget.clear()
116        self.clear_button.setEnabled(False)
117        self.has_file.emit(False)
118
119        # Notify the main UI to update its state
120        if self.context and "update_main_buttons" in self.context:
121            self.context["update_main_buttons"]()

Clears all selected files and updates UI state.

def set_selected_files(self, file_paths):
123    def set_selected_files(self, file_paths):
124        """
125        Updates the internal file list and UI after user selection.
126
127        Args:
128            file_paths (list[str]): List of selected file paths.
129        """
130        if self.allow_multiple:
131            # Keep only valid NIfTI files (exclude folders and invalid extensions)
132            file_paths = [
133                file for file in file_paths
134                if os.path.exists(file)
135                and not os.path.isdir(file)
136                and (file.endswith('.nii.gz') or file.endswith('.nii'))
137            ]
138        else:
139            # If only one file is allowed, keep the last one
140            file_paths = [file_paths[-1]]
141
142        self.selected_files = file_paths
143        self.file_list_widget.clear()
144
145        # Populate the list widget with file names
146        for path in file_paths:
147            item = QListWidgetItem(QIcon.fromTheme("document"), os.path.basename(path))
148            item.setToolTip(path)
149            self.file_list_widget.addItem(item)
150
151        # Enable/disable buttons based on selection
152        self.clear_button.setEnabled(bool(file_paths))
153        self.has_file.emit(bool(file_paths))
154
155        # Notify main UI of state change
156        if self.context and "update_main_buttons" in self.context:
157            self.context["update_main_buttons"]()

Updates the internal file list and UI after user selection.

Args: file_paths (list[str]): List of selected file paths.

def get_selected_files(self):
162    def get_selected_files(self):
163        """Return the list of selected file paths."""
164        return self.selected_files

Return the list of selected file paths.

def set_processing_mode(self, processing):
166    def set_processing_mode(self, processing):
167        """
168        Enable/disable user interaction when the app is processing data.
169
170        Args:
171            processing (bool): True to disable file selection during processing.
172        """
173        self.file_button.setEnabled(not processing)
174        self.clear_button.setEnabled(not processing and bool(self.selected_files))
175        if processing:
176            self.context["selected_files_signal"].disconnect()
177        else:
178            self.context["selected_files_signal"].connect(self.set_selected_files)

Enable/disable user interaction when the app is processing data.

Args: processing (bool): True to disable file selection during processing.