Asset-Frameworker/ProjectNotes/GUI_User_Friendliness_Enhancement_Plan.md
2025-05-03 18:09:00 +02:00

13 KiB

Implementation Plan: GUI User-Friendliness Enhancements

This document outlines the plan for implementing three key GUI improvements for the Asset Processor Tool, focusing on user-friendliness and workflow efficiency.

Target Audience: Developers implementing these features. Status: Planning Phase

Feature 1: Editable Asset Name

Goal: Allow users to edit the name of an asset directly in the main view, and automatically update the 'Target Asset' field of all associated child files to reflect the new name.

Affected Components:

  • gui/unified_view_model.py (UnifiedViewModel)
  • gui/delegates.py (LineEditDelegate)
  • gui/main_window.py (or view setup location)
  • rule_structure.py (AssetRule, FileRule)
  • Potentially a new handler or modifications to gui/asset_restructure_handler.py

Implementation Steps:

  1. Enable Editing in Model (UnifiedViewModel):

    • Modify flags(): For an index pointing to an AssetRule, return Qt.ItemIsEditable in addition to default flags when index.column() is COL_NAME.
    • Modify setData():
      • Add logic to handle isinstance(item, AssetRule) and column == self.COL_NAME.
      • Get the new_asset_name from the value.
      • Validation: Before proceeding, check if an AssetRule with new_asset_name already exists within the same parent SourceRule. If so, log a warning and return False to prevent duplicate names.
      • Store the old_asset_name = item.asset_name.
      • If new_asset_name is valid and different from old_asset_name:
        • Update item.asset_name = new_asset_name.
        • Set changed = True.
        • Crucial - Child Update: Iterate through all SourceRules, AssetRules, and FileRules in the model (self._source_rules). For each FileRule found where file_rule.target_asset_name_override == old_asset_name, update file_rule.target_asset_name_override = new_asset_name. Emit dataChanged for the COL_TARGET_ASSET index of each modified FileRule. (See Potential Challenges regarding performance).
        • Emit dataChanged for the edited AssetRule's COL_NAME index.
      • Return changed.
    • (Alternative Signal Approach): Instead of performing the child update directly in setData, emit a new signal like assetNameChanged = Signal(QModelIndex, str, str) carrying the AssetRule index, old name, and new name. A dedicated handler would connect to this signal to perform the child updates. This improves separation of concerns.
  2. Assign Delegate (main_window.py / View Setup):

    • Ensure the LineEditDelegate is assigned to the view for the COL_NAME using view.setItemDelegateForColumn(UnifiedViewModel.COL_NAME, line_edit_delegate_instance).
  3. Handling Child Updates (if using Signal Approach):

    • Create a new handler class (e.g., AssetNameChangeHandler) or add a slot to AssetRestructureHandler.
    • Connect the UnifiedViewModel.assetNameChanged signal to this slot.
    • The slot receives the AssetRule index, old name, and new name. It iterates through the model's FileRules, updates their target_asset_name_override where it matches the old name, and emits dataChanged for those files.

Data Model Impact:

  • AssetRule.asset_name becomes directly mutable via the GUI.
  • The relationship between files and their intended parent asset (represented by FileRule.target_asset_name_override) is maintained automatically when the parent asset's name changes.

Potential Challenges/Considerations:

  • Performance: The child update logic requires iterating through potentially all files in the model. For very large datasets, this could be slow. Consider optimizing by maintaining an index/lookup map (Dict[str, List[FileRule]]) mapping target asset override names to the list of FileRules using them. This map would need careful updating whenever overrides change or files are moved.
  • Duplicate Asset Names: The plan includes basic validation in setData. Robust handling (e.g., user feedback, preventing the edit) is needed.
  • Undo/Redo: Reversing an asset name change requires reverting the name and reverting all the child target_asset_name_override changes, adding complexity.
  • Scope of Child Update: The current plan updates any FileRule whose override matches the old name. Confirm if this update should be restricted only to files originally under the renamed asset or within the same SourceRule. The current approach seems most logical based on how target_asset_name_override works.
