diff --git a/src/view/dialogs/asset_mgmt_dialog.py b/src/view/dialogs/asset_mgmt_dialog.py new file mode 100644 index 00000000..5c288d02 --- /dev/null +++ b/src/view/dialogs/asset_mgmt_dialog.py @@ -0,0 +1,92 @@ +"""Contains the asset management dialog.""" + +from __future__ import annotations + +import os + +from PySide6.QtGui import QAction, QIcon +from PySide6.QtWidgets import QDialog, QDialogButtonBox, QFileDialog, QMessageBox, QToolBar, QVBoxLayout, QWidget + +from controller.utils.process_notifications import ProcessNotifier, get_process_notifier +from model.media_assets.image import LocalImage +from model.media_assets.media_type import MediaType +from view.utility_widgets.asset_selection_widget import AssetSelectionWidget + +_SUPPORTED_FILE_ENDINGS: dict[MediaType, list[str]] = { + MediaType.TEXT: [".txt"], + MediaType.IMAGE: [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".pbm", ".pgm", ".ppm", ".xbm", ".xpm"], + MediaType.VIDEO: [".mp4"], + MediaType.AUDIO: [".wav", ".mp3", ".flac"], + MediaType.MODEL_3D: [".stl", ".obj"] +} + + +class AssetManagementDialog(QDialog): + """Provide a GUI method to manage assets.""" + + def __init__(self, parent: QWidget | None = None, show_file_path: str | None = None) -> None: + """Initialize the asset management dialog.""" + super().__init__(parent) + layout = QVBoxLayout() + + self._action_button_group = QToolBar() + + self._load_asset_file = QAction() + self._load_asset_file.setIcon(QIcon.fromTheme("document-open")) + self._load_asset_file.setText("Load asset from file") + self._load_asset_file.triggered.connect(self._open_file) + self._action_button_group.addAction(self._load_asset_file) + + layout.addWidget(self._action_button_group) + + self._asset_display = AssetSelectionWidget(self) + layout.addWidget(self._asset_display) + + self._close_button_group = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) + self._close_button_group.button(QDialogButtonBox.StandardButton.Close).clicked.connect(self.close) + layout.addWidget(self._close_button_group) + + self.setLayout(layout) + self.setModal(True) + self._dialog: QFileDialog | None = None + self._show_file_path = show_file_path if show_file_path is not None else "" + self.setMinimumWidth(800) + + def _open_file(self) -> None: + self._dialog = QFileDialog(self, "Open file") + self._dialog.setModal(True) + self._dialog.setNameFilters( + [f"{k.get_long_description()} ({" ".join(f"*{va}" for va in v)})" + for k, v in _SUPPORTED_FILE_ENDINGS.items()] + ) + self._dialog.setDefaultSuffix(".jpg") + self._dialog.setFileMode(QFileDialog.FileMode.ExistingFiles) + self._dialog.setDirectory(os.path.expanduser("~") if self._show_file_path is None else self._show_file_path) + self._dialog.filesSelected.connect(self._process_loading_files) + self._dialog.open() + + def _process_loading_files(self, list_of_files: list[str]) -> None: + self._dialog.close() + if len(list_of_files) == 0: + return + accumulated_errors = "" + pn = get_process_notifier("Asset Loading", len(list_of_files)) + for file_path in list_of_files: + pn.current_step_description = f"Loading Asset from {file_path}" + extension = os.path.splitext(file_path)[1] + try: + if extension in _SUPPORTED_FILE_ENDINGS[MediaType.IMAGE]: + LocalImage(file_path, show_file_path=self._show_file_path) + else: + accumulated_errors += f"Unable to load asset from {file_path}: extension {extension} unknown.\n" + except Exception as e: + accumulated_errors += f"Unable to load asset from {file_path}: {e}\n" + pn.current_step_number += 1 + self._asset_display.reload_model() + pn.close() + if len(accumulated_errors) > 0: + msg_box = QMessageBox(QMessageBox.Icon.Critical, "Loading Assets Failed", + "Errors occurred during asset loading:", parent=self, + detailedText=accumulated_errors) + msg_box.show() + self._dialog = msg_box diff --git a/src/view/main_window.py b/src/view/main_window.py index bdec8655..06bdb728 100644 --- a/src/view/main_window.py +++ b/src/view/main_window.py @@ -21,6 +21,7 @@ from utility import resource_path from view.action_setup_view.combined_action_setup_widget import CombinedActionSetupWidget from view.console_mode.console_universe_selector import UniverseSelector +from view.dialogs.asset_mgmt_dialog import AssetManagementDialog from view.dialogs.colum_dialog import ColumnDialog from view.logging_view.logging_widget import LoggingWidget from view.main_widget import MainWidget @@ -199,6 +200,8 @@ def _setup_menubar(self) -> None: "Tools": [ # ("Scene Wizard", self._open_scene_setup_wizard, None), ("Patch Plan Export", self._open_patch_plan_export_dialog, None), + ("Asset Management", self._open_asset_mgmt_dialog, None), + ("---", None, None), ("&Toggle Terminal", self._toggle_terminal, "T"), ], "Help": [ @@ -395,3 +398,7 @@ def _toggle_terminal(self) -> None: else: self._terminal_widget.show() self._terminal_widget.focusWidget() + + def _open_asset_mgmt_dialog(self) -> None: + self._settings_dialog = AssetManagementDialog(self, self._board_configuration.file_path) + self._settings_dialog.show() diff --git a/src/view/show_mode/show_ui_widgets/macro_buttons_ui_widget.py b/src/view/show_mode/show_ui_widgets/macro_buttons_ui_widget.py index c7e9e69f..cae5ab09 100644 --- a/src/view/show_mode/show_ui_widgets/macro_buttons_ui_widget.py +++ b/src/view/show_mode/show_ui_widgets/macro_buttons_ui_widget.py @@ -57,7 +57,6 @@ def __init__(self, ui_widget: MacroButtonUIWidget, button_list: QListWidget, upd self._text_tb = QLineEdit(self) layout.addRow("Display Text: ", self._text_tb) - # TODO add Icon selection from show media storage self._icon_selection = AssetSelectionWidget(self, allowed_types=[MediaType.IMAGE], multiselection_allowed=False) layout.addRow("Select Icon", self._icon_selection) # TODO add clear icon button diff --git a/src/view/utility_widgets/asset_selection_widget.py b/src/view/utility_widgets/asset_selection_widget.py index e9b38d13..fd157ab7 100644 --- a/src/view/utility_widgets/asset_selection_widget.py +++ b/src/view/utility_widgets/asset_selection_widget.py @@ -30,7 +30,7 @@ def __init__(self) -> None: self._name_filter: str = "" self._filtered_asset_list: list[MediaAsset] = [] - def apply_filter(self, name: str, types: set[MediaType]) -> None: + def apply_filter(self, name: str, types: set[MediaType], force_update: bool = False) -> None: """Apply the filter parameters to the asset selection. Warning: this operation may be quite expensive. It is therefore better to wait for a filter to be entered @@ -39,11 +39,12 @@ def apply_filter(self, name: str, types: set[MediaType]) -> None: Args: name: If set to anything than an empty string, all assets needs to contain this in their name. types: The types of assets to show. + force_update: If set to true, updating the model is forced. """ - if types == self._selected_media_types and name == self._name_filter: + if (types == self._selected_media_types and name == self._name_filter) and not force_update: return - if len(name) < len(self._name_filter): + if len(name) < len(self._name_filter) or force_update: self._filtered_asset_list.clear() self._selected_media_types.clear() types_to_add = types - self._selected_media_types @@ -199,16 +200,20 @@ def __init__(self, parent: QWidget | None = None, allowed_types: list[MediaType] self.setLayout(layout) self._update_filter() - def _update_filter(self) -> None: + def _update_filter(self, force: bool = False) -> None: selected_types: set[MediaType] = set() for asset_type, action in self._type_checkboxes: if action.isChecked(): selected_types.add(asset_type) self._asset_view.setEnabled(False) - self._model.apply_filter(self._search_bar.text(), selected_types) + self._model.apply_filter(self._search_bar.text(), selected_types, force_update=force) self._asset_view.setEnabled(True) self.update() + def reload_model(self) -> None: + """Forcefully update the model.""" + self._update_filter(force=True) + @property def selected_asset(self) -> list[MediaAsset]: """Get or set the selected assets."""