GUI - File Type Keybinds And F2 Renaming

This commit is contained in:
2025-05-06 20:31:53 +02:00
parent ff548e902e
commit 9a27d23a4c
7 changed files with 530 additions and 73 deletions

View File

@@ -18,40 +18,43 @@ class AssetRestructureHandler(QObject):
if not isinstance(model, UnifiedViewModel):
raise TypeError("AssetRestructureHandler requires a UnifiedViewModel instance.")
self.model = model
# Connect to the modified signal (passes FileRule object)
self.model.targetAssetOverrideChanged.connect(self.handle_target_asset_override)
# Connect to the new signal for AssetRule name changes
self.model.assetNameChanged.connect(self.handle_asset_name_changed)
log.debug("AssetRestructureHandler initialized.")
@Slot(QModelIndex, object)
def handle_target_asset_override(self, index: QModelIndex, new_target_path: object):
@Slot(FileRule, str, QModelIndex)
def handle_target_asset_override(self, file_rule_item: FileRule, new_target_name: str, index: QModelIndex): # Ensure FileRule is imported
"""
Slot connected to UnifiedViewModel.targetAssetOverrideChanged.
Orchestrates model changes based on the new target asset path.
Args:
index: The QModelIndex of the FileRule whose override changed.
new_target_path: The new target asset path (string or None).
file_rule_item: The FileRule object whose override changed.
new_target_name: The new target asset path (string).
index: The QModelIndex of the changed item (passed by the signal).
"""
log.debug(f"Handler received targetAssetOverrideChanged: Index=({index.row()},{index.column()}), New Path='{new_target_path}'")
if not index.isValid():
log.warning("Handler received invalid index. Aborting.")
if not isinstance(file_rule_item, FileRule): # Check the correct parameter
log.warning(f"Handler received targetAssetOverrideChanged for non-FileRule item: {type(file_rule_item)}. Aborting.")
return
file_item = self.model.getItem(index)
if not isinstance(file_item, FileRule):
log.warning(f"Handler received index for non-FileRule item: {type(file_item)}. Aborting.")
return
# Crucially, use file_rule_item for all logic. 'index' is for context or if model interaction is *unavoidable* (which it shouldn't be here).
log.debug(f"Handler received targetAssetOverrideChanged: OBJECT='{file_rule_item!r}', FILE_PATH='{file_rule_item.file_path}', NEW_NAME='{new_target_name}'")
# Ensure new_target_path is a string or None
new_target_name = str(new_target_path).strip() if new_target_path is not None else None
if new_target_name == "": new_target_name = None # Treat empty string as None
# Ensure new_target_name is a string or None (already string from signal, but good practice if it could be object)
effective_new_target_name = str(new_target_name).strip() if new_target_name is not None else None
if effective_new_target_name == "": effective_new_target_name = None # Treat empty string as None
# --- Get necessary context ---
old_parent_asset = getattr(file_item, 'parent_asset', None)
# Use file_rule_item directly
old_parent_asset = getattr(file_rule_item, 'parent_asset', None)
if not old_parent_asset:
log.error(f"Handler: File item '{Path(file_item.file_path).name}' has no parent asset. Cannot restructure.")
log.error(f"Handler: File item '{Path(file_rule_item.file_path).name}' has no parent asset. Cannot restructure.")
# Note: Data change already happened in setData, cannot easily revert here.
return
# Use file_rule_item directly
source_rule = getattr(old_parent_asset, 'parent_source', None)
if not source_rule:
log.error(f"Handler: Could not find SourceRule for parent asset '{old_parent_asset.asset_name}'. Cannot restructure.")
@@ -59,80 +62,165 @@ class AssetRestructureHandler(QObject):
# --- Logic based on the new target name ---
target_parent_asset = None
target_parent_index = QModelIndex()
target_parent_index = QModelIndex() # This will be the QModelIndex of the target AssetRule
move_occurred = False
# 1. Find existing target parent AssetRule within the same SourceRule
if new_target_name:
if effective_new_target_name:
for i, asset in enumerate(source_rule.assets):
if asset.asset_name == new_target_name:
if asset.asset_name == effective_new_target_name:
target_parent_asset = asset
# Get index for the target parent
# Get QModelIndex for the target parent AssetRule
try:
source_rule_row = self.model._source_rules.index(source_rule)
source_rule_index = self.model.createIndex(source_rule_row, 0, source_rule)
target_parent_index = self.model.index(i, 0, source_rule_index)
target_parent_index = self.model.index(i, 0, source_rule_index) # QModelIndex for the target AssetRule
if not target_parent_index.isValid():
log.error(f"Handler: Failed to create valid index for existing target parent '{new_target_name}'.")
log.error(f"Handler: Failed to create valid QModelIndex for existing target parent '{effective_new_target_name}'.")
target_parent_asset = None # Reset if index is invalid
except ValueError:
log.error(f"Handler: Could not find SourceRule index while looking for target parent '{new_target_name}'.")
log.error(f"Handler: Could not find SourceRule index while looking for target parent '{effective_new_target_name}'.")
target_parent_asset = None # Reset if index is invalid
break # Found the asset
# 2. Handle Move or Creation
if target_parent_asset:
if target_parent_asset: # An existing AssetRule to move to was found
# --- Move to Existing Parent ---
if target_parent_asset != old_parent_asset:
log.info(f"Handler: Moving file '{Path(file_item.file_path).name}' to existing asset '{target_parent_asset.asset_name}'.")
if self.model.moveFileRule(index, target_parent_index):
log.info(f"Handler: Moving file '{Path(file_rule_item.file_path).name}' to existing asset '{target_parent_asset.asset_name}'.")
# The 'index' parameter IS the QModelIndex of the FileRule being changed.
# No need to re-fetch or re-validate it if the signal emits it correctly.
# The core issue was using a stale index to get the *object*, now we *have* the object.
source_file_qmodelindex = index # Use the index passed by the signal
if not source_file_qmodelindex or not source_file_qmodelindex.isValid(): # Should always be valid if signal emits it
log.error(f"Handler: Received invalid QModelIndex for source file '{Path(file_rule_item.file_path).name}'. Cannot move.")
return
if self.model.moveFileRule(source_file_qmodelindex, target_parent_index): # target_parent_index is for the AssetRule
move_occurred = True
else:
log.error(f"Handler: Model failed to move file rule to existing asset '{target_parent_asset.asset_name}'.")
# Consider how to handle failure - maybe log and continue to cleanup?
else:
# Target is the same as the old parent. No move needed.
log.debug(f"Handler: Target asset '{new_target_name}' is the same as the current parent. No move required.")
pass # No move needed, but might still need cleanup if old parent becomes empty later (unlikely in this specific case)
log.debug(f"Handler: Target asset '{effective_new_target_name}' is the same as the current parent. No move required.")
elif new_target_name: # Only create if a *new* specific target name was given
elif effective_new_target_name: # No existing AssetRule found, but a new name is provided. Create it.
# --- Create New Parent AssetRule and Move ---
log.info(f"Handler: Creating new asset '{new_target_name}' and moving file '{Path(file_item.file_path).name}'.")
# Create the new asset rule using the model's method
new_asset_index = self.model.createAssetRule(source_rule, new_target_name, copy_from_asset=old_parent_asset)
log.info(f"Handler: Creating new asset '{effective_new_target_name}' and moving file '{Path(file_rule_item.file_path).name}'.")
new_asset_qmodelindex = self.model.createAssetRule(source_rule, effective_new_target_name, copy_from_asset=old_parent_asset)
if new_asset_index.isValid():
# Now move the file to the newly created asset
if self.model.moveFileRule(index, new_asset_index):
if new_asset_qmodelindex.isValid():
target_parent_asset = new_asset_qmodelindex.internalPointer() # Get the newly created AssetRule object
target_parent_index = new_asset_qmodelindex # The QModelIndex of the new AssetRule
source_file_qmodelindex = index # Use the index passed by the signal
if not source_file_qmodelindex or not source_file_qmodelindex.isValid(): # Should always be valid
log.error(f"Handler: Received invalid QModelIndex for source file '{Path(file_rule_item.file_path).name}'. Cannot move to new asset.")
self.model.removeAssetRule(target_parent_asset) # Attempt to clean up newly created asset
return
if self.model.moveFileRule(source_file_qmodelindex, target_parent_index): # Move to the new AssetRule
move_occurred = True
target_parent_asset = new_asset_index.internalPointer() # Update for cleanup check
else:
log.error(f"Handler: Model failed to move file rule to newly created asset '{new_target_name}'.")
# If move fails after creation, should we remove the created asset? Maybe.
# For now, just log the error.
log.error(f"Handler: Model failed to move file rule to newly created asset '{effective_new_target_name}'.")
# Consider removing the newly created asset if the move fails
self.model.removeAssetRule(target_parent_asset) # Attempt to clean up
else:
log.error(f"Handler: Model failed to create new asset rule '{new_target_name}'. Cannot move file.")
log.error(f"Handler: Model failed to create new asset rule '{effective_new_target_name}'. Cannot move file.")
else: # new_target_name is None or empty
# --- Moving back to original/default parent (Clearing Override) ---
# The file *should* already be under its original parent if the override was just cleared.
# However, if it was previously moved *away* from its original parent due to an override,
# clearing the override *should* ideally move it back.
# This logic is complex: we need to know the *original* parent before any overrides.
# The current structure doesn't explicitly store this.
# For now, assume clearing the override means it stays in its *current* parent,
# and we only handle cleanup if that parent becomes empty.
# A more robust solution might involve finding the asset matching the file's *directory* name.
log.debug(f"Handler: Target asset override cleared for '{Path(file_item.file_path).name}'. File remains in parent '{old_parent_asset.asset_name}'.")
# No move occurs in this simplified interpretation.
else: # effective_new_target_name is None or empty (override cleared)
log.debug(f"Handler: Target asset override cleared for '{Path(file_rule_item.file_path).name}'. File remains in parent '{old_parent_asset.asset_name}'.")
# No move occurs in this interpretation if the override is simply cleared.
# The file_rule_item.target_asset_name_override is now None (set by model.setData).
# 3. Cleanup Empty Old Parent (only if a move occurred)
# Check the old_parent_asset *after* the potential move
if move_occurred and old_parent_asset and not old_parent_asset.files:
# 3. Cleanup Empty Old Parent (only if a move occurred and old parent is now empty)
if move_occurred and old_parent_asset and not old_parent_asset.files and old_parent_asset != target_parent_asset:
log.info(f"Handler: Attempting to remove empty old parent asset '{old_parent_asset.asset_name}'.")
if not self.model.removeAssetRule(old_parent_asset):
log.warning(f"Handler: Model failed to remove empty old parent asset '{old_parent_asset.asset_name}'.")
elif move_occurred:
log.debug(f"Handler: Old parent asset '{old_parent_asset.asset_name}' still contains files. No removal needed.")
log.debug(f"Handler: Old parent asset '{old_parent_asset.asset_name}' still contains files or is the target. No removal needed.")
log.debug(f"Handler finished processing targetAssetOverrideChanged for '{Path(file_item.file_path).name}'.")
log.debug(f"Handler finished processing targetAssetOverrideChanged for '{Path(file_rule_item.file_path).name}'.")
def _get_qmodelindex_for_item(self, item_to_find):
"""
Helper to find the QModelIndex for a given FileRule or AssetRule item.
Returns a valid QModelIndex or QModelIndex() if not found/invalid.
"""
if isinstance(item_to_find, FileRule):
parent_asset = getattr(item_to_find, 'parent_asset', None)
if not parent_asset: return QModelIndex()
source_rule = getattr(parent_asset, 'parent_source', None)
if not source_rule: return QModelIndex()
try:
source_rule_row = self.model._source_rules.index(source_rule)
source_rule_index = self.model.createIndex(source_rule_row, 0, source_rule)
if not source_rule_index.isValid(): return QModelIndex()
parent_asset_row = source_rule.assets.index(parent_asset)
parent_asset_index = self.model.index(parent_asset_row, 0, source_rule_index)
if not parent_asset_index.isValid(): return QModelIndex()
item_row = parent_asset.files.index(item_to_find)
return self.model.index(item_row, 0, parent_asset_index)
except ValueError:
log.error(f"Error finding item {item_to_find} in model hierarchy during QModelIndex reconstruction.")
return QModelIndex()
elif isinstance(item_to_find, AssetRule):
source_rule = getattr(item_to_find, 'parent_source', None)
if not source_rule: return QModelIndex()
try:
source_rule_row = self.model._source_rules.index(source_rule)
source_rule_index = self.model.createIndex(source_rule_row, 0, source_rule)
if not source_rule_index.isValid(): return QModelIndex()
item_row = source_rule.assets.index(item_to_find)
return self.model.index(item_row, 0, source_rule_index)
except ValueError:
log.error(f"Error finding asset {item_to_find.asset_name} in model hierarchy during QModelIndex reconstruction.")
return QModelIndex()
return QModelIndex()
@Slot(AssetRule, str, QModelIndex) # Updated signature
def handle_asset_name_changed(self, asset_rule_item: AssetRule, new_name: str, index: QModelIndex): # Ensure AssetRule is imported
"""
Slot connected to UnifiedViewModel.assetNameChanged.
Handles logic when an AssetRule's name is changed.
Args:
asset_rule_item: The AssetRule object whose name changed.
new_name: The new name of the asset.
index: The QModelIndex of the changed AssetRule item.
"""
if not isinstance(asset_rule_item, AssetRule):
log.warning(f"Handler received assetNameChanged for non-AssetRule item: {type(asset_rule_item)}. Aborting.")
return
# The 'old_name' is not directly passed by the new signal signature.
# If needed, it would have to be inferred or stored prior to the change.
# However, the model's setData already handles updating child FileRule targets.
# This handler's main job is to react to the AssetRule object itself.
log.debug(f"Handler received assetNameChanged: OBJECT='{asset_rule_item!r}', ASSET_NAME='{asset_rule_item.asset_name}', NEW_NAME='{new_name}'")
# The UnifiedViewModel.setData has already updated FileRule.target_asset_name_override
# for any FileRules that were pointing to the *old* asset name across the entire model.
# The primary purpose of this handler slot, given the problem description,
# is to ensure that if any restructuring or disk operations were tied to an AssetRule's
# name, they would now correctly use 'asset_rule_item' (the actual object)
# and 'new_name'.
# For this specific task, confirming correct identification is key.
# If this handler were also responsible for renaming directories on disk,
# this is where that logic would go, using asset_rule_item and new_name.
# The old name would need to be retrieved differently if essential for such an operation,
# e.g. by storing it temporarily before the model's setData commits the change,
# or by having the signal pass it (which it currently doesn't in the revised design).
# For now, the model handles the critical part of updating linked FileRules.
log.info(f"Handler correctly identified AssetRule '{new_name}' for processing using the direct object. Model's setData handles related FileRule target updates.")

