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"))
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.
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.
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.
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.
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.
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.
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.
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.