diff --git a/src/illogical/modules/models.py b/src/illogical/modules/models.py index b576dd9..83a28d5 100644 --- a/src/illogical/modules/models.py +++ b/src/illogical/modules/models.py @@ -10,6 +10,7 @@ from PySide6.QtCore import ( QObject, QSortFilterProxyModel, Qt, + Signal, ) from illogical.modules.sf_symbols import sf_symbol @@ -49,6 +50,8 @@ class PluginTableModel(QAbstractTableModel): "Version", ] + edit_requested = Signal(object, int, str) + def __init__(self, parent: QObject | None = None) -> None: super().__init__(parent) self._all_plugins: list[AudioComponent] = [] @@ -96,7 +99,7 @@ class PluginTableModel(QAbstractTableModel): plugin = self._plugins[index.row()] col = index.column() - if role == Qt.ItemDataRole.DisplayRole: + if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole): if col == COL_NAME: return plugin.name if col == COL_CUSTOM_NAME: @@ -113,6 +116,44 @@ class PluginTableModel(QAbstractTableModel): return None + def flags(self, index: QModelIndex) -> Qt.ItemFlag: + base_flags = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable + if index.column() in (COL_CUSTOM_NAME, COL_SHORT_NAME): + return base_flags | Qt.ItemFlag.ItemIsEditable + return base_flags + + def setData( # noqa: N802 + self, index: QModelIndex, value: object, role: int = Qt.ItemDataRole.EditRole + ) -> bool: + if role != Qt.ItemDataRole.EditRole: + return False + if not index.isValid() or not (0 <= index.row() < len(self._plugins)): + return False + col = index.column() + if col not in (COL_CUSTOM_NAME, COL_SHORT_NAME): + return False + + plugin = self._plugins[index.row()] + new_value = str(value) if value else "" + + if col == COL_CUSTOM_NAME: + current_value = plugin.tagset.nickname + else: + current_value = plugin.tagset.shortname + if new_value == (current_value or ""): + return False + + self.edit_requested.emit(plugin, col, new_value) + return False + + def update_plugin_display(self, plugin: AudioComponent, column: int) -> None: + try: + row = self._plugins.index(plugin) + except ValueError: + return + index = self.index(row, column) + self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole]) + def filter_by_category(self, category: str | None) -> None: self.beginResetModel() if category == "Show All": diff --git a/src/illogical/ui/main_window.py b/src/illogical/ui/main_window.py index d4dde06..35e8c2a 100644 --- a/src/illogical/ui/main_window.py +++ b/src/illogical/ui/main_window.py @@ -6,7 +6,10 @@ import pyqt_liquidglass as glass from PySide6.QtCore import Qt, QTimer from PySide6.QtWidgets import QHBoxLayout, QMainWindow, QMessageBox, QSplitter, QWidget +from illogical.modules import backup_manager +from illogical.modules.backup_models import BackupTrigger from illogical.modules.backup_service import BackupService +from illogical.modules.models import COL_CUSTOM_NAME, COL_SHORT_NAME from illogical.modules.plugin_service import PluginService from illogical.modules.settings import Settings from illogical.ui.backup_settings_window import BackupSettingsWindow @@ -17,7 +20,7 @@ from illogical.ui.restore_backup_window import RestoreBackupWindow from illogical.ui.sidebar import Sidebar if TYPE_CHECKING: - from logic_plugin_manager import Logic, SearchResult + from logic_plugin_manager import AudioComponent, Logic, SearchResult from PySide6.QtGui import QCloseEvent, QKeyEvent, QShowEvent from illogical.modules.backup_models import ( @@ -84,6 +87,7 @@ class MainWindow(QMainWindow): self._sidebar.enter_pressed.connect(self._plugin_table.focus_table) self._plugin_table.search_changed.connect(self._on_search_changed) self._plugin_table.plugin_selected.connect(self._on_plugin_selected) + self._plugin_table.edit_requested.connect(self._on_plugin_edit_requested) def _setup_service(self) -> None: self._service = PluginService(self) @@ -152,6 +156,24 @@ class MainWindow(QMainWindow): paths.append("Top Level") self._sidebar.highlight_categories(paths) + def _on_plugin_edit_requested( + self, plugin: AudioComponent, column: int, new_value: str + ) -> None: + try: + if backup_manager.should_create_auto_backup(): + field = "nickname" if column == COL_CUSTOM_NAME else "shortname" + description = f"Before setting {field} of {plugin.name}" + backup_manager.create_backup(BackupTrigger.AUTO, description) + + if column == COL_CUSTOM_NAME: + plugin.set_nickname(new_value) + elif column == COL_SHORT_NAME: + plugin.set_shortname(new_value) + + self._plugin_table.update_plugin_display(plugin, column) + except OSError as e: + QMessageBox.warning(self, "Edit Failed", f"Failed to save changes: {e}") + def _on_search_results(self, results: list[SearchResult]) -> None: plugins = [r.plugin for r in results] self._plugin_table.filter_by_search_results(plugins) diff --git a/src/illogical/ui/plugin_table.py b/src/illogical/ui/plugin_table.py index e6f47df..09d3033 100644 --- a/src/illogical/ui/plugin_table.py +++ b/src/illogical/ui/plugin_table.py @@ -64,6 +64,7 @@ class _VimTableView(QTableView): class PluginTableView(QWidget): search_changed = Signal(str) plugin_selected = Signal(object) + edit_requested = Signal(object, int, str) def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) @@ -78,6 +79,7 @@ class PluginTableView(QWidget): layout.addWidget(self._search_bar) self._model = PluginTableModel() + self._model.edit_requested.connect(self.edit_requested) self._table = _VimTableView() self._table.setModel(self._model) self._table.setAlternatingRowColors(True) @@ -149,6 +151,9 @@ class PluginTableView(QWidget): def filter_by_search_results(self, plugins: list[AudioComponent]) -> None: self._model.filter_by_search_results(plugins) + def update_plugin_display(self, plugin: AudioComponent, column: int) -> None: + self._model.update_plugin_display(plugin, column) + def clear_search(self) -> None: self._search_bar.clear()