Coverage for main/components/file_selector_widget.py: 100%
81 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import os
3from PyQt6.QtGui import QIcon
4from PyQt6.QtWidgets import (
5 QWidget, QHBoxLayout, QVBoxLayout, QListWidget, QPushButton, QListWidgetItem
6)
7from PyQt6.QtCore import Qt, pyqtSignal, QCoreApplication
9from components.nifti_file_dialog import NiftiFileDialog
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 """
18 # Signal emitted when the widget has at least one file selected (True/False)
19 has_file = pyqtSignal(bool)
21 def __init__(self, context, has_existing_function, label, allow_multiple,
22 processing=None, forced_filters=None, parent=None):
23 """
24 Initialize the FileSelectorWidget.
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)
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)
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
49 self.selected_files = None
51 # --- Main Layout ---
52 file_selector_layout = QHBoxLayout(self)
53 self.setLayout(file_selector_layout)
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)
62 # --- Button container (Choose / Clear) ---
63 button_container = QWidget()
64 button_layout = QVBoxLayout(button_container)
65 button_layout.addStretch()
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)
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)
78 button_layout.addStretch()
79 file_selector_layout.addWidget(button_container)
81 # --- Translation support ---
82 self._translate_ui()
83 if context and "language_changed" in context:
84 context["language_changed"].connect(self._translate_ui)
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 )
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]])
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)
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"]()
122 def set_selected_files(self, file_paths):
123 """
124 Updates the internal file list and UI after user selection.
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]]
141 self.selected_files = file_paths
142 self.file_list_widget.clear()
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)
150 # Enable/disable buttons based on selection
151 self.clear_button.setEnabled(bool(file_paths))
152 self.has_file.emit(bool(file_paths))
154 # Notify main UI of state change
155 if self.context and "update_main_buttons" in self.context:
156 self.context["update_main_buttons"]()
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
165 def set_processing_mode(self, processing):
166 """
167 Enable/disable user interaction when the app is processing data.
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)
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"))