Prototype > PreAlpha #67

Merged
Rusfort merged 54 commits from Dev into Stable 2025-05-15 09:10:54 +02:00
3 changed files with 185 additions and 143 deletions
Showing only changes of commit 5c041e3774 - Show all commits

View File

@ -39,47 +39,57 @@ class ConfigEditorDialog(QDialog):
self.button_box.rejected.connect(self.reject)
self.main_layout.addWidget(self.button_box)
self.load_settings()
self.create_tabs()
self.populate_widgets()
self.load_settings() # Load settings FIRST
self.create_tabs() # THEN create widgets based on settings
# self.populate_widgets() # Removed as population is now in load_settings
def load_settings(self):
"""Loads settings from the configuration file."""
"""Loads settings from the configuration file and populates widgets."""
try:
self.settings = load_base_config()
print("Configuration loaded successfully.") # Debug print
# Populate widgets after loading settings and creating tabs
self.populate_widgets_from_settings()
except Exception as e:
QMessageBox.critical(self, "Loading Error", f"Failed to load configuration: {e}")
self.settings = {} # Use empty settings on failure
# Optionally disable save button or widgets if loading fails
self.button_box.button(QDialogButtonBox.Save).setEnabled(False)
def create_tabs(self):
"""Creates tabs based on logical groupings of settings."""
"""Creates tabs based on the redesigned UI plan."""
if not self.settings:
return
# --- Create Tabs ---
self.tabs = {
"general": QWidget(),
"output_naming": QWidget(),
"image_processing": QWidget(),
"definitions": QWidget(),
"paths_output": QWidget(),
"image_settings": QWidget(),
"blender": QWidget(),
"misc": QWidget() # For settings that don't fit elsewhere
"map_merging": QWidget(),
"postprocess_scripts": QWidget()
}
self.tab_widget.addTab(self.tabs["general"], "General")
self.tab_widget.addTab(self.tabs["output_naming"], "Output & Naming")
self.tab_widget.addTab(self.tabs["image_processing"], "Image Processing")
self.tab_widget.addTab(self.tabs["definitions"], "Definitions")
self.tab_widget.addTab(self.tabs["paths_output"], "Paths & Output")
self.tab_widget.addTab(self.tabs["image_settings"], "Image Settings")
self.tab_widget.addTab(self.tabs["blender"], "Blender")
self.tab_widget.addTab(self.tabs["misc"], "Miscellaneous")
self.tab_widget.addTab(self.tabs["map_merging"], "Map Merging")
self.tab_widget.addTab(self.tabs["postprocess_scripts"], "Postprocess Scripts")
# --- Setup Layouts for Tabs ---
self.tab_layouts = {name: QVBoxLayout(tab) for name, tab in self.tabs.items()}
# --- Populate Tabs ---
self.populate_general_tab(self.tab_layouts["general"])
self.populate_output_naming_tab(self.tab_layouts["output_naming"])
self.populate_image_processing_tab(self.tab_layouts["image_processing"])
self.populate_definitions_tab(self.tab_layouts["definitions"])
self.populate_paths_output_tab(self.tab_layouts["paths_output"])
self.populate_image_settings_tab(self.tab_layouts["image_settings"])
self.populate_blender_tab(self.tab_layouts["blender"])
self.populate_misc_tab(self.tab_layouts["misc"])
self.populate_map_merging_tab(self.tab_layouts["map_merging"])
self.populate_postprocess_scripts_tab(self.tab_layouts["postprocess_scripts"])
def create_widget_for_setting(self, parent_layout, key, value, setting_key_prefix=""):
"""Creates an appropriate widget for a single setting key-value pair."""
@ -142,6 +152,7 @@ class ConfigEditorDialog(QDialog):
def populate_definitions_tab(self, layout):
"""Populates the Definitions tab."""
# Reuse existing methods for Asset and File Type Definitions
if "ASSET_TYPE_DEFINITIONS" in self.settings:
group = QGroupBox("Asset Type Definitions")
group_layout = QVBoxLayout(group)
@ -160,15 +171,28 @@ class ConfigEditorDialog(QDialog):
self.create_widget_for_setting(form_layout, "STANDARD_MAP_TYPES", self.settings["STANDARD_MAP_TYPES"])
if "RESPECT_VARIANT_MAP_TYPES" in self.settings:
self.create_widget_for_setting(form_layout, "RESPECT_VARIANT_MAP_TYPES", self.settings["RESPECT_VARIANT_MAP_TYPES"])
if "DEFAULT_ASSET_CATEGORY" in self.settings:
self.create_widget_for_setting(form_layout, "DEFAULT_ASSET_CATEGORY", self.settings["DEFAULT_ASSET_CATEGORY"])
layout.addLayout(form_layout)
layout.addStretch()
def populate_paths_output_tab(self, layout):
"""Populates the Paths & Output tab."""
def populate_general_tab(self, layout):
"""Populates the General tab."""
form_layout = QFormLayout()
# Settings from app_settings.json that fit 'General'
keys_to_include = [
"DEFAULT_ASSET_CATEGORY"
]
for key in keys_to_include:
if key in self.settings:
self.create_widget_for_setting(form_layout, key, self.settings[key])
layout.addLayout(form_layout)
layout.addStretch()
def populate_output_naming_tab(self, layout):
"""Populates the Output & Naming tab."""
form_layout = QFormLayout()
# Settings from app_settings.json that fit 'Output & Naming'
keys_to_include = [
"OUTPUT_BASE_DIR", "EXTRA_FILES_SUBDIR", "METADATA_FILENAME",
"TARGET_FILENAME_PATTERN", "TEMP_DIR_PREFIX"
@ -179,9 +203,10 @@ class ConfigEditorDialog(QDialog):
layout.addLayout(form_layout)
layout.addStretch()
def populate_image_settings_tab(self, layout):
"""Populates the Image Settings tab."""
def populate_image_processing_tab(self, layout):
"""Populates the Image Processing tab."""
form_layout = QFormLayout()
# Simple settings from app_settings.json that fit 'Image Processing'
simple_keys = [
"PNG_COMPRESSION_LEVEL", "JPG_QUALITY", "RESOLUTION_THRESHOLD_FOR_JPG",
"ASPECT_RATIO_DECIMALS", "CALCULATE_STATS_RESOLUTION",
@ -193,75 +218,52 @@ class ConfigEditorDialog(QDialog):
self.create_widget_for_setting(form_layout, key, self.settings[key])
layout.addLayout(form_layout)
# Add complex widgets
# Complex widgets for Image Processing
if "IMAGE_RESOLUTIONS" in self.settings:
group = QGroupBox("Image Resolutions")
group_layout = QVBoxLayout(group)
self.create_image_resolutions_widget(group_layout, self.settings["IMAGE_RESOLUTIONS"])
layout.addWidget(group)
# IMAGE_RESOLUTIONS is a dict in app_settings, need to adapt create_image_resolutions_widget
# For now, display as a simple text field or defer
# Deferring complex dict/list handling for now, except for Definitions and Map Merging
# self.create_image_resolutions_widget(group_layout, self.settings["IMAGE_RESOLUTIONS"])
# Placeholder for IMAGE_RESOLUTIONS
layout.addWidget(QLabel("Image Resolutions (complex structure - deferred)"))
# Add a simple widget for now to show the data
if "IMAGE_RESOLUTIONS" in self.settings:
self.create_widget_for_setting(form_layout, "IMAGE_RESOLUTIONS", str(self.settings["IMAGE_RESOLUTIONS"]))
if "MAP_BIT_DEPTH_RULES" in self.settings:
group = QGroupBox("Map Bit Depth Rules")
group_layout = QVBoxLayout(group)
self.create_map_bit_depth_rules_widget(group_layout, self.settings["MAP_BIT_DEPTH_RULES"])
layout.addWidget(group)
# MAP_BIT_DEPTH_RULES is a dict in app_settings, need to adapt create_map_bit_depth_rules_widget
# For now, display as a simple text field or defer
# Deferring complex dict/list handling for now, except for Definitions and Map Merging
# self.create_map_bit_depth_rules_widget(group_layout, self.settings["MAP_BIT_DEPTH_RULES"])
# Placeholder for MAP_BIT_DEPTH_RULES
layout.addWidget(QLabel("Map Bit Depth Rules (complex structure - deferred)"))
# Add a simple widget for now to show the data
if "MAP_BIT_DEPTH_RULES" in self.settings:
self.create_widget_for_setting(form_layout, "MAP_BIT_DEPTH_RULES", str(self.settings["MAP_BIT_DEPTH_RULES"]))
layout.addStretch()
def populate_map_merging_tab(self, layout):
"""Populates the Map Merging tab."""
# Implement Map Merging UI (ListWidget + Details Form)
if "MAP_MERGE_RULES" in self.settings:
group = QGroupBox("Map Merge Rules")
group_layout = QVBoxLayout(group)
self.create_map_merge_rules_widget(group_layout, self.settings["MAP_MERGE_RULES"])
layout.addWidget(group)
self.create_map_merge_rules_widget(layout, self.settings["MAP_MERGE_RULES"])
layout.addStretch()
def populate_blender_tab(self, layout):
"""Populates the Blender tab."""
def populate_postprocess_scripts_tab(self, layout):
"""Populates the Postprocess Scripts tab."""
form_layout = QFormLayout()
keys_to_include = [
"DEFAULT_NODEGROUP_BLEND_PATH", "DEFAULT_MATERIALS_BLEND_PATH",
"BLENDER_EXECUTABLE_PATH"
]
for key in keys_to_include:
if key in self.settings:
self.create_widget_for_setting(form_layout, key, self.settings[key])
# No explicit settings for postprocess scripts in app_settings.json currently
layout.addWidget(QLabel("No postprocess script settings found."))
layout.addLayout(form_layout)
layout.addStretch()
def populate_misc_tab(self, layout):
"""Populates the Miscellaneous tab with any remaining settings."""
form_layout = QFormLayout()
handled_keys = set()
# Collect keys handled by other tabs
handled_keys.update([
"ASSET_TYPE_DEFINITIONS", "FILE_TYPE_DEFINITIONS", "STANDARD_MAP_TYPES",
"RESPECT_VARIANT_MAP_TYPES", "DEFAULT_ASSET_CATEGORY", "OUTPUT_BASE_DIR",
"EXTRA_FILES_SUBDIR", "METADATA_FILENAME", "TARGET_FILENAME_PATTERN",
"TEMP_DIR_PREFIX", "PNG_COMPRESSION_LEVEL", "JPG_QUALITY",
"RESOLUTION_THRESHOLD_FOR_JPG", "ASPECT_RATIO_DECIMALS",
"CALCULATE_STATS_RESOLUTION", "OUTPUT_FORMAT_16BIT_PRIMARY",
"OUTPUT_FORMAT_16BIT_FALLBACK", "OUTPUT_FORMAT_8BIT",
"IMAGE_RESOLUTIONS", "MAP_BIT_DEPTH_RULES", "MAP_MERGE_RULES",
"DEFAULT_NODEGROUP_BLEND_PATH", "DEFAULT_MATERIALS_BLEND_PATH",
"BLENDER_EXECUTABLE_PATH"
])
for key, value in self.settings.items():
if key not in handled_keys:
# Only create widgets for simple types here
if isinstance(value, (str, int, float, bool, list)):
# Check if list is simple
is_simple_list = isinstance(value, list) and (not value or not isinstance(value[0], (dict, list)))
if not isinstance(value, list) or is_simple_list:
self.create_widget_for_setting(form_layout, key, value)
handled_keys.add(key) # Mark as handled
if form_layout.rowCount() == 0:
layout.addWidget(QLabel("No miscellaneous settings found."))
layout.addLayout(form_layout)
layout.addStretch()
# Remove the old create_widgets_for_section method as it's replaced
# def create_widgets_for_section(self, layout, section_data, section_key):
# ... (old implementation removed) ...
@ -440,11 +442,6 @@ class ConfigEditorDialog(QDialog):
self.merge_rule_details_layout.addRow(label, widget)
self.merge_rule_widgets[key] = widget # Store widget reference
def populate_widgets(self):
"""Populates the created widgets with loaded settings (for simple types)."""
# This method is less critical with the recursive create_widgets_for_section
# but could be used for specific post-creation population if needed.
pass
def browse_path(self, widget, key):
"""Opens a file or directory dialog based on the setting key."""
@ -487,7 +484,11 @@ class ConfigEditorDialog(QDialog):
if i == len(keys) - 1:
# This is the final key, update the value
if isinstance(widget, QLineEdit):
current_dict[k] = widget.text()
# Handle simple lists displayed as comma-separated strings
if key in ["STANDARD_MAP_TYPES", "RESPECT_VARIANT_MAP_TYPES"]:
current_dict[k] = [item.strip() for item in widget.text().split(',') if item.strip()]
else:
current_dict[k] = widget.text()
elif isinstance(widget, QSpinBox):
current_dict[k] = widget.value()
elif isinstance(widget, QDoubleSpinBox):
@ -501,7 +502,7 @@ class ConfigEditorDialog(QDialog):
print(f"Warning: Structure mismatch for key part '{k}' in '{key}'")
break # Stop processing this key
current_dict = current_dict[k]
# Handle TableWidgets (for definitions, resolutions, bit depth rules)
# Handle TableWidgets (for definitions)
elif isinstance(widget, QTableWidget):
keys = key.split('.')
if len(keys) >= 2:
@ -536,63 +537,22 @@ class ConfigEditorDialog(QDialog):
color = color_widget.text()
new_definitions[file_type] = {"description": description, "color": color}
new_settings[section_key][list_key] = new_definitions
elif list_key == "IMAGE_RESOLUTIONS":
new_resolutions = []
for row in range(widget.rowCount()):
width_item = widget.item(row, 0)
height_item = widget.item(row, 1)
if width_item and height_item:
try:
width = int(width_item.text())
height = int(height_item.text())
new_resolutions.append([width, height])
except ValueError:
print(f"Warning: Invalid resolution value at row {row}")
new_settings[section_key][list_key] = new_resolutions
elif list_key == "MAP_BIT_DEPTH_RULES":
new_rules = []
for row in range(widget.rowCount()):
pattern_item = widget.item(row, 0)
bit_depth_item = widget.item(row, 1)
if pattern_item and bit_depth_item:
try:
bit_depth = int(bit_depth_item.text())
new_rules.append({"pattern": pattern_item.text(), "bit_depth": bit_depth})
except ValueError:
print(f"Warning: Invalid bit depth value at row {row}")
new_settings[section_key][list_key] = new_rules
# Note: IMAGE_RESOLUTIONS and MAP_BIT_DEPTH_RULES tables are not saved here
# as they are currently displayed as simple text fields due to deferred UI complexity.
# Handle Map Merge Rules (more complex)
# This requires reading from the details form for the currently selected item
# and updating the corresponding dictionary in the original list stored in self.widgets
# Reconstruct the list from the list widget items' data.
# Note: Changes made in the details form are NOT saved with this implementation
# due to deferred complexity in updating the list item's data.
elif key == "IMAGE_PROCESSING_SETTINGS.MAP_MERGE_RULES":
# The original list is stored in self.widgets["IMAGE_PROCESSING_SETTINGS.MAP_MERGE_RULES"]
# We need to iterate through the list widget items and update the corresponding
# dictionary in the list based on the details form if that item was selected and edited.
# A simpler approach for now is to just read the currently displayed rule details
# and update the corresponding item in the list widget's data, then reconstruct the list.
# Reconstruct the list from the list widget items' data
new_merge_rules = []
for i in range(self.merge_rules_list.count()):
item = self.merge_rules_list.item(i)
rule_data = item.data(Qt.UserRole)
if rule_data:
# If this item is the currently selected one, update its data from the details widgets
if item == self.merge_rules_list.currentItem():
updated_rule_data = {}
for detail_key, detail_widget in self.merge_rule_widgets.items():
if isinstance(detail_widget, QLineEdit):
updated_rule_data[detail_key] = detail_widget.text()
elif isinstance(detail_widget, QSpinBox):
updated_rule_data[detail_key] = detail_widget.value()
elif isinstance(detail_widget, QDoubleSpinBox):
updated_rule_data[detail_key] = detail_widget.value()
elif isinstance(detail_widget, QCheckBox):
updated_rule_data[detail_key] = detail_widget.isChecked()
# Add handling for other widget types in details form if needed
# Merge updated data with original data (in case some fields weren't in details form)
rule_data.update(updated_rule_data)
# Append the rule data stored in the list item.
# This data reflects the state when the dialog was opened,
# not changes made in the details form.
new_merge_rules.append(rule_data)
# Update the new_settings dictionary with the reconstructed list
@ -612,6 +572,90 @@ class ConfigEditorDialog(QDialog):
except Exception as e:
QMessageBox.critical(self, "Saving Error", f"Failed to save configuration: {e}")
def populate_widgets_from_settings(self):
"""Populates the created widgets with loaded settings."""
if not self.settings or not self.widgets:
return
for key, value in self.settings.items():
# Handle simple settings directly if they have a corresponding widget
if key in self.widgets and isinstance(self.widgets[key], (QLineEdit, QSpinBox, QDoubleSpinBox, QCheckBox)):
widget = self.widgets[key]
if isinstance(widget, QLineEdit):
# Handle simple lists displayed as comma-separated strings
if key in ["STANDARD_MAP_TYPES", "RESPECT_VARIANT_MAP_TYPES"] and isinstance(value, list):
widget.setText(", ".join(map(str, value)))
elif isinstance(value, (str, int, float, bool)): # Also handle cases where simple types might be in QLineEdit
widget.setText(str(value))
elif isinstance(widget, QSpinBox) and isinstance(value, int):
widget.setValue(value)
elif isinstance(widget, QDoubleSpinBox) and isinstance(value, (int, float)):
widget.setValue(float(value))
elif isinstance(widget, QCheckBox) and isinstance(value, bool):
widget.setChecked(value)
# Add other simple widget types if needed
# Handle complex structures with dedicated widgets
elif key == "ASSET_TYPE_DEFINITIONS" and "DEFINITION_SETTINGS.ASSET_TYPE_DEFINITIONS" in self.widgets:
self.populate_asset_definitions_table(self.widgets["DEFINITION_SETTINGS.ASSET_TYPE_DEFINITIONS"], value)
elif key == "FILE_TYPE_DEFINITIONS" and "DEFINITION_SETTINGS.FILE_TYPE_DEFINITIONS" in self.widgets:
self.populate_file_type_definitions_table(self.widgets["DEFINITION_SETTINGS.FILE_TYPE_DEFINITIONS"], value)
elif key == "MAP_MERGE_RULES" and hasattr(self, 'merge_rules_list'): # Check if the list widget exists
self.populate_merge_rules_list(value)
# Select the first item to display details if the list is not empty
if self.merge_rules_list.count() > 0:
self.merge_rules_list.setCurrentRow(0)
# Handle complex dicts/lists displayed as strings (if they were created with create_widget_for_setting)
# These are already handled by the simple widget logic above if they were created as QLineEdit
# with the string representation.
def populate_asset_definitions_table(self, table: QTableWidget, definitions_data: dict):
"""Populates the asset definitions table."""
table.setRowCount(len(definitions_data))
row = 0
for asset_type, details in definitions_data.items():
table.setItem(row, 0, QTableWidgetItem(asset_type))
table.setItem(row, 1, QTableWidgetItem(details.get("description", "")))
# Recreate the color widget for population
color_widget = QLineEdit(details.get("color", ""))
color_button = QPushButton("Pick Color...")
color_button.clicked.connect(lambda checked, w=color_widget: self.pick_color(w))
h_layout = QHBoxLayout()
h_layout.addWidget(color_widget)
h_layout.addWidget(color_button)
cell_widget = QWidget()
cell_widget.setLayout(h_layout)
table.setCellWidget(row, 2, cell_widget)
row += 1
def populate_file_type_definitions_table(self, table: QTableWidget, definitions_data: dict):
"""Populates the file type definitions table."""
table.setRowCount(len(definitions_data))
row = 0
for file_type, details in definitions_data.items():
table.setItem(row, 0, QTableWidgetItem(file_type))
table.setItem(row, 1, QTableWidgetItem(details.get("description", "")))
# Recreate the color widget for population
color_widget = QLineEdit(details.get("color", ""))
color_button = QPushButton("Pick Color...")
color_button.clicked.connect(lambda checked, w=color_widget: self.pick_color(w))
h_layout = QHBoxLayout()
h_layout.addWidget(color_widget)
h_layout.addWidget(color_button)
cell_widget = QWidget()
cell_widget.setLayout(h_layout)
table.setCellWidget(row, 2, cell_widget)
row += 1
# Example usage (for testing the dialog independently)
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication

View File

@ -429,7 +429,8 @@ class MainWindow(QMainWindow):
self.unified_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.unified_view.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked | QAbstractItemView.EditTrigger.SelectedClicked | QAbstractItemView.EditTrigger.EditKeyPressed)
self.unified_view.header().setStretchLastSection(False) # Adjust as needed
# self.unified_view.header().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) # Example: Stretch first column
# Set the "Name" column (index 0) to resize to contents
self.unified_view.header().setSectionResizeMode(UnifiedViewModel.COL_NAME, QHeaderView.ResizeMode.ResizeToContents)
# self.unified_view.header().setSectionResizeMode(1, QHeaderView.ResizeMode.ResizeToContents) # Example: Resize others to contents
# Add the Unified View to the main layout

View File

@ -18,9 +18,8 @@ class UnifiedViewModel(QAbstractItemModel):
of SourceRule -> AssetRule -> FileRule.
"""
Columns = [
"Name", "Supplier", "Asset-Type Override", # Renamed "Supplier Override"
"Target Asset Name Override", "Item-Type Override",
"Status", "Output Path"
"Name", "Supplier", "Asset Type",
"Target Asset", "Item Type"
]
COL_NAME = 0
@ -28,8 +27,8 @@ class UnifiedViewModel(QAbstractItemModel):
COL_ASSET_TYPE = 2
COL_TARGET_ASSET = 3
COL_ITEM_TYPE = 4
COL_STATUS = 5
COL_OUTPUT_PATH = 6
# COL_STATUS = 5 # Removed
# COL_OUTPUT_PATH = 6 # Removed
def __init__(self, parent=None):
super().__init__(parent)
@ -257,8 +256,7 @@ class UnifiedViewModel(QAbstractItemModel):
if column == self.COL_ASSET_TYPE:
display_value = item.asset_type_override if item.asset_type_override is not None else item.asset_type
return display_value if display_value else ""
if column == self.COL_STATUS: return "" # Status (Not handled yet)
if column == self.COL_OUTPUT_PATH: return "" # Output Path (Not handled yet)
# Removed Status and Output Path columns
elif role == Qt.EditRole:
if column == self.COL_ASSET_TYPE:
return item.asset_type_override # Return string or None
@ -278,8 +276,7 @@ class UnifiedViewModel(QAbstractItemModel):
return override
else:
return initial_type if initial_type else ""
if column == self.COL_STATUS: return "" # Status (Not handled yet)
if column == self.COL_OUTPUT_PATH: return "" # Output Path (Not handled yet)
# Removed Status and Output Path columns
elif role == Qt.EditRole:
if column == self.COL_TARGET_ASSET: return item.target_asset_name_override if item.target_asset_name_override is not None else ""
if column == self.COL_ITEM_TYPE: return item.item_type_override # Return string or None