Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
115
gui/delegates.py
115
gui/delegates.py
@@ -1,7 +1,8 @@
|
||||
# gui/delegates.py
|
||||
from PySide6.QtWidgets import QStyledItemDelegate, QLineEdit, QComboBox
|
||||
from PySide6.QtCore import Qt, QModelIndex
|
||||
from config import ALLOWED_ASSET_TYPES, ALLOWED_FILE_TYPES # Import config lists
|
||||
# Import the new config dictionaries
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS
|
||||
|
||||
class LineEditDelegate(QStyledItemDelegate):
|
||||
"""Delegate for editing string values using a QLineEdit."""
|
||||
@@ -41,17 +42,17 @@ class ComboBoxDelegate(QStyledItemDelegate):
|
||||
# Add a "clear" option first, associating None with it.
|
||||
editor.addItem("---", None) # UserData = None
|
||||
|
||||
# Populate based on column using lists from config
|
||||
items_list = None
|
||||
# Populate based on column using keys from config dictionaries
|
||||
items_keys = None
|
||||
if column == 2: # Asset-Type Override (AssetRule)
|
||||
items_list = ALLOWED_ASSET_TYPES
|
||||
items_keys = list(ASSET_TYPE_DEFINITIONS.keys())
|
||||
elif column == 4: # Item-Type Override (FileRule)
|
||||
items_list = ALLOWED_FILE_TYPES
|
||||
items_keys = list(FILE_TYPE_DEFINITIONS.keys())
|
||||
|
||||
if items_list:
|
||||
for item_str in items_list:
|
||||
# Add item with the string itself as text and UserData
|
||||
editor.addItem(item_str, item_str)
|
||||
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.
|
||||
@@ -86,4 +87,100 @@ class ComboBoxDelegate(QStyledItemDelegate):
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
# Ensures the editor widget is placed correctly within the cell.
|
||||
editor.setGeometry(option.rect)
|
||||
# gui/delegates.py - New content to insert
|
||||
|
||||
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 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)
|
||||
@@ -29,6 +29,7 @@ from rule_structure import SourceRule, AssetRule, FileRule # Import Rule Structu
|
||||
# Removed: from gui.preview_table_model import PreviewTableModel, PreviewSortFilterProxyModel
|
||||
# Removed: from gui.rule_hierarchy_model import RuleHierarchyModel
|
||||
from gui.unified_view_model import UnifiedViewModel # Import the new unified model
|
||||
from gui.delegates import LineEditDelegate, ComboBoxDelegate, SupplierSearchDelegate # Import delegates
|
||||
from gui.delegates import LineEditDelegate, ComboBoxDelegate # Import delegates
|
||||
|
||||
# --- Backend Imports ---
|
||||
@@ -171,6 +172,9 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# --- Internal State ---
|
||||
self.current_asset_paths = set() # Store unique paths of assets added
|
||||
self._pending_predictions = set() # Track input paths awaiting prediction results
|
||||
self._accumulated_rules = {} # Store {input_path: SourceRule} as results arrive
|
||||
self._source_file_lists = {} # Store {input_path: [file_list]} for context
|
||||
# Removed: self.rule_hierarchy_model = RuleHierarchyModel()
|
||||
# Removed: self._current_source_rule = None # The new model will hold the data
|
||||
|
||||
@@ -218,7 +222,7 @@ class MainWindow(QMainWindow):
|
||||
# --- Connect Editor Signals ---
|
||||
self._connect_editor_change_signals()
|
||||
|
||||
# --- Adjust Splitter ---
|
||||
# --- Adjust Splitter ---
|
||||
self.splitter.setSizes([400, 800]) # Initial size ratio
|
||||
|
||||
# --- UI Setup Methods ---
|
||||
@@ -402,10 +406,11 @@ class MainWindow(QMainWindow):
|
||||
# Instantiate Delegates
|
||||
lineEditDelegate = LineEditDelegate(self.unified_view)
|
||||
comboBoxDelegate = ComboBoxDelegate(self.unified_view)
|
||||
supplierSearchDelegate = SupplierSearchDelegate(self.unified_view) # Instantiate the new delegate
|
||||
|
||||
# Set Delegates for Columns (adjust column indices as per UnifiedViewModel)
|
||||
# Assuming columns are: Name (0), Supplier (1), AssetType (2), TargetAsset (3), ItemType (4)
|
||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_SUPPLIER, lineEditDelegate)
|
||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_SUPPLIER, supplierSearchDelegate) # Use the new delegate for Supplier
|
||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_ASSET_TYPE, comboBoxDelegate)
|
||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_TARGET_ASSET, lineEditDelegate)
|
||||
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_ITEM_TYPE, comboBoxDelegate)
|
||||
@@ -667,6 +672,10 @@ class MainWindow(QMainWindow):
|
||||
if file_list is not None: # Check if extraction was successful (not None)
|
||||
log.debug(f"Extracted {len(file_list)} files for {input_path_str}. Emitting signal.")
|
||||
log.info(f"VERIFY: Extracted file list for '{input_path_str}'. Count: {len(file_list)}. Emitting prediction signal.") # DEBUG Verify
|
||||
# Store file list and mark as pending before emitting
|
||||
self._source_file_lists[input_path_str] = file_list
|
||||
self._pending_predictions.add(input_path_str)
|
||||
log.debug(f"Added '{input_path_str}' to pending predictions. Current pending: {self._pending_predictions}")
|
||||
self.start_prediction_signal.emit(input_path_str, file_list, selected_preset)
|
||||
else:
|
||||
log.warning(f"Skipping prediction for {input_path_str} due to extraction error.")
|
||||
@@ -844,7 +853,12 @@ class MainWindow(QMainWindow):
|
||||
self.current_asset_paths.clear()
|
||||
# self.preview_model.clear_data() # Old model removed
|
||||
self.unified_model.clear_data() # Clear the new model data
|
||||
self.statusBar().showMessage("Asset queue cleared.", 3000)
|
||||
# Clear accumulation state
|
||||
self._pending_predictions.clear()
|
||||
self._accumulated_rules.clear()
|
||||
self._source_file_lists.clear()
|
||||
log.info("Cleared accumulation state (_pending_predictions, _accumulated_rules, _source_file_lists).")
|
||||
self.statusBar().showMessage("Asset queue and prediction state cleared.", 3000)
|
||||
else:
|
||||
self.statusBar().showMessage("Asset queue is already empty.", 3000)
|
||||
|
||||
@@ -924,6 +938,15 @@ class MainWindow(QMainWindow):
|
||||
|
||||
log.info(f"[{time.time():.4f}] Requesting background preview update for {len(input_paths)} items using Preset='{selected_preset}'")
|
||||
self.statusBar().showMessage(f"Updating preview for '{selected_preset}'...", 0)
|
||||
|
||||
# --- Reset Accumulation State for this batch ---
|
||||
log.debug("Clearing accumulated rules for new preview batch.")
|
||||
self._accumulated_rules.clear()
|
||||
# Reset pending predictions to only include paths in this update request
|
||||
self._pending_predictions = set(input_paths)
|
||||
log.debug(f"Reset pending predictions for batch: {self._pending_predictions}")
|
||||
# Keep _source_file_lists, it might contain lists for paths already processed
|
||||
|
||||
# Clearing is handled by model's set_data now, no need to clear table view directly
|
||||
if self.prediction_thread and self.prediction_handler:
|
||||
# REMOVED Placeholder SourceRule creation
|
||||
@@ -981,8 +1004,10 @@ class MainWindow(QMainWindow):
|
||||
# Connect the new signal to the handler's run_prediction slot using QueuedConnection
|
||||
self.start_prediction_signal.connect(self.prediction_handler.run_prediction, Qt.ConnectionType.QueuedConnection)
|
||||
# Removed: self.prediction_handler.prediction_results_ready.connect(self.on_prediction_results_ready) # Old signal
|
||||
self.prediction_handler.rule_hierarchy_ready.connect(self._on_rule_hierarchy_ready) # Connect the LIST signal
|
||||
self.prediction_handler.prediction_finished.connect(self.on_prediction_finished)
|
||||
# Assume PredictionHandler.rule_hierarchy_ready signal is changed to Signal(str, list) -> input_path, rules_list
|
||||
self.prediction_handler.rule_hierarchy_ready.connect(self._on_rule_hierarchy_ready) # Connect the LIST signal (now with input_path)
|
||||
# Assume PredictionHandler.prediction_finished signal is changed to Signal(str) -> input_path
|
||||
self.prediction_handler.prediction_finished.connect(self.on_prediction_finished) # Connect finish signal (now with input_path)
|
||||
self.prediction_handler.status_message.connect(self.show_status_message)
|
||||
# --- REMOVED connections causing thread/handler cleanup ---
|
||||
# self.prediction_handler.prediction_finished.connect(self.prediction_thread.quit)
|
||||
@@ -1027,12 +1052,30 @@ class MainWindow(QMainWindow):
|
||||
# # This is no longer needed as _on_rule_hierarchy_ready handles data loading for the new model.
|
||||
# pass
|
||||
|
||||
@Slot()
|
||||
def on_prediction_finished(self):
|
||||
log.info(f"[{time.time():.4f}] --> Prediction finished signal received.")
|
||||
# Optionally update status bar or re-enable controls if needed after prediction finishes
|
||||
# (Controls are primarily managed by processing_finished, but prediction is a separate background task)
|
||||
self.statusBar().showMessage("Preview updated.", 3000)
|
||||
# Slot signature assumes prediction_finished signal is updated to emit input_path: Signal(str)
|
||||
# Slot signature assumes prediction_finished signal is updated to emit input_path: Signal(str)
|
||||
@Slot(str)
|
||||
def on_prediction_finished(self, input_path: str):
|
||||
"""Handles the completion (potentially failure) of a single prediction task."""
|
||||
log.info(f"[{time.time():.4f}] --> Prediction finished signal received for: {input_path}")
|
||||
|
||||
# Ensure path is removed from pending even if rule_hierarchy_ready wasn't emitted (e.g., critical error)
|
||||
if input_path in self._pending_predictions:
|
||||
log.warning(f"Prediction finished for '{input_path}', but it was still marked as pending. Removing.")
|
||||
self._pending_predictions.discard(input_path)
|
||||
# Check if this was the last pending item after an error
|
||||
if not self._pending_predictions:
|
||||
log.info("Prediction finished, and no more predictions are pending (potentially due to error). Finalizing model update.")
|
||||
self._finalize_model_update()
|
||||
else:
|
||||
# Update status about remaining items
|
||||
remaining_count = len(self._pending_predictions)
|
||||
self.statusBar().showMessage(f"Prediction failed/finished for {Path(input_path).name}. Waiting for {remaining_count} more...", 5000)
|
||||
else:
|
||||
log.debug(f"Prediction finished for '{input_path}', which was already processed.")
|
||||
|
||||
# Original status message might be misleading now, handled by accumulation logic.
|
||||
# self.statusBar().showMessage("Preview updated.", 3000) # Removed
|
||||
|
||||
@Slot(str, str, str)
|
||||
def update_file_status(self, input_path_str, status, message):
|
||||
@@ -1580,18 +1623,74 @@ class MainWindow(QMainWindow):
|
||||
# @Slot(object)
|
||||
# def _on_rule_updated(self, rule_object): ...
|
||||
|
||||
@Slot(list) # Changed signature to accept list
|
||||
# Slot signature assumes rule_hierarchy_ready signal is updated to emit input_path: Signal(str, list)
|
||||
# Slot signature matches rule_hierarchy_ready = Signal(list)
|
||||
@Slot(list)
|
||||
def _on_rule_hierarchy_ready(self, source_rules_list: list):
|
||||
log.debug(f"--> Entered _on_rule_hierarchy_ready with {len(source_rules_list)} SourceRule(s)")
|
||||
"""Receives the generated list of SourceRule hierarchies and updates the unified view model."""
|
||||
# Removed: log.info(f"Received rule hierarchy ready signal for input: {source_rule.input_path}")
|
||||
# Removed: self._current_source_rule = source_rule # This concept might need rethinking if processing needs a specific rule
|
||||
# Removed: self.rule_hierarchy_model.set_root_rule(source_rule)
|
||||
# Removed: self.hierarchy_tree_view.expandToDepth(0)
|
||||
"""Receives prediction results (a list containing one SourceRule) for a single input path,
|
||||
accumulates them, and updates the model when all are ready."""
|
||||
|
||||
# Load the LIST of data into the new UnifiedViewModel
|
||||
self.unified_model.load_data(source_rules_list) # Pass the list
|
||||
log.debug("Unified view model updated with new list of SourceRules.")
|
||||
# --- Extract input_path from the received rule ---
|
||||
input_path = None
|
||||
source_rule = None
|
||||
if source_rules_list and isinstance(source_rules_list[0], SourceRule):
|
||||
source_rule = source_rules_list[0]
|
||||
input_path = source_rule.input_path
|
||||
log.debug(f"--> Entered _on_rule_hierarchy_ready for '{input_path}' with {len(source_rules_list)} SourceRule(s)")
|
||||
elif source_rules_list:
|
||||
log.error(f"Received non-SourceRule object in list: {type(source_rules_list[0])}. Cannot process.")
|
||||
# Attempt to find which pending prediction this might correspond to? Difficult.
|
||||
# For now, we can't reliably remove from pending without the path.
|
||||
return
|
||||
else:
|
||||
# This case might happen if prediction failed critically before creating a rule.
|
||||
# The prediction_finished signal (which now includes input_path) should handle removing from pending.
|
||||
log.warning("Received empty source_rules_list in _on_rule_hierarchy_ready. Prediction likely failed.")
|
||||
return # Nothing to accumulate
|
||||
|
||||
if input_path is None:
|
||||
log.error("Could not determine input_path from received source_rules_list. Aborting accumulation.")
|
||||
return
|
||||
|
||||
if input_path not in self._pending_predictions:
|
||||
log.warning(f"Received rule hierarchy for '{input_path}', but it was not in the pending set. Ignoring stale result? Pending: {self._pending_predictions}")
|
||||
return # Ignore if not expected
|
||||
|
||||
# --- Accumulate Result ---
|
||||
if source_rule: # Check if we successfully got the rule object
|
||||
self._accumulated_rules[input_path] = source_rule
|
||||
log.debug(f"Accumulated rule for '{input_path}'. Total accumulated: {len(self._accumulated_rules)}")
|
||||
else:
|
||||
# This path is already handled by the initial checks, but log just in case.
|
||||
log.warning(f"No valid SourceRule found for '{input_path}' to accumulate.")
|
||||
|
||||
# --- Mark as Completed ---
|
||||
self._pending_predictions.discard(input_path)
|
||||
log.debug(f"Removed '{input_path}' from pending predictions. Remaining: {self._pending_predictions}")
|
||||
|
||||
# --- Check for Completion ---
|
||||
if not self._pending_predictions:
|
||||
log.info("All pending predictions received. Finalizing model update.")
|
||||
self._finalize_model_update()
|
||||
else:
|
||||
# Update status bar with progress
|
||||
completed_count = len(self._accumulated_rules)
|
||||
pending_count = len(self._pending_predictions)
|
||||
total_count = completed_count + pending_count # This might be slightly off if some failed without rules
|
||||
status_msg = f"Preview updated for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_count} requested)..."
|
||||
self.statusBar().showMessage(status_msg, 5000)
|
||||
log.debug(status_msg)
|
||||
|
||||
|
||||
def _finalize_model_update(self):
|
||||
"""Combines accumulated rules and updates the UI model and view."""
|
||||
log.debug("Entering _finalize_model_update")
|
||||
final_rules = list(self._accumulated_rules.values())
|
||||
log.info(f"Finalizing model with {len(final_rules)} accumulated SourceRule(s).")
|
||||
|
||||
# Load the FINAL LIST of data into the UnifiedViewModel
|
||||
self.unified_model.load_data(final_rules)
|
||||
log.debug("Unified view model updated with final list of SourceRules.")
|
||||
|
||||
# Resize columns to fit content after loading data
|
||||
for col in range(self.unified_model.columnCount()):
|
||||
@@ -1599,6 +1698,8 @@ class MainWindow(QMainWindow):
|
||||
log.debug("Unified view columns resized to contents.")
|
||||
self.unified_view.expandToDepth(1) # Expand Source -> Asset level
|
||||
|
||||
self.statusBar().showMessage(f"Preview complete for {len(final_rules)} asset(s).", 5000)
|
||||
|
||||
|
||||
# --- Main Execution ---
|
||||
def run_gui():
|
||||
|
||||
@@ -26,8 +26,8 @@ try:
|
||||
# from asset_processor import AssetProcessor, AssetProcessingError
|
||||
from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType, ItemType
|
||||
import config as app_config # Import project's config module
|
||||
# Import the lists directly for easier access
|
||||
from config import ALLOWED_ASSET_TYPES, ALLOWED_FILE_TYPES
|
||||
# Import the new dictionaries directly for easier access
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS
|
||||
BACKEND_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"ERROR (PredictionHandler): Failed to import backend/config modules: {e}")
|
||||
@@ -209,8 +209,8 @@ class PredictionHandler(QObject):
|
||||
# --- Signals ---
|
||||
# Emitted when the hierarchical rule structure is ready for a single source
|
||||
rule_hierarchy_ready = Signal(list) # Emits a LIST containing ONE SourceRule object
|
||||
# Emitted when prediction/hierarchy generation for a source is done
|
||||
prediction_finished = Signal()
|
||||
# Emitted when prediction/hierarchy generation for a source is done (emits the input_source_identifier)
|
||||
prediction_finished = Signal(str)
|
||||
# Emitted for status updates
|
||||
status_message = Signal(str, int)
|
||||
|
||||
@@ -261,7 +261,7 @@ class PredictionHandler(QObject):
|
||||
log.warning(f"Input source path does not exist: '{input_source_identifier}'. Skipping prediction.")
|
||||
self.status_message.emit("Input path not found.", 3000)
|
||||
self.rule_hierarchy_ready.emit([])
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
return
|
||||
|
||||
|
||||
@@ -269,30 +269,30 @@ class PredictionHandler(QObject):
|
||||
self.status_message.emit(f"Analyzing '{source_path.name}'...", 0)
|
||||
|
||||
config: Configuration | None = None
|
||||
allowed_asset_types: List[str] = []
|
||||
allowed_file_types: List[str] = [] # These are ItemType names
|
||||
asset_type_definitions: Dict[str, Dict] = {}
|
||||
file_type_definitions: Dict[str, Dict] = {} # These are ItemType names
|
||||
|
||||
try:
|
||||
config = Configuration(preset_name)
|
||||
# Load allowed types from the project's config module
|
||||
# Load allowed types from the project's config module (now dictionaries)
|
||||
if app_config:
|
||||
allowed_asset_types = getattr(app_config, 'ALLOWED_ASSET_TYPES', [])
|
||||
allowed_file_types = getattr(app_config, 'ALLOWED_FILE_TYPES', [])
|
||||
log.debug(f"Loaded allowed AssetTypes: {allowed_asset_types}")
|
||||
log.debug(f"Loaded allowed FileTypes (ItemTypes): {allowed_file_types}")
|
||||
asset_type_definitions = getattr(app_config, 'ASSET_TYPE_DEFINITIONS', {})
|
||||
file_type_definitions = getattr(app_config, 'FILE_TYPE_DEFINITIONS', {})
|
||||
log.debug(f"Loaded AssetType Definitions: {list(asset_type_definitions.keys())}")
|
||||
log.debug(f"Loaded FileType Definitions (ItemTypes): {list(file_type_definitions.keys())}")
|
||||
else:
|
||||
log.warning("Project config module not loaded. Cannot get allowed types.")
|
||||
log.warning("Project config module not loaded. Cannot get type definitions.")
|
||||
|
||||
except ConfigurationError as e:
|
||||
log.error(f"Failed to load configuration for preset '{preset_name}': {e}")
|
||||
self.status_message.emit(f"Error loading preset '{preset_name}': {e}", 5000)
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
return
|
||||
except Exception as e:
|
||||
log.exception(f"Unexpected error loading configuration or allowed types for preset '{preset_name}': {e}")
|
||||
self.status_message.emit(f"Unexpected error loading preset '{preset_name}'.", 5000)
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
return
|
||||
|
||||
@@ -303,7 +303,7 @@ class PredictionHandler(QObject):
|
||||
except Exception as e:
|
||||
log.exception(f"Error during file classification for source '{input_source_identifier}': {e}")
|
||||
self.status_message.emit(f"Error classifying files: {e}", 5000)
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
return
|
||||
|
||||
@@ -311,7 +311,7 @@ class PredictionHandler(QObject):
|
||||
log.warning(f"Classification yielded no assets for source '{input_source_identifier}'.")
|
||||
self.status_message.emit("No assets identified from files.", 3000)
|
||||
self.rule_hierarchy_ready.emit([]) # Emit empty list
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
return
|
||||
|
||||
@@ -348,16 +348,16 @@ class PredictionHandler(QObject):
|
||||
|
||||
# Ensure the predicted type is allowed, fallback if necessary
|
||||
# Now predicted_asset_type is already a string
|
||||
if allowed_asset_types and predicted_asset_type not in allowed_asset_types:
|
||||
log.warning(f"Predicted AssetType '{predicted_asset_type}' for asset '{asset_name}' is not in ALLOWED_ASSET_TYPES. Falling back.")
|
||||
if asset_type_definitions and predicted_asset_type not in asset_type_definitions:
|
||||
log.warning(f"Predicted AssetType '{predicted_asset_type}' for asset '{asset_name}' is not in ASSET_TYPE_DEFINITIONS. Falling back.")
|
||||
# Fallback logic: use the default from config if allowed, else first allowed type
|
||||
default_type = getattr(app_config, 'DEFAULT_ASSET_CATEGORY', 'Surface')
|
||||
if default_type in allowed_asset_types:
|
||||
if default_type in asset_type_definitions:
|
||||
predicted_asset_type = default_type
|
||||
elif allowed_asset_types:
|
||||
predicted_asset_type = allowed_asset_types[0]
|
||||
elif asset_type_definitions:
|
||||
predicted_asset_type = list(asset_type_definitions.keys())[0] # Use first key
|
||||
else:
|
||||
pass # Keep the original prediction if allowed list is empty
|
||||
pass # Keep the original prediction if definitions are empty
|
||||
|
||||
|
||||
asset_rule = AssetRule(
|
||||
@@ -370,35 +370,42 @@ class PredictionHandler(QObject):
|
||||
file_rules = []
|
||||
for file_info in files_info:
|
||||
# Determine FileRule level overrides/defaults
|
||||
item_type_override = file_info['item_type'] # From classification
|
||||
base_item_type = file_info['item_type'] # Type from classification (e.g., COL, NRM, EXTRA)
|
||||
target_asset_name_override = file_info['asset_name'] # From classification
|
||||
|
||||
# Ensure the predicted item type is allowed (check against prefixed version), skipping EXTRA and FILE_IGNORE
|
||||
# Only prefix if it's a map type that doesn't already have the prefix
|
||||
prefixed_item_type = f"MAP_{item_type_override}" if not item_type_override.startswith("MAP_") and item_type_override not in ["FILE_IGNORE", "EXTRA", "MODEL"] else item_type_override
|
||||
# Check if the (potentially prefixed) type is allowed, but only if it's not supposed to be ignored or extra
|
||||
if allowed_file_types and prefixed_item_type not in allowed_file_types and item_type_override not in ["FILE_IGNORE", "EXTRA"]:
|
||||
log.warning(f"Predicted ItemType '{item_type_override}' (checked as '{prefixed_item_type}') for file '{file_info['file_path']}' is not in ALLOWED_FILE_TYPES. Setting to FILE_IGNORE.")
|
||||
item_type_override = "FILE_IGNORE" # Fallback to FILE_IGNORE string
|
||||
# Determine the final item_type string (prefix maps, check if allowed)
|
||||
final_item_type = base_item_type # Start with the base type
|
||||
if not base_item_type.startswith("MAP_") and base_item_type not in ["FILE_IGNORE", "EXTRA", "MODEL"]:
|
||||
# Prefix map types that don't already have it
|
||||
final_item_type = f"MAP_{base_item_type}"
|
||||
|
||||
# Check if the final type is allowed (exists as a key in config)
|
||||
if file_type_definitions and final_item_type not in file_type_definitions and base_item_type not in ["FILE_IGNORE", "EXTRA"]:
|
||||
log.warning(f"Predicted ItemType '{base_item_type}' (checked as '{final_item_type}') for file '{file_info['file_path']}' is not in FILE_TYPE_DEFINITIONS. Setting base type to FILE_IGNORE.")
|
||||
final_item_type = "FILE_IGNORE" # Fallback base type to FILE_IGNORE string
|
||||
|
||||
# Output format is determined by the engine, not predicted here. Leave as None.
|
||||
output_format_override = None
|
||||
|
||||
# User override for item type starts as None
|
||||
item_type_override = None
|
||||
|
||||
# --- DEBUG LOG: Inspect data before FileRule creation ---
|
||||
log.debug(f" Creating FileRule for: {file_info['file_path']}")
|
||||
log.debug(f" Using item_type_override: {item_type_override}")
|
||||
log.debug(f" Using target_asset_name_override: {target_asset_name_override}")
|
||||
log.debug(f" Base Item Type (from classification): {base_item_type}")
|
||||
log.debug(f" Final Item Type (for model): {final_item_type}")
|
||||
log.debug(f" Target Asset Name Override: {target_asset_name_override}")
|
||||
# Explicitly check and log the flag value from file_info
|
||||
is_gloss_source_value = file_info.get('is_gloss_source', 'MISSING') # Get value or 'MISSING'
|
||||
log.debug(f" Value for 'is_gloss_source' from file_info: {is_gloss_source_value}")
|
||||
# --- End DEBUG LOG ---
|
||||
|
||||
# TODO: Need to verify FileRule constructor accepts is_gloss_source
|
||||
# and pass is_gloss_source_value if it does.
|
||||
# Pass the retrieved flag value to the constructor
|
||||
file_rule = FileRule(
|
||||
file_path=file_info['file_path'], # This is static info based on input
|
||||
item_type=final_item_type, # Set the new base item_type field
|
||||
# --- Populate ONLY Overridable Fields ---
|
||||
item_type_override=item_type_override,
|
||||
# Initialize override with the classified type for display
|
||||
item_type_override=final_item_type,
|
||||
target_asset_name_override=target_asset_name_override,
|
||||
output_format_override=output_format_override,
|
||||
is_gloss_source=is_gloss_source_value if isinstance(is_gloss_source_value, bool) else False, # Pass the flag, ensure boolean
|
||||
@@ -421,7 +428,7 @@ class PredictionHandler(QObject):
|
||||
log.exception(f"Error building rule hierarchy for source '{input_source_identifier}': {e}")
|
||||
self.status_message.emit(f"Error building rules: {e}", 5000)
|
||||
# Don't emit hierarchy, just finish
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
# Removed erroneous temp_dir_obj cleanup
|
||||
return
|
||||
@@ -439,7 +446,7 @@ class PredictionHandler(QObject):
|
||||
# Removed prediction_results_ready signal emission
|
||||
|
||||
self.status_message.emit(f"Analysis complete for '{input_source_identifier}'.", 3000)
|
||||
self.prediction_finished.emit()
|
||||
self.prediction_finished.emit(input_source_identifier)
|
||||
self._is_running = False
|
||||
# Removed temp_dir_obj cleanup - not relevant here
|
||||
log.info(f"[{time.time():.4f}][T:{thread_id}] <-- Exiting PredictionHandler.run_prediction.")
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
# gui/unified_view_model.py
|
||||
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt
|
||||
from PySide6.QtCore import QAbstractItemModel, QModelIndex, Qt, Signal # Added Signal
|
||||
from PySide6.QtGui import QColor # Added for background role
|
||||
from pathlib import Path # Added for file_name extraction
|
||||
from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType, ItemType import
|
||||
from config import ASSET_TYPE_DEFINITIONS, FILE_TYPE_DEFINITIONS # Added for coloring
|
||||
|
||||
class UnifiedViewModel(QAbstractItemModel):
|
||||
# --- Color Constants for Row Backgrounds ---
|
||||
# Old colors removed, using config now + fixed source color
|
||||
SOURCE_RULE_COLOR = QColor("#306091") # Fixed color for SourceRule rows
|
||||
# -----------------------------------------
|
||||
|
||||
"""
|
||||
A QAbstractItemModel for displaying and editing the hierarchical structure
|
||||
of SourceRule -> AssetRule -> FileRule.
|
||||
"""
|
||||
Columns = [
|
||||
"Name", "Supplier Override", "Asset-Type Override",
|
||||
"Name", "Supplier", "Asset-Type Override", # Renamed "Supplier Override"
|
||||
"Target Asset Name Override", "Item-Type Override",
|
||||
"Status", "Output Path"
|
||||
]
|
||||
@@ -165,55 +172,101 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
item = index.internalPointer()
|
||||
column = index.column()
|
||||
|
||||
# --- Handle different item types ---
|
||||
if isinstance(item, SourceRule): # This might only be relevant if SourceRule is displayed
|
||||
if role == Qt.DisplayRole:
|
||||
if column == 0: return item.input_path
|
||||
# Use supplier_override if set, otherwise empty string
|
||||
if column == self.COL_SUPPLIER: return item.supplier_override if item.supplier_override is not None else ""
|
||||
# Other columns return None or "" for SourceRule
|
||||
elif role == Qt.EditRole:
|
||||
# Return supplier_override for editing
|
||||
if column == self.COL_SUPPLIER: return item.supplier_override if item.supplier_override is not None else ""
|
||||
# --- Handle Background Role ---
|
||||
if role == Qt.BackgroundRole:
|
||||
# item is already fetched at line 172
|
||||
if isinstance(item, SourceRule):
|
||||
return self.SOURCE_RULE_COLOR # Use the class constant
|
||||
elif isinstance(item, AssetRule):
|
||||
# Determine effective asset type
|
||||
asset_type = item.asset_type_override if item.asset_type_override else item.asset_type
|
||||
if asset_type:
|
||||
type_info = ASSET_TYPE_DEFINITIONS.get(asset_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for asset type '{asset_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for asset type '{asset_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# Optional: Add logging for missing asset type definition
|
||||
# print(f"Warning: Asset type '{asset_type}' not found in ASSET_TYPE_DEFINITIONS.")
|
||||
return None # Fallback if type not in config
|
||||
else:
|
||||
return None # Fallback if no asset_type determined
|
||||
elif isinstance(item, FileRule):
|
||||
# Determine effective item type: Prioritize override, then use base type
|
||||
effective_item_type = item.item_type_override if item.item_type_override is not None else item.item_type
|
||||
if effective_item_type:
|
||||
type_info = FILE_TYPE_DEFINITIONS.get(effective_item_type)
|
||||
if type_info:
|
||||
hex_color = type_info.get("color")
|
||||
if hex_color:
|
||||
try:
|
||||
return QColor(hex_color)
|
||||
except ValueError:
|
||||
# Optional: Add logging for invalid hex color
|
||||
# print(f"Warning: Invalid hex color '{hex_color}' for file type '{item_type}' in config.")
|
||||
return None # Fallback for invalid hex
|
||||
else:
|
||||
# Optional: Add logging for missing color key
|
||||
# print(f"Warning: No color defined for file type '{item_type}' in config.")
|
||||
return None # Fallback if color key missing
|
||||
else:
|
||||
# File types often don't have specific colors, so no warning needed unless debugging
|
||||
return None # Fallback if type not in config
|
||||
else:
|
||||
return None # Fallback if no item_type determined
|
||||
else: # Other item types or if item is None
|
||||
return None
|
||||
|
||||
# --- Handle other roles (Display, Edit, etc.) ---
|
||||
if isinstance(item, SourceRule):
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole: # Combine Display and Edit logic
|
||||
if column == self.COL_NAME:
|
||||
return Path(item.input_path).name # Display only basename for SourceRule
|
||||
elif column == self.COL_SUPPLIER:
|
||||
# Return override if set, otherwise the original identifier, else empty string
|
||||
display_value = item.supplier_override if item.supplier_override is not None else item.supplier_identifier
|
||||
return display_value if display_value is not None else ""
|
||||
# Other columns return None or "" for SourceRule in Display/Edit roles
|
||||
return None # Default for SourceRule for other roles/columns
|
||||
|
||||
elif isinstance(item, AssetRule):
|
||||
if role == Qt.DisplayRole:
|
||||
if column == self.COL_NAME: return item.asset_name
|
||||
# Use asset_type_override if set, otherwise fall back to predicted asset_type
|
||||
if column == self.COL_ASSET_TYPE:
|
||||
display_value = item.asset_type_override if item.asset_type_override is not None else item.asset_type
|
||||
return display_value if display_value else ""
|
||||
# Placeholder columns
|
||||
if column == self.COL_STATUS: return "" # Status (Not handled yet)
|
||||
if column == self.COL_OUTPUT_PATH: return "" # Output Path (Not handled yet)
|
||||
elif role == Qt.EditRole:
|
||||
# Return asset_type_override for editing (delegate expects string or None)
|
||||
if column == self.COL_ASSET_TYPE:
|
||||
return item.asset_type_override # Return string or None
|
||||
return None # Default for AssetRule
|
||||
|
||||
|
||||
elif isinstance(item, FileRule):
|
||||
if role == Qt.DisplayRole:
|
||||
if column == self.COL_NAME: return Path(item.file_path).name # Display only filename
|
||||
# Use target_asset_name_override if set, otherwise empty string
|
||||
if column == self.COL_TARGET_ASSET:
|
||||
return item.target_asset_name_override if item.target_asset_name_override is not None else ""
|
||||
# Use item_type_override if set, otherwise empty string (assuming predicted isn't stored directly)
|
||||
if column == self.COL_ITEM_TYPE:
|
||||
# Assuming item_type_override stores the string name of the ItemType enum
|
||||
return item.item_type_override if item.item_type_override else ""
|
||||
if column == self.COL_STATUS: return "" # Status (Not handled yet)
|
||||
if column == self.COL_OUTPUT_PATH: return "" # Output Path (Not handled yet)
|
||||
elif role == Qt.EditRole:
|
||||
# Return target_asset_name_override for editing
|
||||
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else ""
|
||||
# Return item_type_override for editing (delegate expects string or None)
|
||||
if column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
|
||||
return None # Default for FileRule
|
||||
|
||||
return None # Should not be reached if item is one of the known types
|
||||
return None # Default return if role/item combination not handled
|
||||
|
||||
def setData(self, index: QModelIndex, value, role: int = Qt.EditRole) -> bool:
|
||||
"""Sets the role data for the item at index to value."""
|
||||
@@ -229,10 +282,17 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
# --- Handle different item types ---
|
||||
if isinstance(item, SourceRule): # If SourceRule is editable
|
||||
if column == self.COL_SUPPLIER:
|
||||
# Ensure value is string or None
|
||||
new_value = str(value).strip() if value is not None else None
|
||||
if new_value == "": new_value = None # Treat empty string as None
|
||||
# Update supplier_override
|
||||
# Get the new value, strip whitespace, treat empty as None
|
||||
new_value = str(value).strip() if value is not None and str(value).strip() else None
|
||||
|
||||
# Get the original identifier (assuming it exists on SourceRule)
|
||||
original_identifier = getattr(item, 'supplier_identifier', None)
|
||||
|
||||
# If the new value is the same as the original, clear the override
|
||||
if new_value == original_identifier:
|
||||
new_value = None # Effectively removes the override
|
||||
|
||||
# Update supplier_override only if it's different
|
||||
if item.supplier_override != new_value:
|
||||
item.supplier_override = new_value
|
||||
changed = True
|
||||
@@ -254,8 +314,122 @@ class UnifiedViewModel(QAbstractItemModel):
|
||||
if new_value == "": new_value = None # Treat empty string as None
|
||||
# Update target_asset_name_override
|
||||
if item.target_asset_name_override != new_value:
|
||||
old_value = item.target_asset_name_override # Store old value for potential revert/comparison
|
||||
item.target_asset_name_override = new_value
|
||||
changed = True
|
||||
|
||||
# --- Start: New Direct Model Restructuring Logic ---
|
||||
old_parent_asset = getattr(item, 'parent_asset', None)
|
||||
if old_parent_asset: # Ensure we have the old parent
|
||||
source_rule = getattr(old_parent_asset, 'parent_source', None)
|
||||
if source_rule: # Ensure we have the grandparent
|
||||
new_target_name = new_value # Can be None or a string
|
||||
|
||||
# Get old parent index and source row
|
||||
try:
|
||||
grandparent_row = self._source_rules.index(source_rule)
|
||||
old_parent_row = source_rule.assets.index(old_parent_asset)
|
||||
source_row = old_parent_asset.files.index(item)
|
||||
old_parent_index = self.createIndex(old_parent_row, 0, old_parent_asset)
|
||||
grandparent_index = self.createIndex(grandparent_row, 0, source_rule) # Needed for insert/remove parent
|
||||
except ValueError:
|
||||
print("Error: Could not find item, parent, or grandparent in model structure during setData.")
|
||||
item.target_asset_name_override = old_value # Revert data change
|
||||
return False # Indicate failure
|
||||
|
||||
target_parent_asset = None
|
||||
target_parent_index = QModelIndex()
|
||||
target_parent_row = -1 # Row within source_rule.assets
|
||||
target_row = -1 # Row within target_parent_asset.files
|
||||
move_occurred = False # Flag to track if a move happened
|
||||
|
||||
# 1. Find existing target parent
|
||||
if new_target_name: # Only search if a specific target is given
|
||||
for i, asset in enumerate(source_rule.assets):
|
||||
if asset.asset_name == new_target_name:
|
||||
target_parent_asset = asset
|
||||
target_parent_row = i
|
||||
target_parent_index = self.createIndex(target_parent_row, 0, target_parent_asset)
|
||||
break
|
||||
|
||||
# 2. Handle Move/Creation
|
||||
if target_parent_asset:
|
||||
# --- Move to Existing Parent ---
|
||||
if target_parent_asset != old_parent_asset: # Don't move if target is the same as old parent
|
||||
target_row = len(target_parent_asset.files) # Append to the end
|
||||
# print(f"DEBUG: Moving {Path(item.file_path).name} from {old_parent_asset.asset_name} ({source_row}) to {target_parent_asset.asset_name} ({target_row})")
|
||||
self.beginMoveRows(old_parent_index, source_row, source_row, target_parent_index, target_row)
|
||||
# Restructure internal data
|
||||
old_parent_asset.files.pop(source_row)
|
||||
target_parent_asset.files.append(item)
|
||||
item.parent_asset = target_parent_asset # Update parent reference
|
||||
self.endMoveRows()
|
||||
move_occurred = True
|
||||
else:
|
||||
# Target is the same as the old parent. No move needed.
|
||||
pass
|
||||
|
||||
elif new_target_name: # Only create if a *new* specific target name was given
|
||||
# --- Create New Parent and Move ---
|
||||
# print(f"DEBUG: Creating new parent '{new_target_name}' and moving {Path(item.file_path).name}")
|
||||
# Create new AssetRule
|
||||
new_asset_rule = AssetRule(asset_name=new_target_name)
|
||||
new_asset_rule.asset_type = old_parent_asset.asset_type # Copy type from old parent
|
||||
new_asset_rule.asset_type_override = old_parent_asset.asset_type_override # Copy override too
|
||||
new_asset_rule.parent_source = source_rule # Set parent reference
|
||||
|
||||
# Determine insertion row for the new parent (e.g., append)
|
||||
new_parent_row = len(source_rule.assets)
|
||||
# print(f"DEBUG: Inserting new parent at row {new_parent_row} under {Path(source_rule.input_path).name}")
|
||||
|
||||
# Emit signals for inserting the new parent row
|
||||
self.beginInsertRows(grandparent_index, new_parent_row, new_parent_row)
|
||||
source_rule.assets.insert(new_parent_row, new_asset_rule) # Insert into data structure
|
||||
self.endInsertRows()
|
||||
|
||||
# Get index for the newly inserted parent
|
||||
target_parent_index = self.createIndex(new_parent_row, 0, new_asset_rule)
|
||||
target_row = 0 # Insert file at the beginning of the new parent (for signal)
|
||||
|
||||
# Emit signals for moving the file row
|
||||
# print(f"DEBUG: Moving {Path(item.file_path).name} from {old_parent_asset.asset_name} ({source_row}) to new {new_asset_rule.asset_name} ({target_row})")
|
||||
self.beginMoveRows(old_parent_index, source_row, source_row, target_parent_index, target_row)
|
||||
# Restructure internal data
|
||||
old_parent_asset.files.pop(source_row)
|
||||
new_asset_rule.files.append(item) # Append is fine, target_row=0 was for signal
|
||||
item.parent_asset = new_asset_rule # Update parent reference
|
||||
self.endMoveRows()
|
||||
move_occurred = True
|
||||
|
||||
# Update target_parent_asset for potential cleanup check later
|
||||
target_parent_asset = new_asset_rule
|
||||
|
||||
else: # new_target_name is None or empty
|
||||
# No move happens when the override is simply cleared.
|
||||
pass
|
||||
|
||||
# 3. Cleanup Empty Old Parent (only if a move occurred and old parent is empty)
|
||||
if move_occurred and not old_parent_asset.files:
|
||||
# print(f"DEBUG: Removing empty old parent {old_parent_asset.asset_name}")
|
||||
try:
|
||||
# Find the row of the old parent again, as it might have shifted
|
||||
old_parent_row_for_removal = source_rule.assets.index(old_parent_asset)
|
||||
# print(f"DEBUG: Removing parent at row {old_parent_row_for_removal} under {Path(source_rule.input_path).name}")
|
||||
self.beginRemoveRows(grandparent_index, old_parent_row_for_removal, old_parent_row_for_removal)
|
||||
source_rule.assets.pop(old_parent_row_for_removal)
|
||||
self.endRemoveRows()
|
||||
except ValueError:
|
||||
print(f"Error: Could not find old parent '{old_parent_asset.asset_name}' for removal.")
|
||||
# Log error, but continue
|
||||
else:
|
||||
print("Error: Could not find grandparent SourceRule during setData restructuring.")
|
||||
item.target_asset_name_override = old_value # Revert
|
||||
return False
|
||||
else:
|
||||
print("Error: Could not find parent AssetRule during setData restructuring.")
|
||||
item.target_asset_name_override = old_value # Revert
|
||||
return False
|
||||
# --- End: New Direct Model Restructuring Logic ---
|
||||
elif column == self.COL_ITEM_TYPE: # Item-Type Override
|
||||
# Delegate provides string value (e.g., "MAP_COL") or None
|
||||
new_value = str(value) if value is not None else None
|
||||
|
||||
Reference in New Issue
Block a user