View File

@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
)
from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject, QModelIndex, QItemSelectionModel, QPoint, QTimer # Added Signal, QObject, QModelIndex, QItemSelectionModel, QPoint, QTimer
from PySide6.QtGui import QColor, QAction, QPalette, QClipboard # Add QColor import, QAction, QPalette, QClipboard
from PySide6.QtGui import QKeySequence
# --- Local GUI Imports ---
from .preset_editor_widget import PresetEditorWidget
@@ -34,7 +35,7 @@ from rule_structure import SourceRule, AssetRule, FileRule # Import Rule Structu
# --- GUI Model Imports ---
# 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.unified_view_model import UnifiedViewModel, CustomRoles # Import the new unified model and CustomRoles
# Removed delegate imports, now handled by MainPanelWidget
from .prediction_handler import RuleBasedPredictionHandler # Corrected import path
from .llm_interaction_handler import LLMInteractionHandler # Import the new handler
@@ -230,12 +231,39 @@ class MainWindow(QMainWindow):
# --- Connect Model Signals ---
self.unified_model.targetAssetOverrideChanged.connect(self.restructure_handler.handle_target_asset_override)
self.unified_model.assetNameChanged.connect(self.restructure_handler.handle_asset_name_changed) # Added connection
# --- Connect LLM Editor Signals ---
self.llm_editor_widget.settings_saved.connect(self._on_llm_settings_saved) # Connect save signal
# --- Adjust Splitter ---
self.splitter.setSizes([400, 800]) # Initial size ratio
# --- Initialize Keybind Map ---
self.key_char_to_qt_key = {
'C': Qt.Key_C, 'R': Qt.Key_R, 'N': Qt.Key_N, 'M': Qt.Key_M,
'D': Qt.Key_D, 'E': Qt.Key_E, 'X': Qt.Key_X
}
self.qt_key_to_ftd_map = {}
try:
base_settings = load_base_config()
file_type_defs = base_settings.get('FILE_TYPE_DEFINITIONS', {})
for ftd_key, ftd_value in file_type_defs.items():
if isinstance(ftd_value, dict) and 'keybind' in ftd_value:
char_key = ftd_value['keybind']
qt_key_val = self.key_char_to_qt_key.get(char_key)
if qt_key_val:
if qt_key_val not in self.qt_key_to_ftd_map:
self.qt_key_to_ftd_map[qt_key_val] = []
# Ensure consistent order for toggleable types if they are defined together under one key
# For example, if 'R' maps to ROUGH then GLOSS, they should appear in that order.
# This relies on the order in app_settings.json and dict iteration (Python 3.7+).
self.qt_key_to_ftd_map[qt_key_val].append(ftd_key)
log.info(f"Loaded keybind map: {self.qt_key_to_ftd_map}")
except Exception as e:
log.error(f"Failed to load keybind configurations: {e}")
# self.qt_key_to_ftd_map will be empty, keybinds won't work.
# --- UI Setup Methods ---
# setup_editor_panel_ui, _create_editor_general_tab, _create_editor_mapping_tab moved to PresetEditorWidget
@@ -1312,11 +1340,209 @@ class MainWindow(QMainWindow):
log.warning("get_llm_source_preset_name called before preset_editor_widget was initialized.")
return None
def keyPressEvent(self, event):
"""Handles key press events for implementing keybinds."""
log.debug(f"KeyPressEvent: key={event.key()}, modifiers={event.modifiers()}, text='{event.text()}'")
if not self.main_panel_widget or not self.unified_model:
log.warning("Key press ignored: Main panel or unified model not available.")
super().keyPressEvent(event)
return
selected_view_indexes = self.main_panel_widget.unified_view.selectionModel().selectedIndexes()
if not selected_view_indexes:
log.debug("Key press ignored: No items selected.")
super().keyPressEvent(event)
return
# Get unique model indexes (typically one per selected row)
# Assuming unified_view uses unified_model directly or proxy maps correctly
model_indexes_to_process = []
unique_rows = set()
for view_idx in selected_view_indexes:
# If using a proxy:
# model_idx = self.main_panel_widget.unified_view.model().mapToSource(view_idx)
model_idx = view_idx # Assuming direct model usage for now
if model_idx.row() not in unique_rows: # Process each underlying model row only once
# Ensure we are getting the index for column 0 if multiple columns are selected for the same row
model_indexes_to_process.append(self.unified_model.index(model_idx.row(), 0, model_idx.parent()))
unique_rows.add(model_idx.row())
if not model_indexes_to_process:
super().keyPressEvent(event)
return
pressed_key = event.key()
modifiers = event.modifiers()
keybind_processed = False
# --- Asset Name Keybind (F2) ---
if pressed_key == Qt.Key_F2 and not modifiers: # No modifiers for F2
log.debug("F2 pressed for asset name change.")
# Get current asset name from the first selected item as a suggestion
first_selected_item_index = model_indexes_to_process[0] # This is a col 0 index
first_item_object = self.unified_model.getItem(first_selected_item_index)
current_name_suggestion = ""
if isinstance(first_item_object, AssetRule):
# For AssetRule, its name is in COL_NAME (which is first_selected_item_index's column, typically 0)
# The index itself (first_selected_item_index) can be used as it's for COL_NAME.
current_name_suggestion = self.unified_model.data(first_selected_item_index, Qt.DisplayRole) or ""
elif isinstance(first_item_object, FileRule):
# For FileRule, its target asset name override is in COL_TARGET_ASSET
target_asset_col_idx = self.unified_model.COL_TARGET_ASSET
target_asset_index_for_suggestion = first_selected_item_index.siblingAtColumn(target_asset_col_idx)
current_name_suggestion = self.unified_model.data(target_asset_index_for_suggestion, Qt.DisplayRole) or ""
new_name_input, ok = QInputDialog.getText(self, "Set Name", "Enter new name for selected items:", QLineEdit.EchoMode.Normal, current_name_suggestion)
if ok and new_name_input is not None:
stripped_name = new_name_input.strip()
if stripped_name:
log.info(f"User entered new name: '{stripped_name}' for selected items.")
# Step 1: Collect Objects
initial_selected_indices = self.main_panel_widget.unified_view.selectedIndexes()
objects_to_rename = []
processed_rows_for_object_collection = set() # To avoid processing same underlying item multiple times if multiple columns selected
for view_idx in initial_selected_indices:
# Assuming direct model usage or correct proxy mapping by the view
model_idx_for_item = self.unified_model.index(view_idx.row(), 0, view_idx.parent()) # Get column 0 index
if model_idx_for_item.row() not in processed_rows_for_object_collection:
item = self.unified_model.getItem(model_idx_for_item)
if isinstance(item, (AssetRule, FileRule)):
objects_to_rename.append(item)
processed_rows_for_object_collection.add(model_idx_for_item.row())
else:
log.debug(f"F2 RENAME: Skipping item {item!r} (type: {type(item)}) during object collection as it's not AssetRule or FileRule.")
log.debug(f"F2 RENAME: Collected {len(objects_to_rename)} AssetRule/FileRule objects to rename.")
# Step 2: Iterate Over Objects and Update
successful_renames = 0
for item_object in objects_to_rename:
current_model_index = self.unified_model.findIndexForItem(item_object)
if current_model_index is None or not current_model_index.isValid():
item_repr = getattr(item_object, 'asset_name', getattr(item_object, 'file_path', repr(item_object)))
log.warning(f"F2 RENAME: Could not find current index for item {item_repr!r}. It might have been moved/deleted unexpectedly. Skipping.")
continue
target_column = -1
item_description_for_log = ""
if isinstance(item_object, AssetRule):
target_column = self.unified_model.COL_NAME
item_description_for_log = f"AssetRule '{item_object.asset_name}'"
elif isinstance(item_object, FileRule):
target_column = self.unified_model.COL_TARGET_ASSET
item_description_for_log = f"FileRule '{Path(item_object.file_path).name}'"
if target_column == -1:
log.warning(f"F2 RENAME: Unknown item type for {item_object!r}. Cannot determine target column. Skipping.")
continue
index_to_update_in_column = current_model_index.siblingAtColumn(target_column)
log.debug(f"F2 RENAME: Attempting to set new name '{stripped_name}' for {item_description_for_log} at index r={index_to_update_in_column.row()}, c={index_to_update_in_column.column()}")
success = self.unified_model.setData(index_to_update_in_column, stripped_name, Qt.EditRole)
if success:
successful_renames += 1
log.info(f"F2 RENAME: Successfully renamed {item_description_for_log} to '{stripped_name}'.")
else:
log.warning(f"F2 RENAME: Failed to rename {item_description_for_log} to '{stripped_name}'. setData returned False.")
self.statusBar().showMessage(f"{successful_renames} item(s) renamed to '{stripped_name}'.", 3000)
keybind_processed = True
else:
log.debug("Asset name change aborted: name was empty after stripping.")
else:
log.debug("Asset name change cancelled or empty name entered.")
event.accept()
return
# --- File Type Keybinds (Ctrl + Key) ---
if modifiers == Qt.ControlModifier:
log.debug(f"Ctrl modifier detected with key: {pressed_key}")
qt_key_sequence_str = QKeySequence(pressed_key).toString() # For logging
if pressed_key in self.qt_key_to_ftd_map:
target_ftd_keys = self.qt_key_to_ftd_map[pressed_key]
log.debug(f"Keybind match: Ctrl+{qt_key_sequence_str} maps to FTDs: {target_ftd_keys}")
if not target_ftd_keys:
log.warning(f"No FTDs configured for key Ctrl+{qt_key_sequence_str}")
super().keyPressEvent(event)
return
# self.unified_model.beginResetModel() # Potentially too broad
for index in model_indexes_to_process:
item = self.unified_model.getItem(index) # index is for col 0
# Check if the item is a FileRule instance
# --- BEGIN ADDED LOGGING ---
log.debug(f"Processing item for keybind: row={index.row()}, column={index.column()}")
log.debug(f" Item object: {item!r}") # !r calls __repr__
log.debug(f" Item type: {type(item)}")
log.debug(f" Is instance of FileRule: {isinstance(item, FileRule)}")
if hasattr(item, '__dict__'): # Log attributes if it's a custom object
log.debug(f" Item attributes: {item.__dict__}")
# --- END ADDED LOGGING ---
if not isinstance(item, FileRule): # This is the existing check
log.debug(f"Skipping item at row {index.row()} because it's not a FileRule instance (actual type: {type(item)}).")
continue
# Get current map type using COL_ITEM_TYPE and DisplayRole
item_type_display_index = self.unified_model.index(index.row(), self.unified_model.COL_ITEM_TYPE, index.parent())
current_map_type = self.unified_model.data(item_type_display_index, Qt.DisplayRole)
log.debug(f"Item at row {index.row()} ({Path(item.file_path).name}), current map_type (DisplayRole): '{current_map_type}'")
new_map_type = ""
if len(target_ftd_keys) == 1: # Single target type
new_map_type = target_ftd_keys[0]
log.debug(f" Single target FTD: '{new_map_type}'")
else: # Toggle logic for multiple target types
log.debug(f" Toggle FTDs: {target_ftd_keys}. Current: '{current_map_type}'")
try:
current_ftd_index = target_ftd_keys.index(current_map_type)
next_ftd_index = (current_ftd_index + 1) % len(target_ftd_keys)
new_map_type = target_ftd_keys[next_ftd_index]
log.debug(f" Calculated next FTD: '{new_map_type}'")
except ValueError: # current_map_type is not in the toggle list
new_map_type = target_ftd_keys[0] # Default to the first one
log.debug(f" Current not in toggle list, defaulting to first: '{new_map_type}'")
if new_map_type and new_map_type != current_map_type:
log.debug(f" Updating item at row {index.row()} ({Path(item.file_path).name}) from '{current_map_type}' to '{new_map_type}'")
# Set new map type using COL_ITEM_TYPE and EditRole
item_type_edit_index = self.unified_model.index(index.row(), self.unified_model.COL_ITEM_TYPE, index.parent())
success = self.unified_model.setData(item_type_edit_index, new_map_type, Qt.EditRole)
log.debug(f" setData call successful: {success}")
elif not new_map_type:
log.debug(f" Skipping update for item at row {index.row()}, new_map_type is empty.")
else: # new_map_type == current_map_type
log.debug(f" Skipping update for item at row {index.row()}, new_map_type ('{new_map_type}') is same as current ('{current_map_type}').")
# self.unified_model.endResetModel() # Potentially too broad
# The model should emit dataChanged for each setData call.
self.statusBar().showMessage(f"File types updated for selected items.", 3000)
keybind_processed = True
event.accept()
return
if not keybind_processed:
log.debug("Key press not handled by custom keybinds, passing to super.")
super().keyPressEvent(event)
# --- Main Execution ---
# --- Main Execution ---
def run_gui():
"""Initializes and runs the Qt application."""
print("--- Reached run_gui() ---")
# Ensure QInputDialog is imported if not already at the top
# from PySide6.QtWidgets import QInputDialog (already handled by being part of PySide6.QtWidgets import *)
from PySide6.QtGui import QKeySequence # Ensure QKeySequence is imported if used standalone
app = QApplication(sys.argv)
#app.setStyle('Fusion')

