New Definitions editor
This commit is contained in:
parent
344ae078a8
commit
87673507d8
62
ProjectNotes/issue_definitions_editor_list_selection.md
Normal file
62
ProjectNotes/issue_definitions_editor_list_selection.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Issue: List item selection not working in Definitions Editor
|
||||||
|
|
||||||
|
**Date:** 2025-05-13
|
||||||
|
|
||||||
|
**Affected File:** [`gui/definitions_editor_dialog.py`](gui/definitions_editor_dialog.py)
|
||||||
|
|
||||||
|
**Problem Description:**
|
||||||
|
User mouse clicks on items within the `QListWidget` instances (for Asset Types, File Types, and Suppliers) in the Definitions Editor dialog do not trigger item selection or the `currentItemChanged` signal. The first item is selected by default and its details are displayed correctly. Programmatic selection of items (e.g., via a diagnostic button) *does* correctly trigger the `currentItemChanged` signal and updates the UI detail views. The issue is specific to user-initiated mouse clicks for selection after the initial load.
|
||||||
|
|
||||||
|
**Debugging Steps Taken & Findings:**
|
||||||
|
|
||||||
|
1. **Initial Analysis:**
|
||||||
|
* Reviewed GUI internals documentation ([`Documentation/02_Developer_Guide/06_GUI_Internals.md`](Documentation/02_Developer_Guide/06_GUI_Internals.md)) and [`gui/definitions_editor_dialog.py`](gui/definitions_editor_dialog.py) source code.
|
||||||
|
* Confirmed signal connections (`currentItemChanged` to display slots) are made.
|
||||||
|
|
||||||
|
2. **Logging in Display Slots (`_display_*_details`):**
|
||||||
|
* Added logging to display slots. Confirmed they are called for the initial (default) item selection.
|
||||||
|
* No further calls to these slots occur on user clicks, indicating `currentItemChanged` is not firing.
|
||||||
|
|
||||||
|
3. **Color Swatch Palette Role:**
|
||||||
|
* Investigated and corrected `QPalette.ColorRole` for color swatches (reverted from `Background` to `Window`). This fixed an `AttributeError` but did not resolve the selection issue.
|
||||||
|
|
||||||
|
4. **Robust Error Handling in Display Slots:**
|
||||||
|
* Wrapped display slot logic in `try...finally` blocks with detailed logging. Confirmed slots complete without error for initial selection and signals for detail widgets are reconnected.
|
||||||
|
|
||||||
|
5. **Diagnostic Lambda for `currentItemChanged`:**
|
||||||
|
* Added a lambda logger to `currentItemChanged` alongside the main display slot.
|
||||||
|
* Confirmed both lambda and display slot fire for initial programmatic selection.
|
||||||
|
* Neither fires for subsequent user clicks. This proved the `QListWidget` itself was not emitting the signal.
|
||||||
|
|
||||||
|
6. **Explicit `setEnabled` and `setSelectionMode` on `QListWidget`:**
|
||||||
|
* Explicitly set these properties. No change in behavior.
|
||||||
|
|
||||||
|
7. **Explicit `setEnabled` and `setFocusPolicy(Qt.ClickFocus)` on `tab_page` (parent of `QListWidget` layout):**
|
||||||
|
* This change **allowed programmatic selection via a diagnostic button to correctly fire `currentItemChanged` and update the UI**.
|
||||||
|
* However, user mouse clicks still did not work and did not fire the signal.
|
||||||
|
|
||||||
|
8. **Event Filter Investigation:**
|
||||||
|
* **Filter on `QListWidget`:** Did NOT receive mouse press/release events from user clicks.
|
||||||
|
* **Filter on `tab_page` (parent of `QListWidget`'s layout):** Did NOT receive mouse press/release events.
|
||||||
|
* **Filter on `self.tab_widget` (QTabWidget):** DID receive mouse press/release events.
|
||||||
|
* Modified `self.tab_widget`'s event filter to return `False` for events over the current page, attempting to ensure propagation.
|
||||||
|
* **Result:** With the modified `tab_widget` filter, an event filter re-added to `asset_type_list_widget` *did* start receiving mouse press/release events. **However, `asset_type_list_widget` still did not emit `currentItemChanged` from these user clicks.**
|
||||||
|
|
||||||
|
9. **`DebugListWidget` (Subclassing `QListWidget`):**
|
||||||
|
* Created `DebugListWidget` overriding `mousePressEvent` with logging.
|
||||||
|
* Used `DebugListWidget` for `asset_type_list_widget`.
|
||||||
|
* **Initial user report indicated that `DebugListWidget.mousePressEvent` logs were NOT appearing for user clicks.** This means that even with the `QTabWidget` event filter attempting to propagate events, and the `asset_type_list_widget`'s filter (from step 8) confirming it received them, the `mousePressEvent` of the `QListWidget` itself was not being triggered by those propagated events. This is the current mystery.
|
||||||
|
|
||||||
|
**Current Status:**
|
||||||
|
- Programmatic selection works and fires signals.
|
||||||
|
- User clicks are received by an event filter on `asset_type_list_widget` (after `QTabWidget` filter modification) but do not result in `mousePressEvent` being called on the `QListWidget` (or `DebugListWidget`) itself, and thus no `currentItemChanged` signal is emitted.
|
||||||
|
- The issue seems to be a very low-level event processing problem specifically for user mouse clicks within the `QListWidget` instances when they are children of the `QTabWidget` pages, even when events appear to reach the list widget via an event filter.
|
||||||
|
|
||||||
|
**Next Steps (When Resuming):**
|
||||||
|
1. Re-verify the logs from the `DebugListWidget.mousePressEvent` test. If it's truly not being called despite its event filter seeing events, this is extremely unusual.
|
||||||
|
2. Simplify the `_create_tab_pane` method drastically for one tab:
|
||||||
|
* Remove the right-hand pane.
|
||||||
|
* Add the `DebugListWidget` directly to the `tab_page`'s layout without the intermediate `left_pane_layout`.
|
||||||
|
3. Consider if any styles applied to `QListWidget` or its parents via stylesheets could be interfering with hit testing or event processing (unlikely for this specific symptom, but possible).
|
||||||
|
4. Explore alternative ways to populate/manage the `QListWidget` or its items if a subtle corruption is occurring.
|
||||||
|
5. If all else fails, consider replacing the `QListWidget` with a `QListView` and a `QStringListModel` as a more fundamental change to see if the issue is specific to `QListWidget` in this context.
|
||||||
@ -1,39 +1,39 @@
|
|||||||
{
|
{
|
||||||
"ASSET_TYPE_DEFINITIONS": {
|
"ASSET_TYPE_DEFINITIONS": {
|
||||||
"Surface": {
|
"Surface": {
|
||||||
"description": "A single Standard PBR material set for a surface.",
|
|
||||||
"color": "#1f3e5d",
|
"color": "#1f3e5d",
|
||||||
|
"description": "A single Standard PBR material set for a surface.",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Set: Wood01_COL + Wood01_NRM + WOOD01_ROUGH",
|
"Set: Wood01_COL + Wood01_NRM + WOOD01_ROUGH",
|
||||||
"Set: Dif_Concrete + Normal_Concrete + Refl_Concrete"
|
"Set: Dif_Concrete + Normal_Concrete + Refl_Concrete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Model": {
|
"Model": {
|
||||||
"description": "A set that contains models, can include PBR textureset",
|
|
||||||
"color": "#b67300",
|
"color": "#b67300",
|
||||||
|
"description": "A set that contains models, can include PBR textureset",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Single = Chair.fbx",
|
"Single = Chair.fbx",
|
||||||
"Set = Plant02.fbx + Plant02_col + Plant02_SSS"
|
"Set = Plant02.fbx + Plant02_col + Plant02_SSS"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Decal": {
|
"Decal": {
|
||||||
"description": "A alphamasked textureset",
|
|
||||||
"color": "#68ac68",
|
"color": "#68ac68",
|
||||||
|
"description": "A alphamasked textureset",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Set = DecalGraffiti01_Col + DecalGraffiti01_Alpha",
|
"Set = DecalGraffiti01_Col + DecalGraffiti01_Alpha",
|
||||||
"Single = DecalLeakStain03"
|
"Single = DecalLeakStain03"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Atlas": {
|
"Atlas": {
|
||||||
"description": "A texture, name usually hints that it's an atlas",
|
|
||||||
"color": "#955b8b",
|
"color": "#955b8b",
|
||||||
|
"description": "A texture, name usually hints that it's an atlas",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Set = FoliageAtlas01_col + FoliageAtlas01_nrm"
|
"Set = FoliageAtlas01_col + FoliageAtlas01_nrm"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"UtilityMap": {
|
"UtilityMap": {
|
||||||
"description": "A useful image-asset consisting of only a single texture. Therefor each Utilitymap can only contain a single item.",
|
|
||||||
"color": "#706b87",
|
"color": "#706b87",
|
||||||
|
"description": "A useful image-asset consisting of only a single texture. Therefor each Utilitymap can only contain a single item.",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Single = imperfection.png",
|
"Single = imperfection.png",
|
||||||
"Single = smudges.png",
|
"Single = smudges.png",
|
||||||
|
|||||||
@ -1,149 +1,155 @@
|
|||||||
{
|
{
|
||||||
"FILE_TYPE_DEFINITIONS": {
|
"FILE_TYPE_DEFINITIONS": {
|
||||||
"MAP_COL": {
|
"MAP_COL": {
|
||||||
"description": "Color/Albedo Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#ffaa00",
|
"color": "#ffaa00",
|
||||||
|
"description": "Color/Albedo Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_col.",
|
"_col.",
|
||||||
"_basecolor.",
|
"_basecolor.",
|
||||||
"albedo",
|
"albedo",
|
||||||
"diffuse"
|
"diffuse"
|
||||||
],
|
],
|
||||||
"standard_type": "COL",
|
|
||||||
"bit_depth_rule": "force_8bit",
|
|
||||||
"is_grayscale": false,
|
"is_grayscale": false,
|
||||||
"keybind": "C"
|
"keybind": "C",
|
||||||
|
"standard_type": "COL"
|
||||||
},
|
},
|
||||||
"MAP_NRM": {
|
"MAP_NRM": {
|
||||||
"description": "Normal Map",
|
"bit_depth_rule": "respect",
|
||||||
"color": "#cca2f1",
|
"color": "#cca2f1",
|
||||||
|
"description": "Normal Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_nrm.",
|
"_nrm.",
|
||||||
"_normal."
|
"_normal."
|
||||||
],
|
],
|
||||||
"standard_type": "NRM",
|
|
||||||
"bit_depth_rule": "respect",
|
|
||||||
"is_grayscale": false,
|
"is_grayscale": false,
|
||||||
"keybind": "N"
|
"keybind": "N",
|
||||||
|
"standard_type": "NRM"
|
||||||
},
|
},
|
||||||
"MAP_METAL": {
|
"MAP_METAL": {
|
||||||
"description": "Metalness Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#dcf4f2",
|
"color": "#dcf4f2",
|
||||||
|
"description": "Metalness Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_metal.",
|
"_metal.",
|
||||||
"_met."
|
"_met."
|
||||||
],
|
],
|
||||||
"standard_type": "METAL",
|
|
||||||
"bit_depth_rule": "force_8bit",
|
|
||||||
"is_grayscale": true,
|
"is_grayscale": true,
|
||||||
"keybind": "M"
|
"keybind": "M",
|
||||||
|
"standard_type": "METAL"
|
||||||
},
|
},
|
||||||
"MAP_ROUGH": {
|
"MAP_ROUGH": {
|
||||||
"description": "Roughness Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#bfd6bf",
|
"color": "#bfd6bf",
|
||||||
|
"description": "Roughness Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_rough.",
|
"_rough.",
|
||||||
"_rgh.",
|
"_rgh.",
|
||||||
"_gloss"
|
"_gloss"
|
||||||
],
|
],
|
||||||
"standard_type": "ROUGH",
|
|
||||||
"bit_depth_rule": "force_8bit",
|
|
||||||
"is_grayscale": true,
|
"is_grayscale": true,
|
||||||
"keybind": "R"
|
"keybind": "R",
|
||||||
|
"standard_type": "ROUGH"
|
||||||
},
|
},
|
||||||
"MAP_GLOSS": {
|
"MAP_GLOSS": {
|
||||||
"description": "Glossiness Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#d6bfd6",
|
"color": "#d6bfd6",
|
||||||
|
"description": "Glossiness Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_gloss.",
|
"_gloss.",
|
||||||
"_gls."
|
"_gls."
|
||||||
],
|
],
|
||||||
"standard_type": "GLOSS",
|
|
||||||
"bit_depth_rule": "force_8bit",
|
|
||||||
"is_grayscale": true,
|
"is_grayscale": true,
|
||||||
"keybind": "R"
|
"keybind": "R",
|
||||||
|
"standard_type": "GLOSS"
|
||||||
},
|
},
|
||||||
"MAP_AO": {
|
"MAP_AO": {
|
||||||
"description": "Ambient Occlusion Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#e3c7c7",
|
"color": "#e3c7c7",
|
||||||
|
"description": "Ambient Occlusion Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_ao.",
|
"_ao.",
|
||||||
"_ambientocclusion."
|
"_ambientocclusion."
|
||||||
],
|
],
|
||||||
"standard_type": "AO",
|
"is_grayscale": true,
|
||||||
"bit_depth_rule": "force_8bit",
|
"keybind": "",
|
||||||
"is_grayscale": true
|
"standard_type": "AO"
|
||||||
},
|
},
|
||||||
"MAP_DISP": {
|
"MAP_DISP": {
|
||||||
"description": "Displacement/Height Map",
|
"bit_depth_rule": "respect",
|
||||||
"color": "#c6ddd5",
|
"color": "#c6ddd5",
|
||||||
|
"description": "Displacement/Height Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_disp.",
|
"_disp.",
|
||||||
"_height."
|
"_height."
|
||||||
],
|
],
|
||||||
"standard_type": "DISP",
|
|
||||||
"bit_depth_rule": "respect",
|
|
||||||
"is_grayscale": true,
|
"is_grayscale": true,
|
||||||
"keybind": "D"
|
"keybind": "D",
|
||||||
|
"standard_type": "DISP"
|
||||||
},
|
},
|
||||||
"MAP_REFL": {
|
"MAP_REFL": {
|
||||||
"description": "Reflection/Specular Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#c2c2b9",
|
"color": "#c2c2b9",
|
||||||
|
"description": "Reflection/Specular Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_refl.",
|
"_refl.",
|
||||||
"_specular."
|
"_specular."
|
||||||
],
|
],
|
||||||
"standard_type": "REFL",
|
|
||||||
"bit_depth_rule": "force_8bit",
|
|
||||||
"is_grayscale": true,
|
"is_grayscale": true,
|
||||||
"keybind": "M"
|
"keybind": "M",
|
||||||
|
"standard_type": "REFL"
|
||||||
},
|
},
|
||||||
"MAP_SSS": {
|
"MAP_SSS": {
|
||||||
"description": "Subsurface Scattering Map",
|
"bit_depth_rule": "respect",
|
||||||
"color": "#a0d394",
|
"color": "#a0d394",
|
||||||
|
"description": "Subsurface Scattering Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_sss.",
|
"_sss.",
|
||||||
"_subsurface."
|
"_subsurface."
|
||||||
],
|
],
|
||||||
"standard_type": "SSS",
|
"is_grayscale": true,
|
||||||
"bit_depth_rule": "respect",
|
"keybind": "",
|
||||||
"is_grayscale": true
|
"standard_type": "SSS"
|
||||||
},
|
},
|
||||||
"MAP_FUZZ": {
|
"MAP_FUZZ": {
|
||||||
"description": "Fuzz/Sheen Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#a2d1da",
|
"color": "#a2d1da",
|
||||||
|
"description": "Fuzz/Sheen Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_fuzz.",
|
"_fuzz.",
|
||||||
"_sheen."
|
"_sheen."
|
||||||
],
|
],
|
||||||
"standard_type": "FUZZ",
|
"is_grayscale": true,
|
||||||
"bit_depth_rule": "force_8bit",
|
"keybind": "",
|
||||||
"is_grayscale": true
|
"standard_type": "FUZZ"
|
||||||
},
|
},
|
||||||
"MAP_IDMAP": {
|
"MAP_IDMAP": {
|
||||||
"description": "ID Map (for masking)",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#ca8fb4",
|
"color": "#ca8fb4",
|
||||||
|
"description": "ID Map (for masking)",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_id.",
|
"_id.",
|
||||||
"_matid."
|
"_matid."
|
||||||
],
|
],
|
||||||
"standard_type": "IDMAP",
|
"is_grayscale": false,
|
||||||
"bit_depth_rule": "force_8bit",
|
"keybind": "",
|
||||||
"is_grayscale": false
|
"standard_type": "IDMAP"
|
||||||
},
|
},
|
||||||
"MAP_MASK": {
|
"MAP_MASK": {
|
||||||
"description": "Generic Mask Map",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#c6e2bf",
|
"color": "#c6e2bf",
|
||||||
|
"description": "Generic Mask Map",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_mask."
|
"_mask."
|
||||||
],
|
],
|
||||||
"standard_type": "MASK",
|
"is_grayscale": true,
|
||||||
"bit_depth_rule": "force_8bit",
|
"keybind": "",
|
||||||
"is_grayscale": true
|
"standard_type": "MASK"
|
||||||
},
|
},
|
||||||
"MAP_IMPERFECTION": {
|
"MAP_IMPERFECTION": {
|
||||||
"description": "Imperfection Map (scratches, dust)",
|
"bit_depth_rule": "force_8bit",
|
||||||
"color": "#e6d1a6",
|
"color": "#e6d1a6",
|
||||||
|
"description": "Imperfection Map (scratches, dust)",
|
||||||
"examples": [
|
"examples": [
|
||||||
"_imp.",
|
"_imp.",
|
||||||
"_imperfection.",
|
"_imperfection.",
|
||||||
@ -153,24 +159,26 @@
|
|||||||
"hairs",
|
"hairs",
|
||||||
"fingerprints"
|
"fingerprints"
|
||||||
],
|
],
|
||||||
"standard_type": "IMPERFECTION",
|
"is_grayscale": true,
|
||||||
"bit_depth_rule": "force_8bit",
|
"keybind": "",
|
||||||
"is_grayscale": true
|
"standard_type": "IMPERFECTION"
|
||||||
},
|
},
|
||||||
"MODEL": {
|
"MODEL": {
|
||||||
"description": "3D Model File",
|
"bit_depth_rule": "",
|
||||||
"color": "#3db2bd",
|
"color": "#3db2bd",
|
||||||
|
"description": "3D Model File",
|
||||||
"examples": [
|
"examples": [
|
||||||
".fbx",
|
".fbx",
|
||||||
".obj"
|
".obj"
|
||||||
],
|
],
|
||||||
"standard_type": "",
|
"is_grayscale": false,
|
||||||
"bit_depth_rule": "",
|
"keybind": "",
|
||||||
"is_grayscale": false
|
"standard_type": ""
|
||||||
},
|
},
|
||||||
"EXTRA": {
|
"EXTRA": {
|
||||||
"description": "asset previews or metadata",
|
"bit_depth_rule": "",
|
||||||
"color": "#8c8c8c",
|
"color": "#8c8c8c",
|
||||||
|
"description": "asset previews or metadata",
|
||||||
"examples": [
|
"examples": [
|
||||||
".txt",
|
".txt",
|
||||||
".zip",
|
".zip",
|
||||||
@ -180,22 +188,21 @@
|
|||||||
"_Cube.",
|
"_Cube.",
|
||||||
"thumb"
|
"thumb"
|
||||||
],
|
],
|
||||||
"standard_type": "",
|
|
||||||
"bit_depth_rule": "",
|
|
||||||
"is_grayscale": false,
|
"is_grayscale": false,
|
||||||
"keybind": "E"
|
"keybind": "E",
|
||||||
|
"standard_type": ""
|
||||||
},
|
},
|
||||||
"FILE_IGNORE": {
|
"FILE_IGNORE": {
|
||||||
"description": "File to be ignored",
|
"bit_depth_rule": "",
|
||||||
"color": "#673d35",
|
"color": "#673d35",
|
||||||
|
"description": "File to be ignored",
|
||||||
"examples": [
|
"examples": [
|
||||||
"Thumbs.db",
|
"Thumbs.db",
|
||||||
".DS_Store"
|
".DS_Store"
|
||||||
],
|
],
|
||||||
"standard_type": "",
|
|
||||||
"bit_depth_rule": "",
|
|
||||||
"is_grayscale": false,
|
"is_grayscale": false,
|
||||||
"keybind": "X"
|
"keybind": "X",
|
||||||
|
"standard_type": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QTabWidget, QWidget, QListWidget, QPushButton,
|
QDialog, QVBoxLayout, QTabWidget, QWidget, QListWidget, QListWidgetItem, QPushButton,
|
||||||
QHBoxLayout, QLabel, QGroupBox, QDialogButtonBox, QFormLayout,
|
QHBoxLayout, QLabel, QGroupBox, QDialogButtonBox, QFormLayout,
|
||||||
QTextEdit, QColorDialog, QInputDialog, QMessageBox, QFrame, QComboBox,
|
QTextEdit, QColorDialog, QInputDialog, QMessageBox, QFrame, QComboBox,
|
||||||
QLineEdit, QCheckBox
|
QLineEdit, QCheckBox, QAbstractItemView
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import QColor, QPalette
|
from PySide6.QtGui import QColor, QPalette, QMouseEvent # Added QMouseEvent
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, QEvent
|
||||||
|
|
||||||
# Assuming load_asset_definitions, load_file_type_definitions, load_supplier_settings
|
# Assuming load_asset_definitions, load_file_type_definitions, load_supplier_settings
|
||||||
# are in configuration.py at the root level.
|
# are in configuration.py at the root level.
|
||||||
@ -38,6 +38,17 @@ except ImportError as e:
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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):
|
class DefinitionsEditorDialog(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -48,6 +59,7 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
self.file_type_data = {}
|
self.file_type_data = {}
|
||||||
self.supplier_data = {}
|
self.supplier_data = {}
|
||||||
self.unsaved_changes = False # For unsaved changes tracking
|
self.unsaved_changes = False # For unsaved changes tracking
|
||||||
|
self.asset_types_tab_page_for_filtering = None # For event filtering
|
||||||
|
|
||||||
self._load_all_definitions()
|
self._load_all_definitions()
|
||||||
|
|
||||||
@ -64,6 +76,8 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
main_layout.addWidget(self.button_box)
|
main_layout.addWidget(self.button_box)
|
||||||
|
|
||||||
self.setLayout(main_layout)
|
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):
|
def _load_all_definitions(self):
|
||||||
logger.info("Loading all definitions...")
|
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_file_types_tab(), "File Type Definitions")
|
||||||
self.tab_widget.addTab(self._create_suppliers_tab(), "Supplier Settings")
|
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):
|
def _create_tab_pane(self, title_singular, data_dict, list_widget_name):
|
||||||
tab_page = QWidget()
|
tab_page = QWidget()
|
||||||
|
tab_page.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
|
||||||
tab_layout = QHBoxLayout(tab_page)
|
tab_layout = QHBoxLayout(tab_page)
|
||||||
|
|
||||||
# Left Pane
|
# Left Pane
|
||||||
@ -105,12 +156,32 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
lbl_list_title = QLabel(f"{title_singular}s:")
|
lbl_list_title = QLabel(f"{title_singular}s:")
|
||||||
left_pane_layout.addWidget(lbl_list_title)
|
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
|
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):
|
if isinstance(data_dict, dict):
|
||||||
for key, value_dict in data_dict.items(): # Iterate over items for UserRole data
|
for key, value_dict in data_dict.items(): # Iterate over items for UserRole data
|
||||||
item = QListWidgetItem(key)
|
item = QListWidgetItem(key)
|
||||||
item.setData(Qt.UserRole, value_dict) # Store the whole dict
|
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)
|
list_widget.addItem(item)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Data for {title_singular} is not a dictionary, cannot populate list.")
|
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":
|
if list_widget_name == "asset_type_list_widget":
|
||||||
btn_add.clicked.connect(self._add_asset_type)
|
btn_add.clicked.connect(self._add_asset_type)
|
||||||
btn_remove.clicked.connect(self._remove_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)
|
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":
|
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_add.clicked.connect(self._add_file_type)
|
||||||
btn_remove.clicked.connect(self._remove_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)
|
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
|
elif list_widget_name == "supplier_list_widget": # Connections for Supplier tab
|
||||||
btn_add.clicked.connect(self._add_supplier)
|
btn_add.clicked.connect(self._add_supplier)
|
||||||
btn_remove.clicked.connect(self._remove_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)
|
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_add)
|
||||||
buttons_layout.addWidget(btn_remove)
|
buttons_layout.addWidget(btn_remove)
|
||||||
@ -145,11 +238,16 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
right_pane_widget = QWidget() # Create a generic widget to be returned
|
right_pane_widget = QWidget() # Create a generic widget to be returned
|
||||||
tab_layout.addWidget(right_pane_widget, 2) # 2 parts for right pane
|
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)
|
tab_page.setLayout(tab_layout)
|
||||||
return tab_page, right_pane_widget # Return the pane for customization
|
return tab_page, right_pane_widget # Return the pane for customization
|
||||||
|
|
||||||
def _create_asset_types_tab(self):
|
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")
|
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
|
# Customize the right pane for Asset Types
|
||||||
right_pane_groupbox = QGroupBox("Details for Selected Asset Type")
|
right_pane_groupbox = QGroupBox("Details for Selected Asset Type")
|
||||||
@ -229,41 +327,61 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
self.asset_type_list_widget.addItem(item)
|
self.asset_type_list_widget.addItem(item)
|
||||||
|
|
||||||
def _display_asset_type_details(self, current_item, previous_item=None):
|
def _display_asset_type_details(self, current_item, previous_item=None):
|
||||||
# Disconnect signals temporarily to prevent feedback loops during population
|
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 hasattr(self, 'asset_description_edit'):
|
|
||||||
try:
|
|
||||||
self.asset_description_edit.textChanged.disconnect(self._on_asset_detail_changed)
|
|
||||||
except TypeError: # Signal not connected
|
|
||||||
pass
|
|
||||||
|
|
||||||
if current_item:
|
if current_item:
|
||||||
asset_data = current_item.data(Qt.UserRole)
|
logger.info(f"Current item text: {current_item.text()}")
|
||||||
if not isinstance(asset_data, dict): # Should not happen if _populate is correct
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
# Clear details if no item is selected
|
logger.info("Current item is None for asset_type_details.")
|
||||||
self.asset_description_edit.clear()
|
|
||||||
self._update_color_swatch("#ffffff")
|
|
||||||
self.asset_examples_list_widget.clear()
|
|
||||||
|
|
||||||
# Reconnect signals
|
try:
|
||||||
if hasattr(self, 'asset_description_edit'):
|
# Disconnect signals temporarily to prevent feedback loops during population
|
||||||
self.asset_description_edit.textChanged.connect(self._on_asset_detail_changed)
|
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):
|
def _update_color_swatch(self, color_hex):
|
||||||
if hasattr(self, 'asset_color_swatch_label'):
|
if hasattr(self, 'asset_color_swatch_label'):
|
||||||
palette = self.asset_color_swatch_label.palette()
|
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)
|
self.asset_color_swatch_label.setPalette(palette)
|
||||||
|
|
||||||
def _choose_asset_color(self):
|
def _choose_asset_color(self):
|
||||||
@ -420,9 +538,9 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
|
|
||||||
|
|
||||||
def _update_file_type_color_swatch(self, color_hex, swatch_label):
|
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 = swatch_label.palette()
|
||||||
palette.setColor(QPalette.Background, QColor(color_hex))
|
palette.setColor(QPalette.Window, QColor(color_hex))
|
||||||
swatch_label.setPalette(palette)
|
swatch_label.setPalette(palette)
|
||||||
|
|
||||||
def _create_file_types_tab(self):
|
def _create_file_types_tab(self):
|
||||||
@ -526,69 +644,88 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
self.file_type_list_widget.addItem(item)
|
self.file_type_list_widget.addItem(item)
|
||||||
|
|
||||||
def _display_file_type_details(self, current_item, previous_item=None):
|
def _display_file_type_details(self, current_item, previous_item=None):
|
||||||
# Disconnect signals temporarily
|
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'}")
|
||||||
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.
|
|
||||||
|
|
||||||
if current_item:
|
if current_item:
|
||||||
ft_data = current_item.data(Qt.UserRole)
|
logger.info(f"Current item text: {current_item.text()}")
|
||||||
if not isinstance(ft_data, dict):
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
||||||
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', ''))
|
|
||||||
else:
|
else:
|
||||||
# Clear details if no item is selected
|
logger.info("Current item is None for file_type_details.")
|
||||||
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()
|
|
||||||
|
|
||||||
# Reconnect signals
|
try:
|
||||||
self.ft_description_edit.textChanged.connect(self._on_file_type_detail_changed)
|
# Disconnect signals temporarily
|
||||||
self.ft_standard_type_edit.textChanged.connect(self._on_file_type_detail_changed)
|
logger.debug("Disconnecting file type detail signals...")
|
||||||
self.ft_bit_depth_combo.currentIndexChanged.connect(self._on_file_type_detail_changed)
|
try: self.ft_description_edit.textChanged.disconnect(self._on_file_type_detail_changed)
|
||||||
self.ft_is_grayscale_check.stateChanged.connect(self._on_file_type_detail_changed)
|
except TypeError: pass
|
||||||
self.ft_keybind_edit.textChanged.connect(self._on_file_type_detail_changed)
|
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):
|
def _update_color_swatch_generic(self, swatch_label, color_hex):
|
||||||
"""Generic color swatch update for any QLabel."""
|
"""Generic color swatch update for any QLabel."""
|
||||||
if swatch_label: # Check if the swatch label exists and is passed correctly
|
if swatch_label: # Check if the swatch label exists and is passed correctly
|
||||||
palette = swatch_label.palette()
|
palette = swatch_label.palette()
|
||||||
palette.setColor(QPalette.Background, QColor(color_hex))
|
palette.setColor(QPalette.Window, QColor(color_hex))
|
||||||
swatch_label.setPalette(palette)
|
swatch_label.setPalette(palette)
|
||||||
swatch_label.update() # Ensure the label repaints
|
swatch_label.update() # Ensure the label repaints
|
||||||
|
|
||||||
@ -814,41 +951,59 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
self.supplier_list_widget.addItem(item)
|
self.supplier_list_widget.addItem(item)
|
||||||
|
|
||||||
def _display_supplier_details(self, current_item, previous_item=None):
|
def _display_supplier_details(self, current_item, previous_item=None):
|
||||||
# Disconnect signals temporarily
|
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 hasattr(self, 'supplier_normal_map_type_combo'):
|
|
||||||
try: self.supplier_normal_map_type_combo.currentIndexChanged.disconnect(self._on_supplier_detail_changed)
|
|
||||||
except TypeError: pass
|
|
||||||
|
|
||||||
if current_item:
|
if current_item:
|
||||||
supplier_name = current_item.text()
|
logger.info(f"Current item text: {current_item.text()}")
|
||||||
# Prefer getting data directly from self.supplier_data to ensure it's the master copy
|
logger.info(f"Current item data (UserRole): {current_item.data(Qt.UserRole)}")
|
||||||
# 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
|
|
||||||
else:
|
else:
|
||||||
# Clear details if no item is selected
|
logger.info("Current item is None for supplier_details.")
|
||||||
if hasattr(self, 'supplier_normal_map_type_combo'):
|
|
||||||
self.supplier_normal_map_type_combo.setCurrentIndex(0) # Default to OpenGL
|
|
||||||
|
|
||||||
# Reconnect signals
|
try:
|
||||||
if hasattr(self, 'supplier_normal_map_type_combo'):
|
# Disconnect signals temporarily
|
||||||
self.supplier_normal_map_type_combo.currentIndexChanged.connect(self._on_supplier_detail_changed)
|
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):
|
def _on_supplier_detail_changed(self):
|
||||||
current_item = self.supplier_list_widget.currentItem()
|
current_item = self.supplier_list_widget.currentItem()
|
||||||
@ -1036,6 +1191,71 @@ class DefinitionsEditorDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
event.accept()
|
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__':
|
if __name__ == '__main__':
|
||||||
# This is for testing the dialog independently
|
# This is for testing the dialog independently
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user