New Definitions editor

This commit is contained in:
2025-05-13 13:08:52 +02:00
parent 344ae078a8
commit 87673507d8
4 changed files with 481 additions and 192 deletions

View File

@@ -1,12 +1,12 @@
import logging
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QTabWidget, QWidget, QListWidget, QPushButton,
QDialog, QVBoxLayout, QTabWidget, QWidget, QListWidget, QListWidgetItem, QPushButton,
QHBoxLayout, QLabel, QGroupBox, QDialogButtonBox, QFormLayout,
QTextEdit, QColorDialog, QInputDialog, QMessageBox, QFrame, QComboBox,
QLineEdit, QCheckBox
QLineEdit, QCheckBox, QAbstractItemView
)
from PySide6.QtGui import QColor, QPalette
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette, QMouseEvent # Added QMouseEvent
from PySide6.QtCore import Qt, QEvent
# Assuming load_asset_definitions, load_file_type_definitions, load_supplier_settings
# are in configuration.py at the root level.
@@ -38,6 +38,17 @@ except ImportError as e:
logger = logging.getLogger(__name__)
class DebugListWidget(QListWidget):
def mousePressEvent(self, event: QMouseEvent): # QMouseEvent needs to be imported from PySide6.QtGui
logger.info(f"DebugListWidget.mousePressEvent: pos={event.pos()}")
item = self.itemAt(event.pos())
if item:
logger.info(f"DebugListWidget.mousePressEvent: Item under cursor: {item.text()}")
else:
logger.info("DebugListWidget.mousePressEvent: No item under cursor.")
super().mousePressEvent(event)
logger.info("DebugListWidget.mousePressEvent: super call finished.")
class DefinitionsEditorDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
@@ -48,6 +59,7 @@ class DefinitionsEditorDialog(QDialog):
self.file_type_data = {}
self.supplier_data = {}
self.unsaved_changes = False # For unsaved changes tracking
self.asset_types_tab_page_for_filtering = None # For event filtering
self._load_all_definitions()
@@ -64,6 +76,8 @@ class DefinitionsEditorDialog(QDialog):
main_layout.addWidget(self.button_box)
self.setLayout(main_layout)
# self.tab_widget.installEventFilter(self) # Temporarily disable event filter on tab_widget for this test
# logger.info(f"Event filter on self.tab_widget ({self.tab_widget}) TEMPORARILY DISABLED for DebugListWidget test.")
def _load_all_definitions(self):
logger.info("Loading all definitions...")
@@ -95,8 +109,45 @@ class DefinitionsEditorDialog(QDialog):
self.tab_widget.addTab(self._create_file_types_tab(), "File Type Definitions")
self.tab_widget.addTab(self._create_suppliers_tab(), "Supplier Settings")
# Add a diagnostic button
self.diag_button = QPushButton("Test Select Item 2 (Asset)")
self.diag_button.clicked.connect(self._run_diag_selection)
# Assuming main_layout is accessible here or passed if _create_ui is part of __init__
# If main_layout is self.layout() established in __init__
if self.layout(): # Check if layout exists
self.layout().addWidget(self.diag_button)
else:
logger.error("Main layout not found for diagnostic button in _create_ui. Button not added.")
def _run_diag_selection(self):
logger.info("Diagnostic button clicked. Attempting to select second item in asset_type_list_widget.")
if hasattr(self, 'asset_type_list_widget') and self.asset_type_list_widget.count() > 1:
logger.info(f"Asset type list widget isEnabled: {self.asset_type_list_widget.isEnabled()}") # Check if enabled
logger.info(f"Asset type list widget signalsBlocked: {self.asset_type_list_widget.signalsBlocked()}")
self.asset_type_list_widget.setFocus() # Explicitly set focus
logger.info(f"Attempted to set focus to asset_type_list_widget. Has focus: {self.asset_type_list_widget.hasFocus()}")
item_to_select = self.asset_type_list_widget.item(1) # Select the second item (index 1)
if item_to_select:
logger.info(f"Programmatically selecting: {item_to_select.text()}")
self.asset_type_list_widget.setCurrentItem(item_to_select)
# Check if it's actually selected
if self.asset_type_list_widget.currentItem() == item_to_select:
logger.info(f"Programmatic selection successful. Current item is now: {self.asset_type_list_widget.currentItem().text()}")
else:
logger.warning("Programmatic selection FAILED. Current item did not change as expected.")
else:
logger.warning("Second item not found in asset_type_list_widget.")
elif hasattr(self, 'asset_type_list_widget'):
logger.warning("asset_type_list_widget has less than 2 items for diagnostic selection.")
else:
logger.warning("asset_type_list_widget not found for diagnostic selection.")
def _create_tab_pane(self, title_singular, data_dict, list_widget_name):
tab_page = QWidget()
tab_page.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
tab_layout = QHBoxLayout(tab_page)
# Left Pane
@@ -105,12 +156,32 @@ class DefinitionsEditorDialog(QDialog):
lbl_list_title = QLabel(f"{title_singular}s:")
left_pane_layout.addWidget(lbl_list_title)
list_widget = QListWidget()
if list_widget_name == "asset_type_list_widget":
logger.info(f"Creating DebugListWidget for {list_widget_name}")
list_widget = DebugListWidget(self) # Pass parent
else:
list_widget = QListWidget(self) # Pass parent
from PySide6.QtWidgets import QAbstractItemView
list_widget.setSelectionMode(QAbstractItemView.SingleSelection)
list_widget.setEnabled(True)
logger.info(f"For {list_widget_name}, SelectionMode set to SingleSelection, Enabled set to True.")
setattr(self, list_widget_name, list_widget) # e.g., self.asset_type_list_widget = list_widget
logger.info(f"Creating tab pane for {title_singular}, list_widget_name: {list_widget_name}")
logger.info(f"List widget instance for {list_widget_name}: {list_widget}")
# Ensure no other event filters are active on the list_widget for this specific test
if list_widget_name == "asset_type_list_widget":
# If an event filter was installed on list_widget by a previous debug step via self.installEventFilter(list_widget),
# it would need to be removed here, or the logic installing it should be conditional.
# For now, we assume no other filter is on list_widget itself.
logger.info(f"Ensuring no stray event filter on DebugListWidget instance for {list_widget_name}.")
if isinstance(data_dict, dict):
for key, value_dict in data_dict.items(): # Iterate over items for UserRole data
item = QListWidgetItem(key)
item.setData(Qt.UserRole, value_dict) # Store the whole dict
item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEnabled) # Explicitly set flags
list_widget.addItem(item)
else:
logger.warning(f"Data for {title_singular} is not a dictionary, cannot populate list.")
@@ -125,15 +196,37 @@ class DefinitionsEditorDialog(QDialog):
if list_widget_name == "asset_type_list_widget":
btn_add.clicked.connect(self._add_asset_type)
btn_remove.clicked.connect(self._remove_asset_type)
# The event filter on asset_type_list_widget should be disabled for this test.
# Assuming the Debug mode task that set it up can be told to disable/remove it,
# or we ensure it's not re-added here if it was part of this method.
# For now, we just connect currentItemChanged directly.
list_widget.currentItemChanged.connect(
lambda current, previous, name=list_widget_name:
logger.info(f"LAMBDA: currentItemChanged for {name}. Current: {current.text() if current else 'None'}")
)
list_widget.currentItemChanged.connect(self._display_asset_type_details)
logger.info(f"Connected currentItemChanged for {list_widget_name} to _display_asset_type_details AND diagnostic lambda.")
elif list_widget_name == "file_type_list_widget":
# For other list widgets, keep the previous event filter setup if it was specific,
# or remove if it was generic and now we only want DebugListWidget for assets.
# For this step, we are only changing asset_type_list_widget.
btn_add.clicked.connect(self._add_file_type)
btn_remove.clicked.connect(self._remove_file_type)
list_widget.currentItemChanged.connect(
lambda current, previous, name=list_widget_name:
logger.info(f"LAMBDA: currentItemChanged for {name}. Current: {current.text() if current else 'None'}")
)
list_widget.currentItemChanged.connect(self._display_file_type_details)
logger.info(f"Connected currentItemChanged for {list_widget_name} to _display_file_type_details AND diagnostic lambda.")
elif list_widget_name == "supplier_list_widget": # Connections for Supplier tab
btn_add.clicked.connect(self._add_supplier)
btn_remove.clicked.connect(self._remove_supplier)
list_widget.currentItemChanged.connect(
lambda current, previous, name=list_widget_name:
logger.info(f"LAMBDA: currentItemChanged for {name}. Current: {current.text() if current else 'None'}")
)
list_widget.currentItemChanged.connect(self._display_supplier_details)
logger.info(f"Connected currentItemChanged for {list_widget_name} to _display_supplier_details AND diagnostic lambda.")
buttons_layout.addWidget(btn_add)
buttons_layout.addWidget(btn_remove)
@@ -145,12 +238,17 @@ class DefinitionsEditorDialog(QDialog):
right_pane_widget = QWidget() # Create a generic widget to be returned
tab_layout.addWidget(right_pane_widget, 2) # 2 parts for right pane
tab_page.setEnabled(True) # Explicitly enable the tab page widget
logger.info(f"Tab page for {title_singular} explicitly enabled.")
tab_page.setLayout(tab_layout)
return tab_page, right_pane_widget # Return the pane for customization
def _create_asset_types_tab(self):
tab_page, right_pane_container = self._create_tab_pane("Asset Type", self.asset_type_data, "asset_type_list_widget")
self.asset_types_tab_page_for_filtering = tab_page # Store reference for event filter
# Ensure event filter on tab_page is also disabled if it was installed
# logger.info(f"Event filter on asset_types_tab_page ({tab_page}) should be disabled for DebugListWidget test.")
# Customize the right pane for Asset Types
right_pane_groupbox = QGroupBox("Details for Selected Asset Type")
details_layout = QFormLayout(right_pane_groupbox)
@@ -229,41 +327,61 @@ class DefinitionsEditorDialog(QDialog):
self.asset_type_list_widget.addItem(item)
def _display_asset_type_details(self, current_item, previous_item=None):
# Disconnect signals temporarily to prevent feedback loops during population
if hasattr(self, 'asset_description_edit'):
try:
self.asset_description_edit.textChanged.disconnect(self._on_asset_detail_changed)
except TypeError: # Signal not connected
pass
logger.info(f"_display_asset_type_details called. Current: {current_item.text() if current_item else 'None'}, Previous: {previous_item.text() if previous_item else 'None'}")
if current_item:
asset_data = current_item.data(Qt.UserRole)
if not isinstance(asset_data, dict): # Should not happen if _populate is correct
logger.error(f"Invalid data for item {current_item.text()}. Expected dict, got {type(asset_data)}")
asset_data = {"description": "Error: Invalid data", "color": "#ff0000", "examples": []}
self.asset_description_edit.setText(asset_data.get('description', ''))
color_hex = asset_data.get('color', '#ffffff')
self._update_color_swatch(color_hex)
self.asset_examples_list_widget.clear()
for example in asset_data.get('examples', []):
self.asset_examples_list_widget.addItem(example)
logger.info(f"Current item text: {current_item.text()}")
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
else:
# Clear details if no item is selected
self.asset_description_edit.clear()
self._update_color_swatch("#ffffff")
self.asset_examples_list_widget.clear()
logger.info("Current item is None for asset_type_details.")
# Reconnect signals
if hasattr(self, 'asset_description_edit'):
self.asset_description_edit.textChanged.connect(self._on_asset_detail_changed)
try:
# Disconnect signals temporarily to prevent feedback loops during population
if hasattr(self, 'asset_description_edit'):
try:
self.asset_description_edit.textChanged.disconnect(self._on_asset_detail_changed)
logger.debug("Disconnected asset_description_edit.textChanged")
except TypeError: # Signal not connected
logger.debug("asset_description_edit.textChanged was not connected or already disconnected.")
pass
if current_item:
asset_data = current_item.data(Qt.UserRole)
if not isinstance(asset_data, dict): # Should not happen if _populate is correct
logger.error(f"Invalid data for item {current_item.text()}. Expected dict, got {type(asset_data)}")
asset_data = {"description": "Error: Invalid data", "color": "#ff0000", "examples": []}
self.asset_description_edit.setText(asset_data.get('description', ''))
color_hex = asset_data.get('color', '#ffffff')
self._update_color_swatch(color_hex)
self.asset_examples_list_widget.clear()
for example in asset_data.get('examples', []):
self.asset_examples_list_widget.addItem(example)
logger.debug(f"Populated details for {current_item.text()}")
else:
# Clear details if no item is selected
self.asset_description_edit.clear()
self._update_color_swatch("#ffffff")
self.asset_examples_list_widget.clear()
logger.debug("Cleared asset type details as no item is selected.")
except Exception as e:
logger.error(f"Error in _display_asset_type_details: {e}", exc_info=True)
finally:
# Reconnect signals
if hasattr(self, 'asset_description_edit'):
try:
self.asset_description_edit.textChanged.connect(self._on_asset_detail_changed)
logger.debug("Reconnected asset_description_edit.textChanged")
except Exception as e:
logger.error(f"Failed to reconnect asset_description_edit.textChanged: {e}", exc_info=True)
logger.info("_display_asset_type_details finished.")
def _update_color_swatch(self, color_hex):
if hasattr(self, 'asset_color_swatch_label'):
palette = self.asset_color_swatch_label.palette()
palette.setColor(QPalette.Background, QColor(color_hex))
palette.setColor(QPalette.Window, QColor(color_hex))
self.asset_color_swatch_label.setPalette(palette)
def _choose_asset_color(self):
@@ -420,9 +538,9 @@ class DefinitionsEditorDialog(QDialog):
def _update_file_type_color_swatch(self, color_hex, swatch_label):
if hasattr(self, swatch_label_name): # Check if the specific swatch label exists
if hasattr(self, swatch_label): # Check if the specific swatch label exists
palette = swatch_label.palette()
palette.setColor(QPalette.Background, QColor(color_hex))
palette.setColor(QPalette.Window, QColor(color_hex))
swatch_label.setPalette(palette)
def _create_file_types_tab(self):
@@ -526,69 +644,88 @@ class DefinitionsEditorDialog(QDialog):
self.file_type_list_widget.addItem(item)
def _display_file_type_details(self, current_item, previous_item=None):
# Disconnect signals temporarily
try: self.ft_description_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_standard_type_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_bit_depth_combo.currentIndexChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_is_grayscale_check.stateChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_keybind_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
# Color and examples are handled by their own buttons/actions, not direct textChanged etc.
logger.info(f"_display_file_type_details called. Current: {current_item.text() if current_item else 'None'}, Previous: {previous_item.text() if previous_item else 'None'}")
if current_item:
ft_data = current_item.data(Qt.UserRole)
if not isinstance(ft_data, dict):
logger.error(f"Invalid data for file type item {current_item.text()}. Expected dict, got {type(ft_data)}")
# Use placeholder data to avoid crashing UI
ft_data = {
"description": "Error: Invalid data", "color": "#ff0000", "examples": [],
"standard_type": "error", "bit_depth_rule": "respect",
"is_grayscale": False, "keybind": "X"
}
self.ft_description_edit.setText(ft_data.get('description', ''))
self._update_color_swatch_generic(self.ft_color_swatch_label, ft_data.get('color', '#ffffff'))
self.ft_examples_list_widget.clear()
for example in ft_data.get('examples', []):
self.ft_examples_list_widget.addItem(example)
self.ft_standard_type_edit.setText(ft_data.get('standard_type', ''))
bdr_index = self.ft_bit_depth_combo.findText(ft_data.get('bit_depth_rule', 'respect'))
if bdr_index != -1:
self.ft_bit_depth_combo.setCurrentIndex(bdr_index)
else:
self.ft_bit_depth_combo.setCurrentIndex(0) # Default to 'respect'
self.ft_is_grayscale_check.setChecked(ft_data.get('is_grayscale', False))
self.ft_keybind_edit.setText(ft_data.get('keybind', ''))
logger.info(f"Current item text: {current_item.text()}")
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
else:
# Clear details if no item is selected
self.ft_description_edit.clear()
self._update_color_swatch_generic(self.ft_color_swatch_label, "#ffffff")
self.ft_examples_list_widget.clear()
self.ft_standard_type_edit.clear()
self.ft_bit_depth_combo.setCurrentIndex(0)
self.ft_is_grayscale_check.setChecked(False)
self.ft_keybind_edit.clear()
logger.info("Current item is None for file_type_details.")
# Reconnect signals
self.ft_description_edit.textChanged.connect(self._on_file_type_detail_changed)
self.ft_standard_type_edit.textChanged.connect(self._on_file_type_detail_changed)
self.ft_bit_depth_combo.currentIndexChanged.connect(self._on_file_type_detail_changed)
self.ft_is_grayscale_check.stateChanged.connect(self._on_file_type_detail_changed)
self.ft_keybind_edit.textChanged.connect(self._on_file_type_detail_changed)
try:
# Disconnect signals temporarily
logger.debug("Disconnecting file type detail signals...")
try: self.ft_description_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_standard_type_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_bit_depth_combo.currentIndexChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_is_grayscale_check.stateChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
try: self.ft_keybind_edit.textChanged.disconnect(self._on_file_type_detail_changed)
except TypeError: pass
logger.debug("Finished disconnecting file type detail signals.")
if current_item:
ft_data = current_item.data(Qt.UserRole)
if not isinstance(ft_data, dict):
logger.error(f"Invalid data for file type item {current_item.text()}. Expected dict, got {type(ft_data)}")
ft_data = {
"description": "Error: Invalid data", "color": "#ff0000", "examples": [],
"standard_type": "error", "bit_depth_rule": "respect",
"is_grayscale": False, "keybind": "X"
}
self.ft_description_edit.setText(ft_data.get('description', ''))
self._update_color_swatch_generic(self.ft_color_swatch_label, ft_data.get('color', '#ffffff'))
self.ft_examples_list_widget.clear()
for example in ft_data.get('examples', []):
self.ft_examples_list_widget.addItem(example)
self.ft_standard_type_edit.setText(ft_data.get('standard_type', ''))
bdr_index = self.ft_bit_depth_combo.findText(ft_data.get('bit_depth_rule', 'respect'))
if bdr_index != -1:
self.ft_bit_depth_combo.setCurrentIndex(bdr_index)
else:
self.ft_bit_depth_combo.setCurrentIndex(0) # Default to 'respect'
self.ft_is_grayscale_check.setChecked(ft_data.get('is_grayscale', False))
self.ft_keybind_edit.setText(ft_data.get('keybind', ''))
logger.debug(f"Populated details for file type {current_item.text()}")
else:
# Clear details if no item is selected
self.ft_description_edit.clear()
self._update_color_swatch_generic(self.ft_color_swatch_label, "#ffffff")
self.ft_examples_list_widget.clear()
self.ft_standard_type_edit.clear()
self.ft_bit_depth_combo.setCurrentIndex(0)
self.ft_is_grayscale_check.setChecked(False)
self.ft_keybind_edit.clear()
logger.debug("Cleared file type details as no item is selected.")
except Exception as e:
logger.error(f"Error in _display_file_type_details: {e}", exc_info=True)
finally:
# Reconnect signals
logger.debug("Reconnecting file type detail signals...")
try:
self.ft_description_edit.textChanged.connect(self._on_file_type_detail_changed)
self.ft_standard_type_edit.textChanged.connect(self._on_file_type_detail_changed)
self.ft_bit_depth_combo.currentIndexChanged.connect(self._on_file_type_detail_changed)
self.ft_is_grayscale_check.stateChanged.connect(self._on_file_type_detail_changed)
self.ft_keybind_edit.textChanged.connect(self._on_file_type_detail_changed)
logger.debug("Finished reconnecting file type detail signals.")
except Exception as e:
logger.error(f"Failed to reconnect file type detail signals: {e}", exc_info=True)
logger.info("_display_file_type_details finished.")
def _update_color_swatch_generic(self, swatch_label, color_hex):
"""Generic color swatch update for any QLabel."""
if swatch_label: # Check if the swatch label exists and is passed correctly
palette = swatch_label.palette()
palette.setColor(QPalette.Background, QColor(color_hex))
palette.setColor(QPalette.Window, QColor(color_hex))
swatch_label.setPalette(palette)
swatch_label.update() # Ensure the label repaints
@@ -814,41 +951,59 @@ class DefinitionsEditorDialog(QDialog):
self.supplier_list_widget.addItem(item)
def _display_supplier_details(self, current_item, previous_item=None):
# Disconnect signals temporarily
if hasattr(self, 'supplier_normal_map_type_combo'):
try: self.supplier_normal_map_type_combo.currentIndexChanged.disconnect(self._on_supplier_detail_changed)
except TypeError: pass
logger.info(f"_display_supplier_details called. Current: {current_item.text() if current_item else 'None'}, Previous: {previous_item.text() if previous_item else 'None'}")
if current_item:
supplier_name = current_item.text()
# Prefer getting data directly from self.supplier_data to ensure it's the master copy
# The UserRole data on the item should be a reflection or copy.
supplier_data = self.supplier_data.get(supplier_name)
if not isinstance(supplier_data, dict):
logger.error(f"Invalid data for supplier item {supplier_name}. Expected dict, got {type(supplier_data)}")
# Fallback if data is somehow corrupted or missing from self.supplier_data
# This might happen if an item is in the list but not in self.supplier_data
item_data_role = current_item.data(Qt.UserRole)
if isinstance(item_data_role, dict):
supplier_data = item_data_role
else:
supplier_data = {"normal_map_type": "OpenGL"} # Absolute fallback
normal_map_type = supplier_data.get('normal_map_type', 'OpenGL')
nmt_index = self.supplier_normal_map_type_combo.findText(normal_map_type)
if nmt_index != -1:
self.supplier_normal_map_type_combo.setCurrentIndex(nmt_index)
else:
self.supplier_normal_map_type_combo.setCurrentIndex(0) # Default to OpenGL
logger.info(f"Current item text: {current_item.text()}")
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
else:
# Clear details if no item is selected
if hasattr(self, 'supplier_normal_map_type_combo'):
self.supplier_normal_map_type_combo.setCurrentIndex(0) # Default to OpenGL
logger.info("Current item is None for supplier_details.")
# Reconnect signals
if hasattr(self, 'supplier_normal_map_type_combo'):
self.supplier_normal_map_type_combo.currentIndexChanged.connect(self._on_supplier_detail_changed)
try:
# Disconnect signals temporarily
if hasattr(self, 'supplier_normal_map_type_combo'):
try:
self.supplier_normal_map_type_combo.currentIndexChanged.disconnect(self._on_supplier_detail_changed)
logger.debug("Disconnected supplier_normal_map_type_combo.currentIndexChanged")
except TypeError:
logger.debug("supplier_normal_map_type_combo.currentIndexChanged was not connected or already disconnected.")
pass
if current_item:
supplier_name = current_item.text()
supplier_data = self.supplier_data.get(supplier_name)
if not isinstance(supplier_data, dict):
logger.error(f"Invalid data for supplier item {supplier_name}. Expected dict, got {type(supplier_data)}")
item_data_role = current_item.data(Qt.UserRole)
if isinstance(item_data_role, dict):
supplier_data = item_data_role
else:
supplier_data = {"normal_map_type": "OpenGL"}
normal_map_type = supplier_data.get('normal_map_type', 'OpenGL')
nmt_index = self.supplier_normal_map_type_combo.findText(normal_map_type)
if nmt_index != -1:
self.supplier_normal_map_type_combo.setCurrentIndex(nmt_index)
else:
self.supplier_normal_map_type_combo.setCurrentIndex(0)
logger.debug(f"Populated details for supplier {current_item.text()}")
else:
# Clear details if no item is selected
if hasattr(self, 'supplier_normal_map_type_combo'):
self.supplier_normal_map_type_combo.setCurrentIndex(0)
logger.debug("Cleared supplier details as no item is selected.")
except Exception as e:
logger.error(f"Error in _display_supplier_details: {e}", exc_info=True)
finally:
# Reconnect signals
if hasattr(self, 'supplier_normal_map_type_combo'):
try:
self.supplier_normal_map_type_combo.currentIndexChanged.connect(self._on_supplier_detail_changed)
logger.debug("Reconnected supplier_normal_map_type_combo.currentIndexChanged")
except Exception as e:
logger.error(f"Failed to reconnect supplier_normal_map_type_combo.currentIndexChanged: {e}", exc_info=True)
logger.info("_display_supplier_details finished.")
def _on_supplier_detail_changed(self):
current_item = self.supplier_list_widget.currentItem()
@@ -1036,6 +1191,71 @@ class DefinitionsEditorDialog(QDialog):
else:
event.accept()
def eventFilter(self, watched, event: QEvent): # Renamed from mouse_event_filter
event_type = event.type()
if watched == self.tab_widget:
# Construct a more identifiable name for the tab widget in logs
tab_widget_name_for_log = self.tab_widget.objectName() if self.tab_widget.objectName() else watched.__class__.__name__
prefix = f"EventFilter (QTabWidget '{tab_widget_name_for_log}'):"
if event_type == QEvent.MouseButtonPress or event_type == QEvent.MouseButtonRelease:
event_name = "Press" if event_type == QEvent.MouseButtonPress else "Release"
# Ensure event has position method (it's a QMouseEvent)
if hasattr(event, 'position') and hasattr(event, 'globalPosition') and hasattr(event, 'button'):
log_line = (f"{prefix} MouseButton{event_name} "
f"global_pos={event.globalPosition().toPoint()}, "
f"widget_pos={event.position().toPoint()}, "
f"button={event.button()}, accepted={event.isAccepted()}")
logger.info(log_line)
current_page = self.tab_widget.currentWidget()
if current_page:
# event.position() is relative to self.tab_widget (the watched object)
tab_widget_event_pos_float = event.position() # QPointF
tab_widget_event_pos = tab_widget_event_pos_float.toPoint() # QPoint
# Map event position from tab_widget coordinates to global, then to page coordinates
global_pos = self.tab_widget.mapToGlobal(tab_widget_event_pos)
page_event_pos = current_page.mapFromGlobal(global_pos)
is_over_page = current_page.rect().contains(page_event_pos)
page_name_for_log = current_page.objectName() if current_page.objectName() else current_page.__class__.__name__
logger.info(f"{prefix} Event mapped to page '{page_name_for_log}' coords: {page_event_pos}. "
f"Page rect: {current_page.rect()}. Is over page: {is_over_page}")
if is_over_page:
logger.info(f"{prefix} Event IS OVER CURRENT PAGE. "
f"Current event.isAccepted(): {event.isAccepted()}. "
f"Returning False from filter to allow propagation to QTabWidget's default handling.")
# Returning False means this filter does not stop the event.
# The event will be sent to self.tab_widget.event() for its default handling,
# which should then propagate to children if appropriate.
return False
else:
logger.info(f"{prefix} Event is NOT over current page (likely on tab bar). Allowing default QTabWidget handling.")
else:
logger.info(f"{prefix} No current page for tab_widget during mouse event.")
else:
logger.warning(f"{prefix} MouseButton{event_name} received, but event object lacks expected QMouseEvent attributes.")
# Example: Log other event types if needed for debugging, but keep it concise
# elif event_type == QEvent.Enter:
# logger.debug(f"{prefix} Enter event")
# elif event_type == QEvent.Leave:
# logger.debug(f"{prefix} Leave event")
# elif event_type == QEvent.FocusIn:
# logger.debug(f"{prefix} FocusIn event")
# elif event_type == QEvent.FocusOut:
# logger.debug(f"{prefix} FocusOut event")
# For other watched objects (if any were installed on), or for events on self.tab_widget
# that were not explicitly handled (e.g., not mouse press/release over page),
# call the base class implementation.
return super().eventFilter(watched, event)
if __name__ == '__main__':
# This is for testing the dialog independently
from PyQt5.QtWidgets import QApplication