# gui/delegates.py from PySide6.QtWidgets import QStyledItemDelegate, QLineEdit, QComboBox from PySide6.QtCore import Qt, QModelIndex # Import the new config dictionaries from configuration import load_base_config # Import load_base_config import json import logging import os # Added for path manipulation if needed, though json.dump handles creation from PySide6.QtWidgets import QCompleter # Added QCompleter # Configure logging log = logging.getLogger(__name__) SUPPLIERS_CONFIG_PATH = "config/suppliers.json" class LineEditDelegate(QStyledItemDelegate): """Delegate for editing string values using a QLineEdit.""" def createEditor(self, parent, option, index): # Creates the QLineEdit editor widget used for editing. editor = QLineEdit(parent) return editor def setEditorData(self, editor: QLineEdit, index: QModelIndex): # Sets the editor's initial data based on the model's data. # Use EditRole to get the raw data suitable for editing. value = index.model().data(index, Qt.EditRole) editor.setText(str(value) if value is not None else "") def setModelData(self, editor: QLineEdit, model, index: QModelIndex): # Commits the editor's data back to the model. value = editor.text() # Pass the potentially modified text back to the model's setData. model.setData(index, value, Qt.EditRole) def updateEditorGeometry(self, editor, option, index): # Ensures the editor widget is placed correctly within the cell. editor.setGeometry(option.rect) class ComboBoxDelegate(QStyledItemDelegate): """ Delegate for editing string values from a predefined list using a QComboBox. Determines the list source based on column index. """ def createEditor(self, parent, option, index: QModelIndex): # Creates the QComboBox editor widget. editor = QComboBox(parent) column = index.column() model = index.model() # Get the model instance # Add a "clear" option first, associating None with it. editor.addItem("---", None) # UserData = None # Populate based on column using keys from config dictionaries items_keys = None try: base_config = load_base_config() # Load base config if column == 2: # Asset-Type Override (AssetRule) items_keys = list(base_config.get('ASSET_TYPE_DEFINITIONS', {}).keys()) # Access from base_config elif column == 4: # Item-Type Override (FileRule) items_keys = list(base_config.get('FILE_TYPE_DEFINITIONS', {}).keys()) # Access from base_config except Exception as e: log.error(f"Error loading base config for ComboBoxDelegate: {e}") items_keys = [] # Fallback to empty list on error if items_keys: for item_key in sorted(items_keys): # Sort keys alphabetically for consistency # Add item with the key string itself as text and UserData editor.addItem(item_key, item_key) else: # If the delegate is incorrectly applied to another column, # it will just have the "---" option. pass return editor def setEditorData(self, editor: QComboBox, index: QModelIndex): # Sets the combo box's current item based on the model's string data. # Get the current string value (or None) from the model via EditRole. value = index.model().data(index, Qt.EditRole) # This should be a string or None idx = -1 if value is not None: # Find the index corresponding to the string value. idx = editor.findText(value) else: # If the model value is None, find the "---" item. idx = editor.findData(None) # Find the item with UserData == None # Set the current index, defaulting to 0 ("---") if not found. editor.setCurrentIndex(idx if idx != -1 else 0) def setModelData(self, editor: QComboBox, model, index: QModelIndex): # Commits the selected combo box data (string or None) back to the model. # Get the UserData associated with the currently selected item. # This will be the string value or None (for the "---" option). value = editor.currentData() # This is either the string or None # Pass this string value or None back to the model's setData. model.setData(index, value, Qt.EditRole) def updateEditorGeometry(self, editor, option, index): # Ensures the editor widget is placed correctly within the cell. editor.setGeometry(option.rect) class SupplierSearchDelegate(QStyledItemDelegate): """ Delegate for editing supplier names using a QLineEdit with auto-completion. Loads known suppliers from config/suppliers.json and allows adding new ones. """ def __init__(self, parent=None): super().__init__(parent) self.known_suppliers = self._load_suppliers() def _load_suppliers(self): """Loads the list of known suppliers from the JSON config file.""" try: with open(SUPPLIERS_CONFIG_PATH, 'r') as f: suppliers = json.load(f) if isinstance(suppliers, list): # Ensure all items are strings return sorted([str(s) for s in suppliers if isinstance(s, str)]) else: log.warning(f"'{SUPPLIERS_CONFIG_PATH}' does not contain a valid list. Starting fresh.") return [] except FileNotFoundError: log.info(f"'{SUPPLIERS_CONFIG_PATH}' not found. Starting with an empty supplier list.") return [] except json.JSONDecodeError: log.error(f"Error decoding JSON from '{SUPPLIERS_CONFIG_PATH}'. Starting fresh.", exc_info=True) return [] except Exception as e: log.error(f"An unexpected error occurred loading '{SUPPLIERS_CONFIG_PATH}': {e}", exc_info=True) return [] def _save_suppliers(self): """Saves the current list of known suppliers back to the JSON config file.""" try: # Ensure the directory exists (though write_to_file handled initial creation) os.makedirs(os.path.dirname(SUPPLIERS_CONFIG_PATH), exist_ok=True) with open(SUPPLIERS_CONFIG_PATH, 'w') as f: json.dump(self.known_suppliers, f, indent=4) # Save sorted list with indentation log.debug(f"Successfully saved updated supplier list to '{SUPPLIERS_CONFIG_PATH}'.") except IOError as e: log.error(f"Could not write to '{SUPPLIERS_CONFIG_PATH}': {e}", exc_info=True) except Exception as e: log.error(f"An unexpected error occurred saving '{SUPPLIERS_CONFIG_PATH}': {e}", exc_info=True) def createEditor(self, parent, option, index): """Creates the QLineEdit editor with a QCompleter.""" editor = QLineEdit(parent) completer = QCompleter(self.known_suppliers, editor) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setFilterMode(Qt.MatchContains) # More flexible matching completer.setCompletionMode(QCompleter.PopupCompletion) # Standard popup editor.setCompleter(completer) return editor def setEditorData(self, editor: QLineEdit, index: QModelIndex): """Sets the editor's initial data from the model.""" # Use EditRole as defined in the model's data() method for supplier value = index.model().data(index, Qt.EditRole) editor.setText(str(value) if value is not None else "") def setModelData(self, editor: QLineEdit, model, index: QModelIndex): """Commits the editor's data back to the model and handles new suppliers.""" final_text = editor.text().strip() value_to_set = final_text if final_text else None # Set None if empty after stripping # Set data in the model first model.setData(index, value_to_set, Qt.EditRole) # Add new supplier if necessary if final_text and final_text not in self.known_suppliers: log.info(f"Adding new supplier '{final_text}' to known list.") self.known_suppliers.append(final_text) self.known_suppliers.sort() # Keep the list sorted # Update the completer's model immediately completer = editor.completer() if completer: completer.model().setStringList(self.known_suppliers) # Save the updated list back to the file self._save_suppliers() def updateEditorGeometry(self, editor, option, index): """Ensures the editor widget is placed correctly.""" editor.setGeometry(option.rect)