Preferences, Config.py Migration, Bug Fixes, and Documention updates

This commit is contained in:
2025-05-01 22:54:05 +02:00
parent 5c041e3774
commit 1ac23eb252
13 changed files with 1236 additions and 498 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -425,13 +425,19 @@ class MainWindow(QMainWindow):
self.unified_view.setItemDelegateForColumn(UnifiedViewModel.COL_ITEM_TYPE, comboBoxDelegate)
# Configure View Appearance (optional, customize as needed)
self.unified_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # Expand horizontally and vertically
self.unified_view.setAlternatingRowColors(True)
self.unified_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.unified_view.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.SelectedClicked | QAbstractItemView.EditTrigger.EditKeyPressed)
self.unified_view.header().setStretchLastSection(False) # Adjust as needed
# Set the "Name" column (index 0) to resize to contents
self.unified_view.header().setSectionResizeMode(UnifiedViewModel.COL_NAME, QHeaderView.ResizeMode.ResizeToContents)
# self.unified_view.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) # Example: Resize others to contents
# Configure Header Resize Modes based on new column order
header = self.unified_view.header()
header.setStretchLastSection(False) # Don't stretch the last section by default
header.setSectionResizeMode(UnifiedViewModel.COL_NAME, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(UnifiedViewModel.COL_TARGET_ASSET, QHeaderView.ResizeMode.Stretch) # Stretch Target Asset
header.setSectionResizeMode(UnifiedViewModel.COL_SUPPLIER, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(UnifiedViewModel.COL_ASSET_TYPE, QHeaderView.ResizeMode.ResizeToContents)
header.setSectionResizeMode(UnifiedViewModel.COL_ITEM_TYPE, QHeaderView.ResizeMode.ResizeToContents)
# Add the Unified View to the main layout
main_layout.addWidget(self.unified_view, 1) # Give it stretch factor 1
@@ -1613,80 +1619,129 @@ class MainWindow(QMainWindow):
@Slot(list)
def _on_rule_hierarchy_ready(self, source_rules_list: list):
"""Receives prediction results (a list containing one SourceRule) for a single input path,
accumulates them, and updates the model when all are ready."""
finds the corresponding existing rule in the model, updates it while preserving overrides,
and emits dataChanged/layoutChanged signals."""
# --- Extract input_path from the received rule ---
# --- Extract input_path and the new rule from the received list ---
input_path = None
source_rule = None
new_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)")
new_source_rule = source_rules_list[0]
input_path = new_source_rule.input_path
log.debug(f"--> Entered _on_rule_hierarchy_ready for '{input_path}' with 1 SourceRule")
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
# Try to deduce input_path if possible (e.g., if only one is pending)
if len(self._pending_predictions) == 1:
input_path = list(self._pending_predictions)[0]
log.warning(f"Assuming failed prediction corresponds to pending path: {input_path}")
else:
log.error("Cannot determine input_path for empty/failed prediction result when multiple predictions are pending.")
return
if input_path is None:
log.error("Could not determine input_path from received source_rules_list. Aborting accumulation.")
log.error("Could not determine input_path from received source_rules_list or pending state.")
return
# Log received rule details (even if it's None due to failure)
log.debug(f"_on_rule_hierarchy_ready: Processing result for '{input_path}'. Received Supplier ID='{getattr(new_source_rule, 'supplier_identifier', 'N/A')}', Received Override='{getattr(new_source_rule, 'supplier_override', 'N/A')}'")
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.")
# --- Find existing rule in the model's internal list ---
# Access the model directly
existing_rule = None
existing_rule_index = -1
model_rules = self.unified_model.get_all_source_rules() # Get current rules from model
for i, rule in enumerate(model_rules):
if rule.input_path == input_path:
existing_rule = rule
existing_rule_index = i
break
# --- Mark as Completed ---
if existing_rule:
log.debug(f"Found existing rule for '{input_path}' in model at index {existing_rule_index}. Updating it.")
if new_source_rule: # Only update if prediction was successful
# Preserve existing user overrides from the rule currently in the model
preserved_supplier_override = existing_rule.supplier_override
# Preserve other potential user overrides if they exist
preserved_asset_overrides = {asset.asset_name: asset.asset_type_override for asset in existing_rule.assets}
preserved_file_overrides = {(file.file_path, 'target'): file.target_asset_name_override for asset in existing_rule.assets for file in asset.files}
preserved_file_overrides.update({(file.file_path, 'item'): file.item_type_override for asset in existing_rule.assets for file in asset.files})
# --- Update existing rule with new prediction data ---
existing_rule.supplier_identifier = new_source_rule.supplier_identifier
existing_rule.preset_name = new_source_rule.preset_name
existing_rule.assets = new_source_rule.assets # Replace assets list
# Re-apply preserved overrides
existing_rule.supplier_override = preserved_supplier_override
for asset in existing_rule.assets:
asset.asset_type_override = preserved_asset_overrides.get(asset.asset_name)
asset.parent_source = existing_rule # Set parent reference
for file in asset.files:
file.target_asset_name_override = preserved_file_overrides.get((file.file_path, 'target'))
file.item_type_override = preserved_file_overrides.get((file.file_path, 'item'))
file.parent_asset = asset # Set parent reference
# --- End Update ---
# Emit dataChanged and layoutChanged for the updated existing rule via the model
start_index = self.unified_model.createIndex(existing_rule_index, 0, existing_rule)
end_index = self.unified_model.createIndex(existing_rule_index, self.unified_model.columnCount() - 1, existing_rule)
log.debug(f"Emitting dataChanged and layoutChanged for updated existing rule at index {existing_rule_index}")
self.unified_model.dataChanged.emit(start_index, end_index)
self.unified_model.layoutChanged.emit() # Signal layout change
else:
log.warning(f"Prediction failed for '{input_path}'. Not updating existing rule data in model.")
else:
# If no existing rule was found (e.g., first time result arrives)
if new_source_rule: # Only add if prediction was successful
log.debug(f"No existing rule found for '{input_path}'. Adding new rule to model.")
# Ensure parent references are set
for asset_rule in new_source_rule.assets:
asset_rule.parent_source = new_source_rule
for file_rule in asset_rule.files:
file_rule.parent_asset = asset_rule
# Add to model's internal list and emit signal via model methods
self.unified_model.beginInsertRows(QModelIndex(), len(model_rules), len(model_rules))
model_rules.append(new_source_rule) # Append to the list obtained from the model
self.unified_model.endInsertRows()
else:
log.warning(f"Prediction failed for '{input_path}' and no existing rule found. Nothing to add to model.")
# --- Remove from pending ---
self._pending_predictions.discard(input_path)
log.debug(f"Removed '{input_path}' from pending predictions. Remaining: {self._pending_predictions}")
log.debug(f"Removed '{input_path}' from pending predictions. Remaining: {len(self._pending_predictions)} -> {self._pending_predictions}")
# --- Check for Completion ---
if not self._pending_predictions:
log.info("All pending predictions received. Finalizing model update.")
self._finalize_model_update()
log.info("All pending predictions processed. Model should be up-to-date.")
self.statusBar().showMessage(f"Preview complete.", 5000) # Update status
# Optional: Resize columns after all updates are done
for col in range(self.unified_model.columnCount()):
self.unified_view.resizeColumnToContents(col)
self.unified_view.expandToDepth(1) # Expand Source -> Asset level
else:
# Update status bar with progress
completed_count = len(self._accumulated_rules)
completed_count = len(self.unified_model.get_all_source_rules()) # Count rules in model
pending_count = len(self._pending_predictions)
# total_count = completed_count + pending_count # This might be slightly off if some failed without rules
# We don't have the total count of *requested* predictions here easily,
# but we can use the initial number of items added.
total_requested = len(self.current_asset_paths) # Use the total number of items added
status_msg = f"Preview finished for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_requested} requested)..."
total_requested = completed_count + pending_count # Estimate total
status_msg = f"Preview updated for {Path(input_path).name}. Waiting for {pending_count} more ({completed_count}/{total_requested} processed)..."
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()):
self.unified_view.resizeColumnToContents(col)
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)
# REMOVED _finalize_model_update method as it's no longer needed
# def _finalize_model_update(self):
# """Combines accumulated rules and updates the UI model and view."""
# ... (old code removed) ...
# --- Main Execution ---

View File

@@ -18,14 +18,14 @@ class UnifiedViewModel(QAbstractItemModel):
of SourceRule -> AssetRule -> FileRule.
"""
Columns = [
"Name", "Supplier", "Asset Type",
"Target Asset", "Item Type"
"Name", "Target Asset", "Supplier",
"Asset Type", "Item Type"
]
COL_NAME = 0
COL_SUPPLIER = 1
COL_ASSET_TYPE = 2
COL_TARGET_ASSET = 3
COL_TARGET_ASSET = 1
COL_SUPPLIER = 2
COL_ASSET_TYPE = 3
COL_ITEM_TYPE = 4
# COL_STATUS = 5 # Removed
# COL_OUTPUT_PATH = 6 # Removed
@@ -242,7 +242,7 @@ class UnifiedViewModel(QAbstractItemModel):
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
return Path(item.input_path).name
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
@@ -253,21 +253,21 @@ class UnifiedViewModel(QAbstractItemModel):
elif isinstance(item, AssetRule):
if role == Qt.DisplayRole:
if column == self.COL_NAME: return item.asset_name
if column == self.COL_ASSET_TYPE:
elif 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 ""
# Removed Status and Output Path columns
elif role == Qt.EditRole:
if column == self.COL_ASSET_TYPE:
return item.asset_type_override # Return string or None
return item.asset_type_override
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
if column == self.COL_TARGET_ASSET:
elif column == self.COL_TARGET_ASSET:
return item.target_asset_name_override if item.target_asset_name_override is not None else ""
if column == self.COL_ITEM_TYPE:
elif column == self.COL_ITEM_TYPE:
# Reverted Logic: Display override if set, otherwise base type. Shows prefixed keys.
override = item.item_type_override
initial_type = item.item_type
@@ -278,8 +278,8 @@ class UnifiedViewModel(QAbstractItemModel):
return initial_type if initial_type else ""
# Removed Status and Output Path columns
elif role == Qt.EditRole:
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else ""
if column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else "" # Return string or ""
elif column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None
return None # Default for FileRule
return None # Default return if role/item combination not handled
@@ -299,6 +299,7 @@ class UnifiedViewModel(QAbstractItemModel):
if isinstance(item, SourceRule): # If SourceRule is editable
if column == self.COL_SUPPLIER:
# Get the new value, strip whitespace, treat empty as None
log.debug(f"setData COL_SUPPLIER: Index=({index.row()},{column}), Value='{value}', Type={type(value)}") # <-- ADDED LOGGING (Corrected Indentation)
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)
@@ -516,12 +517,12 @@ class UnifiedViewModel(QAbstractItemModel):
can_edit = False
# Determine editability based on item type and column
if isinstance(item, SourceRule): # If SourceRule is displayed/editable
if column == 1: can_edit = True
if column == self.COL_SUPPLIER: can_edit = True # Supplier is editable
elif isinstance(item, AssetRule):
if column == 2: can_edit = True
if column == self.COL_ASSET_TYPE: can_edit = True # Asset Type is editable
elif isinstance(item, FileRule):
if column == 3: can_edit = True
if column == 4: can_edit = True
if column == self.COL_TARGET_ASSET: can_edit = True # Target Asset is editable
if column == self.COL_ITEM_TYPE: can_edit = True # Item Type is editable
if can_edit:
return default_flags | Qt.ItemIsEditable
@@ -545,4 +546,5 @@ class UnifiedViewModel(QAbstractItemModel):
item = index.internalPointer()
if item: # Ensure internal pointer is not None
return item
return None # Return None for invalid index or None pointer
return None # Return None for invalid index or None pointer