sequenceDiagram
    participant User
    participant View
    participant LineEditDelegate
    participant UnifiedViewModel
    participant AssetNameChangeHandler

    User->>View: Edits AssetRule Name in COL_NAME
    View->>LineEditDelegate: setModelData(editor, model, index)
    LineEditDelegate->>UnifiedViewModel: setData(index, new_name, EditRole)
    UnifiedViewModel->>UnifiedViewModel: Validate new_name (no duplicates)
    UnifiedViewModel->>UnifiedViewModel: Update AssetRule.asset_name
    alt Signal Approach
        UnifiedViewModel->>AssetNameChangeHandler: emit assetNameChanged(index, old_name, new_name)
        AssetNameChangeHandler->>UnifiedViewModel: Iterate through FileRules
        loop For each FileRule where target_override == old_name
            AssetNameChangeHandler->>UnifiedViewModel: Update FileRule.target_asset_name_override = new_name
            UnifiedViewModel->>View: emit dataChanged(file_rule_target_index)
        end
    else Direct Approach in setData
        UnifiedViewModel->>UnifiedViewModel: Iterate through FileRules
        loop For each FileRule where target_override == old_name
            UnifiedViewModel->>UnifiedViewModel: Update FileRule.target_asset_name_override = new_name
            UnifiedViewModel->>View: emit dataChanged(file_rule_target_index)
        end
    end
    UnifiedViewModel->>View: emit dataChanged(asset_rule_name_index)

Feature 2: Item Type Field Conversion

Goal: Replace the QComboBox delegate for the "Item Type" column (for FileRules) with a QLineEdit that provides auto-suggestions based on defined file types, similar to the existing "Supplier" field.

Affected Components:

  • gui/main_window.py (or view setup location)
  • gui/delegates.py (Requires a new delegate)
  • gui/unified_view_model.py (UnifiedViewModel)
  • config/app_settings.json (Source of file type definitions)

Implementation Steps:

  1. Create New Delegate (delegates.py):

    • Create a new class ItemTypeSearchDelegate(QStyledItemDelegate).
    • createEditor(self, parent, option, index):
      • Create a QLineEdit instance.
      • Get the list of valid item type keys: item_keys = index.model()._file_type_keys (add error handling).
      • Create a QCompleter using item_keys and set it on the QLineEdit (configure case sensitivity, filter mode, completion mode as in SupplierSearchDelegate).
      • Return the editor.
    • setEditorData(self, editor, index):
      • Get the current value using index.model().data(index, Qt.EditRole).
      • Set the editor's text (editor.setText(str(value) if value is not None else "")).
    • setModelData(self, editor, model, index):
      • Get the final_text = editor.text().strip().
      • Determine the value_to_set = final_text if final_text else None.
      • Call model.setData(index, value_to_set, Qt.EditRole).
      • Important: Unlike SupplierSearchDelegate, do not add final_text to the list of known types or save anything back to config. Suggestions are strictly based on config/app_settings.json.
    • updateEditorGeometry(self, editor, option, index):
      • Standard implementation: editor.setGeometry(option.rect).
  2. Assign Delegate (main_window.py / View Setup):

    • Instantiate the new ItemTypeSearchDelegate.
    • Find where delegates are set for the view.
    • Replace the ComboBoxDelegate assignment for UnifiedViewModel.COL_ITEM_TYPE with the new ItemTypeSearchDelegate instance: view.setItemDelegateForColumn(UnifiedViewModel.COL_ITEM_TYPE, item_type_search_delegate_instance).

Data Model Impact:

  • None. The underlying data (FileRule.item_type_override) and its handling remain the same. Only the GUI editor changes.

Potential Challenges/Considerations:

  • None significant. This is a relatively straightforward replacement of one delegate type with another, leveraging existing patterns from SupplierSearchDelegate and data loading from UnifiedViewModel.

Feature 3: Drag-and-Drop File Re-parenting

Goal: Enable users to drag one or more FileRule rows and drop them onto an AssetRule row to change the parent asset of the dragged files.

