diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index b127039b..6b23336c 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -2710,9 +2710,6 @@ def _setup_saved_details_page(self) -> None: self.low_priority_btn.setMaximumSize(QtCore.QSize(100, 100)) self.low_priority_btn.setCheckable(True) self.low_priority_btn.setFlat(True) - self.low_priority_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/indf_svg.svg") - ) self.low_priority_btn.setText("Low") self.low_priority_btn.setProperty("class", "back_btn") self.low_priority_btn.setProperty("button_type", "icon") @@ -2726,9 +2723,6 @@ def _setup_saved_details_page(self) -> None: self.med_priority_btn.setCheckable(True) self.med_priority_btn.setChecked(False) # Don't set default checked self.med_priority_btn.setFlat(True) - self.med_priority_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/indf_svg.svg") - ) self.med_priority_btn.setText("Medium") self.med_priority_btn.setProperty("class", "back_btn") self.med_priority_btn.setProperty("button_type", "icon") @@ -2742,9 +2736,6 @@ def _setup_saved_details_page(self) -> None: self.high_priority_btn.setCheckable(True) self.high_priority_btn.setChecked(False) self.high_priority_btn.setFlat(True) - self.high_priority_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/indf_svg.svg") - ) self.high_priority_btn.setText("High") self.high_priority_btn.setProperty("class", "back_btn") self.high_priority_btn.setProperty("button_type", "icon") diff --git a/BlocksScreen/lib/panels/printTab.py b/BlocksScreen/lib/panels/printTab.py index 848abb3d..0e4f9bda 100644 --- a/BlocksScreen/lib/panels/printTab.py +++ b/BlocksScreen/lib/panels/printTab.py @@ -419,12 +419,6 @@ def _on_delete_file_confirmed(self, filename: str, directory: str) -> None: except (RuntimeError, TypeError): pass - def setProperty(self, name: str, value: typing.Any) -> bool: - """Intercept property changes.""" - if name == "backgroundPixmap": - self.background = value - return super().setProperty(name, value) - def handle_cancel_print(self) -> None: """Handles the print cancel action""" if ( @@ -493,10 +487,6 @@ def setupMainPrintPage(self) -> None: self.setSizePolicy(sizePolicy) self.setMinimumSize(QtCore.QSize(710, 410)) self.setMaximumSize(QtCore.QSize(720, 420)) - self.setProperty( - "backgroundPixmap", - QtGui.QPixmap(":/background/media/graphics/scroll_list_window.svg"), - ) self.print_page = QtWidgets.QWidget() sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.Policy.MinimumExpanding, diff --git a/BlocksScreen/lib/panels/widgets/Common/popupDialogWidget.py b/BlocksScreen/lib/panels/widgets/Common/popupDialogWidget.py index b07cae20..ce184936 100644 --- a/BlocksScreen/lib/panels/widgets/Common/popupDialogWidget.py +++ b/BlocksScreen/lib/panels/widgets/Common/popupDialogWidget.py @@ -31,7 +31,8 @@ def __init__(self, parent) -> None: self.message_type: Popup.MessageType = Popup.MessageType.INFO self.default_background_color = QtGui.QColor(164, 164, 164) self.info_icon = QtGui.QPixmap(":ui/media/btn_icons/info.svg") - self.warning_icon = QtGui.QPixmap(":ui/media/btn_icons/warning.svg") + # no warning asset yet; the yellow ColorCode signals severity + self.warning_icon = QtGui.QPixmap(":ui/media/btn_icons/error.svg") self.error_icon = QtGui.QPixmap(":ui/media/btn_icons/error.svg") self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True) self.setMouseTracking(True) @@ -179,6 +180,8 @@ def _add_popup(self) -> None: self.icon_label.setPixmap(self.warning_icon) case Popup.MessageType.ERROR: self.icon_label.setPixmap(self.error_icon) + case _: + self.icon_label.clear() end_rect = self._calculate_target_geometry() start_rect = end_rect.translated(0, -end_rect.height() * 2) diff --git a/BlocksScreen/lib/panels/widgets/Common/slider_selector_page.py b/BlocksScreen/lib/panels/widgets/Common/slider_selector_page.py index 8d4ca926..eb6ed7ce 100644 --- a/BlocksScreen/lib/panels/widgets/Common/slider_selector_page.py +++ b/BlocksScreen/lib/panels/widgets/Common/slider_selector_page.py @@ -27,7 +27,7 @@ def __init__(self, parent) -> None: self.decrease_button_icon = QtGui.QPixmap( ":/arrow_icons/media/btn_icons/left_arrow.svg" ) - self.background = QtGui.QPixmap(":/ui/background/media/1st_background.png") + self.background = QtGui.QPixmap(":/background/media/1st_background.png") self.setStyleSheet( "#SliderPage{background-image: url(:/background/media/1st_background.png);}\n" ) diff --git a/BlocksScreen/lib/panels/widgets/ControlTab/probeHelperPage.py b/BlocksScreen/lib/panels/widgets/ControlTab/probeHelperPage.py index 76c8d28d..ccd56f3f 100644 --- a/BlocksScreen/lib/panels/widgets/ControlTab/probeHelperPage.py +++ b/BlocksScreen/lib/panels/widgets/ControlTab/probeHelperPage.py @@ -946,9 +946,6 @@ def _setup_ui(self) -> None: self.current_offset_info.setFont(font) self.current_offset_info.setStyleSheet("background: transparent; color: white;") self.current_offset_info.setText("Z:0.000mm") - self.current_offset_info.setPixmap( - QtGui.QPixmap(":/graphics/media/btn_icons/z_offset_adjust.svg") - ) self.current_offset_info.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.current_offset_info.setObjectName("current_offset_info") # Add graphic frame AFTER the offset buttons group box diff --git a/BlocksScreen/lib/panels/widgets/PrintTab/babystepPage.py b/BlocksScreen/lib/panels/widgets/PrintTab/babystepPage.py index f06a8411..808494dd 100644 --- a/BlocksScreen/lib/panels/widgets/PrintTab/babystepPage.py +++ b/BlocksScreen/lib/panels/widgets/PrintTab/babystepPage.py @@ -258,9 +258,6 @@ def _setup_ui(self) -> None: self.bbp_z_offset_current_value.setText( f"Z: {round(self._pending_z_offset, 3) or 0.0:.3f} mm" ) - self.bbp_z_offset_current_value.setPixmap( - QtGui.QPixmap(":/graphics/media/btn_icons/z_offset_adjust.svg") - ) self.bbp_z_offset_current_value.setAlignment( QtCore.Qt.AlignmentFlag.AlignCenter ) diff --git a/BlocksScreen/lib/utils/blocks_label.py b/BlocksScreen/lib/utils/blocks_label.py index 0cff3ea4..7aaef7b4 100644 --- a/BlocksScreen/lib/utils/blocks_label.py +++ b/BlocksScreen/lib/utils/blocks_label.py @@ -212,7 +212,7 @@ def paintEvent(self, a0: QtGui.QPaintEvent) -> None: else: qp.fillRect(rect, self._background_color) - if self.icon_pixmap: + if self.icon_pixmap is not None and not self.icon_pixmap.isNull(): icon_rect = QtCore.QRectF( 0.0 + self.icon_margin, 0.0 + self.icon_margin, diff --git a/tests/ui/test_resource_keys_unit.py b/tests/ui/test_resource_keys_unit.py index 13c02af0..289cc926 100644 --- a/tests/ui/test_resource_keys_unit.py +++ b/tests/ui/test_resource_keys_unit.py @@ -1,10 +1,4 @@ -"""Guards Qt resource keys: no broken ':/' literal, no stale compiled blob. - -Qt resolves an unknown ':/' key to a null QPixmap with no exception, no warning -and no log line. The only feedback is a blank rectangle on the panel, which is -how the keys in XFAIL_KEYS survived for months. These tests turn that silent -failure into a red test. -""" +"""Guards Qt resource keys: Qt renders an unknown ':/' key blank, silently.""" import importlib import re @@ -16,38 +10,15 @@ RESOURCES = PKG_ROOT / "lib" / "ui" / "resources" RC_PACKAGE = "BlocksScreen.lib.ui.resources" -# Known-broken keys, measured 2026-08-31. This dict may only ever shrink: -# test_xfail_keys_are_still_broken fails once an entry stops being broken. +# Known-broken keys; may only shrink, enforced by the stale-entry test. XFAIL_KEYS = { - ":/background/media/graphics/scroll_list_window.svg": ( - "asset is on disk but declared by no .qrc; retired by PR 3" - ), - ":/graphics/media/btn_icons/z_offset_adjust.svg": ( - "asset deleted from the qrc in 927e43c (2025-06-04) and absent from " - "disk, yet 3 call sites still request it; retired by PR 3" - ), ":/network/media/btn_icons/network/{b}bar_wifi{": ( "not broken at runtime: an f-string template the scanner cannot " - "evaluate, whose real keys are the 0bar..3bar matrix; the literal " - "disappears when the Icon enum replaces it in PR 8" - ), - ":/ui/background/media/1st_background.png": ( - "duplicated prefix segment, real key is " - ":/background/media/1st_background.png; retired by PR 3" - ), - ":/ui/media/btn_icons/indf_svg.svg": ( - "no such file in any .qrc or on disk; retired by PR 3" - ), - ":/ui/media/btn_icons/warning.svg": ( - "no such file in any .qrc or on disk, so the warning popup renders a " - "blank icon; needs a new asset, not just a key fix; retired by PR 3" + "evaluate, whose real keys are the 0bar..3bar matrix; retired by #324" ), } -# Deliberately a text scan, not an AST walk: an AST walk only sees ast.Constant, -# so it would silently drop the wifi f-string template above. The optional slash -# catches ":ui/..." too, which Qt resolves the same as ":/ui/..." (verified) and -# which 6 sites in this package use. The trailing + excludes a bare ":". +# Text scan, not AST: AST misses f-strings; '/?' also matches ':ui/...'. _LITERAL = re.compile(r'["\'](:/?[^"\'\s]+)["\']') @@ -70,7 +41,7 @@ def _qrc_entries() -> list[tuple[str, Path]]: def _qrc_keys() -> set[str]: - """Return the resource keys declared by the .qrc XML, the developer-facing source.""" + """Return the resource keys declared by the .qrc XML.""" return {key for key, _ in _qrc_entries()} @@ -80,7 +51,7 @@ def _literal_sites() -> dict[str, list[str]]: for module in sorted((PKG_ROOT / "lib").rglob("*.py")): if module.name.endswith("_rc.py"): continue - text = module.read_text(errors="replace") + text = module.read_text(encoding="utf-8", errors="replace") for number, line in enumerate(text.splitlines(), 1): for match in _LITERAL.finditer(line): where = f"{module.relative_to(REPO_ROOT)}:{number}" @@ -94,15 +65,13 @@ def _qrc_prefix_roots() -> set[str]: def _compiled_keys() -> set[str]: - """Return the resource keys compiled into the _rc.py blobs, under our prefixes only.""" + """Return the resource keys compiled into the _rc.py blobs, our prefixes only.""" from PyQt6.QtCore import QDir, QDirIterator for blob in sorted(RESOURCES.glob("*_rc.py")): importlib.import_module(f"{RC_PACKAGE}.{blob.stem}") - # Walk our own prefixes, never ':/'. Qt registers its own style and PDF - # resources into the same tree as soon as QtGui/QtWidgets is imported, and - # which of those appear depends on what the rest of the suite imported first. + # Skip ':/': Qt's own resources land there, import-order dependent. keys = set() for root in _qrc_prefix_roots(): walk = QDirIterator( @@ -123,7 +92,7 @@ def _report(header: str, detail: dict[str, list[str]]) -> str: def test_resources_dir_is_findable(): - """Fail loudly if the path derivation breaks, so the other tests cannot pass empty.""" + """Fail loudly if path derivation breaks, so the other tests cannot pass empty.""" assert RESOURCES.is_dir(), f"resources dir not found at {RESOURCES}" assert list(RESOURCES.glob("*.qrc")), f"no .qrc files under {RESOURCES}"