{
    "sourceFile": "gui/main_window.py",
    "activeCommit": 0,
    "commits": [
        {
            "activePatchIndex": 43,
            "patches": [
                {
                    "date": 1745236085615,
                    "content": "Index: \n===================================================================\n--- \n+++ \n"
                },
                {
                    "date": 1745236299610,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -605,13 +605,19 @@\n \r\n \r\n     # --- Preview Update Method ---\r\n     def update_preview(self):\r\n-        log.debug(f\"[{time.time():.4f}] update_preview triggered.\")\r\n+        # <<< ADDED LOG >>>\r\n+        log.info(f\"--> Entered update_preview. Checkbox exists: {hasattr(self, 'disable_preview_checkbox')}\")\r\n+        if hasattr(self, 'disable_preview_checkbox'):\r\n+            log.info(f\"    Checkbox checked: {self.disable_preview_checkbox.isChecked()}\")\r\n+        # <<< END ADDED LOG >>>\r\n \r\n         # --- Handle Preview Toggle ---\r\n         if hasattr(self, 'disable_preview_checkbox') and self.disable_preview_checkbox.isChecked():\r\n-            log.info(\"Detailed preview disabled. Showing simple input list.\")\r\n+            # <<< MODIFIED LOG >>>\r\n+            log.info(\"    Preview Toggle Check: DETAILED PREVIEW DISABLED. Preparing simple list view.\")\r\n+            # <<< END MODIFIED LOG >>>\r\n             self.preview_table.setSortingEnabled(False)\r\n             self.preview_table.setRowCount(0) # Clear previous\r\n             self.preview_table.setColumnCount(1)\r\n             self.preview_table.setHorizontalHeaderLabels([\"Input Path\"])\r\n@@ -632,9 +638,11 @@\n             return # Stop here, do not run PredictionHandler\r\n         # --- End Preview Toggle Handling ---\r\n \r\n         # --- Proceed with Detailed Preview ---\r\n-        log.debug(\"Detailed preview enabled. Proceeding with prediction handler.\")\r\n+        # <<< ADDED LOG >>>\r\n+        log.info(\"    Preview Toggle Check: DETAILED PREVIEW ENABLED. Preparing for prediction handler.\")\r\n+        # <<< END ADDED LOG >>>\r\n         # Reset table headers for detailed view\r\n         self.preview_table.setColumnCount(4)\r\n         self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n         self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n"
                },
                {
                    "date": 1745236975603,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -10,12 +10,13 @@\n     QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, # Added QSplitter\r\n     QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView,\r\n     QProgressBar, QLabel, QFrame, QCheckBox, QSpinBox, QListWidget, QTextEdit, # Added QListWidget, QTextEdit\r\n     QLineEdit, QMessageBox, QFileDialog, QInputDialog, QListWidgetItem, QTabWidget, # Added more widgets\r\n-    QFormLayout, QGroupBox, QAbstractItemView, QSizePolicy # Added more layout/widget items\r\n+    QFormLayout, QGroupBox, QAbstractItemView, QSizePolicy, # Added more layout/widget items\r\n+    QMenuBar, QMenu # Added for menu\r\n )\r\n-from PySide6.QtCore import Qt, QThread, Slot, Signal # Added Signal\r\n-from PySide6.QtGui import QColor # Add QColor import\r\n+from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject # Added Signal, QObject\r\n+from PySide6.QtGui import QColor, QAction # Add QColor import, QAction\r\n \r\n # --- Backend Imports ---\r\n script_dir = Path(__file__).parent\r\n project_root = script_dir.parent\r\n@@ -49,8 +50,31 @@\n      # Set level back to INFO for normal operation\r\n      logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') # Reverted level and format\r\n \r\n \r\n+# --- Custom Log Handler ---\r\n+class QtLogHandler(logging.Handler, QObject):\r\n+    \"\"\"\r\n+    Custom logging handler that emits a Qt signal for each log record.\r\n+    Inherits from QObject to support signals.\r\n+    \"\"\"\r\n+    log_record_received = Signal(str) # Signal emitting the formatted log string\r\n+\r\n+    def __init__(self, parent=None):\r\n+        logging.Handler.__init__(self)\r\n+        QObject.__init__(self, parent) # Initialize QObject part\r\n+\r\n+    def emit(self, record):\r\n+        \"\"\"\r\n+        Overrides the default emit method to format the record and emit a signal.\r\n+        \"\"\"\r\n+        try:\r\n+            msg = self.format(record)\r\n+            self.log_record_received.emit(msg)\r\n+        except Exception:\r\n+            self.handleError(record)\r\n+\r\n+\r\n # --- Helper Functions (from PresetEditorDialog) ---\r\n # NOTE: Consider moving these to a utils file if reused elsewhere\r\n \r\n def setup_list_widget_with_controls(parent_layout, label_text, attribute_name, instance):\r\n@@ -155,16 +179,18 @@\n \r\n         # --- Setup UI Elements for each panel ---\r\n         self.setup_editor_panel_ui()\r\n         self.setup_main_panel_ui()\r\n+        self.setup_menu_bar() # Setup menu bar\r\n \r\n         # --- Status Bar ---\r\n         self.statusBar().showMessage(\"Ready\")\r\n \r\n         # --- Initial State ---\r\n         self._clear_editor() # Clear/disable editor fields initially\r\n         self._set_editor_enabled(False) # Disable editor initially\r\n         self.populate_presets() # Populate both preset list and combo box\r\n+        self.setup_logging_handler() # Setup the custom log handler\r\n \r\n         # --- Connect Editor Signals ---\r\n         self._connect_editor_change_signals()\r\n \r\n@@ -177,8 +203,22 @@\n         \"\"\"Sets up the UI elements for the left preset editor panel.\"\"\"\r\n         editor_layout = QVBoxLayout(self.editor_panel)\r\n         editor_layout.setContentsMargins(5, 5, 5, 5) # Reduce margins\r\n \r\n+        # --- Log Console Output (Initially Hidden) ---\r\n+        self.log_console_widget = QWidget()\r\n+        log_console_layout = QVBoxLayout(self.log_console_widget)\r\n+        log_console_layout.setContentsMargins(0, 0, 0, 5) # Add some bottom margin\r\n+        log_console_label = QLabel(\"Log Console:\")\r\n+        self.log_console_output = QTextEdit()\r\n+        self.log_console_output.setReadOnly(True)\r\n+        self.log_console_output.setMaximumHeight(150) # Limit initial height\r\n+        self.log_console_output.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)\r\n+        log_console_layout.addWidget(log_console_label)\r\n+        log_console_layout.addWidget(self.log_console_output)\r\n+        self.log_console_widget.setVisible(False) # Start hidden\r\n+        editor_layout.addWidget(self.log_console_widget) # Add it at the top\r\n+\r\n         # Preset List and Controls\r\n         list_layout = QVBoxLayout()\r\n         list_layout.addWidget(QLabel(\"Presets:\"))\r\n         self.editor_preset_list = QListWidget()\r\n@@ -366,15 +406,15 @@\n         self.overwrite_checkbox = QCheckBox(\"Overwrite Existing\")\r\n         self.overwrite_checkbox.setToolTip(\"If checked, existing output folders for processed assets will be deleted and replaced.\")\r\n         bottom_controls_layout.addWidget(self.overwrite_checkbox)\r\n \r\n-        self.disable_preview_checkbox = QCheckBox(\"Disable Detailed Preview\")\r\n-        self.disable_preview_checkbox.setToolTip(\"If checked, shows only the list of input assets instead of detailed file predictions.\")\r\n-        self.disable_preview_checkbox.setChecked(False) # Default is detailed preview enabled\r\n-        self.disable_preview_checkbox.toggled.connect(self.update_preview) # Update preview when toggled\r\n-        bottom_controls_layout.addWidget(self.disable_preview_checkbox)\r\n+        # self.disable_preview_checkbox = QCheckBox(\"Disable Detailed Preview\") # REMOVED - Moved to View Menu\r\n+        # self.disable_preview_checkbox.setToolTip(\"If checked, shows only the list of input assets instead of detailed file predictions.\")\r\n+        # self.disable_preview_checkbox.setChecked(False) # Default is detailed preview enabled\r\n+        # self.disable_preview_checkbox.toggled.connect(self.update_preview) # Update preview when toggled\r\n+        # bottom_controls_layout.addWidget(self.disable_preview_checkbox)\r\n \r\n-        bottom_controls_layout.addSpacing(20) # Add some space\r\n+        # bottom_controls_layout.addSpacing(20) # Add some space # REMOVED - No longer needed after checkbox removal\r\n \r\n         self.workers_label = QLabel(\"Workers:\")\r\n         self.workers_spinbox = QSpinBox()\r\n         default_workers = 1\r\n@@ -606,17 +646,17 @@\n \r\n     # --- Preview Update Method ---\r\n     def update_preview(self):\r\n         # <<< ADDED LOG >>>\r\n-        log.info(f\"--> Entered update_preview. Checkbox exists: {hasattr(self, 'disable_preview_checkbox')}\")\r\n-        if hasattr(self, 'disable_preview_checkbox'):\r\n-            log.info(f\"    Checkbox checked: {self.disable_preview_checkbox.isChecked()}\")\r\n+        log.info(f\"--> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n+        if hasattr(self, 'toggle_preview_action'):\r\n+             log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n         # <<< END ADDED LOG >>>\r\n \r\n         # --- Handle Preview Toggle ---\r\n-        if hasattr(self, 'disable_preview_checkbox') and self.disable_preview_checkbox.isChecked():\r\n+        if hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked(): # Check menu action state\r\n             # <<< MODIFIED LOG >>>\r\n-            log.info(\"    Preview Toggle Check: DETAILED PREVIEW DISABLED. Preparing simple list view.\")\r\n+            log.info(\"    Preview Toggle Check: DETAILED PREVIEW DISABLED (via menu). Preparing simple list view.\")\r\n             # <<< END MODIFIED LOG >>>\r\n             self.preview_table.setSortingEnabled(False)\r\n             self.preview_table.setRowCount(0) # Clear previous\r\n             self.preview_table.setColumnCount(1)\r\n@@ -639,9 +679,9 @@\n         # --- End Preview Toggle Handling ---\r\n \r\n         # --- Proceed with Detailed Preview ---\r\n         # <<< ADDED LOG >>>\r\n-        log.info(\"    Preview Toggle Check: DETAILED PREVIEW ENABLED. Preparing for prediction handler.\")\r\n+        log.info(\"    Preview Toggle Check: DETAILED PREVIEW ENABLED (via menu). Preparing for prediction handler.\")\r\n         # <<< END ADDED LOG >>>\r\n         # Reset table headers for detailed view\r\n         self.preview_table.setColumnCount(4)\r\n         self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n@@ -1200,8 +1240,74 @@\n             except Exception as e:\r\n                 log.exception(f\"Error deleting preset file {preset_path}: {e}\")\r\n                 QMessageBox.critical(self, \"Delete Error\", f\"Could not delete preset file:\\n{preset_path}\\n\\nError: {e}\")\r\n \r\n+    # --- Menu Bar Setup ---\r\n+    def setup_menu_bar(self):\r\n+        \"\"\"Creates the main menu bar and View menu.\"\"\"\r\n+        self.menu_bar = self.menuBar()\r\n+        view_menu = self.menu_bar.addMenu(\"&View\")\r\n+\r\n+        # Log Console Action\r\n+        self.toggle_log_action = QAction(\"Show Log Console\", self, checkable=True)\r\n+        self.toggle_log_action.setChecked(False) # Start hidden\r\n+        self.toggle_log_action.toggled.connect(self._toggle_log_console_visibility)\r\n+        view_menu.addAction(self.toggle_log_action)\r\n+\r\n+        # Detailed Preview Action\r\n+        self.toggle_preview_action = QAction(\"Disable Detailed Preview\", self, checkable=True)\r\n+        self.toggle_preview_action.setChecked(False) # Start enabled (detailed view)\r\n+        # Connect to update_preview, which now checks this action's state\r\n+        self.toggle_preview_action.toggled.connect(self.update_preview)\r\n+        view_menu.addAction(self.toggle_preview_action)\r\n+\r\n+    # --- Logging Handler Setup ---\r\n+    def setup_logging_handler(self):\r\n+        \"\"\"Creates and configures the custom QtLogHandler.\"\"\"\r\n+        self.log_handler = QtLogHandler(self)\r\n+        # Set the formatter to match the basicConfig format\r\n+        log_format = '%(levelname)s: %(message)s' # Simpler format for UI console\r\n+        formatter = logging.Formatter(log_format)\r\n+        self.log_handler.setFormatter(formatter)\r\n+        # Set level (e.g., INFO to capture standard messages)\r\n+        self.log_handler.setLevel(logging.INFO)\r\n+        # Add handler to the root logger to capture logs from all modules\r\n+        logging.getLogger().addHandler(self.log_handler)\r\n+        # Connect the signal to the slot\r\n+        self.log_handler.log_record_received.connect(self._append_log_message)\r\n+        log.info(\"UI Log Handler Initialized.\") # Log that the handler is ready\r\n+\r\n+    # --- Slots for Menu Actions and Logging ---\r\n+    @Slot(bool)\r\n+    def _toggle_log_console_visibility(self, checked):\r\n+        \"\"\"Shows or hides the log console widget based on menu action.\"\"\"\r\n+        if hasattr(self, 'log_console_widget'):\r\n+            self.log_console_widget.setVisible(checked)\r\n+            log.debug(f\"Log console visibility set to: {checked}\")\r\n+\r\n+    @Slot(str)\r\n+    def _append_log_message(self, message):\r\n+        \"\"\"Appends a log message to the QTextEdit console.\"\"\"\r\n+        if hasattr(self, 'log_console_output'):\r\n+            # Optional: Add basic coloring (can be expanded)\r\n+            # if message.startswith(\"ERROR\"):\r\n+            #     message = f\"<font color='red'>{message}</font>\"\r\n+            # elif message.startswith(\"WARNING\"):\r\n+            #      message = f\"<font color='orange'>{message}</font>\"\r\n+\r\n+            self.log_console_output.append(message) # Use append for plain text\r\n+            # Optional: Limit history size\r\n+            # MAX_LINES = 500\r\n+            # if self.log_console_output.document().blockCount() > MAX_LINES:\r\n+            #     cursor = self.log_console_output.textCursor()\r\n+            #     cursor.movePosition(QTextCursor.MoveOperation.Start)\r\n+            #     cursor.select(QTextCursor.SelectionType.BlockUnderCursor)\r\n+            #     cursor.removeSelectedText()\r\n+            #     cursor.deletePreviousChar() # Remove the newline potentially left behind\r\n+            # Ensure the view scrolls to the bottom\r\n+            self.log_console_output.verticalScrollBar().setValue(self.log_console_output.verticalScrollBar().maximum())\r\n+\r\n+\r\n     # --- Overridden Close Event ---\r\n     def closeEvent(self, event):\r\n         \"\"\"Overrides close event to check for unsaved changes in the editor.\"\"\"\r\n         if self._check_editor_unsaved_changes():\r\n"
                },
                {
                    "date": 1745262283650,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -400,8 +400,56 @@\n         self.progress_bar.setValue(0)\r\n         self.progress_bar.setTextVisible(True)\r\n         main_layout.addWidget(self.progress_bar)\r\n \r\n+        # --- Blender Integration Controls ---\r\n+        blender_group = QGroupBox(\"Blender Post-Processing\")\r\n+        blender_layout = QVBoxLayout(blender_group)\r\n+\r\n+        self.blender_integration_checkbox = QCheckBox(\"Run Blender Scripts After Processing\")\r\n+        self.blender_integration_checkbox.setToolTip(\"If checked, attempts to run create_nodegroups.py and create_materials.py in Blender.\")\r\n+        blender_layout.addWidget(self.blender_integration_checkbox)\r\n+\r\n+        # Nodegroup Blend Path\r\n+        nodegroup_layout = QHBoxLayout()\r\n+        nodegroup_layout.addWidget(QLabel(\"Nodegroup .blend:\"))\r\n+        self.nodegroup_blend_path_input = QLineEdit()\r\n+        self.browse_nodegroup_blend_button = QPushButton(\"...\")\r\n+        self.browse_nodegroup_blend_button.setFixedWidth(30)\r\n+        self.browse_nodegroup_blend_button.clicked.connect(self._browse_for_nodegroup_blend)\r\n+        nodegroup_layout.addWidget(self.nodegroup_blend_path_input)\r\n+        nodegroup_layout.addWidget(self.browse_nodegroup_blend_button)\r\n+        blender_layout.addLayout(nodegroup_layout)\r\n+\r\n+        # Materials Blend Path\r\n+        materials_layout = QHBoxLayout()\r\n+        materials_layout.addWidget(QLabel(\"Materials .blend:\"))\r\n+        self.materials_blend_path_input = QLineEdit()\r\n+        self.browse_materials_blend_button = QPushButton(\"...\")\r\n+        self.browse_materials_blend_button.setFixedWidth(30)\r\n+        self.browse_materials_blend_button.clicked.connect(self._browse_for_materials_blend)\r\n+        materials_layout.addWidget(self.materials_blend_path_input)\r\n+        materials_layout.addWidget(self.browse_materials_blend_button)\r\n+        blender_layout.addLayout(materials_layout)\r\n+\r\n+        # Initialize paths from config\r\n+        try:\r\n+            default_ng_path = getattr(core_config, 'DEFAULT_NODEGROUP_BLEND_PATH', '')\r\n+            default_mat_path = getattr(core_config, 'DEFAULT_MATERIALS_BLEND_PATH', '')\r\n+            self.nodegroup_blend_path_input.setText(default_ng_path if default_ng_path else \"\")\r\n+            self.materials_blend_path_input.setText(default_mat_path if default_mat_path else \"\")\r\n+        except Exception as e:\r\n+            log.error(f\"Error reading default Blender paths from config: {e}\")\r\n+\r\n+        # Disable Blender controls initially if checkbox is unchecked\r\n+        self.nodegroup_blend_path_input.setEnabled(False)\r\n+        self.browse_nodegroup_blend_button.setEnabled(False)\r\n+        self.materials_blend_path_input.setEnabled(False)\r\n+        self.browse_materials_blend_button.setEnabled(False)\r\n+        self.blender_integration_checkbox.toggled.connect(self._toggle_blender_controls)\r\n+\r\n+        main_layout.addWidget(blender_group) # Add the group box to the main layout\r\n+\r\n         # --- Bottom Controls ---\r\n         bottom_controls_layout = QHBoxLayout()\r\n         self.overwrite_checkbox = QCheckBox(\"Overwrite Existing\")\r\n         self.overwrite_checkbox.setToolTip(\"If checked, existing output folders for processed assets will be deleted and replaced.\")\r\n@@ -604,9 +652,13 @@\n             try: self.processing_thread.started.disconnect()\r\n             except RuntimeError: pass\r\n             self.processing_thread.started.connect(\r\n                 lambda: self.processing_handler.run_processing(\r\n-                    input_paths, selected_preset, output_dir_str, overwrite, num_workers\r\n+                    input_paths, selected_preset, output_dir_str, overwrite, num_workers,\r\n+                    # Pass Blender integration settings\r\n+                    run_blender=self.blender_integration_checkbox.isChecked(),\r\n+                    nodegroup_blend_path=self.nodegroup_blend_path_input.text(),\r\n+                    materials_blend_path=self.materials_blend_path_input.text()\r\n                 )\r\n             )\r\n             self.processing_thread.start()\r\n             log.info(\"Processing thread started.\")\r\n@@ -900,10 +952,47 @@\n         self.drag_drop_area.setEnabled(enabled)\r\n         self.preview_table.setEnabled(enabled)\r\n         # Editor panel controls (should generally be enabled unless processing)\r\n         self.editor_panel.setEnabled(enabled) # Enable/disable the whole panel\r\n+        # Blender controls\r\n+        self.blender_integration_checkbox.setEnabled(enabled)\r\n+        # Only enable path inputs if checkbox is checked AND main controls are enabled\r\n+        blender_paths_enabled = enabled and self.blender_integration_checkbox.isChecked()\r\n+        self.nodegroup_blend_path_input.setEnabled(blender_paths_enabled)\r\n+        self.browse_nodegroup_blend_button.setEnabled(blender_paths_enabled)\r\n+        self.materials_blend_path_input.setEnabled(blender_paths_enabled)\r\n+        self.browse_materials_blend_button.setEnabled(blender_paths_enabled)\r\n \r\n \r\n+    @Slot(bool)\r\n+    def _toggle_blender_controls(self, checked):\r\n+        \"\"\"Enable/disable Blender path inputs based on the checkbox state.\"\"\"\r\n+        self.nodegroup_blend_path_input.setEnabled(checked)\r\n+        self.browse_nodegroup_blend_button.setEnabled(checked)\r\n+        self.materials_blend_path_input.setEnabled(checked)\r\n+        self.browse_materials_blend_button.setEnabled(checked)\r\n+\r\n+    def _browse_for_blend_file(self, line_edit_widget: QLineEdit):\r\n+        \"\"\"Opens a dialog to select a .blend file and updates the line edit.\"\"\"\r\n+        current_path = line_edit_widget.text()\r\n+        start_dir = str(Path(current_path).parent) if current_path and Path(current_path).exists() else str(project_root)\r\n+\r\n+        file_path, _ = QFileDialog.getOpenFileName(\r\n+            self,\r\n+            \"Select Blender File\",\r\n+            start_dir,\r\n+            \"Blender Files (*.blend);;All Files (*)\"\r\n+        )\r\n+        if file_path:\r\n+            line_edit_widget.setText(file_path)\r\n+            log.info(f\"User selected blend file: {file_path}\")\r\n+\r\n+    def _browse_for_nodegroup_blend(self):\r\n+        self._browse_for_blend_file(self.nodegroup_blend_path_input)\r\n+\r\n+    def _browse_for_materials_blend(self):\r\n+        self._browse_for_blend_file(self.materials_blend_path_input)\r\n+\r\n     # --- Preset Editor Methods (Adapted from PresetEditorDialog) ---\r\n \r\n     def _editor_add_list_item(self, list_widget: QListWidget):\r\n         \"\"\"Adds an editable item to the specified list widget in the editor.\"\"\"\r\n"
                },
                {
                    "date": 1745269724128,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -375,8 +375,9 @@\n         drag_drop_layout.addWidget(drag_drop_label)\r\n         self.drag_drop_area.setMinimumHeight(100)\r\n         self.setAcceptDrops(True) # Main window handles drops initially\r\n         main_layout.addWidget(self.drag_drop_area)\r\n+        self.drag_drop_area.setVisible(False) # Hide the specific visual drag/drop area\r\n \r\n         # --- Preview Area (Table) ---\r\n         self.preview_label = QLabel(\"File Preview (using selected processing preset):\")\r\n         self.preview_table = QTableWidget()\r\n@@ -568,10 +569,21 @@\n             else: self.statusBar().showMessage(f\"Input path not found: {p.name}\", 5000); print(f\"Input path not found: {p_str}\")\r\n         if added_count > 0:\r\n             log.info(f\"Added {added_count} new asset paths: {newly_added_paths}\")\r\n             self.statusBar().showMessage(f\"Added {added_count} asset(s). Updating preview...\", 3000)\r\n-            self.update_preview()\r\n \r\n+            # --- Auto-disable detailed preview if > 10 assets ---\r\n+            preview_toggled = False\r\n+            if hasattr(self, 'toggle_preview_action') and len(self.current_asset_paths) > 10:\r\n+                if not self.toggle_preview_action.isChecked(): # Only check it if it's not already checked\r\n+                    log.info(f\"Asset count ({len(self.current_asset_paths)}) > 10. Forcing simple preview.\")\r\n+                    self.toggle_preview_action.setChecked(True) # This will trigger update_preview via its signal\r\n+                    preview_toggled = True\r\n+\r\n+            # Only call update_preview directly if the toggle wasn't triggered\r\n+            if not preview_toggled:\r\n+                self.update_preview()\r\n+\r\n     def _browse_for_output_directory(self):\r\n         \"\"\"Opens a dialog to select the output directory.\"\"\"\r\n         current_path = self.output_path_edit.text()\r\n         if not current_path or not Path(current_path).is_dir():\r\n"
                },
                {
                    "date": 1745314361728,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -668,9 +668,11 @@\n                     input_paths, selected_preset, output_dir_str, overwrite, num_workers,\r\n                     # Pass Blender integration settings\r\n                     run_blender=self.blender_integration_checkbox.isChecked(),\r\n                     nodegroup_blend_path=self.nodegroup_blend_path_input.text(),\r\n-                    materials_blend_path=self.materials_blend_path_input.text()\r\n+                    materials_blend_path=self.materials_blend_path_input.text(),\r\n+                    # Pass verbose setting\r\n+                    verbose=self.toggle_verbose_action.isChecked()\r\n                 )\r\n             )\r\n             self.processing_thread.start()\r\n             log.info(\"Processing thread started.\")\r\n@@ -1360,8 +1362,14 @@\n         # Connect to update_preview, which now checks this action's state\r\n         self.toggle_preview_action.toggled.connect(self.update_preview)\r\n         view_menu.addAction(self.toggle_preview_action)\r\n \r\n+        # Verbose Logging Action\r\n+        self.toggle_verbose_action = QAction(\"Verbose Logging (DEBUG)\", self, checkable=True)\r\n+        self.toggle_verbose_action.setChecked(False) # Start disabled (INFO level)\r\n+        self.toggle_verbose_action.toggled.connect(self._toggle_verbose_logging)\r\n+        view_menu.addAction(self.toggle_verbose_action)\r\n+\r\n     # --- Logging Handler Setup ---\r\n     def setup_logging_handler(self):\r\n         \"\"\"Creates and configures the custom QtLogHandler.\"\"\"\r\n         self.log_handler = QtLogHandler(self)\r\n@@ -1384,8 +1392,23 @@\n         if hasattr(self, 'log_console_widget'):\r\n             self.log_console_widget.setVisible(checked)\r\n             log.debug(f\"Log console visibility set to: {checked}\")\r\n \r\n+    @Slot(bool)\r\n+    def _toggle_verbose_logging(self, checked):\r\n+        \"\"\"Sets the logging level for the root logger and the GUI handler.\"\"\"\r\n+        if not hasattr(self, 'log_handler'):\r\n+            log.error(\"Log handler not initialized, cannot change level.\")\r\n+            return\r\n+\r\n+        new_level = logging.DEBUG if checked else logging.INFO\r\n+        root_logger = logging.getLogger() # Get the root logger\r\n+        root_logger.setLevel(new_level)\r\n+        self.log_handler.setLevel(new_level)\r\n+        log.info(f\"Root and GUI logging level set to: {logging.getLevelName(new_level)}\")\r\n+        # Update status bar or log console to indicate change\r\n+        self.statusBar().showMessage(f\"Logging level set to {logging.getLevelName(new_level)}\", 3000)\r\n+\r\n     @Slot(str)\r\n     def _append_log_message(self, message):\r\n         \"\"\"Appends a log message to the QTextEdit console.\"\"\"\r\n         if hasattr(self, 'log_console_output'):\r\n"
                },
                {
                    "date": 1745317826770,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -860,76 +860,105 @@\n \r\n     # Slot for prediction results (Updated for new format and coloring)\r\n     @Slot(list)\r\n     def on_prediction_results_ready(self, results: list):\r\n-        \"\"\"Populates the preview table with detailed prediction results.\"\"\"\r\n+        \"\"\"Populates the preview table with detailed prediction results, handling multi-asset display.\"\"\"\r\n         log.debug(f\"[{time.time():.4f}] on_prediction_results_ready received {len(results)} file details.\")\r\n         self.preview_table.setSortingEnabled(False) # Disable sorting during population\r\n         self.preview_table.setRowCount(0) # Clear previous results\r\n \r\n+        # Update column headers to include Predicted Asset Name\r\n+        self.preview_table.setColumnCount(5)\r\n+        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"])\r\n+        # Adjust resize modes\r\n+        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n+        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) # Predicted Asset\r\n+        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n+        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Predicted Output\r\n+        self.preview_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents) # Details\r\n+\r\n         self.preview_table.setRowCount(len(results))\r\n+        last_source_asset = None\r\n+        asset_group_color_toggle = False # To alternate background slightly for different assets\r\n+\r\n+        # Sort results primarily by source_asset, then by original_path for grouping\r\n+        results.sort(key=lambda x: (x.get(\"source_asset\", \"\"), x.get(\"original_path\", \"\")))\r\n+\r\n         for row, file_details in enumerate(results):\r\n-            # Ensure file_details is a dictionary before accessing keys\r\n             if not isinstance(file_details, dict):\r\n                 log.warning(f\"Received non-dict item in prediction results: {file_details}\")\r\n-                # Add a row indicating an error (adjust column order)\r\n+                # Add error row (adjust column count)\r\n                 item_status = QTableWidgetItem(\"Error\")\r\n+                item_pred_asset = QTableWidgetItem(\"\")\r\n                 item_orig = QTableWidgetItem(\"[Invalid Data Received]\")\r\n-                item_pred_name = QTableWidgetItem(\"\")\r\n-                item_details_display = QTableWidgetItem(\"Invalid data format from handler\")\r\n+                item_pred_out = QTableWidgetItem(\"\")\r\n+                item_details_display = QTableWidgetItem(\"Invalid data format\")\r\n                 self.preview_table.setItem(row, 0, item_status)\r\n-                self.preview_table.setItem(row, 1, item_orig)\r\n-                self.preview_table.setItem(row, 2, item_pred_name)\r\n-                self.preview_table.setItem(row, 3, item_details_display)\r\n+                self.preview_table.setItem(row, 1, item_pred_asset)\r\n+                self.preview_table.setItem(row, 2, item_orig)\r\n+                self.preview_table.setItem(row, 3, item_pred_out)\r\n+                self.preview_table.setItem(row, 4, item_details_display)\r\n                 continue\r\n \r\n             # Extract data using the new keys\r\n             original_path = file_details.get('original_path', '[Missing Path]')\r\n-            predicted_name = file_details.get('predicted_name', '') # Can be None or empty\r\n+            predicted_asset_name = file_details.get('predicted_asset_name') # Can be None\r\n+            predicted_output_name = file_details.get('predicted_output_name') # Can be None\r\n             status = file_details.get('status', '[No Status]')\r\n-            details = file_details.get('details', '') # Tooltip info\r\n-            source_asset = file_details.get('source_asset', 'N/A') # For tooltip\r\n+            details = file_details.get('details', '')\r\n+            source_asset = file_details.get('source_asset', 'N/A')\r\n \r\n+            # Check if source asset changed for visual grouping\r\n+            if source_asset != last_source_asset:\r\n+                asset_group_color_toggle = not asset_group_color_toggle\r\n+                last_source_asset = source_asset\r\n+\r\n             # Create items\r\n+            item_status = QTableWidgetItem(status)\r\n+            item_pred_asset = QTableWidgetItem(predicted_asset_name if predicted_asset_name else \"N/A\")\r\n             item_orig = QTableWidgetItem(original_path)\r\n-            item_pred_name = QTableWidgetItem(predicted_name if predicted_name else \"\") # Display empty if None\r\n-            item_status = QTableWidgetItem(status)\r\n-            item_details_display = QTableWidgetItem(details if details else \"\") # Show details directly for now\r\n+            item_pred_out = QTableWidgetItem(predicted_output_name if predicted_output_name else \"\")\r\n+            item_details_display = QTableWidgetItem(details if details else \"\")\r\n \r\n             # Set tooltips\r\n             item_orig.setToolTip(f\"Source Asset: {source_asset}\\nFull Path: {original_path}\")\r\n-            item_status.setToolTip(details if details else status) # Use details for status tooltip\r\n+            item_status.setToolTip(details if details else status)\r\n+            item_pred_asset.setToolTip(f\"Predicted Asset Name: {predicted_asset_name if predicted_asset_name else 'None'}\")\r\n+            item_pred_out.setToolTip(f\"Predicted Output Name: {predicted_output_name if predicted_output_name else 'None'}\")\r\n             item_details_display.setToolTip(details if details else \"\")\r\n \r\n             # --- Apply Text Coloring based on Status ---\r\n             color = None\r\n-            if status == \"Mapped\":\r\n-                color = QColor(\"#9dd9db\") # Light blue/teal\r\n-            elif status == \"Ignored\":\r\n-                color = QColor(\"#c1753d\") # Orange/brown\r\n-            elif status == \"Extra\":\r\n-                color = QColor(\"#cfdca4\") # Light green/yellow\r\n-            elif status == \"Unrecognised\":\r\n-                color = QColor(\"#92371f\") # User specified dark red/brown\r\n-            elif status == \"Model\":\r\n-                color = QColor(\"#a4b8dc\") # Light blue/grey (Example)\r\n-            elif status == \"Error\":\r\n-                 color = QColor(Qt.GlobalColor.red) # Example for Error\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 \r\n+            # Apply color to all items in the row\r\n+            items_in_row = [item_status, item_pred_asset, item_orig, item_pred_out, item_details_display]\r\n             if color:\r\n-                item_status.setForeground(color)\r\n-                item_orig.setForeground(color)\r\n-                item_pred_name.setForeground(color)\r\n-                item_details_display.setForeground(color)\r\n-            # --- End Coloring ---\r\n+                for item in items_in_row:\r\n+                    if item: item.setForeground(color)\r\n \r\n+            # --- Apply Subtle Background Shading for Asset Grouping ---\r\n+            # bg_color = QColor(\"#FFFFFF\") # Default white\r\n+            # if asset_group_color_toggle:\r\n+            #     bg_color = QColor(\"#F0F0F0\") # Slightly grey for alternate groups\r\n+            # for item in items_in_row:\r\n+            #      if item: item.setBackground(bg_color)\r\n+            # Note: Background coloring might interfere with text color, disable for now\r\n+\r\n             # Set items in table (new column order)\r\n             self.preview_table.setItem(row, 0, item_status)\r\n-            self.preview_table.setItem(row, 1, item_orig)\r\n-            self.preview_table.setItem(row, 2, item_pred_name)\r\n-            self.preview_table.setItem(row, 3, item_details_display)\r\n+            self.preview_table.setItem(row, 1, item_pred_asset)\r\n+            self.preview_table.setItem(row, 2, item_orig)\r\n+            self.preview_table.setItem(row, 3, item_pred_out)\r\n+            self.preview_table.setItem(row, 4, item_details_display)\r\n \r\n-        self.preview_table.sortItems(1, Qt.SortOrder.AscendingOrder) # Sort by original path (now column 1)\r\n+        # self.preview_table.sortItems(1, Qt.SortOrder.AscendingOrder) # Sorting is done before loop now\r\n         self.preview_table.setSortingEnabled(True) # Re-enable sorting\r\n         log.debug(f\"[{time.time():.4f}] Preview table populated with detailed results and coloring.\")\r\n \r\n \r\n"
                },
                {
                    "date": 1745323149423,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -16,8 +16,11 @@\n )\r\n from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject # Added Signal, QObject\r\n from PySide6.QtGui import QColor, QAction # Add QColor import, QAction\r\n \r\n+# --- GUI Model Imports ---\r\n+from gui.preview_table_model import PreviewTableModel, PreviewSortFilterProxyModel\r\n+\r\n # --- Backend Imports ---\r\n script_dir = Path(__file__).parent\r\n project_root = script_dir.parent\r\n if str(project_root) not in sys.path:\r\n@@ -379,23 +382,51 @@\n         self.drag_drop_area.setVisible(False) # Hide the specific visual drag/drop area\r\n \r\n         # --- Preview Area (Table) ---\r\n         self.preview_label = QLabel(\"File Preview (using selected processing preset):\")\r\n-        self.preview_table = QTableWidget()\r\n-        self.preview_table.setColumnCount(4)\r\n-        # New column order: Status, Original Path, Predicted Name, Details\r\n-        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n-        # Adjust resize modes\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) # Predicted Name\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Details (Slimmer)\r\n-        self.preview_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)\r\n-        self.preview_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)\r\n-        self.preview_table.setAlternatingRowColors(True)\r\n-        self.preview_table.setSortingEnabled(True)\r\n+        self.preview_table = QTableWidget() # Keep QTableWidget for now, will replace with QTableView later\r\n+        # Initialize models\r\n+        self.preview_model = PreviewTableModel()\r\n+        self.preview_proxy_model = PreviewSortFilterProxyModel()\r\n+        self.preview_proxy_model.setSourceModel(self.preview_model)\r\n+\r\n+        # Use the proxy model for the table view\r\n+        # NOTE: QTableWidget is simpler but less flexible with models.\r\n+        # For full model/view benefits (like multi-column sorting via proxy),\r\n+        # we should ideally switch to QTableView. Sticking with QTableWidget for minimal change first.\r\n+        # However, QTableWidget doesn't fully support QSortFilterProxyModel for sorting.\r\n+        # Let's switch to QTableView now for proper model/proxy integration.\r\n+        from PySide6.QtWidgets import QTableView # Import QTableView\r\n+\r\n+        self.preview_table_view = QTableView() # Use QTableView instead of QTableWidget\r\n+        self.preview_table_view.setModel(self.preview_proxy_model) # Set the proxy model\r\n+\r\n+        # Set headers and resize modes using the model's headerData\r\n+        # The model defines the columns and headers\r\n+        header = self.preview_table_view.horizontalHeader()\r\n+        header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n+        header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n+        header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.Stretch)\r\n+        # Remove Predicted Output column as requested\r\n+        # header.setSectionResizeMode(self.preview_model.COL_PREDICTED_OUTPUT, QHeaderView.ResizeMode.ResizeToContents)\r\n+        header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n+\r\n+        # Hide the Predicted Output column\r\n+        self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True)\r\n+\r\n+        # Set selection behavior and alternating colors\r\n+        self.preview_table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)\r\n+        self.preview_table_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)\r\n+        self.preview_table_view.setAlternatingRowColors(True)\r\n+\r\n+        # Enable sorting via header clicks\r\n+        self.preview_table_view.setSortingEnabled(True)\r\n+        # Set default sort column (Status) - the proxy model's lessThan handles the custom order\r\n+        self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n+\r\n+\r\n         main_layout.addWidget(self.preview_label)\r\n-        main_layout.addWidget(self.preview_table, 1) # Allow table to stretch\r\n+        main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n         # --- Progress Bar ---\r\n         self.progress_bar = QProgressBar()\r\n         self.progress_bar.setValue(0)\r\n@@ -703,9 +734,9 @@\n \r\n         if hasattr(self, 'current_asset_paths') and self.current_asset_paths:\r\n             log.info(f\"Clearing asset queue ({len(self.current_asset_paths)} items).\")\r\n             self.current_asset_paths.clear()\r\n-            self.preview_table.setRowCount(0)\r\n+            self.preview_model.clear_data() # Clear the model data\r\n             self.statusBar().showMessage(\"Asset queue cleared.\", 3000)\r\n         else:\r\n             self.statusBar().showMessage(\"Asset queue is already empty.\", 3000)\r\n \r\n"
                },
                {
                    "date": 1745324014707,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -891,109 +891,14 @@\n \r\n     # Slot for prediction results (Updated for new format and coloring)\r\n     @Slot(list)\r\n     def on_prediction_results_ready(self, results: list):\r\n-        \"\"\"Populates the preview table with detailed prediction results, handling multi-asset display.\"\"\"\r\n+        \"\"\"Populates the preview table model with detailed prediction results.\"\"\"\r\n         log.debug(f\"[{time.time():.4f}] on_prediction_results_ready received {len(results)} file details.\")\r\n-        self.preview_table.setSortingEnabled(False) # Disable sorting during population\r\n-        self.preview_table.setRowCount(0) # Clear previous results\r\n+        # Update the model with the new data\r\n+        self.preview_model.set_data(results)\r\n+        log.debug(f\"[{time.time():.4f}] Preview model updated with detailed results.\")\r\n \r\n-        # Update column headers to include Predicted Asset Name\r\n-        self.preview_table.setColumnCount(5)\r\n-        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Predicted Asset\", \"Original Path\", \"Predicted Output\", \"Details\"])\r\n-        # Adjust resize modes\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) # Predicted Asset\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Predicted Output\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents) # Details\r\n-\r\n-        self.preview_table.setRowCount(len(results))\r\n-        last_source_asset = None\r\n-        asset_group_color_toggle = False # To alternate background slightly for different assets\r\n-\r\n-        # Sort results primarily by source_asset, then by original_path for grouping\r\n-        results.sort(key=lambda x: (x.get(\"source_asset\", \"\"), x.get(\"original_path\", \"\")))\r\n-\r\n-        for row, file_details in enumerate(results):\r\n-            if not isinstance(file_details, dict):\r\n-                log.warning(f\"Received non-dict item in prediction results: {file_details}\")\r\n-                # Add error row (adjust column count)\r\n-                item_status = QTableWidgetItem(\"Error\")\r\n-                item_pred_asset = QTableWidgetItem(\"\")\r\n-                item_orig = QTableWidgetItem(\"[Invalid Data Received]\")\r\n-                item_pred_out = QTableWidgetItem(\"\")\r\n-                item_details_display = QTableWidgetItem(\"Invalid data format\")\r\n-                self.preview_table.setItem(row, 0, item_status)\r\n-                self.preview_table.setItem(row, 1, item_pred_asset)\r\n-                self.preview_table.setItem(row, 2, item_orig)\r\n-                self.preview_table.setItem(row, 3, item_pred_out)\r\n-                self.preview_table.setItem(row, 4, item_details_display)\r\n-                continue\r\n-\r\n-            # Extract data using the new keys\r\n-            original_path = file_details.get('original_path', '[Missing Path]')\r\n-            predicted_asset_name = file_details.get('predicted_asset_name') # Can be None\r\n-            predicted_output_name = file_details.get('predicted_output_name') # Can be None\r\n-            status = file_details.get('status', '[No Status]')\r\n-            details = file_details.get('details', '')\r\n-            source_asset = file_details.get('source_asset', 'N/A')\r\n-\r\n-            # Check if source asset changed for visual grouping\r\n-            if source_asset != last_source_asset:\r\n-                asset_group_color_toggle = not asset_group_color_toggle\r\n-                last_source_asset = source_asset\r\n-\r\n-            # Create items\r\n-            item_status = QTableWidgetItem(status)\r\n-            item_pred_asset = QTableWidgetItem(predicted_asset_name if predicted_asset_name else \"N/A\")\r\n-            item_orig = QTableWidgetItem(original_path)\r\n-            item_pred_out = QTableWidgetItem(predicted_output_name if predicted_output_name else \"\")\r\n-            item_details_display = QTableWidgetItem(details if details else \"\")\r\n-\r\n-            # Set tooltips\r\n-            item_orig.setToolTip(f\"Source Asset: {source_asset}\\nFull Path: {original_path}\")\r\n-            item_status.setToolTip(details if details else status)\r\n-            item_pred_asset.setToolTip(f\"Predicted Asset Name: {predicted_asset_name if predicted_asset_name else 'None'}\")\r\n-            item_pred_out.setToolTip(f\"Predicted Output Name: {predicted_output_name if predicted_output_name else 'None'}\")\r\n-            item_details_display.setToolTip(details if details else \"\")\r\n-\r\n-            # --- Apply Text Coloring based on 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-\r\n-            # Apply color to all items in the row\r\n-            items_in_row = [item_status, item_pred_asset, item_orig, item_pred_out, item_details_display]\r\n-            if color:\r\n-                for item in items_in_row:\r\n-                    if item: item.setForeground(color)\r\n-\r\n-            # --- Apply Subtle Background Shading for Asset Grouping ---\r\n-            # bg_color = QColor(\"#FFFFFF\") # Default white\r\n-            # if asset_group_color_toggle:\r\n-            #     bg_color = QColor(\"#F0F0F0\") # Slightly grey for alternate groups\r\n-            # for item in items_in_row:\r\n-            #      if item: item.setBackground(bg_color)\r\n-            # Note: Background coloring might interfere with text color, disable for now\r\n-\r\n-            # Set items in table (new column order)\r\n-            self.preview_table.setItem(row, 0, item_status)\r\n-            self.preview_table.setItem(row, 1, item_pred_asset)\r\n-            self.preview_table.setItem(row, 2, item_orig)\r\n-            self.preview_table.setItem(row, 3, item_pred_out)\r\n-            self.preview_table.setItem(row, 4, item_details_display)\r\n-\r\n-        # self.preview_table.sortItems(1, Qt.SortOrder.AscendingOrder) # Sorting is done before loop now\r\n-        self.preview_table.setSortingEnabled(True) # Re-enable sorting\r\n-        log.debug(f\"[{time.time():.4f}] Preview table populated with detailed results and coloring.\")\r\n-\r\n-\r\n     @Slot()\r\n     def on_prediction_finished(self):\r\n         log.debug(f\"[{time.time():.4f}] Prediction finished signal received.\")\r\n \r\n@@ -1023,9 +928,10 @@\n         self.preset_combo.setEnabled(enabled)\r\n         self.start_button.setEnabled(enabled)\r\n         self.setAcceptDrops(enabled)\r\n         self.drag_drop_area.setEnabled(enabled)\r\n-        self.preview_table.setEnabled(enabled)\r\n+        # self.preview_table.setEnabled(enabled) # This was the old QTableWidget\r\n+        self.preview_table_view.setEnabled(enabled) # Enable/disable the QTableView instead\r\n         # Editor panel controls (should generally be enabled unless processing)\r\n         self.editor_panel.setEnabled(enabled) # Enable/disable the whole panel\r\n         # Blender controls\r\n         self.blender_integration_checkbox.setEnabled(enabled)\r\n"
                },
                {
                    "date": 1745324072991,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -422,8 +422,13 @@\n         self.preview_table_view.setSortingEnabled(True)\r\n         # Set default sort column (Status) - the proxy model's lessThan handles the custom order\r\n         self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n \r\n+        # Move \"Original Path\" column to the far right\r\n+        original_path_logical_index = self.preview_model.COL_ORIGINAL_PATH\r\n+        original_path_visual_index = header.visualIndex(original_path_logical_index)\r\n+        if original_path_visual_index != -1: # Check if the column is visible\r\n+             header.moveSection(original_path_visual_index, header.count() - 1)\r\n \r\n         main_layout.addWidget(self.preview_label)\r\n         main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n"
                },
                {
                    "date": 1745324180112,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -5,8 +5,11 @@\n import time\r\n from pathlib import Path\r\n from functools import partial # For connecting signals with arguments\r\n \r\n+log = logging.getLogger(__name__)\r\n+log.info(f\"sys.path: {sys.path}\")\r\n+\r\n from PySide6.QtWidgets import (\r\n     QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, # Added QSplitter\r\n     QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView,\r\n     QProgressBar, QLabel, QFrame, QCheckBox, QSpinBox, QListWidget, QTextEdit, # Added QListWidget, QTextEdit\r\n"
                },
                {
                    "date": 1745324454266,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -19,8 +19,15 @@\n )\r\n from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject # Added Signal, QObject\r\n from PySide6.QtGui import QColor, QAction # Add QColor import, QAction\r\n \r\n+# --- Debugging sys.path ---\r\n+import sys\r\n+import logging # Ensure logging is imported early\r\n+log = logging.getLogger(__name__) # Get logger early\r\n+log.info(f\"Initial sys.path: {sys.path}\")\r\n+# --- End Debugging ---\r\n+\r\n # --- GUI Model Imports ---\r\n from gui.preview_table_model import PreviewTableModel, PreviewSortFilterProxyModel\r\n \r\n # --- Backend Imports ---\r\n"
                },
                {
                    "date": 1745324581046,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -19,15 +19,8 @@\n )\r\n from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject # Added Signal, QObject\r\n from PySide6.QtGui import QColor, QAction # Add QColor import, QAction\r\n \r\n-# --- Debugging sys.path ---\r\n-import sys\r\n-import logging # Ensure logging is imported early\r\n-log = logging.getLogger(__name__) # Get logger early\r\n-log.info(f\"Initial sys.path: {sys.path}\")\r\n-# --- End Debugging ---\r\n-\r\n # --- GUI Model Imports ---\r\n from gui.preview_table_model import PreviewTableModel, PreviewSortFilterProxyModel\r\n \r\n # --- Backend Imports ---\r\n"
                },
                {
                    "date": 1745326087649,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -335,17 +335,8 @@\n         \"\"\"Sets up the UI elements for the right main processing panel.\"\"\"\r\n         main_layout = QVBoxLayout(self.main_panel)\r\n         main_layout.setContentsMargins(5, 5, 5, 5) # Reduce margins\r\n \r\n-        # --- Top Section: Preset Selection (for processing) ---\r\n-        top_layout = QHBoxLayout()\r\n-        self.preset_label = QLabel(\"Preset (for processing):\")\r\n-        self.preset_combo = QComboBox() # This is for selecting the preset to USE for processing\r\n-        self.preset_combo.currentIndexChanged.connect(self.update_preview) # Update preview when processing preset changes\r\n-        top_layout.addWidget(self.preset_label)\r\n-        top_layout.addWidget(self.preset_combo, 1)\r\n-        main_layout.addLayout(top_layout)\r\n-\r\n         # --- Output Directory Selection ---\r\n         output_layout = QHBoxLayout()\r\n         self.output_dir_label = QLabel(\"Output Directory:\")\r\n         self.output_path_edit = QLineEdit()\r\n@@ -384,9 +375,9 @@\n         main_layout.addWidget(self.drag_drop_area)\r\n         self.drag_drop_area.setVisible(False) # Hide the specific visual drag/drop area\r\n \r\n         # --- Preview Area (Table) ---\r\n-        self.preview_label = QLabel(\"File Preview (using selected processing preset):\")\r\n+        self.preview_label = QLabel(\"File Preview:\") # Updated Label\r\n         self.preview_table = QTableWidget() # Keep QTableWidget for now, will replace with QTableView later\r\n         # Initialize models\r\n         self.preview_model = PreviewTableModel()\r\n         self.preview_proxy_model = PreviewSortFilterProxyModel()\r\n@@ -655,11 +646,14 @@\n         input_paths = list(self.current_asset_paths)\r\n         if not input_paths:\r\n             self.statusBar().showMessage(\"No assets added to process.\", 3000)\r\n             return\r\n-        selected_preset = self.preset_combo.currentText() # Use the processing combo box\r\n+        # --- Get preset from editor list ---\r\n+        current_editor_item = self.editor_preset_list.currentItem()\r\n+        selected_preset = current_editor_item.text() if current_editor_item else None\r\n+        # --- End Get preset ---\r\n         if not selected_preset:\r\n-            self.statusBar().showMessage(\"Please select a preset for processing.\", 3000)\r\n+            self.statusBar().showMessage(\"Please select a preset from the list on the left.\", 3000) # Updated message\r\n             return\r\n         overwrite = self.overwrite_checkbox.isChecked()\r\n         num_workers = self.workers_spinbox.value()\r\n \r\n@@ -802,13 +796,17 @@\n             return\r\n         if PredictionHandler is None:\r\n             self.statusBar().showMessage(\"Error: Prediction components not loaded.\", 5000)\r\n             return\r\n-        selected_preset = self.preset_combo.currentText() # Use the processing combo box\r\n+        # --- Get preset from editor list ---\r\n+        current_editor_item = self.editor_preset_list.currentItem()\r\n+        selected_preset = current_editor_item.text() if current_editor_item else None\r\n+        # --- End Get preset ---\r\n         if not selected_preset:\r\n-            log.debug(\"Update preview called with no processing preset selected.\")\r\n-            self.preview_table.setRowCount(0)\r\n-            self.statusBar().showMessage(\"Select a processing preset to update preview.\", 3000)\r\n+            log.debug(\"Update preview called with no preset selected in the editor list.\")\r\n+            # self.preview_table.setRowCount(0) # Clearing is handled by model now\r\n+            self.preview_model.clear_data() # Clear model if no preset selected\r\n+            self.statusBar().showMessage(\"Select a preset from the list on the left to update preview.\", 3000) # Updated message\r\n             return\r\n         if not hasattr(self, 'current_asset_paths') or not self.current_asset_paths:\r\n             log.debug(\"Update preview called with no assets tracked.\")\r\n             self.preview_table.setRowCount(0)\r\n@@ -1162,9 +1160,13 @@\n                 self.editor_preset_list.setCurrentItem(previous_item)\r\n                 self.editor_preset_list.blockSignals(False)\r\n             return\r\n         if current_item:\r\n-            self._load_preset_for_editing(current_item.data(Qt.ItemDataRole.UserRole))\r\n+            preset_path = current_item.data(Qt.ItemDataRole.UserRole)\r\n+            self._load_preset_for_editing(preset_path)\r\n+            # --- Trigger preview update after loading editor ---\r\n+            self.update_preview()\r\n+            # --- End Trigger ---\r\n         else:\r\n              self._clear_editor() # Clear editor if selection is cleared\r\n \r\n     def _gather_editor_data(self) -> dict:\r\n"
                },
                {
                    "date": 1745326177903,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -523,16 +523,16 @@\n     # --- Preset Population and Handling ---\r\n \r\n     def populate_presets(self):\r\n         \"\"\"Scans presets dir and populates BOTH the editor list and processing combo.\"\"\"\r\n-        log.debug(\"Populating preset list and combo box...\")\r\n-        # Store current selections\r\n-        current_combo_selection = self.preset_combo.currentText()\r\n+        log.debug(\"Populating preset list...\") # Updated log message\r\n+        # Store current list selection\r\n+        # current_combo_selection = self.preset_combo.currentText() # REMOVED\r\n         current_list_item = self.editor_preset_list.currentItem()\r\n         current_list_selection_text = current_list_item.text() if current_list_item else None\r\n \r\n-        # Clear both\r\n-        self.preset_combo.clear()\r\n+        # Clear list\r\n+        # self.preset_combo.clear() # REMOVED\r\n         self.editor_preset_list.clear()\r\n \r\n         if not PRESETS_DIR.is_dir():\r\n             msg = f\"Error: Presets directory not found at {PRESETS_DIR}\"\r\n@@ -548,22 +548,20 @@\n             msg = \"Warning: No presets found in presets directory.\"\r\n             self.statusBar().showMessage(msg)\r\n             log.warning(msg)\r\n         else:\r\n-            # Populate Combo Box (for processing)\r\n-            self.preset_combo.addItems(preset_names)\r\n             # Populate List Widget (for editing)\r\n             for preset_path in presets:\r\n                 item = QListWidgetItem(preset_path.stem)\r\n                 item.setData(Qt.ItemDataRole.UserRole, preset_path) # Store full path\r\n                 self.editor_preset_list.addItem(item)\r\n \r\n             self.statusBar().showMessage(f\"Loaded {len(presets)} presets.\")\r\n \r\n-            # Try to restore selections\r\n-            combo_index = self.preset_combo.findText(current_combo_selection)\r\n-            if combo_index != -1:\r\n-                self.preset_combo.setCurrentIndex(combo_index)\r\n+            # Try to restore list selection\r\n+            # combo_index = self.preset_combo.findText(current_combo_selection) # REMOVED\r\n+            # if combo_index != -1: # REMOVED\r\n+            #     self.preset_combo.setCurrentIndex(combo_index) # REMOVED\r\n \r\n             if current_list_selection_text:\r\n                 items = self.editor_preset_list.findItems(current_list_selection_text, Qt.MatchFlag.MatchExactly)\r\n                 if items:\r\n"
                },
                {
                    "date": 1745326406869,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -928,9 +928,8 @@\n \r\n     def set_controls_enabled(self, enabled: bool):\r\n         \"\"\"Enables/disables input controls during processing.\"\"\"\r\n         # Main panel controls\r\n-        self.preset_combo.setEnabled(enabled)\r\n         self.start_button.setEnabled(enabled)\r\n         self.setAcceptDrops(enabled)\r\n         self.drag_drop_area.setEnabled(enabled)\r\n         # self.preview_table.setEnabled(enabled) # This was the old QTableWidget\r\n"
                },
                {
                    "date": 1745332291002,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -742,97 +742,108 @@\n \r\n \r\n     # --- Preview Update Method ---\r\n     def update_preview(self):\r\n-        # <<< ADDED LOG >>>\r\n         log.info(f\"--> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n         if hasattr(self, 'toggle_preview_action'):\r\n              log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n-        # <<< END ADDED LOG >>>\r\n \r\n-        # --- Handle Preview Toggle ---\r\n-        if hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked(): # Check menu action state\r\n-            # <<< MODIFIED LOG >>>\r\n-            log.info(\"    Preview Toggle Check: DETAILED PREVIEW DISABLED (via menu). Preparing simple list view.\")\r\n-            # <<< END MODIFIED LOG >>>\r\n-            self.preview_table.setSortingEnabled(False)\r\n-            self.preview_table.setRowCount(0) # Clear previous\r\n-            self.preview_table.setColumnCount(1)\r\n-            self.preview_table.setHorizontalHeaderLabels([\"Input Path\"])\r\n-            self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)\r\n+        # Determine mode based on menu action\r\n+        simple_mode_enabled = hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked()\r\n \r\n+        # Set the model's mode\r\n+        self.preview_model.set_simple_mode(simple_mode_enabled)\r\n+\r\n+        # Configure the QTableView based on the mode\r\n+        header = self.preview_table_view.horizontalHeader()\r\n+        if simple_mode_enabled:\r\n+            log.info(\"    Configuring QTableView for SIMPLE mode.\")\r\n+            # Hide detailed columns, show simple column\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_STATUS, True)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_ASSET, True)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_ORIGINAL_PATH, True)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True) # Already hidden, but good practice\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_DETAILS, True)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, False) # Show the simple path column\r\n+\r\n+            # Set resize mode for the single visible column\r\n+            header.setSectionResizeMode(self.preview_model.COL_SIMPLE_PATH, QHeaderView.ResizeMode.Stretch)\r\n+\r\n+            # Disable sorting in simple mode (optional, but makes sense)\r\n+            self.preview_table_view.setSortingEnabled(False)\r\n+\r\n+            # Update status bar\r\n             if hasattr(self, 'current_asset_paths') and self.current_asset_paths:\r\n-                input_paths_list = sorted(list(self.current_asset_paths))\r\n-                self.preview_table.setRowCount(len(input_paths_list))\r\n-                for row, path_str in enumerate(input_paths_list):\r\n-                    item = QTableWidgetItem(path_str)\r\n-                    item.setToolTip(path_str) # Simple tooltip\r\n-                    self.preview_table.setItem(row, 0, item)\r\n-                self.statusBar().showMessage(f\"Preview disabled. Showing {len(input_paths_list)} input assets.\", 3000)\r\n+                 self.statusBar().showMessage(f\"Preview disabled. Showing {len(self.current_asset_paths)} input assets.\", 3000)\r\n             else:\r\n-                self.statusBar().showMessage(\"Preview disabled. No assets added.\", 3000)\r\n+                 self.statusBar().showMessage(\"Preview disabled. No assets added.\", 3000)\r\n \r\n-            self.preview_table.setSortingEnabled(True)\r\n-            return # Stop here, do not run PredictionHandler\r\n-        # --- End Preview Toggle Handling ---\r\n+            # Stop here, do not run PredictionHandler in simple mode\r\n+            return\r\n+        else:\r\n+            # --- Proceed with Detailed Preview ---\r\n+            log.info(\"    Configuring QTableView for DETAILED mode.\")\r\n+            # Show detailed columns, hide simple column\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_STATUS, False)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_ASSET, False)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_ORIGINAL_PATH, False)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True) # Keep this hidden\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_DETAILS, False)\r\n+            self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, True) # Hide the simple path column\r\n \r\n-        # --- Proceed with Detailed Preview ---\r\n-        # <<< ADDED LOG >>>\r\n-        log.info(\"    Preview Toggle Check: DETAILED PREVIEW ENABLED (via menu). Preparing for prediction handler.\")\r\n-        # <<< END ADDED LOG >>>\r\n-        # Reset table headers for detailed view\r\n-        self.preview_table.setColumnCount(4)\r\n-        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) # Predicted Name\r\n-        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Details (Slimmer)\r\n+            # Set resize modes for detailed columns\r\n+            header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n+            header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n+            header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.Stretch)\r\n+            header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n \r\n+            # Re-enable sorting for detailed mode\r\n+            self.preview_table_view.setSortingEnabled(True)\r\n+            # Reset sort order if needed (optional, proxy model handles default)\r\n+            # self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n \r\n-        if self.prediction_handler and self.prediction_handler.is_running:\r\n-            log.warning(f\"[{time.time():.4f}] Preview update requested, but already running.\")\r\n-            # Optionally: Request cancel previous prediction? For now, just return.\r\n-            return\r\n-        if PredictionHandler is None:\r\n-            self.statusBar().showMessage(\"Error: Prediction components not loaded.\", 5000)\r\n-            return\r\n-        # --- Get preset from editor list ---\r\n-        current_editor_item = self.editor_preset_list.currentItem()\r\n-        selected_preset = current_editor_item.text() if current_editor_item else None\r\n-        # --- End Get preset ---\r\n-        if not selected_preset:\r\n-            log.debug(\"Update preview called with no preset selected in the editor list.\")\r\n-            # self.preview_table.setRowCount(0) # Clearing is handled by model now\r\n-            self.preview_model.clear_data() # Clear model if no preset selected\r\n-            self.statusBar().showMessage(\"Select a preset from the list on the left to update preview.\", 3000) # Updated message\r\n-            return\r\n-        if not hasattr(self, 'current_asset_paths') or not self.current_asset_paths:\r\n-            log.debug(\"Update preview called with no assets tracked.\")\r\n-            self.preview_table.setRowCount(0)\r\n-            return\r\n-        input_paths = list(self.current_asset_paths)\r\n-        if not input_paths:\r\n-            log.debug(\"Update preview called but no input paths derived.\")\r\n-            self.preview_table.setRowCount(0)\r\n-            return\r\n+            # --- Trigger Prediction Handler ---\r\n+            if self.prediction_handler and self.prediction_handler.is_running:\r\n+                log.warning(f\"[{time.time():.4f}] Preview update requested, but already running.\")\r\n+                return\r\n+            if PredictionHandler is None:\r\n+                self.statusBar().showMessage(\"Error: Prediction components not loaded.\", 5000)\r\n+                return\r\n+            # Get preset from editor list\r\n+            current_editor_item = self.editor_preset_list.currentItem()\r\n+            selected_preset = current_editor_item.text() if current_editor_item else None\r\n+            if not selected_preset:\r\n+                log.debug(\"Update preview called with no preset selected in the editor list.\")\r\n+                self.preview_model.clear_data() # Clear model if no preset selected\r\n+                self.statusBar().showMessage(\"Select a preset from the list on the left to update preview.\", 3000)\r\n+                return\r\n+            if not hasattr(self, 'current_asset_paths') or not self.current_asset_paths:\r\n+                log.debug(\"Update preview called with no assets tracked.\")\r\n+                self.preview_model.clear_data() # Clear model if no assets\r\n+                return\r\n+            input_paths = list(self.current_asset_paths)\r\n+            if not input_paths:\r\n+                log.debug(\"Update preview called but no input paths derived.\")\r\n+                self.preview_model.clear_data() # Clear model if no paths\r\n+                return\r\n \r\n-        log.info(f\"[{time.time():.4f}] Requesting background preview update for {len(input_paths)} items, Preset='{selected_preset}'\")\r\n-        self.statusBar().showMessage(f\"Updating preview for '{selected_preset}'...\", 0)\r\n-        self.preview_table.setRowCount(0) # Clear before starting prediction\r\n-        self.setup_threads() # Ensure threads are ready\r\n-        if self.prediction_thread and self.prediction_handler:\r\n-            try: self.prediction_thread.started.disconnect() # Disconnect previous lambda if any\r\n-            except RuntimeError: pass\r\n-            # Connect the lambda to start the prediction\r\n-            self.prediction_thread.started.connect(\r\n-                lambda: self.prediction_handler.run_prediction(input_paths, selected_preset)\r\n-            )\r\n-            log.debug(f\"[{time.time():.4f}] Starting prediction thread...\")\r\n-            self.prediction_thread.start()\r\n-            log.debug(f\"[{time.time():.4f}] Prediction thread start requested.\")\r\n-        else:\r\n-            log.error(f\"[{time.time():.4f}] Failed to start prediction: Thread or handler not initialized.\")\r\n-            self.statusBar().showMessage(\"Error: Failed to initialize prediction thread.\", 5000)\r\n+            log.info(f\"[{time.time():.4f}] Requesting background preview update for {len(input_paths)} items, Preset='{selected_preset}'\")\r\n+            self.statusBar().showMessage(f\"Updating preview for '{selected_preset}'...\", 0)\r\n+            # Clearing is handled by model's set_data now, no need to clear table view directly\r\n+            self.setup_threads() # Ensure threads are ready\r\n+            if self.prediction_thread and self.prediction_handler:\r\n+                try: self.prediction_thread.started.disconnect() # Disconnect previous lambda if any\r\n+                except RuntimeError: pass\r\n+                # Connect the lambda to start the prediction\r\n+                self.prediction_thread.started.connect(\r\n+                    lambda: self.prediction_handler.run_prediction(input_paths, selected_preset)\r\n+                )\r\n+                log.debug(f\"[{time.time():.4f}] Starting prediction thread...\")\r\n+                self.prediction_thread.start()\r\n+                log.debug(f\"[{time.time():.4f}] Prediction thread start requested.\")\r\n+            else:\r\n+                log.error(f\"[{time.time():.4f}] Failed to start prediction: Thread or handler not initialized.\")\r\n+                self.statusBar().showMessage(\"Error: Failed to initialize prediction thread.\", 5000)\r\n \r\n     # --- Threading and Processing Control ---\r\n     def setup_threads(self):\r\n         # Setup Processing Thread\r\n"
                },
                {
                    "date": 1745332400975,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -750,8 +750,9 @@\n         # Determine mode based on menu action\r\n         simple_mode_enabled = hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked()\r\n \r\n         # Set the model's mode\r\n+        log.debug(f\"    Setting model simple mode to: {simple_mode_enabled}\")\r\n         self.preview_model.set_simple_mode(simple_mode_enabled)\r\n \r\n         # Configure the QTableView based on the mode\r\n         header = self.preview_table_view.horizontalHeader()\r\n@@ -762,12 +763,16 @@\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_ASSET, True)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_ORIGINAL_PATH, True)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True) # Already hidden, but good practice\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_DETAILS, True)\r\n-            self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, False) # Show the simple path column\r\n+            # Ensure the simple path column exists and is visible\r\n+            if self.preview_model.columnCount() > self.preview_model.COL_SIMPLE_PATH:\r\n+                 self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, False) # Show the simple path column\r\n+                 # Set resize mode for the single visible column\r\n+                 header.setSectionResizeMode(self.preview_model.COL_SIMPLE_PATH, QHeaderView.ResizeMode.Stretch)\r\n+            else:\r\n+                 log.error(\"    Simple path column index out of bounds for model.\")\r\n \r\n-            # Set resize mode for the single visible column\r\n-            header.setSectionResizeMode(self.preview_model.COL_SIMPLE_PATH, QHeaderView.ResizeMode.Stretch)\r\n \r\n             # Disable sorting in simple mode (optional, but makes sense)\r\n             self.preview_table_view.setSortingEnabled(False)\r\n \r\n@@ -776,8 +781,18 @@\n                  self.statusBar().showMessage(f\"Preview disabled. Showing {len(self.current_asset_paths)} input assets.\", 3000)\r\n             else:\r\n                  self.statusBar().showMessage(\"Preview disabled. No assets added.\", 3000)\r\n \r\n+            # In simple mode, the model's data is derived from current_asset_paths.\r\n+            # We need to ensure the model's simple data is up-to-date.\r\n+            # The simplest way is to re-set the data, which will re-extract simple data.\r\n+            # This might be slightly inefficient if only the mode changed, but safe.\r\n+            # A more optimized approach would be to have a separate method in the model\r\n+            # to just update the simple data from a list of paths.\r\n+            # For now, let's re-set the data.\r\n+            log.debug(\"    Simple mode enabled. Re-setting model data to trigger simple data update.\")\r\n+            self.preview_model.set_data(list(self.current_asset_paths)) # Pass the list of paths\r\n+\r\n             # Stop here, do not run PredictionHandler in simple mode\r\n             return\r\n         else:\r\n             # --- Proceed with Detailed Preview ---\r\n@@ -787,10 +802,15 @@\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_ASSET, False)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_ORIGINAL_PATH, False)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True) # Keep this hidden\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_DETAILS, False)\r\n-            self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, True) # Hide the simple path column\r\n+            # Ensure the simple path column exists and is hidden\r\n+            if self.preview_model.columnCount() > self.preview_model.COL_SIMPLE_PATH:\r\n+                 self.preview_table_view.setColumnHidden(self.preview_model.COL_SIMPLE_PATH, True) # Hide the simple path column\r\n+            else:\r\n+                 log.warning(\"    Simple path column index out of bounds for model when hiding.\")\r\n \r\n+\r\n             # Set resize modes for detailed columns\r\n             header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n             header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n             header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.Stretch)\r\n"
                },
                {
                    "date": 1745332587088,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -927,16 +927,19 @@\n     # Slot for prediction results (Updated for new format and coloring)\r\n     @Slot(list)\r\n     def on_prediction_results_ready(self, results: list):\r\n         \"\"\"Populates the preview table model with detailed prediction results.\"\"\"\r\n-        log.debug(f\"[{time.time():.4f}] on_prediction_results_ready received {len(results)} file details.\")\r\n+        log.info(f\"[{time.time():.4f}] --> on_prediction_results_ready received {len(results)} file details.\")\r\n         # Update the model with the new data\r\n         self.preview_model.set_data(results)\r\n-        log.debug(f\"[{time.time():.4f}] Preview model updated with detailed results.\")\r\n+        log.info(f\"[{time.time():.4f}] Preview model updated with detailed results.\")\r\n \r\n     @Slot()\r\n     def on_prediction_finished(self):\r\n-        log.debug(f\"[{time.time():.4f}] Prediction finished signal received.\")\r\n+        log.info(f\"[{time.time():.4f}] --> Prediction finished signal received.\")\r\n+        # Optionally update status bar or re-enable controls if needed after prediction finishes\r\n+        # (Controls are primarily managed by processing_finished, but prediction is a separate background task)\r\n+        self.statusBar().showMessage(\"Preview updated.\", 3000)\r\n \r\n     @Slot(str, str, str)\r\n     def update_file_status(self, input_path_str, status, message):\r\n         # TODO: Update status bar or potentially find rows in table later\r\n"
                },
                {
                    "date": 1745332710872,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -746,11 +746,25 @@\n         log.info(f\"--> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n         if hasattr(self, 'toggle_preview_action'):\r\n              log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n \r\n+    # --- Preview Update Method ---\r\n+    def update_preview(self):\r\n+        log.info(f\"--> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n+        if hasattr(self, 'toggle_preview_action'):\r\n+             log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n+\r\n         # Determine mode based on menu action\r\n         simple_mode_enabled = hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked()\r\n \r\n+        # --- Cancel Prediction if Running ---\r\n+        if self.prediction_handler and self.prediction_handler.is_running:\r\n+            log.warning(f\"[{time.time():.4f}] Prediction is running. Requesting cancellation before changing preview mode.\")\r\n+            self.prediction_handler.request_cancel()\r\n+            # Note: Cancellation is not immediate. The thread will stop when it next checks the flag.\r\n+            # We proceed with updating the UI immediately, which might briefly show inconsistent state\r\n+            # if the thread hasn't fully stopped, but it prevents the crash.\r\n+\r\n         # Set the model's mode\r\n         log.debug(f\"    Setting model simple mode to: {simple_mode_enabled}\")\r\n         self.preview_model.set_simple_mode(simple_mode_enabled)\r\n \r\n"
                },
                {
                    "date": 1745332971203,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -748,26 +748,35 @@\n              log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n \r\n     # --- Preview Update Method ---\r\n     def update_preview(self):\r\n-        log.info(f\"--> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n+        thread_id = QThread.currentThreadId()\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n         if hasattr(self, 'toggle_preview_action'):\r\n-             log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n+             log.info(f\"[{time.time():.4f}][T:{thread_id}]     Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n \r\n         # Determine mode based on menu action\r\n         simple_mode_enabled = hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked()\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}]     Determined simple_mode_enabled: {simple_mode_enabled}\")\r\n \r\n         # --- Cancel Prediction if Running ---\r\n         if self.prediction_handler and self.prediction_handler.is_running:\r\n-            log.warning(f\"[{time.time():.4f}] Prediction is running. Requesting cancellation before changing preview mode.\")\r\n-            self.prediction_handler.request_cancel()\r\n-            # Note: Cancellation is not immediate. The thread will stop when it next checks the flag.\r\n-            # We proceed with updating the UI immediately, which might briefly show inconsistent state\r\n-            # if the thread hasn't fully stopped, but it prevents the crash.\r\n+            log.warning(f\"[{time.time():.4f}][T:{thread_id}] Prediction is running. Attempting to call prediction_handler.request_cancel()...\")\r\n+            try:\r\n+                # --- THIS METHOD DOES NOT EXIST IN PredictionHandler ---\r\n+                self.prediction_handler.request_cancel()\r\n+                log.info(f\"[{time.time():.4f}][T:{thread_id}]     Called prediction_handler.request_cancel() (Method might be missing!).\")\r\n+            except AttributeError as e:\r\n+                log.error(f\"[{time.time():.4f}][T:{thread_id}]     AttributeError calling prediction_handler.request_cancel(): {e}. Prediction cannot be cancelled.\")\r\n+            except Exception as e:\r\n+                 log.exception(f\"[{time.time():.4f}][T:{thread_id}]     Unexpected error calling prediction_handler.request_cancel(): {e}\")\r\n+            # Note: Cancellation is not immediate even if it existed. The thread would stop when it next checks the flag.\r\n+            # We proceed with updating the UI immediately.\r\n \r\n         # Set the model's mode\r\n-        log.debug(f\"    Setting model simple mode to: {simple_mode_enabled}\")\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}]     Calling preview_model.set_simple_mode({simple_mode_enabled})...\")\r\n         self.preview_model.set_simple_mode(simple_mode_enabled)\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}]     Returned from preview_model.set_simple_mode({simple_mode_enabled}).\")\r\n \r\n         # Configure the QTableView based on the mode\r\n         header = self.preview_table_view.horizontalHeader()\r\n         if simple_mode_enabled:\r\n@@ -806,12 +815,13 @@\n             log.debug(\"    Simple mode enabled. Re-setting model data to trigger simple data update.\")\r\n             self.preview_model.set_data(list(self.current_asset_paths)) # Pass the list of paths\r\n \r\n             # Stop here, do not run PredictionHandler in simple mode\r\n+            log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting update_preview (Simple Mode).\")\r\n             return\r\n         else:\r\n             # --- Proceed with Detailed Preview ---\r\n-            log.info(\"    Configuring QTableView for DETAILED mode.\")\r\n+            log.info(f\"[{time.time():.4f}][T:{thread_id}]     Configuring QTableView for DETAILED mode.\")\r\n             # Show detailed columns, hide simple column\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_STATUS, False)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_ASSET, False)\r\n             self.preview_table_view.setColumnHidden(self.preview_model.COL_ORIGINAL_PATH, False)\r\n@@ -874,11 +884,13 @@\n                 log.debug(f\"[{time.time():.4f}] Starting prediction thread...\")\r\n                 self.prediction_thread.start()\r\n                 log.debug(f\"[{time.time():.4f}] Prediction thread start requested.\")\r\n             else:\r\n-                log.error(f\"[{time.time():.4f}] Failed to start prediction: Thread or handler not initialized.\")\r\n+                log.error(f\"[{time.time():.4f}][T:{thread_id}] Failed to start prediction: Thread or handler not initialized.\")\r\n                 self.statusBar().showMessage(\"Error: Failed to initialize prediction thread.\", 5000)\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting update_preview (Detailed Mode).\")\r\n \r\n+\r\n     # --- Threading and Processing Control ---\r\n     def setup_threads(self):\r\n         # Setup Processing Thread\r\n         if ProcessingHandler and self.processing_thread is None:\r\n@@ -941,12 +953,15 @@\n     # Slot for prediction results (Updated for new format and coloring)\r\n     @Slot(list)\r\n     def on_prediction_results_ready(self, results: list):\r\n         \"\"\"Populates the preview table model with detailed prediction results.\"\"\"\r\n-        log.info(f\"[{time.time():.4f}] --> on_prediction_results_ready received {len(results)} file details.\")\r\n+        thread_id = QThread.currentThreadId()\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered on_prediction_results_ready. Received {len(results)} file details.\")\r\n         # Update the model with the new data\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}]     Calling preview_model.set_data()...\")\r\n         self.preview_model.set_data(results)\r\n-        log.info(f\"[{time.time():.4f}] Preview model updated with detailed results.\")\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}]     Returned from preview_model.set_data().\")\r\n+        log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting on_prediction_results_ready.\")\r\n \r\n     @Slot()\r\n     def on_prediction_finished(self):\r\n         log.info(f\"[{time.time():.4f}] --> Prediction finished signal received.\")\r\n"
                },
                {
                    "date": 1745333378823,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -748,9 +748,9 @@\n              log.info(f\"    Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n \r\n     # --- Preview Update Method ---\r\n     def update_preview(self):\r\n-        thread_id = QThread.currentThreadId()\r\n+        thread_id = QThread.currentThread() # Get current thread object\r\n         log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered update_preview. View Action exists: {hasattr(self, 'toggle_preview_action')}\")\r\n         if hasattr(self, 'toggle_preview_action'):\r\n              log.info(f\"[{time.time():.4f}][T:{thread_id}]     Disable Preview Action checked: {self.toggle_preview_action.isChecked()}\")\r\n \r\n@@ -953,9 +953,9 @@\n     # Slot for prediction results (Updated for new format and coloring)\r\n     @Slot(list)\r\n     def on_prediction_results_ready(self, results: list):\r\n         \"\"\"Populates the preview table model with detailed prediction results.\"\"\"\r\n-        thread_id = QThread.currentThreadId()\r\n+        thread_id = QThread.currentThread() # Get current thread object\r\n         log.info(f\"[{time.time():.4f}][T:{thread_id}] --> Entered on_prediction_results_ready. Received {len(results)} file details.\")\r\n         # Update the model with the new data\r\n         log.info(f\"[{time.time():.4f}][T:{thread_id}]     Calling preview_model.set_data()...\")\r\n         self.preview_model.set_data(results)\r\n"
                },
                {
                    "date": 1745333556600,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -811,10 +811,14 @@\n             # This might be slightly inefficient if only the mode changed, but safe.\r\n             # A more optimized approach would be to have a separate method in the model\r\n             # to just update the simple data from a list of paths.\r\n             # For now, let's re-set the data.\r\n-            log.debug(\"    Simple mode enabled. Re-setting model data to trigger simple data update.\")\r\n-            self.preview_model.set_data(list(self.current_asset_paths)) # Pass the list of paths\r\n+            # --- REMOVED REDUNDANT set_data CALL ---\r\n+            # The set_simple_mode(True) call above should be sufficient as the model\r\n+            # already holds the simple data internally. This extra reset seems to cause instability.\r\n+            # log.debug(\"    Simple mode enabled. Re-setting model data to trigger simple data update.\")\r\n+            # self.preview_model.set_data(list(self.current_asset_paths)) # Pass the list of paths\r\n+            # --- END REMOVAL ---\r\n \r\n             # Stop here, do not run PredictionHandler in simple mode\r\n             log.info(f\"[{time.time():.4f}][T:{thread_id}] <-- Exiting update_preview (Simple Mode).\")\r\n             return\r\n"
                },
                {
                    "date": 1745333721426,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -607,11 +607,17 @@\n                     self.toggle_preview_action.setChecked(True) # This will trigger update_preview via its signal\r\n                     preview_toggled = True\r\n \r\n             # Only call update_preview directly if the toggle wasn't triggered\r\n-            if not preview_toggled:\r\n+            # If in simple mode, we need to explicitly update the model with the simple list of paths\r\n+            if hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked():\r\n+                 log.debug(\"Currently in simple preview mode. Updating model with simple paths.\")\r\n+                 self.preview_model.set_data(list(self.current_asset_paths)) # Update model with simple list\r\n+                 self.statusBar().showMessage(f\"Added {added_count} asset(s). Preview updated.\", 3000)\r\n+            elif not preview_toggled:\r\n+                # If not in simple mode and toggle wasn't triggered, run detailed preview update\r\n                 self.update_preview()\r\n-\r\n+ \r\n     def _browse_for_output_directory(self):\r\n         \"\"\"Opens a dialog to select the output directory.\"\"\"\r\n         current_path = self.output_path_edit.text()\r\n         if not current_path or not Path(current_path).is_dir():\r\n"
                },
                {
                    "date": 1745498820388,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -409,9 +409,9 @@\n \r\n         # Set selection behavior and alternating colors\r\n         self.preview_table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)\r\n         self.preview_table_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)\r\n-        self.preview_table_view.setAlternatingRowColors(True)\r\n+        self.preview_table_view.setAlternatingRowColors(False)\r\n \r\n         # Enable sorting via header clicks\r\n         self.preview_table_view.setSortingEnabled(True)\r\n         # Set default sort column (Status) - the proxy model's lessThan handles the custom order\r\n"
                },
                {
                    "date": 1745501490485,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -425,8 +425,11 @@\n \r\n         main_layout.addWidget(self.preview_label)\r\n         main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n+        # Apply style sheet to remove borders and rounded corners\r\n+        self.preview_table_view.setStyleSheet(\"QTableView { border: none; }\")\r\n+\r\n         # --- Progress Bar ---\r\n         self.progress_bar = QProgressBar()\r\n         self.progress_bar.setValue(0)\r\n         self.progress_bar.setTextVisible(True)\r\n"
                },
                {
                    "date": 1745501886976,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1491,8 +1491,9 @@\n # --- Main Execution ---\r\n def run_gui():\r\n     \"\"\"Initializes and runs the Qt application.\"\"\"\r\n     app = QApplication(sys.argv)\r\n+    app.setStyle('Fusion')\r\n     window = MainWindow()\r\n     window.show()\r\n     sys.exit(app.exec())\r\n \r\n"
                },
                {
                    "date": 1745502118371,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -17,9 +17,9 @@\n     QFormLayout, QGroupBox, QAbstractItemView, QSizePolicy, # Added more layout/widget items\r\n     QMenuBar, QMenu # Added for menu\r\n )\r\n from PySide6.QtCore import Qt, QThread, Slot, Signal, QObject # Added Signal, QObject\r\n-from PySide6.QtGui import QColor, QAction # Add QColor import, QAction\r\n+from PySide6.QtGui import QColor, QAction, QPalette # Add QColor import, QAction, QPalette\r\n \r\n # --- GUI Model Imports ---\r\n from gui.preview_table_model import PreviewTableModel, PreviewSortFilterProxyModel\r\n \r\n@@ -1492,8 +1492,20 @@\n def run_gui():\r\n     \"\"\"Initializes and runs the Qt application.\"\"\"\r\n     app = QApplication(sys.argv)\r\n     app.setStyle('Fusion')\r\n+\r\n+    # Set a custom palette to override default Fusion colors\r\n+    palette = app.palette()\r\n+    grey_color = QColor(\"#535353\")\r\n+    palette.setColor(QPalette.ColorRole.Base, grey_color)\r\n+    palette.setColor(QPalette.ColorRole.AlternateBase, grey_color.lighter(110)) # Use a slightly lighter shade for alternate rows if needed\r\n+    # You might need to experiment with other roles depending on which widgets are affected\r\n+    # palette.setColor(QPalette.ColorRole.Window, grey_color)\r\n+    # palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white) # Example: set text color to white\r\n+\r\n+    app.setPalette(palette)\r\n+\r\n     window = MainWindow()\r\n     window.show()\r\n     sys.exit(app.exec())\r\n \r\n"
                },
                {
                    "date": 1745502783700,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1495,9 +1495,9 @@\n     app.setStyle('Fusion')\r\n \r\n     # Set a custom palette to override default Fusion colors\r\n     palette = app.palette()\r\n-    grey_color = QColor(\"#535353\")\r\n+    grey_color = QColor(\"#3a3a3a\")\r\n     palette.setColor(QPalette.ColorRole.Base, grey_color)\r\n     palette.setColor(QPalette.ColorRole.AlternateBase, grey_color.lighter(110)) # Use a slightly lighter shade for alternate rows if needed\r\n     # You might need to experiment with other roles depending on which widgets are affected\r\n     # palette.setColor(QPalette.ColorRole.Window, grey_color)\r\n"
                },
                {
                    "date": 1745504039295,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -398,9 +398,10 @@\n         # The model defines the columns and headers\r\n         header = self.preview_table_view.horizontalHeader()\r\n         header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n         header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n-        header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.Stretch)\r\n+        header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Changed to ResizeToContents\r\n+        header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Added for Additional Files\r\n         # Remove Predicted Output column as requested\r\n         # header.setSectionResizeMode(self.preview_model.COL_PREDICTED_OUTPUT, QHeaderView.ResizeMode.ResizeToContents)\r\n         header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n \r\n@@ -416,13 +417,13 @@\n         self.preview_table_view.setSortingEnabled(True)\r\n         # Set default sort column (Status) - the proxy model's lessThan handles the custom order\r\n         self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n \r\n-        # Move \"Original Path\" column to the far right\r\n-        original_path_logical_index = self.preview_model.COL_ORIGINAL_PATH\r\n-        original_path_visual_index = header.visualIndex(original_path_logical_index)\r\n-        if original_path_visual_index != -1: # Check if the column is visible\r\n-             header.moveSection(original_path_visual_index, header.count() - 1)\r\n+        # Move \"Additional Files\" column to the far right\r\n+        additional_files_logical_index = self.preview_model.COL_ADDITIONAL_FILES\r\n+        additional_files_visual_index = header.visualIndex(additional_files_logical_index)\r\n+        if additional_files_visual_index != -1: # Check if the column is visible\r\n+             header.moveSection(additional_files_visual_index, header.count() - 1)\r\n \r\n         main_layout.addWidget(self.preview_label)\r\n         main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n@@ -849,11 +850,18 @@\n \r\n             # Set resize modes for detailed columns\r\n             header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n             header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n-            header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.Stretch)\r\n+            header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Changed to ResizeToContents\r\n+            header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Added for Additional Files\r\n             header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n \r\n+            # Move \"Additional Files\" column to the far right\r\n+            additional_files_logical_index = self.preview_model.COL_ADDITIONAL_FILES\r\n+            additional_files_visual_index = header.visualIndex(additional_files_logical_index)\r\n+            if additional_files_visual_index != -1: # Check if the column is visible\r\n+                 header.moveSection(additional_files_visual_index, header.count() - 1)\r\n+\r\n             # Re-enable sorting for detailed mode\r\n             self.preview_table_view.setSortingEnabled(True)\r\n             # Reset sort order if needed (optional, proxy model handles default)\r\n             # self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n"
                },
                {
                    "date": 1745504410196,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -849,19 +849,32 @@\n \r\n \r\n             # Set resize modes for detailed columns\r\n             header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n-            header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n-            header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Changed to ResizeToContents\r\n-            header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Added for Additional Files\r\n-            header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n+            header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents) # Fit\r\n+            header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents) # Fit\r\n+            header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Fixed width (using ResizeToContents as closest)\r\n+            header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Stretch (Fit-If-Possible)\r\n \r\n-            # Move \"Additional Files\" column to the far right\r\n-            additional_files_logical_index = self.preview_model.COL_ADDITIONAL_FILES\r\n-            additional_files_visual_index = header.visualIndex(additional_files_logical_index)\r\n-            if additional_files_visual_index != -1: # Check if the column is visible\r\n-                 header.moveSection(additional_files_visual_index, header.count() - 1)\r\n+            # Move columns to the desired order: Status, Predicted Asset, Details, Original Path, Additional Files\r\n+            # Initial logical order: [0, 1, 2, 3(hidden), 4, 5]\r\n+            # Initial visual order: [0, 1, 2, 3, 4, 5] (assuming no initial moves)\r\n+            # Desired visual order: [0, 1, 4, 2, 5, 3(hidden)]\r\n \r\n+            # Move Details (logical 4) after Predicted Asset (logical 1)\r\n+            header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), header.visualIndex(self.preview_model.COL_PREDICTED_ASSET) + 1)\r\n+            # Current visual: [0, 1, 4, 2, 3, 5]\r\n+\r\n+            # Move Original Path (logical 2) after Details (logical 4)\r\n+            # Original Path is now at visual index 3. Details is at visual index 2.\r\n+            # Target visual index is 3. It's already there. No move needed.\r\n+\r\n+            # Move Additional Files (logical 5) after Original Path (logical 2)\r\n+            # Additional Files is at visual index 5. Original Path is at visual index 3.\r\n+            # Target visual index is 4.\r\n+            header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), header.visualIndex(self.preview_model.COL_ORIGINAL_PATH) + 1)\r\n+            # Current visual: [0, 1, 4, 2, 5, 3] - This looks correct.\r\n+\r\n             # Re-enable sorting for detailed mode\r\n             self.preview_table_view.setSortingEnabled(True)\r\n             # Reset sort order if needed (optional, proxy model handles default)\r\n             # self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n"
                },
                {
                    "date": 1745504427537,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -396,15 +396,14 @@\n \r\n         # Set headers and resize modes using the model's headerData\r\n         # The model defines the columns and headers\r\n         header = self.preview_table_view.horizontalHeader()\r\n+        # Set resize modes for detailed columns\r\n         header.setSectionResizeMode(self.preview_model.COL_STATUS, QHeaderView.ResizeMode.ResizeToContents)\r\n-        header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents)\r\n-        header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Changed to ResizeToContents\r\n-        header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Added for Additional Files\r\n-        # Remove Predicted Output column as requested\r\n-        # header.setSectionResizeMode(self.preview_model.COL_PREDICTED_OUTPUT, QHeaderView.ResizeMode.ResizeToContents)\r\n-        header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents)\r\n+        header.setSectionResizeMode(self.preview_model.COL_PREDICTED_ASSET, QHeaderView.ResizeMode.ResizeToContents) # Fit\r\n+        header.setSectionResizeMode(self.preview_model.COL_DETAILS, QHeaderView.ResizeMode.ResizeToContents) # Fit\r\n+        header.setSectionResizeMode(self.preview_model.COL_ORIGINAL_PATH, QHeaderView.ResizeMode.ResizeToContents) # Fixed width (using ResizeToContents as closest)\r\n+        header.setSectionResizeMode(self.preview_model.COL_ADDITIONAL_FILES, QHeaderView.ResizeMode.Stretch) # Stretch (Fit-If-Possible)\r\n \r\n         # Hide the Predicted Output column\r\n         self.preview_table_view.setColumnHidden(self.preview_model.COL_PREDICTED_OUTPUT, True)\r\n \r\n@@ -417,14 +416,27 @@\n         self.preview_table_view.setSortingEnabled(True)\r\n         # Set default sort column (Status) - the proxy model's lessThan handles the custom order\r\n         self.preview_table_view.sortByColumn(self.preview_model.COL_STATUS, Qt.SortOrder.AscendingOrder)\r\n \r\n-        # Move \"Additional Files\" column to the far right\r\n-        additional_files_logical_index = self.preview_model.COL_ADDITIONAL_FILES\r\n-        additional_files_visual_index = header.visualIndex(additional_files_logical_index)\r\n-        if additional_files_visual_index != -1: # Check if the column is visible\r\n-             header.moveSection(additional_files_visual_index, header.count() - 1)\r\n+        # Move columns to the desired order: Status, Predicted Asset, Details, Original Path, Additional Files\r\n+        # Initial logical order: [0, 1, 2, 3(hidden), 4, 5]\r\n+        # Initial visual order: [0, 1, 2, 3, 4, 5] (assuming no initial moves)\r\n+        # Desired visual order: [0, 1, 4, 2, 5, 3(hidden)]\r\n \r\n+        # Move Details (logical 4) after Predicted Asset (logical 1)\r\n+        header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), header.visualIndex(self.preview_model.COL_PREDICTED_ASSET) + 1)\r\n+        # Current visual: [0, 1, 4, 2, 3, 5]\r\n+\r\n+        # Move Original Path (logical 2) after Details (logical 4)\r\n+        # Original Path is now at visual index 3. Details is at visual index 2.\r\n+        # Target visual index is 3. It's already there. No move needed.\r\n+\r\n+        # Move Additional Files (logical 5) after Original Path (logical 2)\r\n+        # Additional Files is at visual index 5. Original Path is at visual index 3.\r\n+        # Target visual index is 4.\r\n+        header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), header.visualIndex(self.preview_model.COL_ORIGINAL_PATH) + 1)\r\n+        # Current visual: [0, 1, 4, 2, 5, 3] - This looks correct.\r\n+\r\n         main_layout.addWidget(self.preview_label)\r\n         main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n         # Apply style sheet to remove borders and rounded corners\r\n"
                },
                {
                    "date": 1745504444836,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -421,20 +421,20 @@\n         # Initial logical order: [0, 1, 2, 3(hidden), 4, 5]\r\n         # Initial visual order: [0, 1, 2, 3, 4, 5] (assuming no initial moves)\r\n         # Desired visual order: [0, 1, 4, 2, 5, 3(hidden)]\r\n \r\n-        # Move Details (logical 4) after Predicted Asset (logical 1)\r\n-        header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), header.visualIndex(self.preview_model.COL_PREDICTED_ASSET) + 1)\r\n+        # Move Predicted Asset (logical 1) to visual index 1 (already there)\r\n+\r\n+        # Move Details (logical 4) to visual index 2\r\n+        header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), 2)\r\n         # Current visual: [0, 1, 4, 2, 3, 5]\r\n \r\n-        # Move Original Path (logical 2) after Details (logical 4)\r\n-        # Original Path is now at visual index 3. Details is at visual index 2.\r\n-        # Target visual index is 3. It's already there. No move needed.\r\n+        # Move Original Path (logical 2) to visual index 3\r\n+        header.moveSection(header.visualIndex(self.preview_model.COL_ORIGINAL_PATH), 3)\r\n+        # Current visual: [0, 1, 4, 2, 3, 5] - Original Path is already at visual index 3 after moving Details\r\n \r\n-        # Move Additional Files (logical 5) after Original Path (logical 2)\r\n-        # Additional Files is at visual index 5. Original Path is at visual index 3.\r\n-        # Target visual index is 4.\r\n-        header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), header.visualIndex(self.preview_model.COL_ORIGINAL_PATH) + 1)\r\n+        # Move Additional Files (logical 5) to visual index 4\r\n+        header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), 4)\r\n         # Current visual: [0, 1, 4, 2, 5, 3] - This looks correct.\r\n \r\n         main_layout.addWidget(self.preview_label)\r\n         main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n@@ -871,20 +871,20 @@\n             # Initial logical order: [0, 1, 2, 3(hidden), 4, 5]\r\n             # Initial visual order: [0, 1, 2, 3, 4, 5] (assuming no initial moves)\r\n             # Desired visual order: [0, 1, 4, 2, 5, 3(hidden)]\r\n \r\n-            # Move Details (logical 4) after Predicted Asset (logical 1)\r\n-            header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), header.visualIndex(self.preview_model.COL_PREDICTED_ASSET) + 1)\r\n+            # Move Predicted Asset (logical 1) to visual index 1 (already there)\r\n+\r\n+            # Move Details (logical 4) to visual index 2\r\n+            header.moveSection(header.visualIndex(self.preview_model.COL_DETAILS), 2)\r\n             # Current visual: [0, 1, 4, 2, 3, 5]\r\n \r\n-            # Move Original Path (logical 2) after Details (logical 4)\r\n-            # Original Path is now at visual index 3. Details is at visual index 2.\r\n-            # Target visual index is 3. It's already there. No move needed.\r\n+            # Move Original Path (logical 2) to visual index 3\r\n+            header.moveSection(header.visualIndex(self.preview_model.COL_ORIGINAL_PATH), 3)\r\n+            # Current visual: [0, 1, 4, 2, 3, 5] - Original Path is already at visual index 3 after moving Details\r\n \r\n-            # Move Additional Files (logical 5) after Original Path (logical 2)\r\n-            # Additional Files is at visual index 5. Original Path is at visual index 3.\r\n-            # Target visual index is 4.\r\n-            header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), header.visualIndex(self.preview_model.COL_ORIGINAL_PATH) + 1)\r\n+            # Move Additional Files (logical 5) to visual index 4\r\n+            header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), 4)\r\n             # Current visual: [0, 1, 4, 2, 5, 3] - This looks correct.\r\n \r\n             # Re-enable sorting for detailed mode\r\n             self.preview_table_view.setSortingEnabled(True)\r\n"
                },
                {
                    "date": 1745507368001,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -602,15 +602,16 @@\n         newly_added_paths = []\r\n         for p_str in paths:\r\n             p = Path(p_str)\r\n             if p.exists():\r\n-                if p.is_dir() or (p.is_file() and p.suffix.lower() == '.zip'):\r\n+                supported_suffixes = ['.zip', '.rar', '.7z']\r\n+                if p.is_dir() or (p.is_file() and p.suffix.lower() in supported_suffixes):\r\n                     if p_str not in self.current_asset_paths:\r\n                         self.current_asset_paths.add(p_str)\r\n                         newly_added_paths.append(p_str)\r\n                         added_count += 1\r\n-                    else: print(f\"Skipping duplicate asset path: {p_str}\")\r\n-                else: self.statusBar().showMessage(f\"Invalid input (not dir or .zip): {p.name}\", 5000); print(f\"Invalid input: {p_str}\")\r\n+                    else: log.debug(f\"Skipping duplicate asset path: {p_str}\") # Changed print to log.debug\r\n+                else: self.statusBar().showMessage(f\"Invalid input (not dir or supported archive): {p.name}\", 5000); log.warning(f\"Invalid input: {p_str}\") # Changed print to log.warning and updated message\r\n             else: self.statusBar().showMessage(f\"Input path not found: {p.name}\", 5000); print(f\"Input path not found: {p_str}\")\r\n         if added_count > 0:\r\n             log.info(f\"Added {added_count} new asset paths: {newly_added_paths}\")\r\n             self.statusBar().showMessage(f\"Added {added_count} asset(s). Updating preview...\", 3000)\r\n"
                },
                {
                    "date": 1745508942440,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -193,11 +193,14 @@\n \r\n         # --- Initial State ---\r\n         self._clear_editor() # Clear/disable editor fields initially\r\n         self._set_editor_enabled(False) # Disable editor initially\r\n-        self.populate_presets() # Populate both preset list and combo box\r\n+        self.populate_presets() # Populate preset list\r\n         self.setup_logging_handler() # Setup the custom log handler\r\n \r\n+        # Set placeholder text for the preview table\r\n+        self.preview_table_view.setPlaceholderText(\"Please select a preset to view file predictions\")\r\n+\r\n         # --- Connect Editor Signals ---\r\n         self._connect_editor_change_signals()\r\n \r\n         # --- Adjust Splitter ---\r\n@@ -577,12 +580,11 @@\n             # combo_index = self.preset_combo.findText(current_combo_selection) # REMOVED\r\n             # if combo_index != -1: # REMOVED\r\n             #     self.preset_combo.setCurrentIndex(combo_index) # REMOVED\r\n \r\n-            if current_list_selection_text:\r\n-                items = self.editor_preset_list.findItems(current_list_selection_text, Qt.MatchFlag.MatchExactly)\r\n-                if items:\r\n-                    self.editor_preset_list.setCurrentItem(items[0]) # This might trigger _load_selected_preset_for_editing\r\n+            # Do NOT attempt to restore list selection by default\r\n+            # The user must explicitly select a preset to trigger preview\r\n+            pass\r\n \r\n     # --- Drag and Drop Event Handlers (Unchanged) ---\r\n     def dragEnterEvent(self, event):\r\n         if event.mimeData().hasUrls(): event.acceptProposedAction()\r\n"
                },
                {
                    "date": 1745508953551,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -631,11 +631,16 @@\n             if hasattr(self, 'toggle_preview_action') and self.toggle_preview_action.isChecked():\r\n                  log.debug(\"Currently in simple preview mode. Updating model with simple paths.\")\r\n                  self.preview_model.set_data(list(self.current_asset_paths)) # Update model with simple list\r\n                  self.statusBar().showMessage(f\"Added {added_count} asset(s). Preview updated.\", 3000)\r\n-            elif not preview_toggled:\r\n-                # If not in simple mode and toggle wasn't triggered, run detailed preview update\r\n+            # Only call update_preview if a preset is currently selected in the editor list\r\n+            current_editor_item = self.editor_preset_list.currentItem()\r\n+            if not preview_toggled and current_editor_item:\r\n+                log.debug(\"Preset selected and not in simple mode. Triggering detailed preview update.\")\r\n                 self.update_preview()\r\n+            elif not current_editor_item:\r\n+                log.debug(\"No preset selected. Not triggering detailed preview update.\")\r\n+                self.statusBar().showMessage(f\"Added {added_count} asset(s). Select a preset to update preview.\", 3000)\r\n  \r\n     def _browse_for_output_directory(self):\r\n         \"\"\"Opens a dialog to select the output directory.\"\"\"\r\n         current_path = self.output_path_edit.text()\r\n"
                },
                {
                    "date": 1745509017091,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -196,11 +196,8 @@\n         self._set_editor_enabled(False) # Disable editor initially\r\n         self.populate_presets() # Populate preset list\r\n         self.setup_logging_handler() # Setup the custom log handler\r\n \r\n-        # Set placeholder text for the preview table\r\n-        self.preview_table_view.setPlaceholderText(\"Please select a preset to view file predictions\")\r\n-\r\n         # --- Connect Editor Signals ---\r\n         self._connect_editor_change_signals()\r\n \r\n         # --- Adjust Splitter ---\r\n"
                },
                {
                    "date": 1745509032066,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -436,10 +436,23 @@\n         header.moveSection(header.visualIndex(self.preview_model.COL_ADDITIONAL_FILES), 4)\r\n         # Current visual: [0, 1, 4, 2, 5, 3] - This looks correct.\r\n \r\n         main_layout.addWidget(self.preview_label)\r\n-        main_layout.addWidget(self.preview_table_view, 1) # Add the table view to layout\r\n \r\n+        # Add placeholder label for the preview area\r\n+        self.preview_placeholder_label = QLabel(\"Please select a preset to view file predictions\")\r\n+        self.preview_placeholder_label.setAlignment(Qt.AlignmentFlag.AlignCenter)\r\n+        self.preview_placeholder_label.setStyleSheet(\"QLabel { font-size: 16px; color: grey; }\") # Optional styling\r\n+\r\n+        # Add both the table view and the placeholder label to the layout\r\n+        # We will manage their visibility later\r\n+        main_layout.addWidget(self.preview_placeholder_label, 1) # Give it stretch factor\r\n+        main_layout.addWidget(self.preview_table_view, 1) # Give it stretch factor\r\n+\r\n+        # Initially hide the table view and show the placeholder\r\n+        self.preview_table_view.setVisible(False)\r\n+        self.preview_placeholder_label.setVisible(True)\r\n+\r\n         # Apply style sheet to remove borders and rounded corners\r\n         self.preview_table_view.setStyleSheet(\"QTableView { border: none; }\")\r\n \r\n         # --- Progress Bar ---\r\n"
                },
                {
                    "date": 1745509042212,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1289,10 +1289,17 @@\n             self._load_preset_for_editing(preset_path)\r\n             # --- Trigger preview update after loading editor ---\r\n             self.update_preview()\r\n             # --- End Trigger ---\r\n+\r\n+            # Hide placeholder and show table view\r\n+            self.preview_placeholder_label.setVisible(False)\r\n+            self.preview_table_view.setVisible(True)\r\n         else:\r\n              self._clear_editor() # Clear editor if selection is cleared\r\n+             # Ensure placeholder is visible if no preset is selected\r\n+             self.preview_placeholder_label.setVisible(True)\r\n+             self.preview_table_view.setVisible(False)\r\n \r\n     def _gather_editor_data(self) -> dict:\r\n         \"\"\"Gathers data from all editor UI widgets and returns a dictionary.\"\"\"\r\n         preset_data = {}\r\n"
                },
                {
                    "date": 1745509439389,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -552,17 +552,16 @@\n     # --- Preset Population and Handling ---\r\n \r\n     def populate_presets(self):\r\n         \"\"\"Scans presets dir and populates BOTH the editor list and processing combo.\"\"\"\r\n-        log.debug(\"Populating preset list...\") # Updated log message\r\n+        log.debug(\"Populating preset list...\")\r\n         # Store current list selection\r\n-        # current_combo_selection = self.preset_combo.currentText() # REMOVED\r\n         current_list_item = self.editor_preset_list.currentItem()\r\n         current_list_selection_text = current_list_item.text() if current_list_item else None\r\n \r\n         # Clear list\r\n-        # self.preset_combo.clear() # REMOVED\r\n         self.editor_preset_list.clear()\r\n+        log.debug(\"Preset list cleared.\")\r\n \r\n         if not PRESETS_DIR.is_dir():\r\n             msg = f\"Error: Presets directory not found at {PRESETS_DIR}\"\r\n             self.statusBar().showMessage(msg)\r\n@@ -591,10 +590,13 @@\n             # if combo_index != -1: # REMOVED\r\n             #     self.preset_combo.setCurrentIndex(combo_index) # REMOVED\r\n \r\n             # Do NOT attempt to restore list selection by default\r\n+            # Do NOT attempt to restore list selection by default\r\n             # The user must explicitly select a preset to trigger preview\r\n-            pass\r\n+            log.debug(\"Preset list populated. Clearing selection.\")\r\n+            self.editor_preset_list.setCurrentItem(None) # Explicitly clear selection\r\n+            self.editor_preset_list.clearSelection() # Also clear selection state\r\n \r\n     # --- Drag and Drop Event Handlers (Unchanged) ---\r\n     def dragEnterEvent(self, event):\r\n         if event.mimeData().hasUrls(): event.acceptProposedAction()\r\n@@ -1256,8 +1258,9 @@\n         if not file_path or not file_path.is_file():\r\n             self._clear_editor()\r\n             return\r\n         log.info(f\"Loading preset into editor: {file_path.name}\")\r\n+        log.info(f\"Loading preset into editor: {file_path.name}\")\r\n         try:\r\n             with open(file_path, 'r', encoding='utf-8') as f: preset_data = json.load(f)\r\n             self._populate_editor_from_data(preset_data)\r\n             self._set_editor_enabled(True)\r\n@@ -1265,8 +1268,11 @@\n             self.editor_unsaved_changes = False\r\n             self.editor_save_button.setEnabled(False)\r\n             self.setWindowTitle(f\"Asset Processor Tool - {file_path.name}\")\r\n             log.info(f\"Preset '{file_path.name}' loaded into editor.\")\r\n+            log.debug(\"Preset loaded. Checking visibility states.\")\r\n+            log.debug(f\"preview_placeholder_label visible: {self.preview_placeholder_label.isVisible()}\")\r\n+            log.debug(f\"preview_table_view visible: {self.preview_table_view.isVisible()}\")\r\n         except json.JSONDecodeError as json_err:\r\n              log.error(f\"Invalid JSON in {file_path.name}: {json_err}\")\r\n              QMessageBox.warning(self, \"Load Error\", f\"Failed to load preset '{file_path.name}'.\\nInvalid JSON structure:\\n{json_err}\")\r\n              self._clear_editor()\r\n@@ -1276,28 +1282,34 @@\n             self._clear_editor()\r\n \r\n     def _load_selected_preset_for_editing(self, current_item: QListWidgetItem, previous_item: QListWidgetItem):\r\n         \"\"\"Loads the preset currently selected in the editor list.\"\"\"\r\n+        log.debug(f\"currentItemChanged signal triggered. current_item: {current_item.text() if current_item else 'None'}, previous_item: {previous_item.text() if previous_item else 'None'}\")\r\n         if self._check_editor_unsaved_changes():\r\n             # If user cancels, revert selection\r\n             if previous_item:\r\n+                log.debug(\"Unsaved changes check cancelled. Reverting selection.\")\r\n                 self.editor_preset_list.blockSignals(True)\r\n                 self.editor_preset_list.setCurrentItem(previous_item)\r\n                 self.editor_preset_list.blockSignals(False)\r\n             return\r\n         if current_item:\r\n+            log.debug(f\"Loading preset for editing: {current_item.text()}\")\r\n             preset_path = current_item.data(Qt.ItemDataRole.UserRole)\r\n             self._load_preset_for_editing(preset_path)\r\n             # --- Trigger preview update after loading editor ---\r\n             self.update_preview()\r\n             # --- End Trigger ---\r\n \r\n             # Hide placeholder and show table view\r\n+            log.debug(\"Preset selected. Hiding placeholder, showing table view.\")\r\n             self.preview_placeholder_label.setVisible(False)\r\n             self.preview_table_view.setVisible(True)\r\n         else:\r\n+             log.debug(\"No preset selected. Clearing editor.\")\r\n              self._clear_editor() # Clear editor if selection is cleared\r\n              # Ensure placeholder is visible if no preset is selected\r\n+             log.debug(\"No preset selected. Showing placeholder, hiding table view.\")\r\n              self.preview_placeholder_label.setVisible(True)\r\n              self.preview_table_view.setVisible(False)\r\n \r\n     def _gather_editor_data(self) -> dict:\r\n"
                },
                {
                    "date": 1745509450464,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1194,8 +1194,15 @@\n         self.editor_unsaved_changes = False\r\n         self.editor_save_button.setEnabled(False)\r\n         self.setWindowTitle(\"Asset Processor Tool\") # Reset window title\r\n         self._set_editor_enabled(False)\r\n+\r\n+        # Ensure placeholder is visible and table is hidden when editor is cleared\r\n+        if hasattr(self, 'preview_placeholder_label') and hasattr(self, 'preview_table_view'):\r\n+            log.debug(\"Clearing editor. Showing placeholder, hiding table view.\")\r\n+            self.preview_placeholder_label.setVisible(True)\r\n+            self.preview_table_view.setVisible(False)\r\n+\r\n         self._is_loading_editor = False\r\n \r\n     def _populate_editor_from_data(self, preset_data: dict):\r\n         \"\"\"Helper method to populate editor UI widgets from a preset data dictionary.\"\"\"\r\n"
                },
                {
                    "date": 1745509617164,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -561,8 +561,15 @@\n         # Clear list\r\n         self.editor_preset_list.clear()\r\n         log.debug(\"Preset list cleared.\")\r\n \r\n+        # Add the \"Select a Preset\" placeholder item\r\n+        placeholder_item = QListWidgetItem(\"--- Select a Preset ---\")\r\n+        # Make it non-selectable and non-editable\r\n+        placeholder_item.setFlags(placeholder_item.flags() & ~Qt.ItemFlag.ItemIsSelectable & ~Qt.ItemFlag.ItemIsEditable)\r\n+        self.editor_preset_list.addItem(placeholder_item)\r\n+        log.debug(\"Added '--- Select a Preset ---' placeholder item.\")\r\n+\r\n         if not PRESETS_DIR.is_dir():\r\n             msg = f\"Error: Presets directory not found at {PRESETS_DIR}\"\r\n             self.statusBar().showMessage(msg)\r\n             log.error(msg)\r\n@@ -590,14 +597,14 @@\n             # if combo_index != -1: # REMOVED\r\n             #     self.preset_combo.setCurrentIndex(combo_index) # REMOVED\r\n \r\n             # Do NOT attempt to restore list selection by default\r\n-            # Do NOT attempt to restore list selection by default\r\n-            # The user must explicitly select a preset to trigger preview\r\n-            log.debug(\"Preset list populated. Clearing selection.\")\r\n-            self.editor_preset_list.setCurrentItem(None) # Explicitly clear selection\r\n-            self.editor_preset_list.clearSelection() # Also clear selection state\r\n+            self.statusBar().showMessage(f\"Loaded {len(presets)} presets.\")\r\n \r\n+            # Select the \"Select a Preset\" item by default\r\n+            log.debug(\"Preset list populated. Selecting '--- Select a Preset ---' item.\")\r\n+            self.editor_preset_list.setCurrentItem(placeholder_item) # Select the placeholder item\r\n+\r\n     # --- Drag and Drop Event Handlers (Unchanged) ---\r\n     def dragEnterEvent(self, event):\r\n         if event.mimeData().hasUrls(): event.acceptProposedAction()\r\n         else: event.ignore()\r\n"
                },
                {
                    "date": 1745514156207,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -565,8 +565,10 @@\n         # Add the \"Select a Preset\" placeholder item\r\n         placeholder_item = QListWidgetItem(\"--- Select a Preset ---\")\r\n         # Make it non-selectable and non-editable\r\n         placeholder_item.setFlags(placeholder_item.flags() & ~Qt.ItemFlag.ItemIsSelectable & ~Qt.ItemFlag.ItemIsEditable)\r\n+        # Set unique data to identify the placeholder\r\n+        placeholder_item.setData(Qt.ItemDataRole.UserRole, \"__PLACEHOLDER__\")\r\n         self.editor_preset_list.addItem(placeholder_item)\r\n         log.debug(\"Added '--- Select a Preset ---' placeholder item.\")\r\n \r\n         if not PRESETS_DIR.is_dir():\r\n@@ -695,13 +697,19 @@\n             self.statusBar().showMessage(\"No assets added to process.\", 3000)\r\n             return\r\n         # --- Get preset from editor list ---\r\n         current_editor_item = self.editor_preset_list.currentItem()\r\n+\r\n+        # Check if the selected item is the placeholder\r\n+        is_placeholder = current_editor_item and current_editor_item.data(Qt.ItemDataRole.UserRole) == \"__PLACEHOLDER__\"\r\n+\r\n+        if is_placeholder:\r\n+            self.statusBar().showMessage(\"Please select a valid preset from the list on the left.\", 3000)\r\n+            log.warning(\"Start processing failed: Placeholder preset selected.\")\r\n+            return\r\n+\r\n+        # Existing logic to get selected_preset text and proceed\r\n         selected_preset = current_editor_item.text() if current_editor_item else None\r\n-        # --- End Get preset ---\r\n-        if not selected_preset:\r\n-            self.statusBar().showMessage(\"Please select a preset from the list on the left.\", 3000) # Updated message\r\n-            return\r\n         overwrite = self.overwrite_checkbox.isChecked()\r\n         num_workers = self.workers_spinbox.value()\r\n \r\n         # --- Get Output Directory from UI and Validate ---\r\n@@ -926,8 +934,23 @@\n                 self.statusBar().showMessage(\"Error: Prediction components not loaded.\", 5000)\r\n                 return\r\n             # Get preset from editor list\r\n             current_editor_item = self.editor_preset_list.currentItem()\r\n+\r\n+            # Check if the selected item is the placeholder\r\n+            is_placeholder = current_editor_item and current_editor_item.data(Qt.ItemDataRole.UserRole) == \"__PLACEHOLDER__\"\r\n+\r\n+            if is_placeholder:\r\n+                log.debug(\"Update preview called with placeholder preset selected. Clearing preview.\")\r\n+                self.preview_model.clear_data() # Clear model if placeholder selected\r\n+                self.statusBar().showMessage(\"Select a preset from the list on the left to update preview.\", 3000)\r\n+                # Ensure placeholder is visible and table is hidden\r\n+                if hasattr(self, 'preview_placeholder_label') and hasattr(self, 'preview_table_view'):\r\n+                     self.preview_placeholder_label.setVisible(True)\r\n+                     self.preview_table_view.setVisible(False)\r\n+                return # Stop prediction as no valid preset is selected\r\n+\r\n+            # Existing logic to get selected_preset text and proceed\r\n             selected_preset = current_editor_item.text() if current_editor_item else None\r\n             if not selected_preset:\r\n                 log.debug(\"Update preview called with no preset selected in the editor list.\")\r\n                 self.preview_model.clear_data() # Clear model if no preset selected\r\n@@ -1297,16 +1320,31 @@\n \r\n     def _load_selected_preset_for_editing(self, current_item: QListWidgetItem, previous_item: QListWidgetItem):\r\n         \"\"\"Loads the preset currently selected in the editor list.\"\"\"\r\n         log.debug(f\"currentItemChanged signal triggered. current_item: {current_item.text() if current_item else 'None'}, previous_item: {previous_item.text() if previous_item else 'None'}\")\r\n+\r\n+        # Check if the selected item is the placeholder\r\n+        is_placeholder = current_item and current_item.data(Qt.ItemDataRole.UserRole) == \"__PLACEHOLDER__\"\r\n+\r\n         if self._check_editor_unsaved_changes():\r\n             # If user cancels, revert selection\r\n             if previous_item:\r\n                 log.debug(\"Unsaved changes check cancelled. Reverting selection.\")\r\n                 self.editor_preset_list.blockSignals(True)\r\n                 self.editor_preset_list.setCurrentItem(previous_item)\r\n                 self.editor_preset_list.blockSignals(False)\r\n             return\r\n+\r\n+        if is_placeholder:\r\n+            log.debug(\"Placeholder item selected. Clearing editor and preview.\")\r\n+            self._clear_editor() # This also hides the table and shows the placeholder label\r\n+            self.preview_model.clear_data() # Ensure the model is empty\r\n+            # Visibility is handled by _clear_editor, but explicitly set here for clarity\r\n+            self.preview_placeholder_label.setVisible(True)\r\n+            self.preview_table_view.setVisible(False)\r\n+            return # Stop processing as no real preset is selected\r\n+\r\n+        # Existing logic for handling real preset items starts here\r\n         if current_item:\r\n             log.debug(f\"Loading preset for editing: {current_item.text()}\")\r\n             preset_path = current_item.data(Qt.ItemDataRole.UserRole)\r\n             self._load_preset_for_editing(preset_path)\r\n@@ -1314,13 +1352,14 @@\n             self.update_preview()\r\n             # --- End Trigger ---\r\n \r\n             # Hide placeholder and show table view\r\n-            log.debug(\"Preset selected. Hiding placeholder, showing table view.\")\r\n+            log.debug(\"Real preset selected. Hiding placeholder, showing table view.\")\r\n             self.preview_placeholder_label.setVisible(False)\r\n             self.preview_table_view.setVisible(True)\r\n         else:\r\n-             log.debug(\"No preset selected. Clearing editor.\")\r\n+             # This case should ideally not be reached if the placeholder is always present\r\n+             log.debug(\"No preset selected (unexpected state if placeholder is present). Clearing editor.\")\r\n              self._clear_editor() # Clear editor if selection is cleared\r\n              # Ensure placeholder is visible if no preset is selected\r\n              log.debug(\"No preset selected. Showing placeholder, hiding table view.\")\r\n              self.preview_placeholder_label.setVisible(True)\r\n"
                },
                {
                    "date": 1745514211949,
                    "content": "Index: \n===================================================================\n--- \n+++ \n@@ -1340,15 +1340,17 @@\n             self.preview_model.clear_data() # Ensure the model is empty\r\n             # Visibility is handled by _clear_editor, but explicitly set here for clarity\r\n             self.preview_placeholder_label.setVisible(True)\r\n             self.preview_table_view.setVisible(False)\r\n+            self.start_button.setEnabled(False) # Disable start button\r\n             return # Stop processing as no real preset is selected\r\n \r\n         # Existing logic for handling real preset items starts here\r\n         if current_item:\r\n             log.debug(f\"Loading preset for editing: {current_item.text()}\")\r\n             preset_path = current_item.data(Qt.ItemDataRole.UserRole)\r\n             self._load_preset_for_editing(preset_path)\r\n+            self.start_button.setEnabled(True) # Enable start button\r\n             # --- Trigger preview update after loading editor ---\r\n             self.update_preview()\r\n             # --- End Trigger ---\r\n \r\n"
                }
            ],
            "date": 1745236085615,
            "name": "Commit-0",
            "content": "import sys\r\nimport os\r\nimport json\r\nimport logging\r\nimport time\r\nfrom pathlib import Path\r\nfrom functools import partial # For connecting signals with arguments\r\n\r\nfrom PySide6.QtWidgets import (\r\n    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, # Added QSplitter\r\n    QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView,\r\n    QProgressBar, QLabel, QFrame, QCheckBox, QSpinBox, QListWidget, QTextEdit, # Added QListWidget, QTextEdit\r\n    QLineEdit, QMessageBox, QFileDialog, QInputDialog, QListWidgetItem, QTabWidget, # Added more widgets\r\n    QFormLayout, QGroupBox, QAbstractItemView, QSizePolicy # Added more layout/widget items\r\n)\r\nfrom PySide6.QtCore import Qt, QThread, Slot, Signal # Added Signal\r\nfrom PySide6.QtGui import QColor # Add QColor import\r\n\r\n# --- Backend Imports ---\r\nscript_dir = Path(__file__).parent\r\nproject_root = script_dir.parent\r\nif str(project_root) not in sys.path:\r\n    sys.path.insert(0, str(project_root))\r\n\r\ntry:\r\n    from configuration import Configuration, ConfigurationError\r\n    from asset_processor import AssetProcessor, AssetProcessingError\r\n    from gui.processing_handler import ProcessingHandler\r\n    from gui.prediction_handler import PredictionHandler\r\n    import config as core_config # Import the config module\r\n    # PresetEditorDialog is no longer needed\r\nexcept ImportError as e:\r\n    print(f\"ERROR: Failed to import backend modules: {e}\")\r\n    print(f\"Ensure GUI is run from project root or backend modules are in PYTHONPATH.\")\r\n    Configuration = None\r\n    AssetProcessor = None\r\n    ProcessingHandler = None\r\n    PredictionHandler = None\r\n    ConfigurationError = Exception\r\n    AssetProcessingError = Exception\r\n\r\n# --- Constants ---\r\nPRESETS_DIR = project_root / \"presets\"\r\nTEMPLATE_PATH = PRESETS_DIR / \"_template.json\"\r\n\r\n# Setup basic logging\r\nlog = logging.getLogger(__name__)\r\nif not log.hasHandlers():\r\n     # Set level back to INFO for normal operation\r\n     logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') # Reverted level and format\r\n\r\n\r\n# --- Helper Functions (from PresetEditorDialog) ---\r\n# NOTE: Consider moving these to a utils file if reused elsewhere\r\n\r\ndef setup_list_widget_with_controls(parent_layout, label_text, attribute_name, instance):\r\n    \"\"\"Adds a QListWidget with Add/Remove buttons to a layout.\"\"\"\r\n    list_widget = QListWidget()\r\n    list_widget.setAlternatingRowColors(True)\r\n    # Make items editable by default in the editor context\r\n    list_widget.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.SelectedClicked | QAbstractItemView.EditTrigger.EditKeyPressed)\r\n    instance.__setattr__(attribute_name, list_widget) # Store list widget on the instance\r\n\r\n    add_button = QPushButton(\"+\")\r\n    remove_button = QPushButton(\"-\")\r\n    add_button.setFixedWidth(30)\r\n    remove_button.setFixedWidth(30)\r\n\r\n    button_layout = QVBoxLayout()\r\n    button_layout.addWidget(add_button)\r\n    button_layout.addWidget(remove_button)\r\n    button_layout.addStretch()\r\n\r\n    list_layout = QHBoxLayout()\r\n    list_layout.addWidget(list_widget)\r\n    list_layout.addLayout(button_layout)\r\n\r\n    group_box = QGroupBox(label_text)\r\n    group_box_layout = QVBoxLayout(group_box)\r\n    group_box_layout.addLayout(list_layout)\r\n\r\n    parent_layout.addWidget(group_box)\r\n\r\n    # Connections (use the instance's methods)\r\n    add_button.clicked.connect(partial(instance._editor_add_list_item, list_widget))\r\n    remove_button.clicked.connect(partial(instance._editor_remove_list_item, list_widget))\r\n    list_widget.itemChanged.connect(instance._mark_editor_unsaved) # Mark unsaved on item edit\r\n\r\ndef setup_table_widget_with_controls(parent_layout, label_text, attribute_name, columns, instance):\r\n    \"\"\"Adds a QTableWidget with Add/Remove buttons to a layout.\"\"\"\r\n    table_widget = QTableWidget()\r\n    table_widget.setColumnCount(len(columns))\r\n    table_widget.setHorizontalHeaderLabels(columns)\r\n    table_widget.setAlternatingRowColors(True)\r\n    instance.__setattr__(attribute_name, table_widget) # Store table widget\r\n\r\n    add_button = QPushButton(\"+ Row\")\r\n    remove_button = QPushButton(\"- Row\")\r\n\r\n    button_layout = QHBoxLayout()\r\n    button_layout.addStretch()\r\n    button_layout.addWidget(add_button)\r\n    button_layout.addWidget(remove_button)\r\n\r\n    group_box = QGroupBox(label_text)\r\n    group_box_layout = QVBoxLayout(group_box)\r\n    group_box_layout.addWidget(table_widget)\r\n    group_box_layout.addLayout(button_layout)\r\n\r\n    parent_layout.addWidget(group_box)\r\n\r\n    # Connections (use the instance's methods)\r\n    add_button.clicked.connect(partial(instance._editor_add_table_row, table_widget))\r\n    remove_button.clicked.connect(partial(instance._editor_remove_table_row, table_widget))\r\n    table_widget.itemChanged.connect(instance._mark_editor_unsaved) # Mark unsaved on item edit\r\n\r\n\r\n# --- Main Window Class ---\r\n\r\nclass MainWindow(QMainWindow):\r\n    # Signal emitted when presets change in the editor panel\r\n    presets_changed_signal = Signal()\r\n\r\n    def __init__(self):\r\n        super().__init__()\r\n\r\n        self.setWindowTitle(\"Asset Processor Tool\")\r\n        self.resize(1200, 700) # Increased default size\r\n\r\n        # --- Internal State ---\r\n        self.current_asset_paths = set() # Store unique paths of assets added\r\n\r\n        # --- Editor State ---\r\n        self.current_editing_preset_path = None\r\n        self.editor_unsaved_changes = False\r\n        self._is_loading_editor = False # Flag to prevent signals during load\r\n\r\n        # --- Threading Setup ---\r\n        self.processing_thread = None\r\n        self.processing_handler = None\r\n        self.prediction_thread = None\r\n        self.prediction_handler = None\r\n        self.setup_threads()\r\n\r\n        # --- Main Layout with Splitter ---\r\n        self.splitter = QSplitter(Qt.Orientation.Horizontal)\r\n        self.setCentralWidget(self.splitter)\r\n\r\n        # --- Create Panels ---\r\n        self.editor_panel = QWidget()\r\n        self.main_panel = QWidget()\r\n\r\n        self.splitter.addWidget(self.editor_panel)\r\n        self.splitter.addWidget(self.main_panel)\r\n\r\n        # --- Setup UI Elements for each panel ---\r\n        self.setup_editor_panel_ui()\r\n        self.setup_main_panel_ui()\r\n\r\n        # --- Status Bar ---\r\n        self.statusBar().showMessage(\"Ready\")\r\n\r\n        # --- Initial State ---\r\n        self._clear_editor() # Clear/disable editor fields initially\r\n        self._set_editor_enabled(False) # Disable editor initially\r\n        self.populate_presets() # Populate both preset list and combo box\r\n\r\n        # --- Connect Editor Signals ---\r\n        self._connect_editor_change_signals()\r\n\r\n        # --- Adjust Splitter ---\r\n        self.splitter.setSizes([400, 800]) # Initial size ratio\r\n\r\n    # --- UI Setup Methods ---\r\n\r\n    def setup_editor_panel_ui(self):\r\n        \"\"\"Sets up the UI elements for the left preset editor panel.\"\"\"\r\n        editor_layout = QVBoxLayout(self.editor_panel)\r\n        editor_layout.setContentsMargins(5, 5, 5, 5) # Reduce margins\r\n\r\n        # Preset List and Controls\r\n        list_layout = QVBoxLayout()\r\n        list_layout.addWidget(QLabel(\"Presets:\"))\r\n        self.editor_preset_list = QListWidget()\r\n        self.editor_preset_list.currentItemChanged.connect(self._load_selected_preset_for_editing)\r\n        list_layout.addWidget(self.editor_preset_list)\r\n\r\n        list_button_layout = QHBoxLayout()\r\n        self.editor_new_button = QPushButton(\"New\")\r\n        self.editor_delete_button = QPushButton(\"Delete\")\r\n        self.editor_new_button.clicked.connect(self._new_preset)\r\n        self.editor_delete_button.clicked.connect(self._delete_selected_preset)\r\n        list_button_layout.addWidget(self.editor_new_button)\r\n        list_button_layout.addWidget(self.editor_delete_button)\r\n        list_layout.addLayout(list_button_layout)\r\n        editor_layout.addLayout(list_layout, 1) # Allow list to stretch\r\n\r\n        # Editor Tabs\r\n        self.editor_tab_widget = QTabWidget()\r\n        self.editor_tab_general_naming = QWidget()\r\n        self.editor_tab_mapping_rules = QWidget()\r\n        self.editor_tab_widget.addTab(self.editor_tab_general_naming, \"General & Naming\")\r\n        self.editor_tab_widget.addTab(self.editor_tab_mapping_rules, \"Mapping & Rules\")\r\n        self._create_editor_general_tab()\r\n        self._create_editor_mapping_tab()\r\n        editor_layout.addWidget(self.editor_tab_widget, 3) # Allow tabs to stretch more\r\n\r\n        # Save Buttons\r\n        save_button_layout = QHBoxLayout()\r\n        self.editor_save_button = QPushButton(\"Save\")\r\n        self.editor_save_as_button = QPushButton(\"Save As...\")\r\n        self.editor_save_button.setEnabled(False) # Disabled initially\r\n        self.editor_save_button.clicked.connect(self._save_current_preset)\r\n        self.editor_save_as_button.clicked.connect(self._save_preset_as)\r\n        save_button_layout.addStretch()\r\n        save_button_layout.addWidget(self.editor_save_button)\r\n        save_button_layout.addWidget(self.editor_save_as_button)\r\n        editor_layout.addLayout(save_button_layout)\r\n\r\n    def _create_editor_general_tab(self):\r\n        \"\"\"Creates the widgets and layout for the 'General & Naming' editor tab.\"\"\"\r\n        layout = QVBoxLayout(self.editor_tab_general_naming)\r\n        form_layout = QFormLayout()\r\n        form_layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)\r\n\r\n        # Basic Info\r\n        self.editor_preset_name = QLineEdit()\r\n        self.editor_supplier_name = QLineEdit()\r\n        self.editor_notes = QTextEdit()\r\n        self.editor_notes.setAcceptRichText(False)\r\n        self.editor_notes.setFixedHeight(60)\r\n        form_layout.addRow(\"Preset Name:\", self.editor_preset_name)\r\n        form_layout.addRow(\"Supplier Name:\", self.editor_supplier_name)\r\n        form_layout.addRow(\"Notes:\", self.editor_notes)\r\n        layout.addLayout(form_layout)\r\n\r\n        # Source Naming Group\r\n        naming_group = QGroupBox(\"Source File Naming Rules\")\r\n        naming_layout_outer = QVBoxLayout(naming_group)\r\n        naming_layout_form = QFormLayout()\r\n        self.editor_separator = QLineEdit()\r\n        self.editor_separator.setMaxLength(1)\r\n        self.editor_spin_base_name_idx = QSpinBox()\r\n        self.editor_spin_base_name_idx.setMinimum(-1)\r\n        self.editor_spin_map_type_idx = QSpinBox()\r\n        self.editor_spin_map_type_idx.setMinimum(-1)\r\n        naming_layout_form.addRow(\"Separator:\", self.editor_separator)\r\n        naming_layout_form.addRow(\"Base Name Index:\", self.editor_spin_base_name_idx)\r\n        naming_layout_form.addRow(\"Map Type Index:\", self.editor_spin_map_type_idx)\r\n        naming_layout_outer.addLayout(naming_layout_form)\r\n        # Gloss Keywords List\r\n        setup_list_widget_with_controls(naming_layout_outer, \"Glossiness Keywords\", \"editor_list_gloss_keywords\", self)\r\n        # Bit Depth Variants Table\r\n        setup_table_widget_with_controls(naming_layout_outer, \"16-bit Variant Patterns\", \"editor_table_bit_depth_variants\", [\"Map Type\", \"Pattern\"], self)\r\n        self.editor_table_bit_depth_variants.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)\r\n        self.editor_table_bit_depth_variants.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)\r\n        layout.addWidget(naming_group)\r\n\r\n        # Extra Files Group\r\n        setup_list_widget_with_controls(layout, \"Move to 'Extra' Folder Patterns\", \"editor_list_extra_patterns\", self)\r\n\r\n        layout.addStretch(1)\r\n\r\n    def _create_editor_mapping_tab(self):\r\n        \"\"\"Creates the widgets and layout for the 'Mapping & Rules' editor tab.\"\"\"\r\n        layout = QVBoxLayout(self.editor_tab_mapping_rules)\r\n\r\n        # Map Type Mapping Group\r\n        setup_table_widget_with_controls(layout, \"Map Type Mapping (Standard Type <- Input Keywords)\", \"editor_table_map_type_mapping\", [\"Standard Type\", \"Input Keywords (comma-sep)\"], self)\r\n        self.editor_table_map_type_mapping.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)\r\n        self.editor_table_map_type_mapping.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)\r\n\r\n        # Category Rules Group\r\n        category_group = QGroupBox(\"Asset Category Rules\")\r\n        category_layout = QVBoxLayout(category_group)\r\n        setup_list_widget_with_controls(category_layout, \"Model File Patterns\", \"editor_list_model_patterns\", self)\r\n        setup_list_widget_with_controls(category_layout, \"Decal Keywords\", \"editor_list_decal_keywords\", self)\r\n        layout.addWidget(category_group)\r\n\r\n        # Archetype Rules Group\r\n        setup_table_widget_with_controls(layout, \"Archetype Rules\", \"editor_table_archetype_rules\", [\"Archetype Name\", \"Match Any (comma-sep)\", \"Match All (comma-sep)\"], self)\r\n        self.editor_table_archetype_rules.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)\r\n        self.editor_table_archetype_rules.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)\r\n        self.editor_table_archetype_rules.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)\r\n\r\n        layout.addStretch(1)\r\n\r\n    def setup_main_panel_ui(self):\r\n        \"\"\"Sets up the UI elements for the right main processing panel.\"\"\"\r\n        main_layout = QVBoxLayout(self.main_panel)\r\n        main_layout.setContentsMargins(5, 5, 5, 5) # Reduce margins\r\n\r\n        # --- Top Section: Preset Selection (for processing) ---\r\n        top_layout = QHBoxLayout()\r\n        self.preset_label = QLabel(\"Preset (for processing):\")\r\n        self.preset_combo = QComboBox() # This is for selecting the preset to USE for processing\r\n        self.preset_combo.currentIndexChanged.connect(self.update_preview) # Update preview when processing preset changes\r\n        top_layout.addWidget(self.preset_label)\r\n        top_layout.addWidget(self.preset_combo, 1)\r\n        main_layout.addLayout(top_layout)\r\n\r\n        # --- Output Directory Selection ---\r\n        output_layout = QHBoxLayout()\r\n        self.output_dir_label = QLabel(\"Output Directory:\")\r\n        self.output_path_edit = QLineEdit()\r\n        # Make read-only for now, user must use Browse\r\n        # self.output_path_edit.setReadOnly(True)\r\n        self.browse_output_button = QPushButton(\"Browse...\")\r\n        self.browse_output_button.clicked.connect(self._browse_for_output_directory)\r\n        output_layout.addWidget(self.output_dir_label)\r\n        output_layout.addWidget(self.output_path_edit, 1)\r\n        output_layout.addWidget(self.browse_output_button)\r\n        main_layout.addLayout(output_layout)\r\n\r\n        # --- Set Initial Output Path ---\r\n        try:\r\n            output_base_dir_config = getattr(core_config, 'OUTPUT_BASE_DIR', '../Asset_Processor_Output') # Default if not found\r\n            # Resolve the path relative to the project root\r\n            default_output_dir = (project_root / output_base_dir_config).resolve()\r\n            self.output_path_edit.setText(str(default_output_dir))\r\n            log.info(f\"Default output directory set to: {default_output_dir}\")\r\n        except Exception as e:\r\n            log.error(f\"Error setting default output directory: {e}\")\r\n            self.output_path_edit.setText(\"\") # Clear on error\r\n            self.statusBar().showMessage(f\"Error setting default output path: {e}\", 5000)\r\n\r\n\r\n        # --- Drag and Drop Area ---\r\n        self.drag_drop_area = QFrame()\r\n        self.drag_drop_area.setFrameShape(QFrame.Shape.StyledPanel)\r\n        self.drag_drop_area.setFrameShadow(QFrame.Shadow.Sunken)\r\n        drag_drop_layout = QVBoxLayout(self.drag_drop_area)\r\n        drag_drop_label = QLabel(\"Drag and Drop Asset Files/Folders Here\")\r\n        drag_drop_label.setAlignment(Qt.AlignmentFlag.AlignCenter)\r\n        drag_drop_layout.addWidget(drag_drop_label)\r\n        self.drag_drop_area.setMinimumHeight(100)\r\n        self.setAcceptDrops(True) # Main window handles drops initially\r\n        main_layout.addWidget(self.drag_drop_area)\r\n\r\n        # --- Preview Area (Table) ---\r\n        self.preview_label = QLabel(\"File Preview (using selected processing preset):\")\r\n        self.preview_table = QTableWidget()\r\n        self.preview_table.setColumnCount(4)\r\n        # New column order: Status, Original Path, Predicted Name, Details\r\n        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n        # Adjust resize modes\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) # Predicted Name\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Details (Slimmer)\r\n        self.preview_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)\r\n        self.preview_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)\r\n        self.preview_table.setAlternatingRowColors(True)\r\n        self.preview_table.setSortingEnabled(True)\r\n        main_layout.addWidget(self.preview_label)\r\n        main_layout.addWidget(self.preview_table, 1) # Allow table to stretch\r\n\r\n        # --- Progress Bar ---\r\n        self.progress_bar = QProgressBar()\r\n        self.progress_bar.setValue(0)\r\n        self.progress_bar.setTextVisible(True)\r\n        main_layout.addWidget(self.progress_bar)\r\n\r\n        # --- Bottom Controls ---\r\n        bottom_controls_layout = QHBoxLayout()\r\n        self.overwrite_checkbox = QCheckBox(\"Overwrite Existing\")\r\n        self.overwrite_checkbox.setToolTip(\"If checked, existing output folders for processed assets will be deleted and replaced.\")\r\n        bottom_controls_layout.addWidget(self.overwrite_checkbox)\r\n\r\n        self.disable_preview_checkbox = QCheckBox(\"Disable Detailed Preview\")\r\n        self.disable_preview_checkbox.setToolTip(\"If checked, shows only the list of input assets instead of detailed file predictions.\")\r\n        self.disable_preview_checkbox.setChecked(False) # Default is detailed preview enabled\r\n        self.disable_preview_checkbox.toggled.connect(self.update_preview) # Update preview when toggled\r\n        bottom_controls_layout.addWidget(self.disable_preview_checkbox)\r\n\r\n        bottom_controls_layout.addSpacing(20) # Add some space\r\n\r\n        self.workers_label = QLabel(\"Workers:\")\r\n        self.workers_spinbox = QSpinBox()\r\n        default_workers = 1\r\n        try:\r\n            cores = os.cpu_count()\r\n            if cores: default_workers = max(1, cores // 2)\r\n        except NotImplementedError: pass\r\n        self.workers_spinbox.setMinimum(1)\r\n        self.workers_spinbox.setMaximum(os.cpu_count() or 32)\r\n        self.workers_spinbox.setValue(default_workers)\r\n        self.workers_spinbox.setToolTip(\"Number of assets to process concurrently.\")\r\n        bottom_controls_layout.addWidget(self.workers_label)\r\n        bottom_controls_layout.addWidget(self.workers_spinbox)\r\n        bottom_controls_layout.addStretch(1)\r\n        self.clear_queue_button = QPushButton(\"Clear Queue\") # Added Clear button\r\n        self.start_button = QPushButton(\"Start Processing\")\r\n        self.cancel_button = QPushButton(\"Cancel\")\r\n        self.cancel_button.setEnabled(False)\r\n        self.clear_queue_button.clicked.connect(self.clear_queue) # Connect signal\r\n        self.start_button.clicked.connect(self.start_processing)\r\n        self.cancel_button.clicked.connect(self.cancel_processing)\r\n        bottom_controls_layout.addWidget(self.clear_queue_button) # Add button to layout\r\n        bottom_controls_layout.addWidget(self.start_button)\r\n        bottom_controls_layout.addWidget(self.cancel_button)\r\n        main_layout.addLayout(bottom_controls_layout)\r\n\r\n    # --- Preset Population and Handling ---\r\n\r\n    def populate_presets(self):\r\n        \"\"\"Scans presets dir and populates BOTH the editor list and processing combo.\"\"\"\r\n        log.debug(\"Populating preset list and combo box...\")\r\n        # Store current selections\r\n        current_combo_selection = self.preset_combo.currentText()\r\n        current_list_item = self.editor_preset_list.currentItem()\r\n        current_list_selection_text = current_list_item.text() if current_list_item else None\r\n\r\n        # Clear both\r\n        self.preset_combo.clear()\r\n        self.editor_preset_list.clear()\r\n\r\n        if not PRESETS_DIR.is_dir():\r\n            msg = f\"Error: Presets directory not found at {PRESETS_DIR}\"\r\n            self.statusBar().showMessage(msg)\r\n            log.error(msg)\r\n            return\r\n\r\n        # Exclude template file starting with _\r\n        presets = sorted([f for f in PRESETS_DIR.glob(\"*.json\") if f.is_file() and not f.name.startswith('_')])\r\n        preset_names = [p.stem for p in presets]\r\n\r\n        if not presets:\r\n            msg = \"Warning: No presets found in presets directory.\"\r\n            self.statusBar().showMessage(msg)\r\n            log.warning(msg)\r\n        else:\r\n            # Populate Combo Box (for processing)\r\n            self.preset_combo.addItems(preset_names)\r\n            # Populate List Widget (for editing)\r\n            for preset_path in presets:\r\n                item = QListWidgetItem(preset_path.stem)\r\n                item.setData(Qt.ItemDataRole.UserRole, preset_path) # Store full path\r\n                self.editor_preset_list.addItem(item)\r\n\r\n            self.statusBar().showMessage(f\"Loaded {len(presets)} presets.\")\r\n\r\n            # Try to restore selections\r\n            combo_index = self.preset_combo.findText(current_combo_selection)\r\n            if combo_index != -1:\r\n                self.preset_combo.setCurrentIndex(combo_index)\r\n\r\n            if current_list_selection_text:\r\n                items = self.editor_preset_list.findItems(current_list_selection_text, Qt.MatchFlag.MatchExactly)\r\n                if items:\r\n                    self.editor_preset_list.setCurrentItem(items[0]) # This might trigger _load_selected_preset_for_editing\r\n\r\n    # --- Drag and Drop Event Handlers (Unchanged) ---\r\n    def dragEnterEvent(self, event):\r\n        if event.mimeData().hasUrls(): event.acceptProposedAction()\r\n        else: event.ignore()\r\n\r\n    def dropEvent(self, event):\r\n        if event.mimeData().hasUrls():\r\n            event.acceptProposedAction()\r\n            urls = event.mimeData().urls()\r\n            paths = [url.toLocalFile() for url in urls]\r\n            self.add_input_paths(paths)\r\n        else: event.ignore()\r\n\r\n    def add_input_paths(self, paths):\r\n        if not hasattr(self, 'current_asset_paths'): self.current_asset_paths = set()\r\n        added_count = 0\r\n        newly_added_paths = []\r\n        for p_str in paths:\r\n            p = Path(p_str)\r\n            if p.exists():\r\n                if p.is_dir() or (p.is_file() and p.suffix.lower() == '.zip'):\r\n                    if p_str not in self.current_asset_paths:\r\n                        self.current_asset_paths.add(p_str)\r\n                        newly_added_paths.append(p_str)\r\n                        added_count += 1\r\n                    else: print(f\"Skipping duplicate asset path: {p_str}\")\r\n                else: self.statusBar().showMessage(f\"Invalid input (not dir or .zip): {p.name}\", 5000); print(f\"Invalid input: {p_str}\")\r\n            else: self.statusBar().showMessage(f\"Input path not found: {p.name}\", 5000); print(f\"Input path not found: {p_str}\")\r\n        if added_count > 0:\r\n            log.info(f\"Added {added_count} new asset paths: {newly_added_paths}\")\r\n            self.statusBar().showMessage(f\"Added {added_count} asset(s). Updating preview...\", 3000)\r\n            self.update_preview()\r\n\r\n    def _browse_for_output_directory(self):\r\n        \"\"\"Opens a dialog to select the output directory.\"\"\"\r\n        current_path = self.output_path_edit.text()\r\n        if not current_path or not Path(current_path).is_dir():\r\n            # Fallback to project root or home directory if current path is invalid\r\n            current_path = str(project_root)\r\n\r\n        directory = QFileDialog.getExistingDirectory(\r\n            self,\r\n            \"Select Output Directory\",\r\n            current_path,\r\n            QFileDialog.Option.ShowDirsOnly | QFileDialog.Option.DontResolveSymlinks\r\n        )\r\n        if directory:\r\n            self.output_path_edit.setText(directory)\r\n            log.info(f\"User selected output directory: {directory}\")\r\n\r\n\r\n    # --- Processing Action Methods ---\r\n    def start_processing(self):\r\n        if self.processing_handler and self.processing_handler.is_running:\r\n            log.warning(\"Start clicked, but processing is already running.\")\r\n            self.statusBar().showMessage(\"Processing is already in progress.\", 3000)\r\n            return\r\n        if ProcessingHandler is None:\r\n             self.statusBar().showMessage(\"Error: Processing components not loaded.\", 5000)\r\n             return\r\n        if not hasattr(self, 'current_asset_paths') or not self.current_asset_paths:\r\n            self.statusBar().showMessage(\"No assets added to process.\", 3000)\r\n            return\r\n        input_paths = list(self.current_asset_paths)\r\n        if not input_paths:\r\n            self.statusBar().showMessage(\"No assets added to process.\", 3000)\r\n            return\r\n        selected_preset = self.preset_combo.currentText() # Use the processing combo box\r\n        if not selected_preset:\r\n            self.statusBar().showMessage(\"Please select a preset for processing.\", 3000)\r\n            return\r\n        overwrite = self.overwrite_checkbox.isChecked()\r\n        num_workers = self.workers_spinbox.value()\r\n\r\n        # --- Get Output Directory from UI and Validate ---\r\n        output_dir_str = self.output_path_edit.text().strip()\r\n        if not output_dir_str:\r\n            self.statusBar().showMessage(\"Error: Output directory cannot be empty.\", 5000)\r\n            log.error(\"Start processing failed: Output directory field is empty.\")\r\n            return\r\n        try:\r\n            output_dir = Path(output_dir_str)\r\n            # Attempt to create the directory if it doesn't exist\r\n            output_dir.mkdir(parents=True, exist_ok=True)\r\n            # Basic writability check (create and delete a temp file)\r\n            # Note: This isn't foolproof due to potential race conditions/permissions issues\r\n            # but provides a basic level of validation.\r\n            temp_file = output_dir / f\".writable_check_{time.time()}\"\r\n            temp_file.touch()\r\n            temp_file.unlink()\r\n            log.info(f\"Using validated output directory: {output_dir_str}\")\r\n        except OSError as e:\r\n            error_msg = f\"Error creating/accessing output directory '{output_dir_str}': {e}\"\r\n            self.statusBar().showMessage(error_msg, 5000)\r\n            log.error(error_msg)\r\n            return\r\n        except Exception as e: # Catch other potential Path errors or unexpected issues\r\n            error_msg = f\"Invalid output directory path '{output_dir_str}': {e}\"\r\n            self.statusBar().showMessage(error_msg, 5000)\r\n            log.error(error_msg)\r\n            return\r\n        # --- End Output Directory Validation ---\r\n\r\n        log.info(f\"Preparing to start processing {len(input_paths)} items to '{output_dir_str}'.\")\r\n        self.set_controls_enabled(False)\r\n        self.cancel_button.setEnabled(True)\r\n        self.start_button.setText(\"Processing...\")\r\n        self.progress_bar.setValue(0)\r\n        self.progress_bar.setFormat(\"%p%\")\r\n        self.setup_threads()\r\n        if self.processing_thread and self.processing_handler:\r\n            try: self.processing_thread.started.disconnect()\r\n            except RuntimeError: pass\r\n            self.processing_thread.started.connect(\r\n                lambda: self.processing_handler.run_processing(\r\n                    input_paths, selected_preset, output_dir_str, overwrite, num_workers\r\n                )\r\n            )\r\n            self.processing_thread.start()\r\n            log.info(\"Processing thread started.\")\r\n            self.statusBar().showMessage(f\"Processing {len(input_paths)} items...\", 0)\r\n        else:\r\n             log.error(\"Failed to start processing: Thread or handler not initialized.\")\r\n             self.statusBar().showMessage(\"Error: Failed to initialize processing thread.\", 5000)\r\n             self.set_controls_enabled(True)\r\n             self.cancel_button.setEnabled(False)\r\n             self.start_button.setText(\"Start Processing\")\r\n\r\n    def cancel_processing(self):\r\n        if self.processing_handler and self.processing_handler.is_running:\r\n            log.info(\"Cancel button clicked. Requesting cancellation.\")\r\n            self.statusBar().showMessage(\"Requesting cancellation...\", 3000)\r\n            self.processing_handler.request_cancel()\r\n            self.cancel_button.setEnabled(False)\r\n            self.start_button.setText(\"Cancelling...\")\r\n        else:\r\n            log.warning(\"Cancel clicked, but no processing is running.\")\r\n            self.statusBar().showMessage(\"Nothing to cancel.\", 3000)\r\n\r\n    def clear_queue(self):\r\n        \"\"\"Clears the current asset queue and the preview table.\"\"\"\r\n        if self.processing_handler and self.processing_handler.is_running:\r\n            self.statusBar().showMessage(\"Cannot clear queue while processing.\", 3000)\r\n            return\r\n\r\n        if hasattr(self, 'current_asset_paths') and self.current_asset_paths:\r\n            log.info(f\"Clearing asset queue ({len(self.current_asset_paths)} items).\")\r\n            self.current_asset_paths.clear()\r\n            self.preview_table.setRowCount(0)\r\n            self.statusBar().showMessage(\"Asset queue cleared.\", 3000)\r\n        else:\r\n            self.statusBar().showMessage(\"Asset queue is already empty.\", 3000)\r\n\r\n\r\n    # --- Preview Update Method ---\r\n    def update_preview(self):\r\n        log.debug(f\"[{time.time():.4f}] update_preview triggered.\")\r\n\r\n        # --- Handle Preview Toggle ---\r\n        if hasattr(self, 'disable_preview_checkbox') and self.disable_preview_checkbox.isChecked():\r\n            log.info(\"Detailed preview disabled. Showing simple input list.\")\r\n            self.preview_table.setSortingEnabled(False)\r\n            self.preview_table.setRowCount(0) # Clear previous\r\n            self.preview_table.setColumnCount(1)\r\n            self.preview_table.setHorizontalHeaderLabels([\"Input Path\"])\r\n            self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)\r\n\r\n            if hasattr(self, 'current_asset_paths') and self.current_asset_paths:\r\n                input_paths_list = sorted(list(self.current_asset_paths))\r\n                self.preview_table.setRowCount(len(input_paths_list))\r\n                for row, path_str in enumerate(input_paths_list):\r\n                    item = QTableWidgetItem(path_str)\r\n                    item.setToolTip(path_str) # Simple tooltip\r\n                    self.preview_table.setItem(row, 0, item)\r\n                self.statusBar().showMessage(f\"Preview disabled. Showing {len(input_paths_list)} input assets.\", 3000)\r\n            else:\r\n                self.statusBar().showMessage(\"Preview disabled. No assets added.\", 3000)\r\n\r\n            self.preview_table.setSortingEnabled(True)\r\n            return # Stop here, do not run PredictionHandler\r\n        # --- End Preview Toggle Handling ---\r\n\r\n        # --- Proceed with Detailed Preview ---\r\n        log.debug(\"Detailed preview enabled. Proceeding with prediction handler.\")\r\n        # Reset table headers for detailed view\r\n        self.preview_table.setColumnCount(4)\r\n        self.preview_table.setHorizontalHeaderLabels([\"Status\", \"Original Path\", \"Predicted Name\", \"Details\"])\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents) # Status\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)         # Original Path\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents) # Predicted Name\r\n        self.preview_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents) # Details (Slimmer)\r\n\r\n\r\n        if self.prediction_handler and self.prediction_handler.is_running:\r\n            log.warning(f\"[{time.time():.4f}] Preview update requested, but already running.\")\r\n            # Optionally: Request cancel previous prediction? For now, just return.\r\n            return\r\n        if PredictionHandler is None:\r\n            self.statusBar().showMessage(\"Error: Prediction components not loaded.\", 5000)\r\n            return\r\n        selected_preset = self.preset_combo.currentText() # Use the processing combo box\r\n        if not selected_preset:\r\n            log.debug(\"Update preview called with no processing preset selected.\")\r\n            self.preview_table.setRowCount(0)\r\n            self.statusBar().showMessage(\"Select a processing preset to update preview.\", 3000)\r\n            return\r\n        if not hasattr(self, 'current_asset_paths') or not self.current_asset_paths:\r\n            log.debug(\"Update preview called with no assets tracked.\")\r\n            self.preview_table.setRowCount(0)\r\n            return\r\n        input_paths = list(self.current_asset_paths)\r\n        if not input_paths:\r\n            log.debug(\"Update preview called but no input paths derived.\")\r\n            self.preview_table.setRowCount(0)\r\n            return\r\n\r\n        log.info(f\"[{time.time():.4f}] Requesting background preview update for {len(input_paths)} items, Preset='{selected_preset}'\")\r\n        self.statusBar().showMessage(f\"Updating preview for '{selected_preset}'...\", 0)\r\n        self.preview_table.setRowCount(0) # Clear before starting prediction\r\n        self.setup_threads() # Ensure threads are ready\r\n        if self.prediction_thread and self.prediction_handler:\r\n            try: self.prediction_thread.started.disconnect() # Disconnect previous lambda if any\r\n            except RuntimeError: pass\r\n            # Connect the lambda to start the prediction\r\n            self.prediction_thread.started.connect(\r\n                lambda: self.prediction_handler.run_prediction(input_paths, selected_preset)\r\n            )\r\n            log.debug(f\"[{time.time():.4f}] Starting prediction thread...\")\r\n            self.prediction_thread.start()\r\n            log.debug(f\"[{time.time():.4f}] Prediction thread start requested.\")\r\n        else:\r\n            log.error(f\"[{time.time():.4f}] Failed to start prediction: Thread or handler not initialized.\")\r\n            self.statusBar().showMessage(\"Error: Failed to initialize prediction thread.\", 5000)\r\n\r\n    # --- Threading and Processing Control ---\r\n    def setup_threads(self):\r\n        # Setup Processing Thread\r\n        if ProcessingHandler and self.processing_thread is None:\r\n            self.processing_thread = QThread(self)\r\n            self.processing_handler = ProcessingHandler()\r\n            self.processing_handler.moveToThread(self.processing_thread)\r\n            self.processing_handler.progress_updated.connect(self.update_progress_bar)\r\n            self.processing_handler.file_status_updated.connect(self.update_file_status)\r\n            self.processing_handler.processing_finished.connect(self.on_processing_finished)\r\n            self.processing_handler.status_message.connect(self.show_status_message)\r\n            self.processing_handler.processing_finished.connect(self.processing_thread.quit)\r\n            self.processing_handler.processing_finished.connect(self.processing_handler.deleteLater)\r\n            self.processing_thread.finished.connect(self.processing_thread.deleteLater)\r\n            self.processing_thread.finished.connect(self._reset_processing_thread_references)\r\n            log.debug(\"Processing thread and handler set up.\")\r\n        elif not ProcessingHandler:\r\n             log.error(\"ProcessingHandler not available. Cannot set up processing thread.\")\r\n             if hasattr(self, 'start_button'):\r\n                 self.start_button.setEnabled(False)\r\n                 self.start_button.setToolTip(\"Error: Backend processing components failed to load.\")\r\n\r\n        # Setup Prediction Thread\r\n        if PredictionHandler and self.prediction_thread is None:\r\n            self.prediction_thread = QThread(self)\r\n            self.prediction_handler = PredictionHandler()\r\n            self.prediction_handler.moveToThread(self.prediction_thread)\r\n            self.prediction_handler.prediction_results_ready.connect(self.on_prediction_results_ready) # Updated slot below\r\n            self.prediction_handler.prediction_finished.connect(self.on_prediction_finished)\r\n            self.prediction_handler.status_message.connect(self.show_status_message)\r\n            self.prediction_handler.prediction_finished.connect(self.prediction_thread.quit)\r\n            self.prediction_handler.prediction_finished.connect(self.prediction_handler.deleteLater)\r\n            self.prediction_thread.finished.connect(self.prediction_thread.deleteLater)\r\n            self.prediction_thread.finished.connect(self._reset_prediction_thread_references)\r\n            log.debug(\"Prediction thread and handler set up.\")\r\n        elif not PredictionHandler:\r\n             log.error(\"PredictionHandler not available. Cannot set up prediction thread.\")\r\n\r\n    @Slot()\r\n    def _reset_processing_thread_references(self):\r\n        log.debug(\"Resetting processing thread and handler references.\")\r\n        self.processing_thread = None\r\n        self.processing_handler = None\r\n\r\n    @Slot()\r\n    def _reset_prediction_thread_references(self):\r\n        log.debug(\"Resetting prediction thread and handler references.\")\r\n        self.prediction_thread = None\r\n        self.prediction_handler = None\r\n\r\n    @Slot(int, int)\r\n    def update_progress_bar(self, current_count, total_count):\r\n        if total_count > 0:\r\n            percentage = int((current_count / total_count) * 100)\r\n            self.progress_bar.setValue(percentage)\r\n            self.progress_bar.setFormat(f\"%p% ({current_count}/{total_count})\")\r\n        else:\r\n            self.progress_bar.setValue(0)\r\n            self.progress_bar.setFormat(\"0/0\")\r\n\r\n    # Slot for prediction results (Updated for new format and coloring)\r\n    @Slot(list)\r\n    def on_prediction_results_ready(self, results: list):\r\n        \"\"\"Populates the preview table with detailed prediction results.\"\"\"\r\n        log.debug(f\"[{time.time():.4f}] on_prediction_results_ready received {len(results)} file details.\")\r\n        self.preview_table.setSortingEnabled(False) # Disable sorting during population\r\n        self.preview_table.setRowCount(0) # Clear previous results\r\n\r\n        self.preview_table.setRowCount(len(results))\r\n        for row, file_details in enumerate(results):\r\n            # Ensure file_details is a dictionary before accessing keys\r\n            if not isinstance(file_details, dict):\r\n                log.warning(f\"Received non-dict item in prediction results: {file_details}\")\r\n                # Add a row indicating an error (adjust column order)\r\n                item_status = QTableWidgetItem(\"Error\")\r\n                item_orig = QTableWidgetItem(\"[Invalid Data Received]\")\r\n                item_pred_name = QTableWidgetItem(\"\")\r\n                item_details_display = QTableWidgetItem(\"Invalid data format from handler\")\r\n                self.preview_table.setItem(row, 0, item_status)\r\n                self.preview_table.setItem(row, 1, item_orig)\r\n                self.preview_table.setItem(row, 2, item_pred_name)\r\n                self.preview_table.setItem(row, 3, item_details_display)\r\n                continue\r\n\r\n            # Extract data using the new keys\r\n            original_path = file_details.get('original_path', '[Missing Path]')\r\n            predicted_name = file_details.get('predicted_name', '') # Can be None or empty\r\n            status = file_details.get('status', '[No Status]')\r\n            details = file_details.get('details', '') # Tooltip info\r\n            source_asset = file_details.get('source_asset', 'N/A') # For tooltip\r\n\r\n            # Create items\r\n            item_orig = QTableWidgetItem(original_path)\r\n            item_pred_name = QTableWidgetItem(predicted_name if predicted_name else \"\") # Display empty if None\r\n            item_status = QTableWidgetItem(status)\r\n            item_details_display = QTableWidgetItem(details if details else \"\") # Show details directly for now\r\n\r\n            # Set tooltips\r\n            item_orig.setToolTip(f\"Source Asset: {source_asset}\\nFull Path: {original_path}\")\r\n            item_status.setToolTip(details if details else status) # Use details for status tooltip\r\n            item_details_display.setToolTip(details if details else \"\")\r\n\r\n            # --- Apply Text Coloring based on Status ---\r\n            color = None\r\n            if status == \"Mapped\":\r\n                color = QColor(\"#9dd9db\") # Light blue/teal\r\n            elif status == \"Ignored\":\r\n                color = QColor(\"#c1753d\") # Orange/brown\r\n            elif status == \"Extra\":\r\n                color = QColor(\"#cfdca4\") # Light green/yellow\r\n            elif status == \"Unrecognised\":\r\n                color = QColor(\"#92371f\") # User specified dark red/brown\r\n            elif status == \"Model\":\r\n                color = QColor(\"#a4b8dc\") # Light blue/grey (Example)\r\n            elif status == \"Error\":\r\n                 color = QColor(Qt.GlobalColor.red) # Example for Error\r\n\r\n            if color:\r\n                item_status.setForeground(color)\r\n                item_orig.setForeground(color)\r\n                item_pred_name.setForeground(color)\r\n                item_details_display.setForeground(color)\r\n            # --- End Coloring ---\r\n\r\n            # Set items in table (new column order)\r\n            self.preview_table.setItem(row, 0, item_status)\r\n            self.preview_table.setItem(row, 1, item_orig)\r\n            self.preview_table.setItem(row, 2, item_pred_name)\r\n            self.preview_table.setItem(row, 3, item_details_display)\r\n\r\n        self.preview_table.sortItems(1, Qt.SortOrder.AscendingOrder) # Sort by original path (now column 1)\r\n        self.preview_table.setSortingEnabled(True) # Re-enable sorting\r\n        log.debug(f\"[{time.time():.4f}] Preview table populated with detailed results and coloring.\")\r\n\r\n\r\n    @Slot()\r\n    def on_prediction_finished(self):\r\n        log.debug(f\"[{time.time():.4f}] Prediction finished signal received.\")\r\n\r\n    @Slot(str, str, str)\r\n    def update_file_status(self, input_path_str, status, message):\r\n        # TODO: Update status bar or potentially find rows in table later\r\n        status_text = f\"Asset '{Path(input_path_str).name}': {status.upper()}\"\r\n        if status == \"failed\" and message: status_text += f\" - Error: {message}\"\r\n        self.statusBar().showMessage(status_text, 5000)\r\n        log.debug(f\"Received file status update: {input_path_str} - {status}\")\r\n\r\n    @Slot(int, int, int)\r\n    def on_processing_finished(self, processed_count, skipped_count, failed_count):\r\n        log.info(f\"GUI received processing_finished signal: P={processed_count}, S={skipped_count}, F={failed_count}\")\r\n        self.set_controls_enabled(True)\r\n        self.cancel_button.setEnabled(False)\r\n        self.start_button.setText(\"Start Processing\")\r\n\r\n    @Slot(str, int)\r\n    def show_status_message(self, message, timeout_ms):\r\n        if timeout_ms > 0: self.statusBar().showMessage(message, timeout_ms)\r\n        else: self.statusBar().showMessage(message)\r\n\r\n    def set_controls_enabled(self, enabled: bool):\r\n        \"\"\"Enables/disables input controls during processing.\"\"\"\r\n        # Main panel controls\r\n        self.preset_combo.setEnabled(enabled)\r\n        self.start_button.setEnabled(enabled)\r\n        self.setAcceptDrops(enabled)\r\n        self.drag_drop_area.setEnabled(enabled)\r\n        self.preview_table.setEnabled(enabled)\r\n        # Editor panel controls (should generally be enabled unless processing)\r\n        self.editor_panel.setEnabled(enabled) # Enable/disable the whole panel\r\n\r\n\r\n    # --- Preset Editor Methods (Adapted from PresetEditorDialog) ---\r\n\r\n    def _editor_add_list_item(self, list_widget: QListWidget):\r\n        \"\"\"Adds an editable item to the specified list widget in the editor.\"\"\"\r\n        text, ok = QInputDialog.getText(self, f\"Add Item\", \"Enter value:\")\r\n        if ok and text:\r\n            item = QListWidgetItem(text)\r\n            # item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) # Already editable by default\r\n            list_widget.addItem(item)\r\n            self._mark_editor_unsaved()\r\n\r\n    def _editor_remove_list_item(self, list_widget: QListWidget):\r\n        \"\"\"Removes the selected item from the specified list widget in the editor.\"\"\"\r\n        selected_items = list_widget.selectedItems()\r\n        if not selected_items: return\r\n        for item in selected_items: list_widget.takeItem(list_widget.row(item))\r\n        self._mark_editor_unsaved()\r\n\r\n    def _editor_add_table_row(self, table_widget: QTableWidget):\r\n        \"\"\"Adds an empty row to the specified table widget in the editor.\"\"\"\r\n        row_count = table_widget.rowCount()\r\n        table_widget.insertRow(row_count)\r\n        for col in range(table_widget.columnCount()): table_widget.setItem(row_count, col, QTableWidgetItem(\"\"))\r\n        self._mark_editor_unsaved()\r\n\r\n    def _editor_remove_table_row(self, table_widget: QTableWidget):\r\n        \"\"\"Removes the selected row(s) from the specified table widget in the editor.\"\"\"\r\n        selected_rows = sorted(list(set(index.row() for index in table_widget.selectedIndexes())), reverse=True)\r\n        if not selected_rows:\r\n            if table_widget.rowCount() > 0: selected_rows = [table_widget.rowCount() - 1]\r\n            else: return\r\n        for row in selected_rows: table_widget.removeRow(row)\r\n        self._mark_editor_unsaved()\r\n\r\n    def _mark_editor_unsaved(self):\r\n        \"\"\"Marks changes in the editor panel as unsaved.\"\"\"\r\n        if self._is_loading_editor: return\r\n        self.editor_unsaved_changes = True\r\n        self.editor_save_button.setEnabled(True)\r\n        preset_name = Path(self.current_editing_preset_path).name if self.current_editing_preset_path else 'New Preset'\r\n        self.setWindowTitle(f\"Asset Processor Tool - {preset_name}*\")\r\n\r\n    def _connect_editor_change_signals(self):\r\n        \"\"\"Connect signals from all editor widgets to mark_editor_unsaved.\"\"\"\r\n        self.editor_preset_name.textChanged.connect(self._mark_editor_unsaved)\r\n        self.editor_supplier_name.textChanged.connect(self._mark_editor_unsaved)\r\n        self.editor_notes.textChanged.connect(self._mark_editor_unsaved)\r\n        self.editor_separator.textChanged.connect(self._mark_editor_unsaved)\r\n        self.editor_spin_base_name_idx.valueChanged.connect(self._mark_editor_unsaved)\r\n        self.editor_spin_map_type_idx.valueChanged.connect(self._mark_editor_unsaved)\r\n        # List/Table widgets are connected via helper functions\r\n\r\n    def _check_editor_unsaved_changes(self) -> bool:\r\n        \"\"\"Checks for unsaved changes in the editor and prompts the user. Returns True if should cancel action.\"\"\"\r\n        if not self.editor_unsaved_changes: return False\r\n        reply = QMessageBox.question(self, \"Unsaved Preset Changes\",\r\n                                     \"You have unsaved changes in the preset editor. Discard them?\",\r\n                                     QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel,\r\n                                     QMessageBox.StandardButton.Cancel)\r\n        if reply == QMessageBox.StandardButton.Save: return not self._save_current_preset() # Return True (cancel) if save fails\r\n        elif reply == QMessageBox.StandardButton.Discard: return False # Discarded, proceed\r\n        else: return True # Cancel action\r\n\r\n    def _set_editor_enabled(self, enabled: bool):\r\n        \"\"\"Enables or disables all editor widgets.\"\"\"\r\n        self.editor_tab_widget.setEnabled(enabled)\r\n        # Also enable/disable save buttons based on editor state, not just processing state\r\n        self.editor_save_button.setEnabled(enabled and self.editor_unsaved_changes)\r\n        self.editor_save_as_button.setEnabled(enabled) # Save As is always possible if editor is enabled\r\n\r\n    def _clear_editor(self):\r\n        \"\"\"Clears the editor fields and resets state.\"\"\"\r\n        self._is_loading_editor = True\r\n        self.editor_preset_name.clear()\r\n        self.editor_supplier_name.clear()\r\n        self.editor_notes.clear()\r\n        self.editor_separator.clear()\r\n        self.editor_spin_base_name_idx.setValue(0)\r\n        self.editor_spin_map_type_idx.setValue(1)\r\n        self.editor_list_gloss_keywords.clear()\r\n        self.editor_table_bit_depth_variants.setRowCount(0)\r\n        self.editor_list_extra_patterns.clear()\r\n        self.editor_table_map_type_mapping.setRowCount(0)\r\n        self.editor_list_model_patterns.clear()\r\n        self.editor_list_decal_keywords.clear()\r\n        self.editor_table_archetype_rules.setRowCount(0)\r\n        self.current_editing_preset_path = None\r\n        self.editor_unsaved_changes = False\r\n        self.editor_save_button.setEnabled(False)\r\n        self.setWindowTitle(\"Asset Processor Tool\") # Reset window title\r\n        self._set_editor_enabled(False)\r\n        self._is_loading_editor = False\r\n\r\n    def _populate_editor_from_data(self, preset_data: dict):\r\n        \"\"\"Helper method to populate editor UI widgets from a preset data dictionary.\"\"\"\r\n        self._is_loading_editor = True\r\n        try:\r\n            self.editor_preset_name.setText(preset_data.get(\"preset_name\", \"\"))\r\n            self.editor_supplier_name.setText(preset_data.get(\"supplier_name\", \"\"))\r\n            self.editor_notes.setText(preset_data.get(\"notes\", \"\"))\r\n            naming_data = preset_data.get(\"source_naming\", {})\r\n            self.editor_separator.setText(naming_data.get(\"separator\", \"_\"))\r\n            indices = naming_data.get(\"part_indices\", {})\r\n            self.editor_spin_base_name_idx.setValue(indices.get(\"base_name\", 0))\r\n            self.editor_spin_map_type_idx.setValue(indices.get(\"map_type\", 1))\r\n            self.editor_list_gloss_keywords.clear()\r\n            self.editor_list_gloss_keywords.addItems(naming_data.get(\"glossiness_keywords\", []))\r\n            self.editor_table_bit_depth_variants.setRowCount(0)\r\n            bit_depth_vars = naming_data.get(\"bit_depth_variants\", {})\r\n            for i, (map_type, pattern) in enumerate(bit_depth_vars.items()):\r\n                self.editor_table_bit_depth_variants.insertRow(i)\r\n                self.editor_table_bit_depth_variants.setItem(i, 0, QTableWidgetItem(map_type))\r\n                self.editor_table_bit_depth_variants.setItem(i, 1, QTableWidgetItem(pattern))\r\n            self.editor_list_extra_patterns.clear()\r\n            self.editor_list_extra_patterns.addItems(preset_data.get(\"move_to_extra_patterns\", []))\r\n            self.editor_table_map_type_mapping.setRowCount(0)\r\n            map_mappings = preset_data.get(\"map_type_mapping\", [])\r\n            # --- UPDATED for new dictionary format ---\r\n            for i, mapping_dict in enumerate(map_mappings):\r\n                if isinstance(mapping_dict, dict) and \"target_type\" in mapping_dict and \"keywords\" in mapping_dict:\r\n                    std_type = mapping_dict[\"target_type\"]\r\n                    keywords = mapping_dict[\"keywords\"]\r\n                    self.editor_table_map_type_mapping.insertRow(i)\r\n                    self.editor_table_map_type_mapping.setItem(i, 0, QTableWidgetItem(std_type))\r\n                    # Ensure keywords are strings before joining\r\n                    keywords_str = [str(k) for k in keywords if isinstance(k, str)]\r\n                    self.editor_table_map_type_mapping.setItem(i, 1, QTableWidgetItem(\", \".join(keywords_str)))\r\n                else:\r\n                    log.warning(f\"Skipping invalid map_type_mapping item during editor population: {mapping_dict}\")\r\n            # --- END UPDATE ---\r\n            category_rules = preset_data.get(\"asset_category_rules\", {})\r\n            self.editor_list_model_patterns.clear()\r\n            self.editor_list_model_patterns.addItems(category_rules.get(\"model_patterns\", []))\r\n            self.editor_list_decal_keywords.clear()\r\n            self.editor_list_decal_keywords.addItems(category_rules.get(\"decal_keywords\", []))\r\n            self.editor_table_archetype_rules.setRowCount(0)\r\n            arch_rules = preset_data.get(\"archetype_rules\", [])\r\n            for i, rule in enumerate(arch_rules):\r\n                 if isinstance(rule, (list, tuple)) and len(rule) == 2:\r\n                     arch_name, conditions = rule\r\n                     match_any = \", \".join(conditions.get(\"match_any\", []))\r\n                     match_all = \", \".join(conditions.get(\"match_all\", []))\r\n                     self.editor_table_archetype_rules.insertRow(i)\r\n                     self.editor_table_archetype_rules.setItem(i, 0, QTableWidgetItem(arch_name))\r\n                     self.editor_table_archetype_rules.setItem(i, 1, QTableWidgetItem(match_any))\r\n                     self.editor_table_archetype_rules.setItem(i, 2, QTableWidgetItem(match_all))\r\n        finally:\r\n            self._is_loading_editor = False\r\n\r\n    def _load_preset_for_editing(self, file_path: Path):\r\n        \"\"\"Loads the content of the selected preset file into the editor widgets.\"\"\"\r\n        if not file_path or not file_path.is_file():\r\n            self._clear_editor()\r\n            return\r\n        log.info(f\"Loading preset into editor: {file_path.name}\")\r\n        try:\r\n            with open(file_path, 'r', encoding='utf-8') as f: preset_data = json.load(f)\r\n            self._populate_editor_from_data(preset_data)\r\n            self._set_editor_enabled(True)\r\n            self.current_editing_preset_path = file_path\r\n            self.editor_unsaved_changes = False\r\n            self.editor_save_button.setEnabled(False)\r\n            self.setWindowTitle(f\"Asset Processor Tool - {file_path.name}\")\r\n            log.info(f\"Preset '{file_path.name}' loaded into editor.\")\r\n        except json.JSONDecodeError as json_err:\r\n             log.error(f\"Invalid JSON in {file_path.name}: {json_err}\")\r\n             QMessageBox.warning(self, \"Load Error\", f\"Failed to load preset '{file_path.name}'.\\nInvalid JSON structure:\\n{json_err}\")\r\n             self._clear_editor()\r\n        except Exception as e:\r\n            log.exception(f\"Error loading preset file {file_path}: {e}\")\r\n            QMessageBox.critical(self, \"Error\", f\"Could not load preset file:\\n{file_path}\\n\\nError: {e}\")\r\n            self._clear_editor()\r\n\r\n    def _load_selected_preset_for_editing(self, current_item: QListWidgetItem, previous_item: QListWidgetItem):\r\n        \"\"\"Loads the preset currently selected in the editor list.\"\"\"\r\n        if self._check_editor_unsaved_changes():\r\n            # If user cancels, revert selection\r\n            if previous_item:\r\n                self.editor_preset_list.blockSignals(True)\r\n                self.editor_preset_list.setCurrentItem(previous_item)\r\n                self.editor_preset_list.blockSignals(False)\r\n            return\r\n        if current_item:\r\n            self._load_preset_for_editing(current_item.data(Qt.ItemDataRole.UserRole))\r\n        else:\r\n             self._clear_editor() # Clear editor if selection is cleared\r\n\r\n    def _gather_editor_data(self) -> dict:\r\n        \"\"\"Gathers data from all editor UI widgets and returns a dictionary.\"\"\"\r\n        preset_data = {}\r\n        preset_data[\"preset_name\"] = self.editor_preset_name.text().strip()\r\n        preset_data[\"supplier_name\"] = self.editor_supplier_name.text().strip()\r\n        preset_data[\"notes\"] = self.editor_notes.toPlainText().strip()\r\n        naming_data = {}\r\n        naming_data[\"separator\"] = self.editor_separator.text()\r\n        naming_data[\"part_indices\"] = { \"base_name\": self.editor_spin_base_name_idx.value(), \"map_type\": self.editor_spin_map_type_idx.value() }\r\n        naming_data[\"glossiness_keywords\"] = [self.editor_list_gloss_keywords.item(i).text() for i in range(self.editor_list_gloss_keywords.count())]\r\n        naming_data[\"bit_depth_variants\"] = {self.editor_table_bit_depth_variants.item(r, 0).text(): self.editor_table_bit_depth_variants.item(r, 1).text()\r\n                                             for r in range(self.editor_table_bit_depth_variants.rowCount()) if self.editor_table_bit_depth_variants.item(r, 0) and self.editor_table_bit_depth_variants.item(r, 1)}\r\n        preset_data[\"source_naming\"] = naming_data\r\n        preset_data[\"move_to_extra_patterns\"] = [self.editor_list_extra_patterns.item(i).text() for i in range(self.editor_list_extra_patterns.count())]\r\n        # --- UPDATED for new dictionary format ---\r\n        map_mappings = []\r\n        for r in range(self.editor_table_map_type_mapping.rowCount()):\r\n            type_item = self.editor_table_map_type_mapping.item(r, 0)\r\n            keywords_item = self.editor_table_map_type_mapping.item(r, 1)\r\n            # Ensure both items exist and have text before processing\r\n            if type_item and type_item.text() and keywords_item and keywords_item.text():\r\n                 target_type = type_item.text().strip()\r\n                 keywords = [k.strip() for k in keywords_item.text().split(',') if k.strip()]\r\n                 if target_type and keywords: # Only add if both parts are valid\r\n                      map_mappings.append({\"target_type\": target_type, \"keywords\": keywords})\r\n                 else:\r\n                      log.warning(f\"Skipping row {r} in map type mapping table due to empty target type or keywords.\")\r\n            else:\r\n                 log.warning(f\"Skipping row {r} in map type mapping table due to missing items.\")\r\n        preset_data[\"map_type_mapping\"] = map_mappings\r\n        # --- END UPDATE ---\r\n        category_rules = {}\r\n        category_rules[\"model_patterns\"] = [self.editor_list_model_patterns.item(i).text() for i in range(self.editor_list_model_patterns.count())]\r\n        category_rules[\"decal_keywords\"] = [self.editor_list_decal_keywords.item(i).text() for i in range(self.editor_list_decal_keywords.count())]\r\n        preset_data[\"asset_category_rules\"] = category_rules\r\n        arch_rules = []\r\n        for r in range(self.editor_table_archetype_rules.rowCount()):\r\n            name_item = self.editor_table_archetype_rules.item(r, 0)\r\n            any_item = self.editor_table_archetype_rules.item(r, 1)\r\n            all_item = self.editor_table_archetype_rules.item(r, 2)\r\n            if name_item and any_item and all_item:\r\n                 match_any = [k.strip() for k in any_item.text().split(',') if k.strip()]\r\n                 match_all = [k.strip() for k in all_item.text().split(',') if k.strip()]\r\n                 arch_rules.append([name_item.text().strip(), {\"match_any\": match_any, \"match_all\": match_all}])\r\n        preset_data[\"archetype_rules\"] = arch_rules\r\n        return preset_data\r\n\r\n    def _save_current_preset(self) -> bool:\r\n        \"\"\"Saves the current editor content to the currently loaded file path.\"\"\"\r\n        if not self.current_editing_preset_path: return self._save_preset_as()\r\n        log.info(f\"Saving preset: {self.current_editing_preset_path.name}\")\r\n        try:\r\n            preset_data = self._gather_editor_data()\r\n            if not preset_data.get(\"preset_name\"): QMessageBox.warning(self, \"Save Error\", \"Preset Name cannot be empty.\"); return False\r\n            if not preset_data.get(\"supplier_name\"): QMessageBox.warning(self, \"Save Error\", \"Supplier Name cannot be empty.\"); return False\r\n            content_to_save = json.dumps(preset_data, indent=4, ensure_ascii=False)\r\n            with open(self.current_editing_preset_path, 'w', encoding='utf-8') as f: f.write(content_to_save)\r\n            self.editor_unsaved_changes = False\r\n            self.editor_save_button.setEnabled(False)\r\n            self.setWindowTitle(f\"Asset Processor Tool - {self.current_editing_preset_path.name}\")\r\n            self.presets_changed_signal.emit() # Signal that presets changed\r\n            log.info(\"Preset saved successfully.\")\r\n            # Refresh lists after save\r\n            self.populate_presets()\r\n            return True\r\n        except Exception as e:\r\n            log.exception(f\"Error saving preset file {self.current_editing_preset_path}: {e}\")\r\n            QMessageBox.critical(self, \"Save Error\", f\"Could not save preset file:\\n{self.current_editing_preset_path}\\n\\nError: {e}\")\r\n            return False\r\n\r\n    def _save_preset_as(self) -> bool:\r\n        \"\"\"Saves the current editor content to a new file chosen by the user.\"\"\"\r\n        log.debug(\"Save As action triggered.\")\r\n        try:\r\n            preset_data = self._gather_editor_data()\r\n            new_preset_name = preset_data.get(\"preset_name\")\r\n            if not new_preset_name: QMessageBox.warning(self, \"Save As Error\", \"Preset Name cannot be empty.\"); return False\r\n            if not preset_data.get(\"supplier_name\"): QMessageBox.warning(self, \"Save As Error\", \"Supplier Name cannot be empty.\"); return False\r\n            content_to_save = json.dumps(preset_data, indent=4, ensure_ascii=False)\r\n            suggested_name = f\"{new_preset_name}.json\"\r\n            default_path = PRESETS_DIR / suggested_name\r\n            file_path_str, _ = QFileDialog.getSaveFileName(self, \"Save Preset As\", str(default_path), \"JSON Files (*.json);;All Files (*)\")\r\n            if not file_path_str: log.debug(\"Save As cancelled by user.\"); return False\r\n            save_path = Path(file_path_str)\r\n            if save_path.suffix.lower() != \".json\": save_path = save_path.with_suffix(\".json\")\r\n            if save_path.exists() and save_path != self.current_editing_preset_path:\r\n                 reply = QMessageBox.warning(self, \"Confirm Overwrite\", f\"Preset '{save_path.name}' already exists. Overwrite?\", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)\r\n                 if reply == QMessageBox.StandardButton.No: log.debug(\"Save As overwrite cancelled.\"); return False\r\n            log.info(f\"Saving preset as: {save_path.name}\")\r\n            with open(save_path, 'w', encoding='utf-8') as f: f.write(content_to_save)\r\n            self.current_editing_preset_path = save_path # Update current path\r\n            self.editor_unsaved_changes = False\r\n            self.editor_save_button.setEnabled(False)\r\n            self.setWindowTitle(f\"Asset Processor Tool - {save_path.name}\")\r\n            self.presets_changed_signal.emit() # Signal change\r\n            log.info(\"Preset saved successfully (Save As).\")\r\n            # Refresh lists and select the new item\r\n            self.populate_presets()\r\n            return True\r\n        except Exception as e:\r\n            log.exception(f\"Error saving preset file (Save As): {e}\")\r\n            QMessageBox.critical(self, \"Save Error\", f\"Could not save preset file.\\n\\nError: {e}\")\r\n            return False\r\n\r\n    def _new_preset(self):\r\n        \"\"\"Clears the editor and loads data from _template.json.\"\"\"\r\n        log.debug(\"New Preset action triggered.\")\r\n        if self._check_editor_unsaved_changes(): return\r\n        self._clear_editor()\r\n        if TEMPLATE_PATH.is_file():\r\n            log.info(\"Loading new preset from _template.json\")\r\n            try:\r\n                with open(TEMPLATE_PATH, 'r', encoding='utf-8') as f: template_data = json.load(f)\r\n                self._populate_editor_from_data(template_data)\r\n                # Override specific fields for a new preset\r\n                self.editor_preset_name.setText(\"NewPreset\")\r\n                self.setWindowTitle(\"Asset Processor Tool - New Preset*\")\r\n            except Exception as e:\r\n                log.exception(f\"Error loading template preset file {TEMPLATE_PATH}: {e}\")\r\n                QMessageBox.critical(self, \"Error\", f\"Could not load template preset file:\\n{TEMPLATE_PATH}\\n\\nError: {e}\")\r\n                self._clear_editor()\r\n                self.setWindowTitle(\"Asset Processor Tool - New Preset*\")\r\n        else:\r\n            log.warning(\"Presets/_template.json not found. Creating empty preset.\")\r\n            self.setWindowTitle(\"Asset Processor Tool - New Preset*\")\r\n            self.editor_preset_name.setText(\"NewPreset\")\r\n            self.editor_supplier_name.setText(\"MySupplier\")\r\n        self._set_editor_enabled(True)\r\n        self.editor_unsaved_changes = True\r\n        self.editor_save_button.setEnabled(True)\r\n\r\n    def _delete_selected_preset(self):\r\n        \"\"\"Deletes the currently selected preset file from the editor list after confirmation.\"\"\"\r\n        current_item = self.editor_preset_list.currentItem()\r\n        if not current_item: QMessageBox.information(self, \"Delete Preset\", \"Please select a preset from the list to delete.\"); return\r\n        preset_path = current_item.data(Qt.ItemDataRole.UserRole)\r\n        preset_name = preset_path.stem\r\n        reply = QMessageBox.warning(self, \"Confirm Delete\", f\"Are you sure you want to permanently delete the preset '{preset_name}'?\", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)\r\n        if reply == QMessageBox.StandardButton.Yes:\r\n            log.info(f\"Deleting preset: {preset_path.name}\")\r\n            try:\r\n                preset_path.unlink()\r\n                log.info(\"Preset deleted successfully.\")\r\n                if self.current_editing_preset_path == preset_path: self._clear_editor()\r\n                self.presets_changed_signal.emit() # Signal change\r\n                # Refresh lists\r\n                self.populate_presets()\r\n            except Exception as e:\r\n                log.exception(f\"Error deleting preset file {preset_path}: {e}\")\r\n                QMessageBox.critical(self, \"Delete Error\", f\"Could not delete preset file:\\n{preset_path}\\n\\nError: {e}\")\r\n\r\n    # --- Overridden Close Event ---\r\n    def closeEvent(self, event):\r\n        \"\"\"Overrides close event to check for unsaved changes in the editor.\"\"\"\r\n        if self._check_editor_unsaved_changes():\r\n            event.ignore() # Ignore close event if user cancels\r\n        else:\r\n            event.accept() # Accept close event\r\n\r\n# --- Main Execution ---\r\ndef run_gui():\r\n    \"\"\"Initializes and runs the Qt application.\"\"\"\r\n    app = QApplication(sys.argv)\r\n    window = MainWindow()\r\n    window.show()\r\n    sys.exit(app.exec())\r\n\r\nif __name__ == \"__main__\":\r\n    run_gui()"
        }
    ]
}