Asset-Frameworker/.lh/gui/preview_table_model.py.json
2025-04-29 18:26:13 +02:00

106 lines
81 KiB
JSON

{
"sourceFile": "gui/preview_table_model.py",
"activeCommit": 0,
"commits": [
{
"activePatchIndex": 22,
"patches": [
{
"date": 1745323013820,
"content": "Index: \n===================================================================\n--- \n+++ \n"
},
{
"date": 1745324033031,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -54,10 +54,33 @@\n \r\n # --- Handle Display Role ---\r\n if role == Qt.ItemDataRole.DisplayRole:\r\n if col == self.COL_STATUS:\r\n- # TODO: Implement status text simplification here later\r\n- return file_details.get('status', '[No Status]')\r\n+ raw_status = file_details.get('status', '[No Status]')\r\n+ details = file_details.get('details', '') # Get details for parsing\r\n+\r\n+ # Implement status text simplification\r\n+ if raw_status == \"Unmatched Extra\":\r\n+ # Check details for regex match info\r\n+ if details and details.startswith(\"[Unmatched Extra (Regex match:\"):\r\n+ try:\r\n+ pattern = details.split(\"match: '\")[1].split(\"'\")[0]\r\n+ return f\"[Extra={pattern}]\"\r\n+ except IndexError:\r\n+ return \"Extra\" # Fallback if parsing fails\r\n+ else:\r\n+ return \"Extra\"\r\n+ elif raw_status == \"Ignored\" and details and \"Superseed by 16bit variant for\" in details:\r\n+ try:\r\n+ # Extract filename after \"for \"\r\n+ filename = details.split(\"Superseed by 16bit variant for \")[1]\r\n+ return f\"Superseeded by 16bit {filename}\"\r\n+ except IndexError:\r\n+ return raw_status # Fallback if parsing fails\r\n+ # Add other specific simplifications here if needed\r\n+ else:\r\n+ return raw_status # Return original status if no simplification applies\r\n+\r\n elif col == self.COL_PREDICTED_ASSET:\r\n return file_details.get('predicted_asset_name', 'N/A')\r\n elif col == self.COL_ORIGINAL_PATH:\r\n return file_details.get('original_path', '[Missing Path]')\r\n"
},
{
"date": 1745324053209,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -152,11 +152,20 @@\n \"\"\"\r\n Custom proxy model for sorting the preview table.\r\n Implements multi-level sorting and custom status order.\r\n \"\"\"\r\n- # Define the desired status order for sorting\r\n- # Error > Mapped > Model > Ignored > Extra\r\n- STATUS_ORDER = [\"Error\", \"Mapped\", \"Model\", \"Ignored\", \"Extra\", \"Unrecognised\", \"Unmatched Extra\", \"[No Status]\"] # Include others for completeness\r\n+ # Define the desired status priority for sorting\r\n+ # Lower numbers sort first. Mapped/Model have same priority.\r\n+ STATUS_PRIORITY = {\r\n+ \"Error\": 0,\r\n+ \"Mapped\": 1,\r\n+ \"Model\": 1,\r\n+ \"Ignored\": 2,\r\n+ \"Extra\": 3,\r\n+ \"Unrecognised\": 3, # Treat as Extra\r\n+ \"Unmatched Extra\": 3, # Treat as Extra\r\n+ \"[No Status]\": 99 # Lowest priority\r\n+ }\r\n \r\n def __init__(self, parent=None):\r\n super().__init__(parent)\r\n # Set default sort column and order (Status column, Ascending)\r\n@@ -185,20 +194,20 @@\n if not left_asset: return True # Empty asset comes first\r\n if not right_asset: return False # Non-empty asset comes first\r\n return left_asset < right_asset # Alphabetical sort for assets\r\n \r\n- # --- Level 2: Sort by Status (Custom Order) ---\r\n+ # --- Level 2: Sort by Status (Custom Priority) ---\r\n left_status = model.data(left.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n right_status = model.data(right.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n \r\n- # Get the index of the status in the defined order\r\n- left_status_index = self.STATUS_ORDER.index(left_status) if left_status in self.STATUS_ORDER else len(self.STATUS_ORDER)\r\n- right_status_index = self.STATUS_ORDER.index(right_status) if right_status in self.STATUS_ORDER else len(self.STATUS_ORDER)\r\n+ # Get the priority from the dictionary (default to low priority if status not found)\r\n+ left_prio = self.STATUS_PRIORITY.get(left_status, 99)\r\n+ right_prio = self.STATUS_PRIORITY.get(right_status, 99)\r\n \r\n- if left_status_index != right_status_index:\r\n- return left_status_index < right_status_index # Sort based on custom order index\r\n+ if left_prio != right_prio:\r\n+ return left_prio < right_prio # Sort based on priority\r\n \r\n- # --- Level 3: Sort by Original Path (Alphabetical) ---\r\n+ # --- Level 3: Sort by Original Path (Alphabetical, only if assets and status priorities are equal) ---\r\n left_path = model.data(left.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n right_path = model.data(right.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n \r\n # Handle None/empty strings\r\n"
},
{
"date": 1745332248186,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -3,48 +3,76 @@\n \r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n- Holds detailed file prediction results.\r\n+ Holds detailed file prediction results or a simple list of source assets.\r\n \"\"\"\r\n- # Define column roles for clarity\r\n+ # Define column roles for clarity (Detailed Mode)\r\n COL_STATUS = 0\r\n COL_PREDICTED_ASSET = 1\r\n COL_ORIGINAL_PATH = 2\r\n- COL_PREDICTED_OUTPUT = 3\r\n+ COL_PREDICTED_OUTPUT = 3 # Kept for internal data access, but hidden in view\r\n COL_DETAILS = 4\r\n \r\n # Define internal data roles for sorting/filtering\r\n ROLE_RAW_STATUS = Qt.ItemDataRole.UserRole + 1\r\n ROLE_SOURCE_ASSET = Qt.ItemDataRole.UserRole + 2\r\n \r\n+ # Column for Simple Mode\r\n+ COL_SIMPLE_PATH = 0\r\n+\r\n def __init__(self, data=None, parent=None):\r\n super().__init__(parent)\r\n # Data format: List of dictionaries, each representing a file's details\r\n # Example: {'original_path': '...', 'predicted_asset_name': '...', 'predicted_output_name': '...', 'status': '...', 'details': '...', 'source_asset': '...'}\r\n- self._data = data or []\r\n- # Initial columns - will be adjusted later\r\n- self._headers = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"]\r\n+ self._data = []\r\n+ self._simple_data = [] # List of unique source asset paths for simple mode\r\n+ self._simple_mode = False # Flag to toggle between detailed and simple view\r\n+ self._headers_detailed = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"]\r\n+ self._headers_simple = [\"Input Path\"]\r\n+ self.set_data(data or []) # Initialize data and simple_data\r\n \r\n+ def set_simple_mode(self, enabled: bool):\r\n+ \"\"\"Toggles the model between detailed and simple view modes.\"\"\"\r\n+ if self._simple_mode != enabled:\r\n+ self.beginResetModel()\r\n+ self._simple_mode = enabled\r\n+ self.endResetModel()\r\n+\r\n def rowCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of rows in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- return len(self._data)\r\n+ return len(self._simple_data) if self._simple_mode else len(self._data)\r\n \r\n def columnCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of columns in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- return len(self._headers)\r\n+ return len(self._headers_simple) if self._simple_mode else len(self._headers_detailed)\r\n \r\n def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the data for a given index and role.\"\"\"\r\n if not index.isValid():\r\n return None\r\n \r\n row = index.row()\r\n col = index.column()\r\n+\r\n+ # --- Simple Mode ---\r\n+ if self._simple_mode:\r\n+ if row >= len(self._simple_data): return None # Bounds check\r\n+ source_asset_path = self._simple_data[row]\r\n+ if role == Qt.ItemDataRole.DisplayRole:\r\n+ if col == self.COL_SIMPLE_PATH:\r\n+ return source_asset_path\r\n+ elif role == Qt.ItemDataRole.ToolTipRole:\r\n+ if col == self.COL_SIMPLE_PATH:\r\n+ return f\"Input Asset: {source_asset_path}\"\r\n+ return None\r\n+\r\n+ # --- Detailed Mode ---\r\n+ if row >= len(self._data): return None # Bounds check\r\n file_details = self._data[row]\r\n \r\n # --- Handle Custom Internal Roles ---\r\n if role == self.ROLE_RAW_STATUS:\r\n@@ -132,16 +160,24 @@\n \r\n def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the header data for a given section, orientation, and role.\"\"\"\r\n if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:\r\n- if 0 <= section < len(self._headers):\r\n- return self._headers[section]\r\n+ headers = self._headers_simple if self._simple_mode else self._headers_detailed\r\n+ if 0 <= section < len(headers):\r\n+ return headers[section]\r\n return None\r\n \r\n def set_data(self, data: list):\r\n- \"\"\"Sets the model's data and emits signals to update views.\"\"\"\r\n+ \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n self.beginResetModel()\r\n- self._data = data\r\n+ self._data = data or []\r\n+ # Extract unique source asset paths for simple mode\r\n+ unique_sources = set()\r\n+ for item in self._data:\r\n+ source = item.get('source_asset')\r\n+ if source:\r\n+ unique_sources.add(source)\r\n+ self._simple_data = sorted(list(unique_sources))\r\n self.endResetModel()\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n"
},
{
"date": 1745332438063,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,7 +1,10 @@\n+import logging # Import logging\r\n from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QSortFilterProxyModel\r\n from PySide6.QtGui import QColor\r\n \r\n+log = logging.getLogger(__name__) # Get logger\r\n+\r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n Holds detailed file prediction results or a simple list of source assets.\r\n@@ -21,8 +24,9 @@\n COL_SIMPLE_PATH = 0\r\n \r\n def __init__(self, data=None, parent=None):\r\n super().__init__(parent)\r\n+ log.debug(\"PreviewTableModel initialized.\")\r\n # Data format: List of dictionaries, each representing a file's details\r\n # Example: {'original_path': '...', 'predicted_asset_name': '...', 'predicted_output_name': '...', 'status': '...', 'details': '...', 'source_asset': '...'}\r\n self._data = []\r\n self._simple_data = [] # List of unique source asset paths for simple mode\r\n@@ -32,24 +36,33 @@\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n \"\"\"Toggles the model between detailed and simple view modes.\"\"\"\r\n+ log.debug(f\"PreviewTableModel.set_simple_mode called with enabled={enabled}. Current mode: {self._simple_mode}\")\r\n if self._simple_mode != enabled:\r\n self.beginResetModel()\r\n self._simple_mode = enabled\r\n+ log.debug(f\"PreviewTableModel mode changed to: {self._simple_mode}\")\r\n self.endResetModel()\r\n+ else:\r\n+ log.debug(\"PreviewTableModel mode is already as requested. No change.\")\r\n \r\n+\r\n def rowCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of rows in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- return len(self._simple_data) if self._simple_mode else len(self._data)\r\n+ row_count = len(self._simple_data) if self._simple_mode else len(self._data)\r\n+ # log.debug(f\"PreviewTableModel.rowCount called. Mode: {self._simple_mode}, Row Count: {row_count}\")\r\n+ return row_count\r\n \r\n def columnCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of columns in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- return len(self._headers_simple) if self._simple_mode else len(self._headers_detailed)\r\n+ col_count = len(self._headers_simple) if self._simple_mode else len(self._headers_detailed)\r\n+ # log.debug(f\"PreviewTableModel.columnCount called. Mode: {self._simple_mode}, Column Count: {col_count}\")\r\n+ return col_count\r\n \r\n def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the data for a given index and role.\"\"\"\r\n if not index.isValid():\r\n@@ -59,9 +72,11 @@\n col = index.column()\r\n \r\n # --- Simple Mode ---\r\n if self._simple_mode:\r\n- if row >= len(self._simple_data): return None # Bounds check\r\n+ if row >= len(self._simple_data):\r\n+ # log.warning(f\"data called with out of bounds row in simple mode: {row}/{len(self._simple_data)}\")\r\n+ return None # Bounds check\r\n source_asset_path = self._simple_data[row]\r\n if role == Qt.ItemDataRole.DisplayRole:\r\n if col == self.COL_SIMPLE_PATH:\r\n return source_asset_path\r\n@@ -70,9 +85,11 @@\n return f\"Input Asset: {source_asset_path}\"\r\n return None\r\n \r\n # --- Detailed Mode ---\r\n- if row >= len(self._data): return None # Bounds check\r\n+ if row >= len(self._data):\r\n+ # log.warning(f\"data called with out of bounds row in detailed mode: {row}/{len(self._data)}\")\r\n+ return None # Bounds check\r\n file_details = self._data[row]\r\n \r\n # --- Handle Custom Internal Roles ---\r\n if role == self.ROLE_RAW_STATUS:\r\n@@ -167,21 +184,31 @@\n return None\r\n \r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n+ log.debug(f\"PreviewTableModel.set_data called with {len(data)} items.\")\r\n self.beginResetModel()\r\n self._data = data or []\r\n # Extract unique source asset paths for simple mode\r\n unique_sources = set()\r\n- for item in self._data:\r\n- source = item.get('source_asset')\r\n- if source:\r\n- unique_sources.add(source)\r\n- self._simple_data = sorted(list(unique_sources))\r\n+ # Check if data is a list of strings (simple paths) or list of dicts (detailed)\r\n+ if data and isinstance(data[0], str):\r\n+ log.debug(\"set_data received list of strings (simple paths).\")\r\n+ self._simple_data = sorted(list(set(data))) # Ensure uniqueness and sort\r\n+ else:\r\n+ log.debug(\"set_data received list of dicts (detailed data). Extracting simple paths.\")\r\n+ for item in self._data:\r\n+ source = item.get('source_asset')\r\n+ if source:\r\n+ unique_sources.add(source)\r\n+ self._simple_data = sorted(list(unique_sources))\r\n+\r\n+ log.debug(f\"Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n self.endResetModel()\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n+ log.debug(\"PreviewTableModel.clear_data called.\")\r\n self.set_data([])\r\n \r\n \r\n class PreviewSortFilterProxyModel(QSortFilterProxyModel):\r\n@@ -203,8 +230,9 @@\n }\r\n \r\n def __init__(self, parent=None):\r\n super().__init__(parent)\r\n+ log.debug(\"PreviewSortFilterProxyModel initialized.\")\r\n # Set default sort column and order (Status column, Ascending)\r\n # This will be overridden by the custom lessThan logic\r\n self.setSortRole(PreviewTableModel.ROLE_RAW_STATUS) # Sort using the raw status role\r\n self.sort(PreviewTableModel.COL_STATUS, Qt.SortOrder.AscendingOrder) # Apply initial sort\r\n@@ -218,10 +246,23 @@\n 3. Original Path (Ascending)\r\n \"\"\"\r\n model = self.sourceModel()\r\n if not model:\r\n+ # log.debug(\"ProxyModel.lessThan: No source model.\")\r\n return super().lessThan(left, right) # Fallback if no source model\r\n \r\n+ # If in simple mode, sort by the simple path column\r\n+ if isinstance(model, PreviewTableModel) and model._simple_mode:\r\n+ left_path = model.data(left.siblingAtColumn(model.COL_SIMPLE_PATH), Qt.ItemDataRole.DisplayRole)\r\n+ right_path = model.data(right.siblingAtColumn(model.COL_SIMPLE_PATH), Qt.ItemDataRole.DisplayRole)\r\n+ # log.debug(f\"ProxyModel.lessThan (Simple Mode): Comparing '{left_path}' < '{right_path}'\")\r\n+ if not left_path: return True\r\n+ if not right_path: return False\r\n+ return left_path < right_path\r\n+\r\n+\r\n+ # --- Detailed Mode Sorting ---\r\n+ # log.debug(\"ProxyModel.lessThan (Detailed Mode).\")\r\n # --- Level 1: Sort by Source Asset ---\r\n left_asset = model.data(left.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n right_asset = model.data(right.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n \r\n@@ -257,5 +298,6 @@\n # We ignore the column and order here and rely on lessThan for multi-level sort\r\n # However, calling this method is necessary to trigger the proxy model's sorting mechanism.\r\n # We can potentially use the column/order to toggle ascending/descending within each level in lessThan,\r\n # but for now, we'll stick to the defined order.\r\n+ log.debug(f\"ProxyModel.sort called with column {column}, order {order}. Triggering lessThan.\")\r\n super().sort(column, order) # Call base class sort to trigger update\n\\ No newline at end of file\n"
},
{
"date": 1745332986402,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -36,16 +36,21 @@\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n \"\"\"Toggles the model between detailed and simple view modes.\"\"\"\r\n- log.debug(f\"PreviewTableModel.set_simple_mode called with enabled={enabled}. Current mode: {self._simple_mode}\")\r\n+ thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_simple_mode(enabled={enabled}). Current mode: {self._simple_mode}\")\r\n if self._simple_mode != enabled:\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from beginResetModel(). Setting mode.\")\r\n self._simple_mode = enabled\r\n- log.debug(f\"PreviewTableModel mode changed to: {self._simple_mode}\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Mode changed to: {self._simple_mode}. Calling endResetModel()...\")\r\n self.endResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n else:\r\n- log.debug(\"PreviewTableModel mode is already as requested. No change.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] PreviewTableModel mode is already as requested. No change.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_simple_mode.\")\r\n \r\n \r\n def rowCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of rows in the model.\"\"\"\r\n@@ -184,10 +189,13 @@\n return None\r\n \r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n- log.debug(f\"PreviewTableModel.set_data called with {len(data)} items.\")\r\n+ thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_data. Received {len(data)} items.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from beginResetModel(). Processing data...\")\r\n self._data = data or []\r\n # Extract unique source asset paths for simple mode\r\n unique_sources = set()\r\n # Check if data is a list of strings (simple paths) or list of dicts (detailed)\r\n@@ -201,14 +209,18 @@\n if source:\r\n unique_sources.add(source)\r\n self._simple_data = sorted(list(unique_sources))\r\n \r\n- log.debug(f\"Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n self.endResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n- log.debug(\"PreviewTableModel.clear_data called.\")\r\n+ thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] PreviewTableModel.clear_data called.\")\r\n self.set_data([])\r\n \r\n \r\n class PreviewSortFilterProxyModel(QSortFilterProxyModel):\r\n"
},
{
"date": 1745333096956,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -1,6 +1,7 @@\n import logging # Import logging\r\n-from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QSortFilterProxyModel\r\n+import time # For logging timestamps\r\n+from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QSortFilterProxyModel, QThread # Import QThread\r\n from PySide6.QtGui import QColor\r\n \r\n log = logging.getLogger(__name__) # Get logger\r\n \r\n"
},
{
"date": 1745333182396,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -190,8 +190,9 @@\n return None\r\n \r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n+ from PySide6.QtCore import QThread # <<< TEMPORARY DIAGNOSTIC IMPORT >>>\r\n thread_id = QThread.currentThreadId() # Get current thread ID\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_data. Received {len(data)} items.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n"
},
{
"date": 1745333288777,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -190,10 +190,10 @@\n return None\r\n \r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n- from PySide6.QtCore import QThread # <<< TEMPORARY DIAGNOSTIC IMPORT >>>\r\n- thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ # Removed diagnostic import here\r\n+ thread_id = self.thread().currentThreadId() if self.thread() else 'N/A' # Get thread ID via instance\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_data. Received {len(data)} items.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from beginResetModel(). Processing data...\")\r\n"
},
{
"date": 1745333297842,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -37,9 +37,9 @@\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n \"\"\"Toggles the model between detailed and simple view modes.\"\"\"\r\n- thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ thread_id = self.thread().currentThreadId() if self.thread() else 'N/A' # Get thread ID via instance\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_simple_mode(enabled={enabled}). Current mode: {self._simple_mode}\")\r\n if self._simple_mode != enabled:\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n"
},
{
"date": 1745333343578,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -219,9 +219,9 @@\n log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n- thread_id = QThread.currentThreadId() # Get current thread ID\r\n+ thread_id = QThread.currentThread() # Get current thread object\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] PreviewTableModel.clear_data called.\")\r\n self.set_data([])\r\n \r\n \r\n"
},
{
"date": 1745333360189,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -37,9 +37,9 @@\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n \"\"\"Toggles the model between detailed and simple view modes.\"\"\"\r\n- thread_id = self.thread().currentThreadId() if self.thread() else 'N/A' # Get thread ID via instance\r\n+ thread_id = QThread.currentThread() # Get current thread object\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_simple_mode(enabled={enabled}). Current mode: {self._simple_mode}\")\r\n if self._simple_mode != enabled:\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n"
},
{
"date": 1745333368717,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -191,9 +191,9 @@\n \r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data, extracts simple data, and emits signals.\"\"\"\r\n # Removed diagnostic import here\r\n- thread_id = self.thread().currentThreadId() if self.thread() else 'N/A' # Get thread ID via instance\r\n+ thread_id = QThread.currentThread() # Get current thread object\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_data. Received {len(data)} items.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from beginResetModel(). Processing data...\")\r\n"
},
{
"date": 1745497377758,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -15,8 +15,9 @@\n COL_PREDICTED_ASSET = 1\r\n COL_ORIGINAL_PATH = 2\r\n COL_PREDICTED_OUTPUT = 3 # Kept for internal data access, but hidden in view\r\n COL_DETAILS = 4\r\n+ COL_ADDITIONAL_FILES = 5 # New column for ignored/extra files\r\n \r\n # Define internal data roles for sorting/filtering\r\n ROLE_RAW_STATUS = Qt.ItemDataRole.UserRole + 1\r\n ROLE_SOURCE_ASSET = Qt.ItemDataRole.UserRole + 2\r\n@@ -28,12 +29,13 @@\n super().__init__(parent)\r\n log.debug(\"PreviewTableModel initialized.\")\r\n # Data format: List of dictionaries, each representing a file's details\r\n # Example: {'original_path': '...', 'predicted_asset_name': '...', 'predicted_output_name': '...', 'status': '...', 'details': '...', 'source_asset': '...'}\r\n- self._data = []\r\n+ self._data = [] # Keep the original flat data for reference if needed, but not for display\r\n+ self._table_rows = [] # New structure for displaying rows\r\n self._simple_data = [] # List of unique source asset paths for simple mode\r\n self._simple_mode = False # Flag to toggle between detailed and simple view\r\n- self._headers_detailed = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"]\r\n+ self._headers_detailed = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\", \"Additional Files\"] # Added new column header\r\n self._headers_simple = [\"Input Path\"]\r\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n@@ -56,17 +58,17 @@\n def rowCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of rows in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- row_count = len(self._simple_data) if self._simple_mode else len(self._data)\r\n+ row_count = len(self._simple_data) if self._simple_mode else len(self._table_rows) # Use _table_rows for detailed mode\r\n # log.debug(f\"PreviewTableModel.rowCount called. Mode: {self._simple_mode}, Row Count: {row_count}\")\r\n return row_count\r\n \r\n def columnCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of columns in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n- col_count = len(self._headers_simple) if self._simple_mode else len(self._headers_detailed)\r\n+ col_count = len(self._headers_simple) if self._simple_mode else len(self._headers_detailed) # Use updated headers_detailed\r\n # log.debug(f\"PreviewTableModel.columnCount called. Mode: {self._simple_mode}, Column Count: {col_count}\")\r\n return col_count\r\n \r\n def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):\r\n@@ -91,88 +93,130 @@\n return f\"Input Asset: {source_asset_path}\"\r\n return None\r\n \r\n # --- Detailed Mode ---\r\n- if row >= len(self._data):\r\n- # log.warning(f\"data called with out of bounds row in detailed mode: {row}/{len(self._data)}\")\r\n+ if row >= len(self._table_rows): # Use _table_rows\r\n+ # log.warning(f\"data called with out of bounds row in detailed mode: {row}/{len(self._table_rows)}\")\r\n return None # Bounds check\r\n- file_details = self._data[row]\r\n+ row_data = self._table_rows[row] # Get data from the structured row\r\n \r\n # --- Handle Custom Internal Roles ---\r\n+ # These roles are now handled by the proxy model based on the structured data\r\n if role == self.ROLE_RAW_STATUS:\r\n- return file_details.get('status', '[No Status]')\r\n+ # Return status of the main file if it exists, otherwise a placeholder for additional rows\r\n+ main_file = row_data.get('main_file')\r\n+ return main_file.get('status', '[No Status]') if main_file else '[Additional]'\r\n if role == self.ROLE_SOURCE_ASSET:\r\n- return file_details.get('source_asset', 'N/A')\r\n+ return row_data.get('source_asset', 'N/A')\r\n \r\n # --- Handle Display Role ---\r\n if role == Qt.ItemDataRole.DisplayRole:\r\n if col == self.COL_STATUS:\r\n- raw_status = file_details.get('status', '[No Status]')\r\n- details = file_details.get('details', '') # Get details for parsing\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ raw_status = main_file.get('status', '[No Status]')\r\n+ details = main_file.get('details', '') # Get details for parsing\r\n \r\n- # Implement status text simplification\r\n- if raw_status == \"Unmatched Extra\":\r\n- # Check details for regex match info\r\n- if details and details.startswith(\"[Unmatched Extra (Regex match:\"):\r\n- try:\r\n- pattern = details.split(\"match: '\")[1].split(\"'\")[0]\r\n- return f\"[Extra={pattern}]\"\r\n- except IndexError:\r\n- return \"Extra\" # Fallback if parsing fails\r\n+ # Implement status text simplification\r\n+ if raw_status == \"Unmatched Extra\":\r\n+ if details and details.startswith(\"[Unmatched Extra (Regex match:\"):\r\n+ try:\r\n+ pattern = details.split(\"match: '\")[1].split(\"'\")[0]\r\n+ return f\"[Extra={pattern}]\"\r\n+ except IndexError:\r\n+ return \"Extra\" # Fallback if parsing fails\r\n+ else:\r\n+ return \"Extra\"\r\n+ elif raw_status == \"Ignored\" and details and \"Superseed by 16bit variant for\" in details:\r\n+ try:\r\n+ filename = details.split(\"Superseed by 16bit variant for \")[1]\r\n+ return f\"Superseeded by 16bit {filename}\"\r\n+ except IndexError:\r\n+ return raw_status # Fallback if parsing fails\r\n else:\r\n- return \"Extra\"\r\n- elif raw_status == \"Ignored\" and details and \"Superseed by 16bit variant for\" in details:\r\n- try:\r\n- # Extract filename after \"for \"\r\n- filename = details.split(\"Superseed by 16bit variant for \")[1]\r\n- return f\"Superseeded by 16bit {filename}\"\r\n- except IndexError:\r\n- return raw_status # Fallback if parsing fails\r\n- # Add other specific simplifications here if needed\r\n+ return raw_status # Return original status if no simplification applies\r\n else:\r\n- return raw_status # Return original status if no simplification applies\r\n+ return \"\" # Empty for additional-only rows\r\n \r\n elif col == self.COL_PREDICTED_ASSET:\r\n- return file_details.get('predicted_asset_name', 'N/A')\r\n+ main_file = row_data.get('main_file')\r\n+ return main_file.get('predicted_asset_name', 'N/A') if main_file else \"\"\r\n elif col == self.COL_ORIGINAL_PATH:\r\n- return file_details.get('original_path', '[Missing Path]')\r\n+ main_file = row_data.get('main_file')\r\n+ return main_file.get('original_path', '[Missing Path]') if main_file else \"\"\r\n elif col == self.COL_PREDICTED_OUTPUT:\r\n- return file_details.get('predicted_output_name', '')\r\n+ main_file = row_data.get('main_file')\r\n+ return main_file.get('predicted_output_name', '') if main_file else \"\"\r\n elif col == self.COL_DETAILS:\r\n- return file_details.get('details', '')\r\n+ main_file = row_data.get('main_file')\r\n+ return main_file.get('details', '') if main_file else \"\"\r\n+ elif col == self.COL_ADDITIONAL_FILES:\r\n+ return row_data.get('additional_file_path', '')\r\n return None # Should not happen with defined columns\r\n \r\n # --- Handle Tooltip Role ---\r\n if role == Qt.ItemDataRole.ToolTipRole:\r\n if col == self.COL_ORIGINAL_PATH:\r\n- source_asset = file_details.get('source_asset', 'N/A')\r\n- original_path = file_details.get('original_path', '[Missing Path]')\r\n- return f\"Source Asset: {source_asset}\\nFull Path: {original_path}\"\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ source_asset = row_data.get('source_asset', 'N/A')\r\n+ original_path = main_file.get('original_path', '[Missing Path]')\r\n+ return f\"Source Asset: {source_asset}\\nFull Path: {original_path}\"\r\n+ else:\r\n+ return \"\" # No tooltip for empty cells\r\n elif col == self.COL_STATUS:\r\n- return file_details.get('details', file_details.get('status', '[No Status]'))\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ return main_file.get('details', main_file.get('status', '[No Status]'))\r\n+ else:\r\n+ return \"\" # No tooltip for empty cells\r\n elif col == self.COL_PREDICTED_ASSET:\r\n- predicted_asset_name = file_details.get('predicted_asset_name', 'None')\r\n- return f\"Predicted Asset Name: {predicted_asset_name}\"\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ predicted_asset_name = main_file.get('predicted_asset_name', 'None')\r\n+ return f\"Predicted Asset Name: {predicted_asset_name}\"\r\n+ else:\r\n+ return \"\" # No tooltip for empty cells\r\n elif col == self.COL_PREDICTED_OUTPUT:\r\n- predicted_output_name = file_details.get('predicted_output_name', 'None')\r\n- return f\"Predicted Output Name: {predicted_output_name}\"\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ predicted_output_name = main_file.get('predicted_output_name', 'None')\r\n+ return f\"Predicted Output Name: {predicted_output_name}\"\r\n+ else:\r\n+ return \"\" # No tooltip for empty cells\r\n elif col == self.COL_DETAILS:\r\n- return file_details.get('details', '')\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ return main_file.get('details', '')\r\n+ else:\r\n+ return \"\" # No tooltip for empty cells\r\n+ elif col == self.COL_ADDITIONAL_FILES:\r\n+ additional_file = row_data.get('additional_file_details')\r\n+ if additional_file:\r\n+ status = additional_file.get('status', '[No Status]')\r\n+ details = additional_file.get('details', '')\r\n+ return f\"Status: {status}\\nDetails: {details}\"\r\n+ else:\r\n+ return \"\" # No tooltip if no additional file in this cell\r\n return None\r\n \r\n # --- Handle Foreground (Text Color) Role ---\r\n if role == Qt.ItemDataRole.ForegroundRole:\r\n- status = file_details.get('status', '[No Status]')\r\n- color = None\r\n- if status == \"Mapped\": color = QColor(\"#9dd9db\")\r\n- elif status == \"Ignored\": color = QColor(\"#c1753d\")\r\n- elif status == \"Extra\": color = QColor(\"#cfdca4\")\r\n- elif status == \"Unrecognised\": color = QColor(\"#92371f\")\r\n- elif status == \"Model\": color = QColor(\"#a4b8dc\")\r\n- elif status == \"Unmatched Extra\": color = QColor(\"#777777\") # Grey for unmatched\r\n- elif status == \"Error\": color = QColor(Qt.GlobalColor.red)\r\n- if color:\r\n- return color\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ status = main_file.get('status', '[No Status]')\r\n+ color = None\r\n+ if status == \"Mapped\": color = QColor(\"#9dd9db\")\r\n+ elif status == \"Ignored\": color = QColor(\"#c1753d\") # This case should not happen with new logic, but keep for safety\r\n+ elif status == \"Extra\": color = QColor(\"#cfdca4\") # This case should not happen with new logic, but keep for safety\r\n+ elif status == \"Unrecognised\": color = QColor(\"#92371f\") # This case should not happen with new logic, but keep for safety\r\n+ elif status == \"Model\": color = QColor(\"#a4b8dc\")\r\n+ elif status == \"Unmatched Extra\": color = QColor(\"#777777\") # This case should not happen with new logic, but keep for safety\r\n+ elif status == \"Error\": color = QColor(Qt.GlobalColor.red)\r\n+ if color:\r\n+ return color\r\n+ # Neutral styling for additional files column and additional-only rows\r\n+ return None # Use default text color\r\n \r\n # --- Handle Background Role (Optional, disabled in main_window for now) ---\r\n # if role == Qt.ItemDataRole.BackgroundRole:\r\n # # This would require passing asset grouping info or calculating it here\r\n@@ -196,23 +240,60 @@\n log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered PreviewTableModel.set_data. Received {len(data)} items.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling beginResetModel()...\")\r\n self.beginResetModel()\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from beginResetModel(). Processing data...\")\r\n- self._data = data or []\r\n- # Extract unique source asset paths for simple mode\r\n+ self._data = data or [] # Keep original data for reference if needed\r\n+ self._table_rows = [] # Clear previous structured data\r\n+\r\n+ # Group files by source asset\r\n+ grouped_data = {}\r\n unique_sources = set()\r\n- # Check if data is a list of strings (simple paths) or list of dicts (detailed)\r\n- if data and isinstance(data[0], str):\r\n- log.debug(\"set_data received list of strings (simple paths).\")\r\n- self._simple_data = sorted(list(set(data))) # Ensure uniqueness and sort\r\n- else:\r\n- log.debug(\"set_data received list of dicts (detailed data). Extracting simple paths.\")\r\n- for item in self._data:\r\n- source = item.get('source_asset')\r\n- if source:\r\n- unique_sources.add(source)\r\n- self._simple_data = sorted(list(unique_sources))\r\n+ if data and isinstance(data[0], dict): # Ensure data is in detailed format\r\n+ for file_details in data:\r\n+ source_asset = file_details.get('source_asset')\r\n+ if source_asset:\r\n+ if source_asset not in grouped_data:\r\n+ grouped_data[source_asset] = {'main_files': [], 'additional_files': []}\r\n+ unique_sources.add(source_asset)\r\n \r\n+ status = file_details.get('status')\r\n+ # Separate into main and additional files based on status\r\n+ if status in [\"Mapped\", \"Model\", \"Error\"]:\r\n+ grouped_data[source_asset]['main_files'].append(file_details)\r\n+ else: # Ignored, Extra, Unrecognised, Unmatched Extra\r\n+ grouped_data[source_asset]['additional_files'].append(file_details)\r\n+\r\n+ # Sort main and additional files within each group (e.g., by original_path)\r\n+ for asset_data in grouped_data.values():\r\n+ asset_data['main_files'].sort(key=lambda x: x.get('original_path', ''))\r\n+ asset_data['additional_files'].sort(key=lambda x: x.get('original_path', '')) # Sort additional by their path\r\n+\r\n+ # Build the _table_rows structure\r\n+ sorted_assets = sorted(list(unique_sources)) # Sort assets alphabetically\r\n+ for asset_name in sorted_assets:\r\n+ asset_data = grouped_data[asset_name]\r\n+ main_files = asset_data['main_files']\r\n+ additional_files = asset_data['additional_files']\r\n+ max_rows = max(len(main_files), len(additional_files))\r\n+\r\n+ for i in range(max_rows):\r\n+ main_file = main_files[i] if i < len(main_files) else None\r\n+ additional_file = additional_files[i] if i < len(additional_files) else None\r\n+\r\n+ row_data = {\r\n+ 'source_asset': asset_name,\r\n+ 'main_file': main_file, # Store the full dict for easy access\r\n+ 'additional_file_path': additional_file.get('original_path', '') if additional_file else '',\r\n+ 'additional_file_details': additional_file, # Store full dict for tooltip\r\n+ 'is_main_row': main_file is not None # True if this row has a main file\r\n+ }\r\n+ self._table_rows.append(row_data)\r\n+\r\n+ # Extract unique source asset paths for simple mode (still needed)\r\n+ self._simple_data = sorted(list(unique_sources))\r\n+\r\n+\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n self.endResetModel()\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n@@ -275,43 +356,58 @@\n \r\n \r\n # --- Detailed Mode Sorting ---\r\n # log.debug(\"ProxyModel.lessThan (Detailed Mode).\")\r\n+ # Get the full row data from the source model's _table_rows\r\n+ left_row_data = model._table_rows[left.row()]\r\n+ right_row_data = model._table_rows[right.row()]\r\n+\r\n # --- Level 1: Sort by Source Asset ---\r\n- left_asset = model.data(left.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n- right_asset = model.data(right.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n+ left_asset = left_row_data.get('source_asset', 'N/A')\r\n+ right_asset = right_row_data.get('source_asset', 'N/A')\r\n \r\n if left_asset != right_asset:\r\n # Handle None/empty strings for consistent sorting\r\n- if not left_asset: return True # Empty asset comes first\r\n- if not right_asset: return False # Non-empty asset comes first\r\n+ if not left_asset or left_asset == 'N/A': return True # Empty asset comes first\r\n+ if not right_asset or right_asset == 'N/A': return False # Non-empty asset comes first\r\n return left_asset < right_asset # Alphabetical sort for assets\r\n \r\n- # --- Level 2: Sort by Status (Custom Priority) ---\r\n- left_status = model.data(left.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n- right_status = model.data(right.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n+ # --- Level 2: Sort by Row Type (Main vs Additional-only) ---\r\n+ # Main rows (is_main_row == True) should come before additional-only rows\r\n+ left_is_main = left_row_data.get('is_main_row', False)\r\n+ right_is_main = right_row_data.get('is_main_row', False)\r\n \r\n- # Get the priority from the dictionary (default to low priority if status not found)\r\n- left_prio = self.STATUS_PRIORITY.get(left_status, 99)\r\n- right_prio = self.STATUS_PRIORITY.get(right_status, 99)\r\n+ if left_is_main != right_is_main:\r\n+ return left_is_main > right_is_main # True > False\r\n \r\n- if left_prio != right_prio:\r\n- return left_prio < right_prio # Sort based on priority\r\n+ # --- Level 3: Sort within the row type ---\r\n+ if left_is_main: # Both are main rows\r\n+ # Sort by Original Path (Alphabetical)\r\n+ left_path = left_row_data.get('main_file', {}).get('original_path', '')\r\n+ right_path = right_row_data.get('main_file', {}).get('original_path', '')\r\n\\ No newline at end of file\n \r\n- # --- Level 3: Sort by Original Path (Alphabetical, only if assets and status priorities are equal) ---\r\n- left_path = model.data(left.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n- right_path = model.data(right.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n+ if not left_path: return True\r\n+ if not right_path: return False\r\n+ return left_path < right_path\r\n \r\n- # Handle None/empty strings\r\n- if not left_path: return True\r\n- if not right_path: return False\r\n+ else: # Both are additional-only rows\r\n+ # Sort by Additional File Path (Alphabetical)\r\n+ left_additional_path = left_row_data.get('additional_file_path', '')\r\n+ right_additional_path = right_row_data.get('additional_file_path', '')\r\n \r\n- return left_path < right_path # Alphabetical sort for paths\r\n+ if not left_additional_path: return True\r\n+ if not right_additional_path: return False\r\n+ return left_additional_path < right_additional_path\r\n \r\n+ # Should not reach here if logic is correct, but include a fallback\r\n+ return super().lessThan(left, right)\r\n+\r\n # Override sort method to ensure custom sorting is used\r\n def sort(self, column: int, order: Qt.SortOrder = Qt.SortOrder.AscendingOrder):\r\n # We ignore the column and order here and rely on lessThan for multi-level sort\r\n # However, calling this method is necessary to trigger the proxy model's sorting mechanism.\r\n # We can potentially use the column/order to toggle ascending/descending within each level in lessThan,\r\n # but for now, we'll stick to the defined order.\r\n log.debug(f\"ProxyModel.sort called with column {column}, order {order}. Triggering lessThan.\")\r\n- super().sort(column, order) # Call base class sort to trigger update\n+ # Call base class sort to trigger update. Pass a valid column, e.g., COL_STATUS,\r\n+ # as the actual sorting logic is in lessThan.\r\n+ super().sort(PreviewTableModel.COL_STATUS, Qt.SortOrder.AscendingOrder)\n\\ No newline at end of file\n"
},
{
"date": 1745498833062,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -4,8 +4,12 @@\n from PySide6.QtGui import QColor\r\n \r\n log = logging.getLogger(__name__) # Get logger\r\n \r\n+# Define colors for alternating asset groups\r\n+COLOR_ASSET_GROUP_1 = QColor(\"#3a3a3a\") # Dark grey 1\r\n+COLOR_ASSET_GROUP_2 = QColor(\"#4a4a4a\") # Dark grey 2\r\n+\r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n Holds detailed file prediction results or a simple list of source assets.\r\n@@ -34,8 +38,9 @@\n self._table_rows = [] # New structure for displaying rows\r\n self._simple_data = [] # List of unique source asset paths for simple mode\r\n self._simple_mode = False # Flag to toggle between detailed and simple view\r\n self._headers_detailed = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\", \"Additional Files\"] # Added new column header\r\n+ self._sorted_unique_assets = [] # Store sorted unique asset names for coloring\r\n self._headers_simple = [\"Input Path\"]\r\n self.set_data(data or []) # Initialize data and simple_data\r\n \r\n def set_simple_mode(self, enabled: bool):\r\n@@ -216,14 +221,26 @@\n return color\r\n # Neutral styling for additional files column and additional-only rows\r\n return None # Use default text color\r\n \r\n- # --- Handle Background Role (Optional, disabled in main_window for now) ---\r\n- # if role == Qt.ItemDataRole.BackgroundRole:\r\n- # # This would require passing asset grouping info or calculating it here\r\n- # # and might conflict with ForegroundRole. Leaving disabled for now.\r\n- # pass\r\n+ # --- Handle Background Role ---\r\n+ if role == Qt.ItemDataRole.BackgroundRole:\r\n+ # Apply alternating background color based on asset group\r\n+ source_asset = row_data.get('source_asset')\r\n+ if source_asset and source_asset in self._sorted_unique_assets:\r\n+ try:\r\n+ asset_index = self._sorted_unique_assets.index(source_asset)\r\n+ if asset_index % 2 == 0:\r\n+ return COLOR_ASSET_GROUP_1\r\n+ else:\r\n+ return COLOR_ASSET_GROUP_2\r\n+ except ValueError:\r\n+ # Should not happen if logic is correct, but handle defensively\r\n+ log.warning(f\"Asset '{source_asset}' not found in _sorted_unique_assets.\")\r\n+ return None # Use default background\r\n+ return None # Use default background for rows without a source asset\r\n \r\n+\r\n return None\r\n \r\n def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the header data for a given section, orientation, and role.\"\"\"\r\n@@ -287,18 +304,19 @@\n 'is_main_row': main_file is not None # True if this row has a main file\r\n }\r\n self._table_rows.append(row_data)\r\n \r\n- # Extract unique source asset paths for simple mode (still needed)\r\n- self._simple_data = sorted(list(unique_sources))\r\n+ # Store sorted unique asset paths for simple mode and coloring\r\n+ self._sorted_unique_assets = sorted(list(unique_sources))\r\n+ self._simple_data = self._sorted_unique_assets # Simple data is just the sorted unique assets\r\n \r\n \r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n- self.endResetModel()\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n+ self.endResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n thread_id = QThread.currentThread() # Get current thread object\r\n"
},
{
"date": 1745498842395,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -305,10 +305,10 @@\n }\r\n self._table_rows.append(row_data)\r\n \r\n # Store sorted unique asset paths for simple mode and coloring\r\n- self._sorted_unique_assets = sorted(list(unique_sources))\r\n- self._simple_data = self._sorted_unique_assets # Simple data is just the sorted unique assets\r\n+ self._sorted_unique_assets = sorted(list(unique_sources))\r\n+ self._simple_data = self._sorted_unique_assets # Simple data is just the sorted unique assets\r\n \r\n \r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n"
},
{
"date": 1745498851319,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -309,14 +309,14 @@\n self._sorted_unique_assets = sorted(list(unique_sources))\r\n self._simple_data = self._sorted_unique_assets # Simple data is just the sorted unique assets\r\n \r\n \r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n- self.endResetModel()\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n- log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Structured data built: {len(self._table_rows)} rows.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Simple data extracted: {len(self._simple_data)} unique sources.\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Calling endResetModel()...\")\r\n+ self.endResetModel()\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] Returned from endResetModel().\")\r\n+ log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting PreviewTableModel.set_data.\")\r\n \r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n thread_id = QThread.currentThread() # Get current thread object\r\n"
},
{
"date": 1745502633059,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -5,10 +5,10 @@\n \r\n log = logging.getLogger(__name__) # Get logger\r\n \r\n # Define colors for alternating asset groups\r\n-COLOR_ASSET_GROUP_1 = QColor(\"#3a3a3a\") # Dark grey 1\r\n-COLOR_ASSET_GROUP_2 = QColor(\"#4a4a4a\") # Dark grey 2\r\n+COLOR_ASSET_GROUP_1 = QColor(\"#292929\") # Dark grey 1\r\n+COLOR_ASSET_GROUP_2 = QColor(\"#343434\") # Dark grey 2\r\n \r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n"
},
{
"date": 1745503403701,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -8,8 +8,20 @@\n # Define colors for alternating asset groups\r\n COLOR_ASSET_GROUP_1 = QColor(\"#292929\") # Dark grey 1\r\n COLOR_ASSET_GROUP_2 = QColor(\"#343434\") # Dark grey 2\r\n \r\n+# Define text colors for statuses\r\n+STATUS_COLORS = {\r\n+ \"Mapped\": QColor(\"#9dd9db\"),\r\n+ \"Ignored\": QColor(\"#c1753d\"),\r\n+ \"Extra\": QColor(\"#cfdca4\"),\r\n+ \"Unrecognised\": QColor(\"#92371f\"),\r\n+ \"Model\": QColor(\"#a4b8dc\"),\r\n+ \"Unmatched Extra\": QColor(\"#777777\"),\r\n+ \"Error\": QColor(Qt.GlobalColor.red),\r\n+ \"[No Status]\": None # Use default color for no status\r\n+}\r\n+\r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n Holds detailed file prediction results or a simple list of source assets.\r\n@@ -205,24 +217,34 @@\n return None\r\n \r\n # --- Handle Foreground (Text Color) Role ---\r\n if role == Qt.ItemDataRole.ForegroundRole:\r\n- main_file = row_data.get('main_file')\r\n- if main_file:\r\n- status = main_file.get('status', '[No Status]')\r\n- color = None\r\n- if status == \"Mapped\": color = QColor(\"#9dd9db\")\r\n- elif status == \"Ignored\": color = QColor(\"#c1753d\") # This case should not happen with new logic, but keep for safety\r\n- elif status == \"Extra\": color = QColor(\"#cfdca4\") # This case should not happen with new logic, but keep for safety\r\n- elif status == \"Unrecognised\": color = QColor(\"#92371f\") # This case should not happen with new logic, but keep for safety\r\n- elif status == \"Model\": color = QColor(\"#a4b8dc\")\r\n- elif status == \"Unmatched Extra\": color = QColor(\"#777777\") # This case should not happen with new logic, but keep for safety\r\n- elif status == \"Error\": color = QColor(Qt.GlobalColor.red)\r\n- if color:\r\n- return color\r\n- # Neutral styling for additional files column and additional-only rows\r\n- return None # Use default text color\r\n+ row_data = self._table_rows[row] # Get data from the structured row\r\n+ status = None\r\n+ color = None\r\n \r\n+ if col == self.COL_STATUS:\r\n+ # Apply color based on main_file status\r\n+ main_file = row_data.get('main_file')\r\n+ if main_file:\r\n+ status = main_file.get('status', '[No Status]')\r\n+ elif col == self.COL_ADDITIONAL_FILES:\r\n+ # Apply color based on additional_file status\r\n+ additional_file = row_data.get('additional_file_details')\r\n+ if additional_file:\r\n+ status = additional_file.get('status', '[No Status]')\r\n+\r\n+ # Look up color based on determined status\r\n+ if status in self.STATUS_COLORS:\r\n+ color = self.STATUS_COLORS[status]\r\n+ # If status is not in the dictionary or is '[No Status]', color will be None,\r\n+ # which results in the default text color being used.\r\n+\r\n+ if color:\r\n+ return color\r\n+ else:\r\n+ return None # Use default text color for other columns or if no specific status color\r\n+\r\n # --- Handle Background Role ---\r\n if role == Qt.ItemDataRole.BackgroundRole:\r\n # Apply alternating background color based on asset group\r\n source_asset = row_data.get('source_asset')\r\n"
},
{
"date": 1745503475048,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -9,24 +9,25 @@\n COLOR_ASSET_GROUP_1 = QColor(\"#292929\") # Dark grey 1\r\n COLOR_ASSET_GROUP_2 = QColor(\"#343434\") # Dark grey 2\r\n \r\n # Define text colors for statuses\r\n-STATUS_COLORS = {\r\n- \"Mapped\": QColor(\"#9dd9db\"),\r\n- \"Ignored\": QColor(\"#c1753d\"),\r\n- \"Extra\": QColor(\"#cfdca4\"),\r\n- \"Unrecognised\": QColor(\"#92371f\"),\r\n- \"Model\": QColor(\"#a4b8dc\"),\r\n- \"Unmatched Extra\": QColor(\"#777777\"),\r\n- \"Error\": QColor(Qt.GlobalColor.red),\r\n- \"[No Status]\": None # Use default color for no status\r\n-}\r\n-\r\n class PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n Holds detailed file prediction results or a simple list of source assets.\r\n \"\"\"\r\n+ # Define text colors for statuses\r\n+ STATUS_COLORS = {\r\n+ \"Mapped\": QColor(\"#9dd9db\"),\r\n+ \"Ignored\": QColor(\"#c1753d\"),\r\n+ \"Extra\": QColor(\"#cfdca4\"),\r\n+ \"Unrecognised\": QColor(\"#92371f\"),\r\n+ \"Model\": QColor(\"#a4b8dc\"),\r\n+ \"Unmatched Extra\": QColor(\"#777777\"),\r\n+ \"Error\": QColor(Qt.GlobalColor.red),\r\n+ \"[No Status]\": None # Use default color for no status\r\n+ }\r\n+\r\n # Define column roles for clarity (Detailed Mode)\r\n COL_STATUS = 0\r\n COL_PREDICTED_ASSET = 1\r\n COL_ORIGINAL_PATH = 2\r\n"
},
{
"date": 1745504469131,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -262,8 +262,18 @@\n return None # Use default background\r\n return None # Use default background for rows without a source asset\r\n \r\n \r\n+ # --- Handle Text Alignment Role ---\r\n+ if role == Qt.ItemDataRole.TextAlignmentRole:\r\n+ if col == self.COL_ORIGINAL_PATH:\r\n+ return int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)\r\n+ elif col == self.COL_ADDITIONAL_FILES:\r\n+ return int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)\r\n+ # For other columns, return default alignment (or None)\r\n+ return None\r\n+\r\n+\r\n return None\r\n \r\n def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the header data for a given section, orientation, and role.\"\"\"\r\n"
},
{
"date": 1745504635365,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -272,8 +272,18 @@\n # For other columns, return default alignment (or None)\r\n return None\r\n \r\n \r\n+ # --- Handle Text Alignment Role ---\r\n+ if role == Qt.ItemDataRole.TextAlignmentRole:\r\n+ if col == self.COL_ORIGINAL_PATH:\r\n+ return int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)\r\n+ elif col == self.COL_ADDITIONAL_FILES:\r\n+ return int(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)\r\n+ # For other columns, return default alignment (or None)\r\n+ return None\r\n+\r\n+\r\n return None\r\n \r\n def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the header data for a given section, orientation, and role.\"\"\"\r\n"
},
{
"date": 1745505011158,
"content": "Index: \n===================================================================\n--- \n+++ \n@@ -220,31 +220,26 @@\n # --- Handle Foreground (Text Color) Role ---\r\n if role == Qt.ItemDataRole.ForegroundRole:\r\n row_data = self._table_rows[row] # Get data from the structured row\r\n status = None\r\n- color = None\r\n \r\n- if col == self.COL_STATUS:\r\n- # Apply color based on main_file status\r\n+ # Determine the relevant status based on column and row data\r\n+ if col in [self.COL_STATUS, self.COL_PREDICTED_ASSET, self.COL_ORIGINAL_PATH, self.COL_PREDICTED_OUTPUT, self.COL_DETAILS]:\r\n+ # These columns relate to the main file\r\n main_file = row_data.get('main_file')\r\n if main_file:\r\n status = main_file.get('status', '[No Status]')\r\n elif col == self.COL_ADDITIONAL_FILES:\r\n- # Apply color based on additional_file status\r\n+ # This column relates to the additional file\r\n additional_file = row_data.get('additional_file_details')\r\n if additional_file:\r\n status = additional_file.get('status', '[No Status]')\r\n \r\n # Look up color based on determined status\r\n if status in self.STATUS_COLORS:\r\n- color = self.STATUS_COLORS[status]\r\n- # If status is not in the dictionary or is '[No Status]', color will be None,\r\n- # which results in the default text color being used.\r\n-\r\n- if color:\r\n- return color\r\n+ return self.STATUS_COLORS[status]\r\n else:\r\n- return None # Use default text color for other columns or if no specific status color\r\n+ return None # Use default text color if no specific status color or no relevant file data\r\n \r\n # --- Handle Background Role ---\r\n if role == Qt.ItemDataRole.BackgroundRole:\r\n # Apply alternating background color based on asset group\r\n"
}
],
"date": 1745323013820,
"name": "Commit-0",
"content": "from PySide6.QtCore import QAbstractTableModel, Qt, QModelIndex, QSortFilterProxyModel\r\nfrom PySide6.QtGui import QColor\r\n\r\nclass PreviewTableModel(QAbstractTableModel):\r\n \"\"\"\r\n Custom table model for the GUI preview table.\r\n Holds detailed file prediction results.\r\n \"\"\"\r\n # Define column roles for clarity\r\n COL_STATUS = 0\r\n COL_PREDICTED_ASSET = 1\r\n COL_ORIGINAL_PATH = 2\r\n COL_PREDICTED_OUTPUT = 3\r\n COL_DETAILS = 4\r\n\r\n # Define internal data roles for sorting/filtering\r\n ROLE_RAW_STATUS = Qt.ItemDataRole.UserRole + 1\r\n ROLE_SOURCE_ASSET = Qt.ItemDataRole.UserRole + 2\r\n\r\n def __init__(self, data=None, parent=None):\r\n super().__init__(parent)\r\n # Data format: List of dictionaries, each representing a file's details\r\n # Example: {'original_path': '...', 'predicted_asset_name': '...', 'predicted_output_name': '...', 'status': '...', 'details': '...', 'source_asset': '...'}\r\n self._data = data or []\r\n # Initial columns - will be adjusted later\r\n self._headers = [\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"]\r\n\r\n def rowCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of rows in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n return len(self._data)\r\n\r\n def columnCount(self, parent=QModelIndex()):\r\n \"\"\"Returns the number of columns in the model.\"\"\"\r\n if parent.isValid():\r\n return 0\r\n return len(self._headers)\r\n\r\n def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the data for a given index and role.\"\"\"\r\n if not index.isValid():\r\n return None\r\n\r\n row = index.row()\r\n col = index.column()\r\n file_details = self._data[row]\r\n\r\n # --- Handle Custom Internal Roles ---\r\n if role == self.ROLE_RAW_STATUS:\r\n return file_details.get('status', '[No Status]')\r\n if role == self.ROLE_SOURCE_ASSET:\r\n return file_details.get('source_asset', 'N/A')\r\n\r\n # --- Handle Display Role ---\r\n if role == Qt.ItemDataRole.DisplayRole:\r\n if col == self.COL_STATUS:\r\n # TODO: Implement status text simplification here later\r\n return file_details.get('status', '[No Status]')\r\n elif col == self.COL_PREDICTED_ASSET:\r\n return file_details.get('predicted_asset_name', 'N/A')\r\n elif col == self.COL_ORIGINAL_PATH:\r\n return file_details.get('original_path', '[Missing Path]')\r\n elif col == self.COL_PREDICTED_OUTPUT:\r\n return file_details.get('predicted_output_name', '')\r\n elif col == self.COL_DETAILS:\r\n return file_details.get('details', '')\r\n return None # Should not happen with defined columns\r\n\r\n # --- Handle Tooltip Role ---\r\n if role == Qt.ItemDataRole.ToolTipRole:\r\n if col == self.COL_ORIGINAL_PATH:\r\n source_asset = file_details.get('source_asset', 'N/A')\r\n original_path = file_details.get('original_path', '[Missing Path]')\r\n return f\"Source Asset: {source_asset}\\nFull Path: {original_path}\"\r\n elif col == self.COL_STATUS:\r\n return file_details.get('details', file_details.get('status', '[No Status]'))\r\n elif col == self.COL_PREDICTED_ASSET:\r\n predicted_asset_name = file_details.get('predicted_asset_name', 'None')\r\n return f\"Predicted Asset Name: {predicted_asset_name}\"\r\n elif col == self.COL_PREDICTED_OUTPUT:\r\n predicted_output_name = file_details.get('predicted_output_name', 'None')\r\n return f\"Predicted Output Name: {predicted_output_name}\"\r\n elif col == self.COL_DETAILS:\r\n return file_details.get('details', '')\r\n return None\r\n\r\n # --- Handle Foreground (Text Color) Role ---\r\n if role == Qt.ItemDataRole.ForegroundRole:\r\n status = file_details.get('status', '[No Status]')\r\n color = None\r\n if status == \"Mapped\": color = QColor(\"#9dd9db\")\r\n elif status == \"Ignored\": color = QColor(\"#c1753d\")\r\n elif status == \"Extra\": color = QColor(\"#cfdca4\")\r\n elif status == \"Unrecognised\": color = QColor(\"#92371f\")\r\n elif status == \"Model\": color = QColor(\"#a4b8dc\")\r\n elif status == \"Unmatched Extra\": color = QColor(\"#777777\") # Grey for unmatched\r\n elif status == \"Error\": color = QColor(Qt.GlobalColor.red)\r\n if color:\r\n return color\r\n\r\n # --- Handle Background Role (Optional, disabled in main_window for now) ---\r\n # if role == Qt.ItemDataRole.BackgroundRole:\r\n # # This would require passing asset grouping info or calculating it here\r\n # # and might conflict with ForegroundRole. Leaving disabled for now.\r\n # pass\r\n\r\n return None\r\n\r\n def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):\r\n \"\"\"Returns the header data for a given section, orientation, and role.\"\"\"\r\n if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:\r\n if 0 <= section < len(self._headers):\r\n return self._headers[section]\r\n return None\r\n\r\n def set_data(self, data: list):\r\n \"\"\"Sets the model's data and emits signals to update views.\"\"\"\r\n self.beginResetModel()\r\n self._data = data\r\n self.endResetModel()\r\n\r\n def clear_data(self):\r\n \"\"\"Clears the model's data.\"\"\"\r\n self.set_data([])\r\n\r\n\r\nclass PreviewSortFilterProxyModel(QSortFilterProxyModel):\r\n \"\"\"\r\n Custom proxy model for sorting the preview table.\r\n Implements multi-level sorting and custom status order.\r\n \"\"\"\r\n # Define the desired status order for sorting\r\n # Error > Mapped > Model > Ignored > Extra\r\n STATUS_ORDER = [\"Error\", \"Mapped\", \"Model\", \"Ignored\", \"Extra\", \"Unrecognised\", \"Unmatched Extra\", \"[No Status]\"] # Include others for completeness\r\n\r\n def __init__(self, parent=None):\r\n super().__init__(parent)\r\n # Set default sort column and order (Status column, Ascending)\r\n # This will be overridden by the custom lessThan logic\r\n self.setSortRole(PreviewTableModel.ROLE_RAW_STATUS) # Sort using the raw status role\r\n self.sort(PreviewTableModel.COL_STATUS, Qt.SortOrder.AscendingOrder) # Apply initial sort\r\n\r\n def lessThan(self, left: QModelIndex, right: QModelIndex):\r\n \"\"\"\r\n Custom comparison logic for multi-level sorting.\r\n Sorts by:\r\n 1. Source Asset (Ascending)\r\n 2. Status (Custom Order: Error > Mapped/Model > Ignored > Extra)\r\n 3. Original Path (Ascending)\r\n \"\"\"\r\n model = self.sourceModel()\r\n if not model:\r\n return super().lessThan(left, right) # Fallback if no source model\r\n\r\n # --- Level 1: Sort by Source Asset ---\r\n left_asset = model.data(left.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n right_asset = model.data(right.siblingAtColumn(model.COL_PREDICTED_ASSET), model.ROLE_SOURCE_ASSET)\r\n\r\n if left_asset != right_asset:\r\n # Handle None/empty strings for consistent sorting\r\n if not left_asset: return True # Empty asset comes first\r\n if not right_asset: return False # Non-empty asset comes first\r\n return left_asset < right_asset # Alphabetical sort for assets\r\n\r\n # --- Level 2: Sort by Status (Custom Order) ---\r\n left_status = model.data(left.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n right_status = model.data(right.siblingAtColumn(model.COL_STATUS), model.ROLE_RAW_STATUS)\r\n\r\n # Get the index of the status in the defined order\r\n left_status_index = self.STATUS_ORDER.index(left_status) if left_status in self.STATUS_ORDER else len(self.STATUS_ORDER)\r\n right_status_index = self.STATUS_ORDER.index(right_status) if right_status in self.STATUS_ORDER else len(self.STATUS_ORDER)\r\n\r\n if left_status_index != right_status_index:\r\n return left_status_index < right_status_index # Sort based on custom order index\r\n\r\n # --- Level 3: Sort by Original Path (Alphabetical) ---\r\n left_path = model.data(left.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n right_path = model.data(right.siblingAtColumn(model.COL_ORIGINAL_PATH), Qt.ItemDataRole.DisplayRole)\r\n\r\n # Handle None/empty strings\r\n if not left_path: return True\r\n if not right_path: return False\r\n\r\n return left_path < right_path # Alphabetical sort for paths\r\n\r\n # Override sort method to ensure custom sorting is used\r\n def sort(self, column: int, order: Qt.SortOrder = Qt.SortOrder.AscendingOrder):\r\n # We ignore the column and order here and rely on lessThan for multi-level sort\r\n # However, calling this method is necessary to trigger the proxy model's sorting mechanism.\r\n # We can potentially use the column/order to toggle ascending/descending within each level in lessThan,\r\n # but for now, we'll stick to the defined order.\r\n super().sort(column, order) # Call base class sort to trigger update"
}
]
}