View File

@@ -8,6 +8,10 @@ from rule_structure import SourceRule, AssetRule, FileRule # Removed AssetType,
from configuration import load_base_config # Import load_base_config
from typing import List # Added for type hinting
class CustomRoles:
MapTypeRole = Qt.UserRole + 1
TargetAssetRole = Qt.UserRole + 2
# Add other custom roles here as needed
class UnifiedViewModel(QAbstractItemModel):
# --- Color Constants for Row Backgrounds ---
# Old colors removed, using config now + fixed source color
@@ -19,8 +23,12 @@ class UnifiedViewModel(QAbstractItemModel):
of SourceRule -> AssetRule -> FileRule.
"""
# Signal emitted when a FileRule's target asset override changes.
# Carries the index of the FileRule and the new target asset path (or None).
targetAssetOverrideChanged = Signal(QModelIndex, object)
# Carries the FileRule object and the new target asset path (or None).
targetAssetOverrideChanged = Signal(FileRule, str, QModelIndex) # Emit FileRule object, new value, and index
# Signal emitted when an AssetRule's name changes.
# Carries the AssetRule object, the new name, and the index.
assetNameChanged = Signal(AssetRule, str, QModelIndex)
Columns = [
"Name", "Target Asset", "Supplier",
@@ -362,6 +370,8 @@ class UnifiedViewModel(QAbstractItemModel):
old_asset_name = item.asset_name
item.asset_name = new_asset_name
changed = True
# Emit signal for asset name change, including the index
self.assetNameChanged.emit(item, new_asset_name, index)
# --- Update Child FileRule Target Asset Overrides ---
log.debug(f"setData: Updating FileRule target overrides from '{old_asset_name}' to '{new_asset_name}'")
@@ -408,9 +418,10 @@ class UnifiedViewModel(QAbstractItemModel):
item.target_asset_name_override = new_value
changed = True
# Emit signal that the override changed, let handler deal with restructuring
self.targetAssetOverrideChanged.emit(index, new_value)
# Pass the FileRule item itself, the new value, and the index
self.targetAssetOverrideChanged.emit(item, new_value, index)
elif column == self.COL_ITEM_TYPE: # Item-Type Override
# Delegate provides string value (e.g., "MAP_COL") or None
# Delegate provides string value (e.g., "MAP_COL") or None
new_value = str(value) if value is not None else None
if new_value == "": new_value = None # Treat empty string as None
# Update item_type_override
@@ -784,11 +795,63 @@ class UnifiedViewModel(QAbstractItemModel):
"""Returns the cached list of file type keys."""
return self._file_type_keys
def findIndexForItem(self, target_item_object) -> QModelIndex | None:
"""
Finds the QModelIndex for a given item object (SourceRule, AssetRule, or FileRule)
by traversing the model's internal tree structure.
Args:
target_item_object: The specific SourceRule, AssetRule, or FileRule object to find.
Returns:
QModelIndex for the item if found, otherwise None.
"""
if target_item_object is None:
return None
for sr_row, source_rule in enumerate(self._source_rules):
if source_rule is target_item_object:
return self.createIndex(sr_row, 0, source_rule) # Top-level item
parent_source_rule_index = self.createIndex(sr_row, 0, source_rule) # Potential parent for children
if not parent_source_rule_index.isValid(): # Should always be valid here
log.error(f"findIndexForItem: Could not create valid index for SourceRule: {source_rule.input_path}")
continue
for ar_row, asset_rule in enumerate(source_rule.assets):
if asset_rule is target_item_object:
return self.index(ar_row, 0, parent_source_rule_index)
parent_asset_rule_index = self.index(ar_row, 0, parent_source_rule_index)
if not parent_asset_rule_index.isValid():
log.error(f"findIndexForItem: Could not create valid index for AssetRule: {asset_rule.asset_name}")
continue # Skip children if parent index is invalid
for fr_row, file_rule in enumerate(asset_rule.files):
if file_rule is target_item_object:
return self.index(fr_row, 0, parent_asset_rule_index)
log.debug(f"findIndexForItem: Item {target_item_object!r} not found in the model.")
return None
# --- removeAssetRule continued (log.debug was separated by the insert) ---
# This log line belongs to the removeAssetRule method defined earlier.
# It's being re-indented here to its correct place if it was part of that method's flow.
# However, looking at the original structure, the `return True` for removeAssetRule
# was at line 802, and the log.debug was at 798. This indicates the log.debug
# was likely the *start* of the problematic section in the previous attempt,
# and the `return True` was the end of `removeAssetRule`.
# The `log.debug` at original line 798 should be part of `removeAssetRule`'s positive path.
# The `return True` at original line 802 should be the final return of `removeAssetRule`.
# Correcting the end of removeAssetRule:
log.debug(f"Removing empty AssetRule '{asset_rule_to_remove.asset_name}' at row {asset_row_for_removal} under '{Path(source_rule.input_path).name}'")
self.beginRemoveRows(grandparent_index, asset_row_for_removal, asset_row_for_removal)
source_rule.assets.pop(asset_row_for_removal)
self.endRemoveRows()
return True
return True # This was the original end of removeAssetRule
def update_status(self, source_path: str, status_text: str):
"""
Finds the SourceRule node for the given source_path and updates its status.