drag+drop support
This commit is contained in:
+105
-7
@@ -108,21 +108,116 @@ class ProjectManager:
|
||||
# =========================================================================
|
||||
# File & Folder Opening Dialogs
|
||||
# =========================================================================
|
||||
|
||||
def is_valid_snirf(self, path: str) -> bool:
|
||||
"""Fast header check to verify HDF5/SNIRF signature and ignore corrupt/shadow files."""
|
||||
try:
|
||||
if os.path.getsize(path) < 8:
|
||||
return False
|
||||
with open(path, "rb") as f:
|
||||
return f.read(8) == b"\x89HDF\r\n\x1a\n"
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def open_file_dialog(self) -> None:
|
||||
"""Opens dialog to pick a single .snirf file."""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self.app, "Open File", "", "SNIRF Files (*.snirf);;All Files (*)"
|
||||
)
|
||||
if file_path:
|
||||
self._load_files_into_pipeline([os.path.normpath(file_path)])
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
norm_path = os.path.normpath(file_path)
|
||||
filename = os.path.basename(norm_path)
|
||||
|
||||
# 1. Check specifically for macOS shadow files
|
||||
if filename.startswith("._"):
|
||||
QMessageBox.warning(
|
||||
self.app,
|
||||
"Invalid SNIRF File",
|
||||
f"'{filename}' is a macOS system shadow file (Apple Double resource fork), not a actual data file."
|
||||
)
|
||||
return
|
||||
|
||||
# 2. Check for general header validity / corruption
|
||||
if not self.is_valid_snirf(norm_path):
|
||||
QMessageBox.warning(
|
||||
self.app,
|
||||
"Invalid SNIRF File",
|
||||
f"'{filename}' is not a valid SNIRF file or has a corrupted header."
|
||||
)
|
||||
return
|
||||
|
||||
self._load_files_into_pipeline([norm_path])
|
||||
|
||||
def open_folder_dialog(self)-> None:
|
||||
"""Recursively finds all .snirf files in a selected directory."""
|
||||
folder_path = QFileDialog.getExistingDirectory(self.app, "Select Folder", "")
|
||||
if folder_path:
|
||||
snirf_files = [os.path.normpath(str(f)) for f in Path(folder_path).rglob("*.snirf")]
|
||||
self._load_files_into_pipeline(snirf_files)
|
||||
if not folder_path:
|
||||
return
|
||||
|
||||
# Automatically filter out shadow files and invalid headers in batch mode
|
||||
snirf_files = [
|
||||
os.path.normpath(str(f))
|
||||
for f in Path(folder_path).rglob("*.snirf")
|
||||
if self.is_valid_snirf(str(f))
|
||||
]
|
||||
|
||||
if not snirf_files:
|
||||
QMessageBox.information(
|
||||
self.app,
|
||||
"No Valid Files",
|
||||
"No valid .snirf files were found in the selected directory."
|
||||
)
|
||||
return
|
||||
|
||||
def load_dropped_files(self, file_paths: List[str]) -> None:
|
||||
"""Public entry point for handling files or folders dropped onto the UI."""
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
# 1. Single file drop: Give explicit feedback like open_file_dialog
|
||||
if len(file_paths) == 1:
|
||||
norm_path = os.path.normpath(file_paths[0])
|
||||
filename = os.path.basename(norm_path)
|
||||
|
||||
if filename.startswith("._"):
|
||||
QMessageBox.warning(
|
||||
self.app,
|
||||
"Invalid SNIRF File",
|
||||
f"'{filename}' is a macOS system shadow file (Apple Double resource fork), not an actual data file."
|
||||
)
|
||||
return
|
||||
|
||||
if not self.is_valid_snirf(norm_path):
|
||||
QMessageBox.warning(
|
||||
self.app,
|
||||
"Invalid SNIRF File",
|
||||
f"'{filename}' is not a valid SNIRF file or has a corrupted header."
|
||||
)
|
||||
return
|
||||
|
||||
valid_files = [norm_path]
|
||||
|
||||
# 2. Batch drop: Silently filter out shadow/corrupt files like open_folder_dialog
|
||||
else:
|
||||
valid_files = [
|
||||
os.path.normpath(p)
|
||||
for p in file_paths
|
||||
if self.is_valid_snirf(os.path.normpath(p))
|
||||
]
|
||||
|
||||
if not valid_files:
|
||||
QMessageBox.information(
|
||||
self.app,
|
||||
"No Valid Files",
|
||||
"None of the dropped items were valid .snirf files."
|
||||
)
|
||||
return
|
||||
|
||||
# Delegate to internal pipeline
|
||||
self._load_files_into_pipeline(valid_files)
|
||||
|
||||
def _load_files_into_pipeline(self, file_paths: List[str]) -> None:
|
||||
"""Loads .snirf files into UI using chunked batches and background workers."""
|
||||
app = self.app
|
||||
@@ -146,8 +241,11 @@ class ProjectManager:
|
||||
if not hasattr(app, "metadata_cache"):
|
||||
app.metadata_cache = {}
|
||||
|
||||
# Filter out files already in the UI to avoid duplicates
|
||||
new_files = [p for p in file_paths if p not in app.selected_paths]
|
||||
# Filter out duplicates AND non-SNIRF files (including macOS ._ shadow files)
|
||||
new_files = [
|
||||
p for p in file_paths
|
||||
if p not in app.selected_paths and self.is_valid_snirf(p)
|
||||
]
|
||||
if not new_files:
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user