1267 lines
65 KiB
Python
1267 lines
65 KiB
Python
import logging
|
|
from PySide6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QTabWidget, QWidget, QListWidget, QListWidgetItem, QPushButton,
|
|
QHBoxLayout, QLabel, QGroupBox, QDialogButtonBox, QFormLayout,
|
|
QTextEdit, QColorDialog, QInputDialog, QMessageBox, QFrame, QComboBox,
|
|
QLineEdit, QCheckBox, QAbstractItemView
|
|
)
|
|
from PySide6.QtGui import QColor, QPalette, QMouseEvent # Added QMouseEvent
|
|
from PySide6.QtCore import Qt, QEvent
|
|
|
|
from PySide6.QtGui import QColor, QPalette, QMouseEvent
|
|
from PySide6.QtCore import Qt, QEvent
|
|
|
|
# Import the Configuration class
|
|
from configuration import Configuration, ConfigurationError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class DebugListWidget(QListWidget):
|
|
def mousePressEvent(self, event: QMouseEvent):
|
|
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, config: Configuration, parent=None):
|
|
super().__init__(parent)
|
|
self.config = config # Store the Configuration object
|
|
self.setWindowTitle("Definitions Editor")
|
|
self.setGeometry(200, 200, 800, 600) # x, y, width, height
|
|
|
|
self.asset_type_data = {}
|
|
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()
|
|
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
self.tab_widget = QTabWidget()
|
|
main_layout.addWidget(self.tab_widget)
|
|
|
|
self._create_ui() # Creates and adds tabs to self.tab_widget
|
|
|
|
self.button_box = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
|
|
self.button_box.accepted.connect(self.save_definitions)
|
|
self.button_box.rejected.connect(self.reject)
|
|
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...")
|
|
try:
|
|
self.asset_type_data = load_asset_definitions()
|
|
logger.info(f"Loaded {len(self.asset_type_data)} asset type definitions.")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load asset type definitions: {e}")
|
|
self.asset_type_data = {} # Ensure it's an empty dict on failure
|
|
|
|
try:
|
|
self.file_type_data = load_file_type_definitions()
|
|
logger.info(f"Loaded {len(self.file_type_data)} file type definitions.")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load file type definitions: {e}")
|
|
self.file_type_data = {}
|
|
|
|
try:
|
|
self.supplier_data = load_supplier_settings()
|
|
logger.info(f"Loaded {len(self.supplier_data)} supplier settings.")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load supplier settings: {e}")
|
|
self.supplier_data = {}
|
|
logger.info("Finished loading definitions.")
|
|
|
|
|
|
def _create_ui(self):
|
|
self.tab_widget.addTab(self._create_asset_types_tab(), "Asset Type Definitions")
|
|
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
|
|
left_pane_layout = QVBoxLayout()
|
|
|
|
lbl_list_title = QLabel(f"{title_singular}s:")
|
|
left_pane_layout.addWidget(lbl_list_title)
|
|
|
|
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.")
|
|
|
|
left_pane_layout.addWidget(list_widget)
|
|
|
|
buttons_layout = QHBoxLayout()
|
|
btn_add = QPushButton(f"Add {title_singular}")
|
|
btn_remove = QPushButton(f"Remove Selected {title_singular}")
|
|
|
|
# Connections for these buttons will be specific to each tab type
|
|
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)
|
|
left_pane_layout.addLayout(buttons_layout)
|
|
|
|
tab_layout.addLayout(left_pane_layout, 1) # 1 part for left pane
|
|
|
|
# Right Pane - This will be customized by specific tab creation methods
|
|
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)
|
|
|
|
# Description
|
|
self.asset_description_edit = QTextEdit()
|
|
details_layout.addRow("Description:", self.asset_description_edit)
|
|
|
|
# Color
|
|
color_layout = QHBoxLayout()
|
|
self.asset_color_swatch_label = QLabel()
|
|
self.asset_color_swatch_label.setFixedSize(20, 20)
|
|
self.asset_color_swatch_label.setAutoFillBackground(True)
|
|
self._update_color_swatch("#ffffff") # Default color
|
|
|
|
btn_choose_color = QPushButton("Choose Color...")
|
|
btn_choose_color.clicked.connect(self._choose_asset_color)
|
|
color_layout.addWidget(self.asset_color_swatch_label)
|
|
color_layout.addWidget(btn_choose_color)
|
|
color_layout.addStretch()
|
|
details_layout.addRow("Color:", color_layout)
|
|
|
|
# Examples
|
|
examples_group = QGroupBox("Examples")
|
|
examples_layout = QVBoxLayout(examples_group)
|
|
|
|
self.asset_examples_list_widget = QListWidget()
|
|
examples_layout.addWidget(self.asset_examples_list_widget)
|
|
|
|
example_buttons_layout = QHBoxLayout()
|
|
btn_add_example = QPushButton("Add Example")
|
|
btn_remove_example = QPushButton("Remove Selected Example")
|
|
btn_add_example.clicked.connect(self._add_asset_example)
|
|
btn_remove_example.clicked.connect(self._remove_asset_example)
|
|
example_buttons_layout.addWidget(btn_add_example)
|
|
example_buttons_layout.addWidget(btn_remove_example)
|
|
examples_layout.addLayout(example_buttons_layout)
|
|
|
|
details_layout.addRow(examples_group)
|
|
|
|
# Replace the generic right_pane_widget with our specific groupbox
|
|
# To do this, we need to find the layout of right_pane_container's parent (which is tab_layout)
|
|
# and replace the widget.
|
|
parent_layout = right_pane_container.parentWidget().layout()
|
|
if parent_layout:
|
|
parent_layout.replaceWidget(right_pane_container, right_pane_groupbox)
|
|
right_pane_container.deleteLater() # Remove the placeholder
|
|
|
|
# Connect signals for editing
|
|
self.asset_description_edit.textChanged.connect(self._on_asset_detail_changed)
|
|
|
|
# Initial population of list widget (if not already done by _create_tab_pane)
|
|
# and display details for the first item if any.
|
|
self._populate_asset_type_list() # Ensure data is loaded with UserRole
|
|
if self.asset_type_list_widget.count() > 0:
|
|
self.asset_type_list_widget.setCurrentRow(0)
|
|
# self._display_asset_type_details(self.asset_type_list_widget.currentItem()) # Already connected
|
|
|
|
return tab_page
|
|
|
|
def _populate_asset_type_list(self):
|
|
self.asset_type_list_widget.clear()
|
|
for key, asset_data_item in self.asset_type_data.items():
|
|
item = QListWidgetItem(key)
|
|
# Ensure asset_data_item is a dictionary, if not, create a default one
|
|
if not isinstance(asset_data_item, dict):
|
|
logger.warning(f"Asset data for '{key}' is not a dict: {asset_data_item}. Using default.")
|
|
asset_data_item = {"description": str(asset_data_item), "color": "#ffffff", "examples": []}
|
|
|
|
# Ensure essential keys exist
|
|
asset_data_item.setdefault('description', '')
|
|
asset_data_item.setdefault('color', '#ffffff')
|
|
asset_data_item.setdefault('examples', [])
|
|
|
|
item.setData(Qt.UserRole, asset_data_item)
|
|
self.asset_type_list_widget.addItem(item)
|
|
|
|
def _display_asset_type_details(self, current_item, previous_item=None):
|
|
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:
|
|
logger.info(f"Current item text: {current_item.text()}")
|
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
|
else:
|
|
logger.info("Current item is None for asset_type_details.")
|
|
|
|
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.Window, QColor(color_hex))
|
|
self.asset_color_swatch_label.setPalette(palette)
|
|
|
|
def _choose_asset_color(self):
|
|
current_item = self.asset_type_list_widget.currentItem()
|
|
if not current_item:
|
|
return
|
|
|
|
asset_data = current_item.data(Qt.UserRole)
|
|
initial_color = QColor(asset_data.get('color', '#ffffff'))
|
|
|
|
color = QColorDialog.getColor(initial_color, self, "Choose Asset Type Color")
|
|
if color.isValid():
|
|
color_hex = color.name()
|
|
self._update_color_swatch(color_hex)
|
|
asset_data['color'] = color_hex
|
|
current_item.setData(Qt.UserRole, asset_data) # Update data in item
|
|
self.unsaved_changes = True
|
|
# No need to call _on_asset_detail_changed explicitly for color, direct update is fine
|
|
|
|
def _on_asset_detail_changed(self):
|
|
current_item = self.asset_type_list_widget.currentItem()
|
|
if not current_item:
|
|
return
|
|
|
|
asset_data = current_item.data(Qt.UserRole)
|
|
if not isinstance(asset_data, dict): return # Should not happen
|
|
|
|
# Update description
|
|
asset_data['description'] = self.asset_description_edit.toPlainText()
|
|
|
|
# Examples are handled by their own add/remove buttons
|
|
# Color is handled by _choose_asset_color
|
|
|
|
current_item.setData(Qt.UserRole, asset_data) # Save changes back to the item's data
|
|
self.unsaved_changes = True
|
|
|
|
def _add_asset_type(self):
|
|
new_name, ok = QInputDialog.getText(self, "Add Asset Type", "Enter name for the new asset type:")
|
|
if ok and new_name:
|
|
if new_name in self.asset_type_data:
|
|
QMessageBox.warning(self, "Name Exists", f"An asset type named '{new_name}' already exists.")
|
|
return
|
|
|
|
default_asset_type = {
|
|
"description": "",
|
|
"color": "#ffffff",
|
|
"examples": []
|
|
}
|
|
self.asset_type_data[new_name] = default_asset_type
|
|
|
|
item = QListWidgetItem(new_name)
|
|
item.setData(Qt.UserRole, default_asset_type) # Store a copy
|
|
self.asset_type_list_widget.addItem(item)
|
|
self.asset_type_list_widget.setCurrentItem(item) # Triggers _display_asset_type_details
|
|
logger.info(f"Added new asset type: {new_name}")
|
|
self.unsaved_changes = True
|
|
elif ok and not new_name:
|
|
QMessageBox.warning(self, "Invalid Name", "Asset type name cannot be empty.")
|
|
|
|
def _remove_asset_type(self):
|
|
current_item = self.asset_type_list_widget.currentItem()
|
|
if not current_item:
|
|
QMessageBox.information(self, "No Selection", "Please select an asset type to remove.")
|
|
return
|
|
|
|
asset_name = current_item.text()
|
|
reply = QMessageBox.question(self, "Confirm Removal",
|
|
f"Are you sure you want to remove the asset type '{asset_name}'?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
if asset_name in self.asset_type_data:
|
|
del self.asset_type_data[asset_name]
|
|
|
|
row = self.asset_type_list_widget.row(current_item)
|
|
self.asset_type_list_widget.takeItem(row)
|
|
logger.info(f"Removed asset type: {asset_name}")
|
|
self.unsaved_changes = True
|
|
|
|
if self.asset_type_list_widget.count() > 0:
|
|
new_row_to_select = max(0, row - 1) if row > 0 else 0
|
|
if self.asset_type_list_widget.count() > new_row_to_select: # Ensure new_row_to_select is valid
|
|
self.asset_type_list_widget.setCurrentRow(new_row_to_select)
|
|
else: # if list becomes empty or selection is out of bounds
|
|
self._display_asset_type_details(None, None)
|
|
else:
|
|
self._display_asset_type_details(None, None) # Clear details if list is empty
|
|
|
|
def _add_asset_example(self):
|
|
current_asset_item = self.asset_type_list_widget.currentItem()
|
|
if not current_asset_item:
|
|
QMessageBox.information(self, "No Asset Type Selected", "Please select an asset type first.")
|
|
return
|
|
|
|
new_example, ok = QInputDialog.getText(self, "Add Example", "Enter new example string:")
|
|
if ok and new_example:
|
|
asset_data = current_asset_item.data(Qt.UserRole)
|
|
if not isinstance(asset_data, dict) or 'examples' not in asset_data:
|
|
logger.error("Asset data is not a dict or 'examples' key is missing.")
|
|
QMessageBox.critical(self, "Error", "Internal data error for selected asset type.")
|
|
return
|
|
|
|
if not isinstance(asset_data['examples'], list): # Ensure 'examples' is a list
|
|
asset_data['examples'] = []
|
|
|
|
asset_data['examples'].append(new_example)
|
|
current_asset_item.setData(Qt.UserRole, asset_data) # Update data in item
|
|
|
|
self.asset_examples_list_widget.addItem(new_example)
|
|
logger.info(f"Added example '{new_example}' to asset type '{current_asset_item.text()}'")
|
|
self.unsaved_changes = True
|
|
elif ok and not new_example:
|
|
QMessageBox.warning(self, "Invalid Example", "Example string cannot be empty.")
|
|
|
|
def _remove_asset_example(self):
|
|
current_asset_item = self.asset_type_list_widget.currentItem()
|
|
if not current_asset_item:
|
|
QMessageBox.information(self, "No Asset Type Selected", "Please select an asset type first.")
|
|
return
|
|
|
|
current_example_item = self.asset_examples_list_widget.currentItem()
|
|
if not current_example_item:
|
|
QMessageBox.information(self, "No Example Selected", "Please select an example to remove.")
|
|
return
|
|
|
|
example_text = current_example_item.text()
|
|
|
|
# No confirmation needed as per typical list item removal, but can be added if desired.
|
|
# reply = QMessageBox.question(self, "Confirm Removal",
|
|
# f"Are you sure you want to remove the example '{example_text}'?",
|
|
# QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
# if reply == QMessageBox.No:
|
|
# return
|
|
|
|
asset_data = current_asset_item.data(Qt.UserRole)
|
|
if not isinstance(asset_data, dict) or 'examples' not in asset_data or not isinstance(asset_data['examples'], list):
|
|
logger.error("Asset data issue during example removal.")
|
|
QMessageBox.critical(self, "Error", "Internal data error for selected asset type.")
|
|
return
|
|
|
|
try:
|
|
asset_data['examples'].remove(example_text)
|
|
current_asset_item.setData(Qt.UserRole, asset_data) # Update data in item
|
|
|
|
row = self.asset_examples_list_widget.row(current_example_item)
|
|
self.asset_examples_list_widget.takeItem(row)
|
|
logger.info(f"Removed example '{example_text}' from asset type '{current_asset_item.text()}'")
|
|
self.unsaved_changes = True
|
|
except ValueError:
|
|
logger.warning(f"Example '{example_text}' not found in internal list for asset '{current_asset_item.text()}'. UI might be out of sync.")
|
|
# Still remove from UI if it was there
|
|
row = self.asset_examples_list_widget.row(current_example_item)
|
|
if row >=0: self.asset_examples_list_widget.takeItem(row)
|
|
|
|
|
|
def _update_file_type_color_swatch(self, color_hex, swatch_label):
|
|
if hasattr(self, swatch_label): # Check if the specific swatch label exists
|
|
palette = swatch_label.palette()
|
|
palette.setColor(QPalette.Window, QColor(color_hex))
|
|
swatch_label.setPalette(palette)
|
|
|
|
def _create_file_types_tab(self):
|
|
tab_page, right_pane_container = self._create_tab_pane("File Type", self.file_type_data, "file_type_list_widget")
|
|
|
|
right_pane_groupbox = QGroupBox("Details for Selected File Type")
|
|
details_layout = QFormLayout(right_pane_groupbox)
|
|
|
|
# Description
|
|
self.ft_description_edit = QTextEdit()
|
|
details_layout.addRow("Description:", self.ft_description_edit)
|
|
|
|
# Color
|
|
ft_color_layout = QHBoxLayout()
|
|
self.ft_color_swatch_label = QLabel()
|
|
self.ft_color_swatch_label.setFixedSize(20, 20)
|
|
self.ft_color_swatch_label.setAutoFillBackground(True)
|
|
self._update_color_swatch_generic(self.ft_color_swatch_label, "#ffffff") # Default
|
|
|
|
btn_ft_choose_color = QPushButton("Choose Color...")
|
|
btn_ft_choose_color.clicked.connect(self._choose_file_type_color)
|
|
ft_color_layout.addWidget(self.ft_color_swatch_label)
|
|
ft_color_layout.addWidget(btn_ft_choose_color)
|
|
ft_color_layout.addStretch()
|
|
details_layout.addRow("Color:", ft_color_layout)
|
|
|
|
# Examples
|
|
ft_examples_group = QGroupBox("Examples")
|
|
ft_examples_layout = QVBoxLayout(ft_examples_group)
|
|
self.ft_examples_list_widget = QListWidget()
|
|
ft_examples_layout.addWidget(self.ft_examples_list_widget)
|
|
ft_example_buttons_layout = QHBoxLayout()
|
|
btn_ft_add_example = QPushButton("Add Example")
|
|
btn_ft_remove_example = QPushButton("Remove Selected Example")
|
|
btn_ft_add_example.clicked.connect(self._add_file_type_example)
|
|
btn_ft_remove_example.clicked.connect(self._remove_file_type_example)
|
|
ft_example_buttons_layout.addWidget(btn_ft_add_example)
|
|
ft_example_buttons_layout.addWidget(btn_ft_remove_example)
|
|
ft_examples_layout.addLayout(ft_example_buttons_layout)
|
|
details_layout.addRow(ft_examples_group)
|
|
|
|
# Standard Type
|
|
self.ft_standard_type_edit = QLineEdit()
|
|
details_layout.addRow("Standard Type:", self.ft_standard_type_edit)
|
|
|
|
# Bit Depth Rule
|
|
self.ft_bit_depth_combo = QComboBox()
|
|
self.ft_bit_depth_combo.addItems(["preserve", "force_8bit", "force_16bit"])
|
|
details_layout.addRow("Bit Depth Policy:", self.ft_bit_depth_combo)
|
|
|
|
# Is Grayscale
|
|
self.ft_is_grayscale_check = QCheckBox("Is Grayscale")
|
|
details_layout.addRow(self.ft_is_grayscale_check) # No label for checkbox itself
|
|
|
|
# Keybind
|
|
self.ft_keybind_edit = QLineEdit()
|
|
self.ft_keybind_edit.setMaxLength(1) # Basic validation
|
|
details_layout.addRow("Keybind:", self.ft_keybind_edit)
|
|
|
|
parent_layout = right_pane_container.parentWidget().layout()
|
|
if parent_layout:
|
|
parent_layout.replaceWidget(right_pane_container, right_pane_groupbox)
|
|
right_pane_container.deleteLater()
|
|
|
|
# Connect signals for editing
|
|
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)
|
|
|
|
self._populate_file_type_list()
|
|
if self.file_type_list_widget.count() > 0:
|
|
self.file_type_list_widget.setCurrentRow(0)
|
|
# _display_file_type_details is connected to currentItemChanged
|
|
|
|
return tab_page
|
|
|
|
def _populate_file_type_list(self):
|
|
self.file_type_list_widget.clear()
|
|
for key, ft_data_item in self.file_type_data.items():
|
|
item = QListWidgetItem(key)
|
|
if not isinstance(ft_data_item, dict):
|
|
logger.warning(f"File type data for '{key}' is not a dict: {ft_data_item}. Using default.")
|
|
ft_data_item = {
|
|
"description": str(ft_data_item), "color": "#ffffff", "examples": [],
|
|
"standard_type": "", "bit_depth_policy": "preserve",
|
|
"is_grayscale": False, "keybind": ""
|
|
}
|
|
|
|
# Ensure all essential keys exist with defaults
|
|
ft_data_item.setdefault('description', '')
|
|
ft_data_item.setdefault('color', '#ffffff')
|
|
ft_data_item.setdefault('examples', [])
|
|
ft_data_item.setdefault('standard_type', '')
|
|
ft_data_item.setdefault('bit_depth_policy', 'preserve')
|
|
ft_data_item.setdefault('is_grayscale', False)
|
|
ft_data_item.setdefault('keybind', '')
|
|
|
|
item.setData(Qt.UserRole, ft_data_item)
|
|
self.file_type_list_widget.addItem(item)
|
|
|
|
def _display_file_type_details(self, current_item, previous_item=None):
|
|
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:
|
|
logger.info(f"Current item text: {current_item.text()}")
|
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
|
else:
|
|
logger.info("Current item is None for file_type_details.")
|
|
|
|
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_policy": "preserve",
|
|
"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_policy', 'preserve'))
|
|
if bdr_index != -1:
|
|
self.ft_bit_depth_combo.setCurrentIndex(bdr_index)
|
|
else:
|
|
self.ft_bit_depth_combo.setCurrentIndex(0) # Default to 'preserve'
|
|
|
|
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.Window, QColor(color_hex))
|
|
swatch_label.setPalette(palette)
|
|
swatch_label.update() # Ensure the label repaints
|
|
|
|
# --- File Type action methods ---
|
|
def _add_file_type(self):
|
|
new_id, ok = QInputDialog.getText(self, "Add File Type", "Enter ID for the new file type (e.g., MAP_ALB):")
|
|
if ok and new_id:
|
|
new_id = new_id.strip() # Remove leading/trailing whitespace
|
|
if not new_id: # Check if empty after strip
|
|
QMessageBox.warning(self, "Invalid ID", "File type ID cannot be empty.")
|
|
return
|
|
if new_id in self.file_type_data:
|
|
QMessageBox.warning(self, "ID Exists", f"A file type with ID '{new_id}' already exists.")
|
|
return
|
|
|
|
default_file_type = {
|
|
"description": "",
|
|
"color": "#ffffff",
|
|
"examples": [],
|
|
"standard_type": "",
|
|
"bit_depth_policy": "preserve",
|
|
"is_grayscale": False,
|
|
"keybind": ""
|
|
}
|
|
self.file_type_data[new_id] = default_file_type
|
|
|
|
item = QListWidgetItem(new_id)
|
|
item.setData(Qt.UserRole, default_file_type.copy()) # Store a copy for the item
|
|
self.file_type_list_widget.addItem(item)
|
|
self.file_type_list_widget.setCurrentItem(item) # Triggers _display_file_type_details
|
|
logger.info(f"Added new file type: {new_id}")
|
|
self.unsaved_changes = True
|
|
elif ok and not new_id.strip(): # Also catch if user entered only spaces and pressed OK
|
|
QMessageBox.warning(self, "Invalid ID", "File type ID cannot be empty.")
|
|
|
|
def _remove_file_type(self):
|
|
current_item = self.file_type_list_widget.currentItem()
|
|
if not current_item:
|
|
QMessageBox.information(self, "No Selection", "Please select a file type to remove.")
|
|
return
|
|
|
|
file_type_id = current_item.text()
|
|
reply = QMessageBox.question(self, "Confirm Removal",
|
|
f"Are you sure you want to remove the file type '{file_type_id}'?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
if file_type_id in self.file_type_data:
|
|
del self.file_type_data[file_type_id]
|
|
|
|
row = self.file_type_list_widget.row(current_item)
|
|
self.file_type_list_widget.takeItem(row)
|
|
logger.info(f"Removed file type: {file_type_id}")
|
|
self.unsaved_changes = True
|
|
|
|
if self.file_type_list_widget.count() > 0:
|
|
new_row_to_select = max(0, row - 1) if row > 0 else 0
|
|
if self.file_type_list_widget.count() > new_row_to_select:
|
|
self.file_type_list_widget.setCurrentRow(new_row_to_select)
|
|
else: # if list becomes empty or selection is out of bounds
|
|
self._display_file_type_details(None, None) # Clear details
|
|
else:
|
|
self._display_file_type_details(None, None) # Clear details if list is empty
|
|
|
|
def _choose_file_type_color(self):
|
|
current_item = self.file_type_list_widget.currentItem()
|
|
if not current_item:
|
|
return
|
|
|
|
ft_data = current_item.data(Qt.UserRole)
|
|
if not isinstance(ft_data, dict): # Should not happen
|
|
logger.error("File type item data is not a dict in _choose_file_type_color.")
|
|
return
|
|
|
|
initial_color = QColor(ft_data.get('color', '#ffffff'))
|
|
|
|
color = QColorDialog.getColor(initial_color, self, "Choose File Type Color")
|
|
if color.isValid():
|
|
color_hex = color.name()
|
|
self._update_color_swatch_generic(self.ft_color_swatch_label, color_hex)
|
|
ft_data['color'] = color_hex
|
|
current_item.setData(Qt.UserRole, ft_data) # Update data in item
|
|
self.unsaved_changes = True
|
|
|
|
def _add_file_type_example(self):
|
|
current_ft_item = self.file_type_list_widget.currentItem()
|
|
if not current_ft_item:
|
|
QMessageBox.information(self, "No File Type Selected", "Please select a file type first.")
|
|
return
|
|
|
|
new_example, ok = QInputDialog.getText(self, "Add File Type Example", "Enter new example string (e.g., _alb.png, .exr):")
|
|
if ok and new_example:
|
|
new_example = new_example.strip()
|
|
if not new_example:
|
|
QMessageBox.warning(self, "Invalid Example", "Example string cannot be empty.")
|
|
return
|
|
|
|
ft_data = current_ft_item.data(Qt.UserRole)
|
|
if not isinstance(ft_data, dict) or 'examples' not in ft_data:
|
|
logger.error("File type data is not a dict or 'examples' key is missing.")
|
|
QMessageBox.critical(self, "Error", "Internal data error for selected file type.")
|
|
return
|
|
|
|
if not isinstance(ft_data['examples'], list): # Ensure 'examples' is a list
|
|
ft_data['examples'] = []
|
|
|
|
if new_example in ft_data['examples']:
|
|
QMessageBox.information(self, "Example Exists", f"The example '{new_example}' already exists for this file type.")
|
|
return
|
|
|
|
ft_data['examples'].append(new_example)
|
|
current_ft_item.setData(Qt.UserRole, ft_data) # Update data in item
|
|
|
|
self.ft_examples_list_widget.addItem(new_example)
|
|
logger.info(f"Added example '{new_example}' to file type '{current_ft_item.text()}'")
|
|
self.unsaved_changes = True
|
|
elif ok and not new_example.strip():
|
|
QMessageBox.warning(self, "Invalid Example", "Example string cannot be empty.")
|
|
|
|
def _remove_file_type_example(self):
|
|
current_ft_item = self.file_type_list_widget.currentItem()
|
|
if not current_ft_item:
|
|
QMessageBox.information(self, "No File Type Selected", "Please select a file type first.")
|
|
return
|
|
|
|
current_example_item = self.ft_examples_list_widget.currentItem()
|
|
if not current_example_item:
|
|
QMessageBox.information(self, "No Example Selected", "Please select an example to remove.")
|
|
return
|
|
|
|
example_text = current_example_item.text()
|
|
|
|
ft_data = current_ft_item.data(Qt.UserRole)
|
|
if not isinstance(ft_data, dict) or 'examples' not in ft_data or not isinstance(ft_data['examples'], list):
|
|
logger.error("File type data issue during example removal.")
|
|
QMessageBox.critical(self, "Error", "Internal data error for selected file type.")
|
|
return
|
|
|
|
try:
|
|
ft_data['examples'].remove(example_text)
|
|
current_ft_item.setData(Qt.UserRole, ft_data) # Update data in item
|
|
|
|
row = self.ft_examples_list_widget.row(current_example_item)
|
|
self.ft_examples_list_widget.takeItem(row)
|
|
logger.info(f"Removed example '{example_text}' from file type '{current_ft_item.text()}'")
|
|
self.unsaved_changes = True
|
|
except ValueError:
|
|
logger.warning(f"Example '{example_text}' not found in internal list for file type '{current_ft_item.text()}'. UI might be out of sync.")
|
|
row = self.ft_examples_list_widget.row(current_example_item)
|
|
if row >=0: self.ft_examples_list_widget.takeItem(row)
|
|
|
|
def _on_file_type_detail_changed(self):
|
|
current_item = self.file_type_list_widget.currentItem()
|
|
if not current_item:
|
|
return
|
|
|
|
ft_data = current_item.data(Qt.UserRole)
|
|
if not isinstance(ft_data, dict):
|
|
logger.error("File type item data is not a dict in _on_file_type_detail_changed.")
|
|
return
|
|
|
|
# Update based on which widget triggered (or update all)
|
|
ft_data['description'] = self.ft_description_edit.toPlainText()
|
|
ft_data['standard_type'] = self.ft_standard_type_edit.text()
|
|
ft_data['bit_depth_policy'] = self.ft_bit_depth_combo.currentText()
|
|
ft_data['is_grayscale'] = self.ft_is_grayscale_check.isChecked()
|
|
|
|
# Keybind validation (force uppercase)
|
|
keybind_text = self.ft_keybind_edit.text()
|
|
if keybind_text: # MaxLength(1) is already set
|
|
# Disconnect to prevent recursive call during setText
|
|
try: self.ft_keybind_edit.textChanged.disconnect(self._on_file_type_detail_changed)
|
|
except TypeError: pass
|
|
self.ft_keybind_edit.setText(keybind_text.upper())
|
|
# Reconnect
|
|
self.ft_keybind_edit.textChanged.connect(self._on_file_type_detail_changed)
|
|
ft_data['keybind'] = keybind_text.upper()
|
|
else:
|
|
ft_data['keybind'] = ''
|
|
|
|
current_item.setData(Qt.UserRole, ft_data)
|
|
logger.debug(f"File type '{current_item.text()}' data updated: {ft_data}")
|
|
self.unsaved_changes = True
|
|
# --- End Placeholder methods ---
|
|
|
|
def _create_suppliers_tab(self):
|
|
tab_page, right_pane_container = self._create_tab_pane("Supplier", self.supplier_data, "supplier_list_widget")
|
|
|
|
right_pane_groupbox = QGroupBox("Details for Selected Supplier")
|
|
details_layout = QFormLayout(right_pane_groupbox)
|
|
|
|
# Normal Map Type
|
|
self.supplier_normal_map_type_combo = QComboBox()
|
|
self.supplier_normal_map_type_combo.addItems(["OpenGL", "DirectX"])
|
|
details_layout.addRow("Normal Map Type:", self.supplier_normal_map_type_combo)
|
|
|
|
# Replace the generic right_pane_widget
|
|
parent_layout = right_pane_container.parentWidget().layout()
|
|
if parent_layout:
|
|
parent_layout.replaceWidget(right_pane_container, right_pane_groupbox)
|
|
right_pane_container.deleteLater()
|
|
|
|
# Connect signals for editing
|
|
self.supplier_normal_map_type_combo.currentIndexChanged.connect(self._on_supplier_detail_changed)
|
|
|
|
# Initial population and display
|
|
self._populate_supplier_list()
|
|
if self.supplier_list_widget.count() > 0:
|
|
self.supplier_list_widget.setCurrentRow(0)
|
|
# _display_supplier_details is connected to currentItemChanged
|
|
|
|
return tab_page
|
|
|
|
def _populate_supplier_list(self):
|
|
self.supplier_list_widget.clear()
|
|
for key, sup_data_item in self.supplier_data.items():
|
|
item = QListWidgetItem(key)
|
|
if not isinstance(sup_data_item, dict):
|
|
logger.warning(f"Supplier data for '{key}' is not a dict: {sup_data_item}. Using default.")
|
|
sup_data_item = {"normal_map_type": "OpenGL"}
|
|
sup_data_item.setdefault('normal_map_type', 'OpenGL') # Ensure key exists
|
|
item.setData(Qt.UserRole, sup_data_item)
|
|
self.supplier_list_widget.addItem(item)
|
|
|
|
def _display_supplier_details(self, current_item, previous_item=None):
|
|
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:
|
|
logger.info(f"Current item text: {current_item.text()}")
|
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
|
else:
|
|
logger.info("Current item is None for supplier_details.")
|
|
|
|
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()
|
|
if not current_item:
|
|
return
|
|
|
|
supplier_name = current_item.text()
|
|
if supplier_name not in self.supplier_data:
|
|
logger.error(f"Supplier '{supplier_name}' not found in self.supplier_data during detail change.")
|
|
return # Or create it, but that might be unexpected here
|
|
|
|
# Ensure the entry in self.supplier_data is a dictionary
|
|
if not isinstance(self.supplier_data[supplier_name], dict):
|
|
self.supplier_data[supplier_name] = {} # Initialize if it's not a dict
|
|
|
|
new_normal_map_type = self.supplier_normal_map_type_combo.currentText()
|
|
self.supplier_data[supplier_name]['normal_map_type'] = new_normal_map_type
|
|
|
|
# Update the item's UserRole data as well to keep it in sync
|
|
current_item.setData(Qt.UserRole, self.supplier_data[supplier_name].copy())
|
|
|
|
logger.debug(f"Supplier '{supplier_name}' normal_map_type updated to: {new_normal_map_type}")
|
|
self.unsaved_changes = True
|
|
|
|
def _add_supplier(self):
|
|
new_name, ok = QInputDialog.getText(self, "Add Supplier", "Enter name for the new supplier:")
|
|
if ok and new_name:
|
|
new_name = new_name.strip()
|
|
if not new_name:
|
|
QMessageBox.warning(self, "Invalid Name", "Supplier name cannot be empty.")
|
|
return
|
|
if new_name in self.supplier_data:
|
|
QMessageBox.warning(self, "Name Exists", f"A supplier named '{new_name}' already exists.")
|
|
return
|
|
|
|
default_supplier_settings = {"normal_map_type": "OpenGL"}
|
|
self.supplier_data[new_name] = default_supplier_settings
|
|
|
|
item = QListWidgetItem(new_name)
|
|
item.setData(Qt.UserRole, default_supplier_settings.copy()) # Store a copy
|
|
self.supplier_list_widget.addItem(item)
|
|
self.supplier_list_widget.setCurrentItem(item) # Triggers display
|
|
logger.info(f"Added new supplier: {new_name}")
|
|
self.unsaved_changes = True
|
|
elif ok and not new_name.strip():
|
|
QMessageBox.warning(self, "Invalid Name", "Supplier name cannot be empty.")
|
|
|
|
def _remove_supplier(self):
|
|
current_item = self.supplier_list_widget.currentItem()
|
|
if not current_item:
|
|
QMessageBox.information(self, "No Selection", "Please select a supplier to remove.")
|
|
return
|
|
|
|
supplier_name = current_item.text()
|
|
reply = QMessageBox.question(self, "Confirm Removal",
|
|
f"Are you sure you want to remove the supplier '{supplier_name}'?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
if supplier_name in self.supplier_data:
|
|
del self.supplier_data[supplier_name]
|
|
|
|
row = self.supplier_list_widget.row(current_item)
|
|
self.supplier_list_widget.takeItem(row)
|
|
logger.info(f"Removed supplier: {supplier_name}")
|
|
self.unsaved_changes = True
|
|
|
|
# Select another item or clear details
|
|
if self.supplier_list_widget.count() > 0:
|
|
new_row_to_select = max(0, row - 1) if row > 0 else 0
|
|
if self.supplier_list_widget.count() > new_row_to_select:
|
|
self.supplier_list_widget.setCurrentRow(new_row_to_select)
|
|
else:
|
|
self._display_supplier_details(None, None)
|
|
else:
|
|
self._display_supplier_details(None, None) # Clear details if list is empty
|
|
|
|
def save_definitions(self):
|
|
logger.info("Attempting to save definitions...")
|
|
try:
|
|
# --- Asset Type Definitions ---
|
|
# Ensure self.asset_type_data is consistent with the QListWidget items.
|
|
# All edits should have updated the item's UserRole data.
|
|
# Add/Remove operations update self.asset_type_data directly.
|
|
# This loop ensures any in-place modifications to item data (like description, color)
|
|
# are reflected in the self.asset_type_data before saving.
|
|
|
|
current_keys_in_list = set()
|
|
if hasattr(self, 'asset_type_list_widget'): # Check if the widget exists
|
|
for i in range(self.asset_type_list_widget.count()):
|
|
item = self.asset_type_list_widget.item(i)
|
|
key = item.text()
|
|
current_keys_in_list.add(key)
|
|
# Update self.asset_type_data with the (potentially modified) UserRole data
|
|
item_data = item.data(Qt.UserRole)
|
|
if isinstance(item_data, dict):
|
|
self.asset_type_data[key] = item_data
|
|
else:
|
|
logger.warning(f"Item '{key}' in asset_type_list_widget has non-dict UserRole data: {type(item_data)}. Skipping update for this item in self.asset_type_data.")
|
|
|
|
# Remove any keys from self.asset_type_data that are no longer in the list
|
|
# (should be handled by _remove_asset_type, but this is a safeguard)
|
|
keys_to_remove_from_dict = set(self.asset_type_data.keys()) - current_keys_in_list
|
|
for key in keys_to_remove_from_dict:
|
|
logger.info(f"Removing orphaned key '{key}' from self.asset_type_data before saving.")
|
|
del self.asset_type_data[key]
|
|
|
|
save_asset_definitions(self.asset_type_data)
|
|
logger.info("Asset Type definitions saved successfully.")
|
|
|
|
# --- File Type Definitions ---
|
|
if hasattr(self, 'file_type_data') and hasattr(self, 'file_type_list_widget'):
|
|
current_ft_keys_in_list = set()
|
|
for i in range(self.file_type_list_widget.count()):
|
|
item = self.file_type_list_widget.item(i)
|
|
key = item.text()
|
|
current_ft_keys_in_list.add(key)
|
|
item_data = item.data(Qt.UserRole)
|
|
if isinstance(item_data, dict):
|
|
self.file_type_data[key] = item_data
|
|
else:
|
|
logger.warning(f"Item '{key}' in file_type_list_widget has non-dict UserRole data: {type(item_data)}. Skipping.")
|
|
|
|
keys_to_remove_ft = set(self.file_type_data.keys()) - current_ft_keys_in_list
|
|
for key in keys_to_remove_ft:
|
|
logger.info(f"Removing orphaned key '{key}' from self.file_type_data before saving.")
|
|
del self.file_type_data[key]
|
|
|
|
save_file_type_definitions(self.file_type_data)
|
|
logger.info("File Type definitions saved successfully.")
|
|
else:
|
|
logger.info("File type data or list widget not found, skipping save for file types.")
|
|
|
|
# --- Supplier Settings ---
|
|
if hasattr(self, 'supplier_data') and hasattr(self, 'supplier_list_widget'):
|
|
current_s_keys_in_list = set()
|
|
for i in range(self.supplier_list_widget.count()):
|
|
item = self.supplier_list_widget.item(i)
|
|
key = item.text()
|
|
current_s_keys_in_list.add(key)
|
|
item_data = item.data(Qt.UserRole)
|
|
if isinstance(item_data, dict):
|
|
self.supplier_data[key] = item_data # Ensure self.supplier_data is up-to-date
|
|
else:
|
|
logger.warning(f"Item '{key}' in supplier_list_widget has non-dict UserRole data: {type(item_data)}. Skipping update for this item in self.supplier_data.")
|
|
|
|
keys_to_remove_s = set(self.supplier_data.keys()) - current_s_keys_in_list
|
|
for key in keys_to_remove_s:
|
|
logger.info(f"Removing orphaned key '{key}' from self.supplier_data before saving.")
|
|
del self.supplier_data[key]
|
|
|
|
save_supplier_settings(self.supplier_data)
|
|
logger.info("Supplier settings saved successfully.")
|
|
else:
|
|
logger.info("Supplier data or list widget not found, skipping save for suppliers.")
|
|
|
|
|
|
QMessageBox.information(self, "Save Successful", "Definitions saved successfully.")
|
|
self.unsaved_changes = False # Reset flag
|
|
self.accept() # Close dialog on successful save
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to save definitions: {e}", exc_info=True)
|
|
QMessageBox.critical(self, "Save Error", f"Could not save definitions: {e}")
|
|
# Optionally, do not close the dialog on error by removing self.accept() or calling self.reject()
|
|
|
|
def reject(self):
|
|
if self.unsaved_changes:
|
|
reply = QMessageBox.question(self, "Unsaved Changes",
|
|
"You have unsaved changes. Are you sure you want to cancel?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
if reply == QMessageBox.No:
|
|
return # Do not close
|
|
super().reject() # Proceed with closing
|
|
|
|
def closeEvent(self, event):
|
|
if self.unsaved_changes:
|
|
reply = QMessageBox.question(self, "Unsaved Changes",
|
|
"You have unsaved changes. Are you sure you want to close?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
if reply == QMessageBox.Yes:
|
|
event.accept()
|
|
else:
|
|
event.ignore()
|
|
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
|
|
import sys
|
|
|
|
# Setup basic logging for testing
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
|
|
|
# Create dummy config files if they don't exist for testing
|
|
config_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'config'))
|
|
os.makedirs(config_dir, exist_ok=True)
|
|
|
|
asset_types_path = os.path.join(config_dir, 'asset_type_definitions.json')
|
|
file_types_path = os.path.join(config_dir, 'file_type_definitions.json')
|
|
suppliers_path = os.path.join(config_dir, 'suppliers.json')
|
|
|
|
if not os.path.exists(asset_types_path):
|
|
with open(asset_types_path, 'w') as f:
|
|
f.write('{"GenericModel": {"description": "A generic 3D model"}, "TextureSet": {"description": "A set of PBR textures"}}')
|
|
if not os.path.exists(file_types_path):
|
|
with open(file_types_path, 'w') as f:
|
|
f.write('{".fbx": {"description": "Filmbox format"}, ".png": {"description": "Portable Network Graphics"}}')
|
|
if not os.path.exists(suppliers_path):
|
|
with open(suppliers_path, 'w') as f:
|
|
f.write('{"Poliigon": {"api_key": "dummy_key"}, "Local": {}}')
|
|
|
|
app = QApplication(sys.argv)
|
|
dialog = DefinitionsEditorDialog()
|
|
dialog.show()
|
|
sys.exit(app.exec_()) |