diff --git a/src/model/media_assets/asset.py b/src/model/media_assets/asset.py index 02dbc984..3c08ebc1 100644 --- a/src/model/media_assets/asset.py +++ b/src/model/media_assets/asset.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from uuid import uuid4 -from model.media_assets.registry import register +from model.media_assets.registry import register, unregister if TYPE_CHECKING: from PySide6.QtGui import QPixmap @@ -102,3 +102,7 @@ def is_local_resource(self) -> bool: Non-local resources need to be provided and copied together with the show file. """ return False + + def unregister(self) -> None: + """Unregister this asset.""" + unregister(self) diff --git a/src/model/media_assets/registry.py b/src/model/media_assets/registry.py index dacc2cc5..4abaf857 100644 --- a/src/model/media_assets/registry.py +++ b/src/model/media_assets/registry.py @@ -30,6 +30,25 @@ def register(asset: MediaAsset, uuid: str) -> bool: d[uuid] = asset return True +def unregister(asset: MediaAsset) -> bool: + """Method unregisters a media asset. + + Args: + asset: the media asset to unregister + + Returns: + True if the asset was successfully unregistered, False otherwise. + + """ + reg = _asset_library.get(asset.get_type()) + if reg is None: + return False + try: + reg.pop(asset.id) + return True + except KeyError: + return False + def get_asset_by_uuid(uuid: str) -> MediaAsset | None: """Get a media asset by its UUID. diff --git a/src/view/dialogs/asset_mgmt_dialog.py b/src/view/dialogs/asset_mgmt_dialog.py index 5c288d02..68aba5ae 100644 --- a/src/view/dialogs/asset_mgmt_dialog.py +++ b/src/view/dialogs/asset_mgmt_dialog.py @@ -36,10 +36,16 @@ def __init__(self, parent: QWidget | None = None, show_file_path: str | None = N 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) + self._delete_selected_asset_action = QAction() + self._delete_selected_asset_action.setIcon(QIcon.fromTheme("edit-delete")) + self._delete_selected_asset_action.setText("Delete selected asset") + self._delete_selected_asset_action.triggered.connect(self._delete_selected_asset) + self._delete_selected_asset_action.setEnabled(False) + self._action_button_group.addAction(self._delete_selected_asset_action) layout.addWidget(self._action_button_group) - self._asset_display = AssetSelectionWidget(self) + self._asset_display = AssetSelectionWidget(self, multiselection_allowed=True) layout.addWidget(self._asset_display) self._close_button_group = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) @@ -51,6 +57,7 @@ def __init__(self, parent: QWidget | None = None, show_file_path: str | None = N self._dialog: QFileDialog | None = None self._show_file_path = show_file_path if show_file_path is not None else "" self.setMinimumWidth(800) + self._asset_display.asset_selection_changed.connect(self._selected_asset_changed) def _open_file(self) -> None: self._dialog = QFileDialog(self, "Open file") @@ -90,3 +97,11 @@ def _process_loading_files(self, list_of_files: list[str]) -> None: detailedText=accumulated_errors) msg_box.show() self._dialog = msg_box + + def _selected_asset_changed(self) -> None: + self._delete_selected_asset_action.setEnabled(len(self._asset_display.selected_asset) > 0) + + def _delete_selected_asset(self) -> None: + for asset in self._asset_display.selected_asset: + asset.unregister() + self._asset_display.reload_model() diff --git a/src/view/dialogs/asset_selection_dialog.py b/src/view/dialogs/asset_selection_dialog.py new file mode 100644 index 00000000..21669fd8 --- /dev/null +++ b/src/view/dialogs/asset_selection_dialog.py @@ -0,0 +1,52 @@ +"""Provide a dialog for selecting assets.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, override + +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QWidget +from qasync import QApplication + +from model.media_assets.asset import MediaAsset +from view.utility_widgets.asset_selection_widget import AssetSelectionWidget + +if TYPE_CHECKING: + from model.media_assets.media_type import MediaType + + +class AssetSelectionDialog(QDialog): + """A dialog for selecting assets.""" + + asset_selected: Signal = Signal(MediaAsset) + + def __init__(self, parent: QWidget | None = None, + allowed_types: list[MediaType] | None = None, multiselection_allowed: bool = False) -> None: + """Initialize the dialog.""" + super().__init__(parent) + layout = QVBoxLayout() + + self._asset_view = AssetSelectionWidget(self, allowed_types, multiselection_allowed) + layout.addWidget(self._asset_view) + + button_box = QDialogButtonBox( + QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, Qt.Orientation.Horizontal, self + ) + layout.addWidget(button_box) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + self.setMinimumWidth(800) + self.setMinimumHeight(600) + self.setLayout(layout) + + @override + def accept(self) -> None: + self.asset_selected.emit(self._asset_view.selected_asset) + QApplication.processEvents() + super().accept() + self.close() + + @override + def reject(self) -> None: + super().reject() + self.close() diff --git a/src/view/main_window.py b/src/view/main_window.py index 62c32acd..888c1f21 100644 --- a/src/view/main_window.py +++ b/src/view/main_window.py @@ -28,6 +28,7 @@ from view.misc.console_dock_widget import ConsoleDockWidget from view.misc.settings.settings_dialog import SettingsDialog from view.patch_view.patch_mode import PatchMode +from view.show_mode.editor.node_editor_widgets.cue_editor.yes_no_dialog import YesNoDialog from view.show_mode.editor.showmanager import ShowEditorWidget from view.show_mode.player.showplayer import ShowPlayerWidget from view.utility_widgets.wizzards.patch_plan_export import PatchPlanExportWizard @@ -136,6 +137,7 @@ def __init__(self, parent: QWidget = None) -> None: self._terminal_widget: ConsoleDockWidget | None = None self.setWindowIcon(QPixmap(resource_path(os.path.join("resources", "logo.png")))) + self._close_now = False @property def fish_connector(self) -> NetworkManager: @@ -216,14 +218,6 @@ def _setup_menubar(self) -> None: self._add_entries_to_menu(menu, entries) self.menuBar().addAction(menu.menuAction()) - @override - def closeEvent(self, event: QCloseEvent, /) -> None: - # TODO use event.ignore() here is there's still stuff to do - super().closeEvent(event) - QApplication.processEvents() - self._broadcaster.application_closing.emit() - QApplication.processEvents() - def _start_connection(self) -> None: # TODO rework to signals self._fish_connector.start(True) @@ -405,3 +399,25 @@ def _toggle_terminal(self) -> None: def _open_asset_mgmt_dialog(self) -> None: self._settings_dialog = AssetManagementDialog(self, self._board_configuration.file_path) self._settings_dialog.show() + + @override + def closeEvent(self, event: QCloseEvent) -> None: + if self._close_now: + super().closeEvent(event) + QApplication.processEvents() + self._broadcaster.application_closing.emit() + QApplication.processEvents() + else: + event.ignore() + self._settings_dialog = YesNoDialog( + self, + "Close Editor", + "Do you really want to close this window? Any unsaved changes will be lost.", + self._close_callback + ) + self._settings_dialog.setModal(True) + self._settings_dialog.show() + + def _close_callback(self) -> None: + self._close_now = True + self.close() 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 cae5ab09..33bcbe79 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 @@ -28,6 +28,7 @@ from model.media_assets.registry import get_asset_by_uuid from utility import resource_path from view.action_setup_view._command_insertion_dialog import escape_argument +from view.dialogs.asset_selection_dialog import AssetSelectionDialog from view.show_mode.editor.editor_tab_widgets.ui_widget_editor._widget_holder import UIWidgetHolder from view.show_mode.editor.show_browser.annotated_item import AnnotatedListWidgetItem from view.utility_widgets.asset_selection_widget import AssetSelectionWidget @@ -35,6 +36,7 @@ if TYPE_CHECKING: from model import UIPage + from model.media_assets.asset import MediaAsset class _AddMacroActionDialog(QDialog): @@ -108,12 +110,9 @@ def __init__(self, parent: QListWidget, item_def: dict[str, str], index: int, up self._icon_bt.setIcon(self._NO_ICON) layout.addWidget(self._icon_bt) layout.addStretch() - icon_label = QLabel(self) - if self._item_def.get("icon", "") != "": - asset = get_asset_by_uuid(self._item_def["icon"]) - if asset is not None: - icon_label.setPixmap(asset.get_thumbnail()) - layout.addWidget(icon_label) + self.icon_label = QLabel(self) + self._update_displayed_icon() + layout.addWidget(self.icon_label) layout.addStretch() self._text_tb = QLineEdit(self) self._text_tb.setText(item_def["text"]) @@ -125,7 +124,18 @@ def __init__(self, parent: QListWidget, item_def: dict[str, str], index: int, up self._command_tb.textChanged.connect(self._command_changed) layout.addWidget(self._command_tb) self.setLayout(layout) - # TODO implement icon changing functionality + self._dialog: QDialog | None = None + self._icon_bt.pressed.connect(self._change_icon_of_button) + + def _update_displayed_icon(self) -> None: + found_icon = False + if self._item_def.get("icon", "") != "": + asset = get_asset_by_uuid(self._item_def["icon"]) + if asset is not None: + self.icon_label.setPixmap(asset.get_thumbnail()) + found_icon = True + if not found_icon: + self.icon_label.setPixmap(self._NO_ICON.pixmap(64, 64)) def _text_changed(self, text: str) -> None: self._item_def["text"] = text @@ -135,6 +145,24 @@ def _command_changed(self, text: str) -> None: self._item_def["command"] = text self._update_button.setEnabled(True) + def _change_icon_of_button(self) -> None: + self._dialog = AssetSelectionDialog(self, allowed_types=[MediaType.IMAGE], multiselection_allowed=False) + self._dialog.setModal(True) + self._dialog.asset_selected.connect(self._icon_changed) + self._dialog.open() + + def _icon_changed(self, asset: list[MediaAsset]) -> None: + self._dialog = None + if len(asset) == 0: + asset_id = "" + else: + asset = asset[-1] + asset_id = asset.id if asset is not None else "" + if asset_id != self._item_def.get("icon", ""): + self._update_button.setEnabled(True) + self._item_def["icon"] = asset_id + self._update_displayed_icon() + @property def item_def(self) -> dict[str, str]: return self._item_def diff --git a/src/view/utility_widgets/asset_selection_widget.py b/src/view/utility_widgets/asset_selection_widget.py index fd157ab7..2d267e11 100644 --- a/src/view/utility_widgets/asset_selection_widget.py +++ b/src/view/utility_widgets/asset_selection_widget.py @@ -5,7 +5,7 @@ import os from typing import TYPE_CHECKING, Any, override -from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt +from PySide6.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal from PySide6.QtGui import QAction, QIcon from PySide6.QtWidgets import QAbstractItemView, QLineEdit, QTableView, QToolBar, QVBoxLayout, QWidget @@ -148,6 +148,8 @@ def get_row_indicies(self, assets: list[MediaAsset]) -> list[int]: class AssetSelectionWidget(QWidget): """Provide a sortable and searchable selection widget for assets.""" + asset_selection_changed: Signal = Signal() + def __init__(self, parent: QWidget | None = None, allowed_types: list[MediaType] | None = None, multiselection_allowed: bool = True) -> None: """Initialize the asset selection widget. @@ -199,6 +201,7 @@ def __init__(self, parent: QWidget | None = None, allowed_types: list[MediaType] self.setLayout(layout) self._update_filter() + self._asset_view.selectionModel().selectionChanged.connect(lambda: self.asset_selection_changed.emit()) def _update_filter(self, force: bool = False) -> None: selected_types: set[MediaType] = set()