Affected Components:

  • gui/main_panel_widget.py or gui/main_window.py (View management)
  • gui/unified_view_model.py (UnifiedViewModel)

Implementation Steps:

  1. Enable Drag/Drop in View (main_panel_widget.py / main_window.py):

    • Get the QTreeView instance (view).
    • view.setSelectionMode(QAbstractItemView.ExtendedSelection) (Allow selecting multiple files)
    • view.setDragEnabled(True)
    • view.setAcceptDrops(True)
    • view.setDropIndicatorShown(True)
    • view.setDefaultDropAction(Qt.MoveAction)
    • view.setDragDropMode(QAbstractItemView.InternalMove)
  2. Implement Drag/Drop Support in Model (UnifiedViewModel):

    • flags(self, index):
      • Modify to include Qt.ItemIsDragEnabled if index.internalPointer() is a FileRule.
      • Modify to include Qt.ItemIsDropEnabled if index.internalPointer() is an AssetRule.
      • Return the combined flags.
    • supportedDropActions(self):
      • Return Qt.MoveAction.
    • mimeData(self, indexes):
      • Create QMimeData.
      • Encode information about the dragged rows (which must be FileRules). Store a list of tuples, each containing (source_parent_row, source_parent_col, source_row) for each valid FileRule index in indexes. Use a custom MIME type (e.g., "application/x-filerule-index-list").
      • Return the QMimeData.
    • canDropMimeData(self, data, action, row, column, parent):
      • Check if action == Qt.MoveAction.
      • Check if data.hasFormat("application/x-filerule-index-list").
      • Check if parent.isValid() and parent.internalPointer() is an AssetRule.
      • Return True if all conditions met, False otherwise.
    • dropMimeData(self, data, action, row, column, parent):
      • Check action and MIME type again for safety.
      • Get the target AssetRule item: target_asset = parent.internalPointer(). If not an AssetRule, return False.
      • Decode the QMimeData to get the list of source index information.
      • Create a list files_to_move = [] containing the actual QModelIndex objects for the source FileRules (reconstruct them using the decoded info and self.index()).
      • Iterate through files_to_move:
        • Get the source_file_index.
        • Get the file_item = source_file_index.internalPointer().
        • Get the old_parent_asset = getattr(file_item, 'parent_asset', None).
        • If target_asset != old_parent_asset:
          • Call self.moveFileRule(source_file_index, parent). This handles the actual move within the model structure and emits beginMoveRows/endMoveRows.
          • After successful move: Update the file's override: file_item.target_asset_name_override = target_asset.asset_name.
          • Emit self.dataChanged.emit(moved_file_index, moved_file_index, [Qt.DisplayRole, Qt.EditRole]) for the COL_TARGET_ASSET column of the now moved file (get its new index).
      • Cleanup: After the loop, identify any original parent AssetRules that became empty as a result of the moves. Call self.removeAssetRule(empty_asset_rule) for each.
      • Return True.

Data Model Impact:

  • Changes the parentage of FileRule items within the model's internal structure.
  • Updates FileRule.target_asset_name_override to match the asset_name of the new parent AssetRule, ensuring consistency between the visual structure and the override field.

Potential Challenges/Considerations:

  • MIME Data Encoding/Decoding: Ensure the index information is reliably encoded and decoded, especially handling potential model changes between drag start and drop. Using persistent IDs instead of row/column numbers might be more robust if available.
  • Cleanup Logic: Reliably identifying and removing empty parent assets after potentially moving multiple files from different original parents requires careful tracking.
  • Transactionality: If moving multiple files and one part fails, should the whole operation roll back? The current plan doesn't explicitly handle this; errors are logged, and subsequent steps might proceed.
  • Interaction with AssetRestructureHandler: The plan suggests handling the move and override update directly within dropMimeData. This means the existing AssetRestructureHandler won't be triggered by the override change during the drop. Ensure the cleanup logic (removing empty parents) is correctly handled either in dropMimeData or by ensuring moveFileRule emits signals that the handler can use for cleanup.