diff --git a/OTAnalytics/adapter_ui/dummy_viewmodel.py b/OTAnalytics/adapter_ui/dummy_viewmodel.py index 74c68e5a0..15b567273 100644 --- a/OTAnalytics/adapter_ui/dummy_viewmodel.py +++ b/OTAnalytics/adapter_ui/dummy_viewmodel.py @@ -563,16 +563,15 @@ async def add_video(self) -> None: for ext in clean_extensions: extension_options[ext] = [ext] - track_files = await self._ui_factory.askopenfilenames( + video_files = await self._ui_factory.askopenfilenames( title="Load video files", filetypes=[("video file", SUPPORTED_VIDEO_FILE_TYPES)], extension_options=extension_options, ) - if not track_files: + if not video_files: return - logger().info(f"Video files to load: {track_files}") - paths = [Path(file) for file in track_files] - self._application.add_videos(files=paths) + logger().info(f"Video files to load: {video_files}") + self._application.add_videos(files=video_files) def remove_videos(self) -> None: self._application.remove_videos() diff --git a/OTAnalytics/adapter_ui/ui_factory.py b/OTAnalytics/adapter_ui/ui_factory.py index 7d4845f39..2f687699f 100644 --- a/OTAnalytics/adapter_ui/ui_factory.py +++ b/OTAnalytics/adapter_ui/ui_factory.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from datetime import datetime from pathlib import Path -from typing import Iterable, Literal +from typing import Iterable from OTAnalytics.adapter_ui.file_export_dto import ExportFileDto from OTAnalytics.adapter_ui.flow_dto import FlowDto @@ -45,7 +45,7 @@ async def askopenfilenames( title: str, filetypes: Iterable[tuple[str, str | list[str] | tuple[str, ...]]], extension_options: dict[str, list[str] | None] | None = None, - ) -> Literal[""] | tuple[str, ...]: + ) -> list[Path]: raise NotImplementedError @abstractmethod diff --git a/OTAnalytics/helpers/file_helpers.py b/OTAnalytics/helpers/file_helpers.py new file mode 100644 index 000000000..1b216121b --- /dev/null +++ b/OTAnalytics/helpers/file_helpers.py @@ -0,0 +1,26 @@ +from pathlib import Path +from typing import Iterable + +from OTAnalytics.application.logger import logger + + +def get_all_files_with_correct_file_ending_in_directory( + files: Iterable[Path], file_types: list[str] +) -> list[Path]: + files_to_save: set[Path] = set() + for file_type in file_types: + for file in files: + file_to_save = file.expanduser() + if file_to_save.is_dir(): + files_in_directory = file_to_save.rglob(file_type) + files_to_save.update(files_in_directory) + continue + + if not file_to_save.exists() or file_to_save.suffix != file_type: + logger().warning( + f"Ottrk file'{file_to_save}' does not exist. Skipping file." + ) + continue + + files_to_save.add(file_to_save) + return list(files_to_save) diff --git a/OTAnalytics/plugin_ui/customtkinter_gui/ctk_ui_factory.py b/OTAnalytics/plugin_ui/customtkinter_gui/ctk_ui_factory.py index 9c4292b41..dde44ca94 100644 --- a/OTAnalytics/plugin_ui/customtkinter_gui/ctk_ui_factory.py +++ b/OTAnalytics/plugin_ui/customtkinter_gui/ctk_ui_factory.py @@ -1,7 +1,7 @@ from datetime import datetime from pathlib import Path from tkinter.filedialog import askopenfilename, askopenfilenames -from typing import Iterable, Literal +from typing import Iterable from OTAnalytics.adapter_ui.file_export_dto import ExportFileDto from OTAnalytics.adapter_ui.flow_dto import FlowDto @@ -57,8 +57,9 @@ async def askopenfilenames( title: str, filetypes: Iterable[tuple[str, str | list[str] | tuple[str, ...]]], extension_options: dict[str, list[str] | None] | None = None, - ) -> Literal[""] | tuple[str, ...]: - return askopenfilenames(title=title, filetypes=filetypes) + ) -> list[Path]: + video_files = askopenfilenames(title=title, filetypes=filetypes) + return [Path(file) for file in video_files] async def askopenfilename( self, diff --git a/OTAnalytics/plugin_ui/nicegui_gui/dialogs/file_picker.py b/OTAnalytics/plugin_ui/nicegui_gui/dialogs/file_picker.py index 12d79517b..53e4e6bae 100644 --- a/OTAnalytics/plugin_ui/nicegui_gui/dialogs/file_picker.py +++ b/OTAnalytics/plugin_ui/nicegui_gui/dialogs/file_picker.py @@ -51,7 +51,7 @@ def __init__( extensions or None for "All Files". If None, uses default options. """ super().__init__() - + self.multiple = multiple self.path: Path = directory.expanduser() self.upper_limit: Path = directory.expanduser() self.show_hidden_files: bool = show_hidden_files @@ -240,7 +240,7 @@ async def _handle_ok(self) -> None: paths: List[Path] = [self._map_to_domain(r[PATH]) for r in rows] # If no rows are selected and we're in directory-only mode, use the current directory # noqa - if not paths and self.show_only_directories: + if not paths and (self.show_only_directories or self.multiple): paths = [self.path] self.submit(paths) diff --git a/OTAnalytics/plugin_ui/nicegui_gui/ui_factory.py b/OTAnalytics/plugin_ui/nicegui_gui/ui_factory.py index a02f1fb1a..335b87917 100644 --- a/OTAnalytics/plugin_ui/nicegui_gui/ui_factory.py +++ b/OTAnalytics/plugin_ui/nicegui_gui/ui_factory.py @@ -1,7 +1,7 @@ import asyncio from datetime import datetime from pathlib import Path -from typing import Any, Iterable, Literal +from typing import Any, Iterable from OTAnalytics.adapter_ui.file_export_dto import ExportFileDto from OTAnalytics.adapter_ui.flow_dto import FlowDto @@ -17,6 +17,9 @@ from OTAnalytics.application.resources.resource_manager import ResourceManager from OTAnalytics.application.use_cases.generate_flows import FlowNameGenerator from OTAnalytics.domain.geometry import RelativeOffsetCoordinate +from OTAnalytics.helpers.file_helpers import ( + get_all_files_with_correct_file_ending_in_directory, +) from OTAnalytics.plugin_ui.nicegui_gui.dialogs.edit_flow_dialog import EditFlowDialog from OTAnalytics.plugin_ui.nicegui_gui.dialogs.edit_section_dialog import ( EditSectionDialog, @@ -27,6 +30,7 @@ from OTAnalytics.plugin_ui.nicegui_gui.dialogs.file_chooser_dialog import ( FileChooserDialog, ) +from OTAnalytics.plugin_ui.nicegui_gui.dialogs.file_picker import LocalFilePicker from OTAnalytics.plugin_ui.nicegui_gui.nicegui.elements.dialog import DialogResult @@ -88,24 +92,29 @@ async def askopenfilenames( title: str, filetypes: Iterable[tuple[str, str | list[str] | tuple[str, ...]]], extension_options: dict[str, list[str] | None] | None = None, - ) -> Literal[""] | tuple[str, ...]: + ) -> list[Path]: # For now, we'll just support selecting a single file # In a real implementation, this would allow selecting multiple files # Convert list/tuple of extensions to individual (desc, ext) pairs converted_filetypes = [] for desc, ext in filetypes: if isinstance(ext, str): - converted_filetypes.append((desc, ext)) + converted_filetypes.append(ext) elif isinstance(ext, (list, tuple)) and ext: # Use the first extension from the list/tuple - converted_filetypes.append((desc, ext[0])) + converted_filetypes.append(ext[0]) - file_path = await self.askopenfilename( - title, converted_filetypes, "", extension_options + file_paths = await LocalFilePicker( + directory=Path.home(), + show_hidden_files=False, + show_files_only_of_type=None, # Show all files + show_only_directories=False, + extension_options=extension_options, + multiple=True, + ) + return get_all_files_with_correct_file_ending_in_directory( + file_paths, converted_filetypes ) - if file_path: - return (file_path,) - return "" async def ask_for_save_file_path( self,