diff --git a/BlocksScreen/lib/panels/mainWindow.py b/BlocksScreen/lib/panels/mainWindow.py index 2b9290ad..1ad30afe 100644 --- a/BlocksScreen/lib/panels/mainWindow.py +++ b/BlocksScreen/lib/panels/mainWindow.py @@ -9,7 +9,7 @@ from devices.amu import AMUManager from devices.storage import USBManager from lib.files import Files -from lib.klipper_message_filter import ( # noqa: F405 +from lib.klipper_message_filter import ( MessageSource, Severity, match_message, @@ -19,7 +19,7 @@ from lib.network import WifiIconKey from lib.panels.controlTab import ControlTab from lib.panels.filamentTab import FilamentTab -from lib.panels.networkWindow import NetworkControlWindow, PixmapCache +from lib.panels.networkWindow import NetworkControlWindow from lib.panels.printTab import PrintTab from lib.panels.utilitiesTab import UtilitiesTab from lib.panels.widgets.basePopup import BasePopup @@ -37,6 +37,7 @@ from lib.ui.resources.main_menu_resources_rc import * from lib.ui.resources.top_bar_resources_rc import * from lib.updater_worker import UpdaterWorker +from lib.utils.blocks_pixmap import BlocksPixmap, Icon from lib.utils.fonts import register_momcake from PyQt6 import QtCore, QtGui, QtWidgets from screensaver import ScreenSaver @@ -66,29 +67,15 @@ def wrapper(*args, **kwargs): class HeaderWifiIconProvider: """Resolves WifiIconKey integer values to cached QPixmaps for the header bar.""" - _WIFI_PATHS: dict[tuple[int, bool], str] = { - ( - b, - p, - ): f":/network/media/btn_icons/network/{b}bar_wifi{'_protected' if p else ''}.svg" - for b in range(5) - for p in (False, True) - } - _ETHERNET_PATH = ":/network/media/btn_icons/network/ethernet_connected.svg" - _HOTSPOT_PATH = ":/network/media/btn_icons/hotspot.svg" - @classmethod def get_pixmap(cls, icon_key: int) -> QtGui.QPixmap: - """Resolve an icon key to a QPixmap (cached via PixmapCache).""" + """Resolve an icon key to a QPixmap (cached via BlocksPixmap).""" key = WifiIconKey(icon_key) if key is WifiIconKey.ETHERNET: - return PixmapCache.get(cls._ETHERNET_PATH) + return BlocksPixmap.get(Icon.ETHERNET_CONNECTED) if key is WifiIconKey.HOTSPOT: - return PixmapCache.get(cls._HOTSPOT_PATH) - path = cls._WIFI_PATHS.get( - (key.bars, key.is_protected), cls._WIFI_PATHS[(0, False)] - ) - return PixmapCache.get(path) + return BlocksPixmap.get(Icon.HOTSPOT) + return BlocksPixmap.get(Icon.wifi(key.bars, key.is_protected)) class MainWindow(QtWidgets.QMainWindow): @@ -120,7 +107,7 @@ class MainWindow(QtWidgets.QMainWindow): def __init__(self): """Set up UI, instantiate subsystems, and wire all inter-component signals.""" - super(MainWindow, self).__init__() + super().__init__() self.config: BlocksScreenConfig = get_configparser() # Before setupUi: the topbar .svg icons carry text and paint on first show. register_momcake() @@ -390,11 +377,14 @@ def show_loadscreen( if not force: if _sender is self.update_page: self._update_in_progress = show - if not show and self._post_update_reconnect: - return - elif not show and self._update_in_progress: - return - elif not show and self._klipper_auto_restart_pending: + if ( + not show + and self._post_update_reconnect + or not show + and self._update_in_progress + or not show + and self._klipper_auto_restart_pending + ): return if _sender == self.filamentPanel: diff --git a/BlocksScreen/lib/panels/networkWindow.py b/BlocksScreen/lib/panels/networkWindow.py index 74be209c..43e565ed 100644 --- a/BlocksScreen/lib/panels/networkWindow.py +++ b/BlocksScreen/lib/panels/networkWindow.py @@ -29,6 +29,7 @@ from lib.utils.blocks_frame import BlocksCustomFrame from lib.utils.blocks_label import BlocksLabel from lib.utils.blocks_linedit import BlocksCustomLinEdit +from lib.utils.blocks_pixmap import BlocksPixmap, Icon from lib.utils.blocks_Scrollbar import CustomScrollBar from lib.utils.blocks_togglebutton import NetworkWidgetbuttons from lib.utils.check_button import BlocksCustomCheckButton @@ -43,48 +44,13 @@ STATUS_CHECK_INTERVAL_MS = 2_000 -class PixmapCache: - """Process-wide cache for QPixmaps loaded from Qt resource paths. - - Every SVG is decoded exactly once. Qt's implicit sharing means the - same QPixmap can be safely referenced by any number of widgets. - Must only be called after QApplication is created. - """ - - _cache: dict[str, QtGui.QPixmap] = {} - - @classmethod - def get(cls, path: str) -> QtGui.QPixmap: - """Return the cached QPixmap for *path*, loading it on first access.""" - if path not in cls._cache: - cls._cache[path] = QtGui.QPixmap(path) - return cls._cache[path] - - @classmethod - def preload(cls, paths: list[str]) -> None: - """Batch-load a list of paths (called once during init).""" - for path in paths: - cls.get(path) - - class WifiIconProvider: - """Maps (signal_strength, is_protected) -> cached QPixmap via PixmapCache.""" - - _PATHS: dict[tuple[int, bool], str] = { - ( - b, - p, - ): f":/network/media/btn_icons/network/{b}bar_wifi{'_protected' if p else ''}.svg" - for b in range(5) - for p in (False, True) - } + """Maps (signal_strength, is_protected) -> cached QPixmap via BlocksPixmap.""" @classmethod def get_pixmap(cls, signal: int, is_protected: bool = False) -> QtGui.QPixmap: """Get pixmap for given signal strength and protection status.""" - bars = signal_to_bars(signal) - path = cls._PATHS.get((bars, is_protected), cls._PATHS[(0, False)]) - return PixmapCache.get(path) + return BlocksPixmap.get(Icon.wifi(signal_to_bars(signal), is_protected)) class IPAddressLineEdit(BlocksCustomLinEdit): @@ -418,9 +384,7 @@ def _on_network_state_changed(self, state: NetworkState) -> None: return # Normal (not connecting) display updates. - if state.ethernet_connected: - self._display_connected_state(state) - elif ( + if state.ethernet_connected or ( state.current_ssid and state.current_ip and state.connectivity @@ -1808,12 +1772,8 @@ def _setupUI(self) -> None: ) self._popup = Popup(self) - self._right_arrow_icon = PixmapCache.get( - ":/arrow_icons/media/btn_icons/right_arrow.svg" - ) - self._hiden_network_icon = PixmapCache.get( - ":/network/media/btn_icons/network/0bar_wifi_protected.svg" - ) + self._right_arrow_icon = BlocksPixmap.get(Icon.RIGHT_ARROW) + self._hiden_network_icon = BlocksPixmap.get(Icon.WIFI_0BAR_PROTECTED) self._setup_main_network_page() self._setup_network_list_page() @@ -1897,9 +1857,7 @@ def _setup_main_network_page(self) -> None: self.network_backButton.setMinimumSize(QtCore.QSize(60, 60)) self.network_backButton.setMaximumSize(QtCore.QSize(60, 60)) self.network_backButton.setFlat(True) - self.network_backButton.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") - ) + self.network_backButton.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) header_layout.addWidget(self.network_backButton) @@ -2125,9 +2083,7 @@ def _setup_network_list_page(self) -> None: self.rescan_button.setMaximumSize(QtCore.QSize(60, 60)) self.rescan_button.setText("Reload") self.rescan_button.setFlat(True) - self.rescan_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/refresh.svg") - ) + self.rescan_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.REFRESH)) self.rescan_button.setProperty("button_type", "icon") header_layout.addWidget(self.rescan_button) @@ -2148,9 +2104,7 @@ def _setup_network_list_page(self) -> None: self.nl_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.nl_back_button.setText("Back") self.nl_back_button.setFlat(True) - self.nl_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") - ) + self.nl_back_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) self.nl_back_button.setProperty("class", "back_btn") self.nl_back_button.setProperty("button_type", "icon") @@ -2278,7 +2232,7 @@ def _setup_add_network_page(self) -> None: self.add_network_page_backButton.setText("Back") self.add_network_page_backButton.setFlat(True) self.add_network_page_backButton.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") + "icon_pixmap", BlocksPixmap.get(Icon.BACK) ) self.add_network_page_backButton.setProperty("class", "back_btn") self.add_network_page_backButton.setProperty("button_type", "icon") @@ -2350,7 +2304,7 @@ def _setup_add_network_page(self) -> None: self.add_network_password_view.setText("View") self.add_network_password_view.setFlat(True) self.add_network_password_view.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/unsee.svg") + "icon_pixmap", BlocksPixmap.get(Icon.UNSEE) ) self.add_network_password_view.setProperty("class", "back_btn") self.add_network_password_view.setProperty("button_type", "icon") @@ -2393,7 +2347,7 @@ def _setup_add_network_page(self) -> None: self.add_network_validation_button.setChecked(False) self.add_network_validation_button.setFlat(True) self.add_network_validation_button.setProperty( - "icon_pixmap", PixmapCache.get(":/dialog/media/btn_icons/yes.svg") + "icon_pixmap", BlocksPixmap.get(Icon.YES) ) self.add_network_validation_button.setText("Activate") self.add_network_validation_button.setObjectName( @@ -2456,7 +2410,7 @@ def _setup_saved_connection_page(self) -> None: self.saved_connection_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.saved_connection_back_button.setFlat(True) self.saved_connection_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") + "icon_pixmap", BlocksPixmap.get(Icon.BACK) ) self.saved_connection_back_button.setProperty("class", "back_btn") self.saved_connection_back_button.setProperty("button_type", "icon") @@ -2688,9 +2642,7 @@ def _setup_saved_details_page(self) -> None: self.snd_back.setMaximumSize(QtCore.QSize(60, 60)) self.snd_back.setText("Back") self.snd_back.setFlat(True) - self.snd_back.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") - ) + self.snd_back.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) self.snd_back.setProperty("class", "back_btn") self.snd_back.setProperty("button_type", "icon") @@ -2777,7 +2729,7 @@ def _setup_saved_details_page(self) -> None: self.saved_connection_change_password_view.setText("View") self.saved_connection_change_password_view.setFlat(True) self.saved_connection_change_password_view.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/unsee.svg") + "icon_pixmap", BlocksPixmap.get(Icon.UNSEE) ) self.saved_connection_change_password_view.setProperty("class", "back_btn") self.saved_connection_change_password_view.setProperty("button_type", "icon") @@ -2882,7 +2834,7 @@ def _setup_saved_details_page(self) -> None: font.setPointSize(16) self.saved_details_save_btn.setFont(font) self.saved_details_save_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/save.svg") + "icon_pixmap", BlocksPixmap.get(Icon.SAVE) ) self.saved_details_save_btn.setText("Save") bottom_btn_layout.addWidget( @@ -2899,7 +2851,7 @@ def _setup_saved_details_page(self) -> None: self.wifi_static_ip_btn.setText("Static\nIP") self.wifi_static_ip_btn.setProperty( "icon_pixmap", - PixmapCache.get(":/network/media/btn_icons/network/static_ip.svg"), + BlocksPixmap.get(Icon.STATIC_IP), ) bottom_btn_layout.addWidget( self.wifi_static_ip_btn, @@ -2944,9 +2896,7 @@ def _setup_hotspot_page(self) -> None: self.hotspot_back_button.setMinimumSize(QtCore.QSize(60, 60)) self.hotspot_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.hotspot_back_button.setFlat(True) - self.hotspot_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") - ) + self.hotspot_back_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) self.hotspot_back_button.setProperty("class", "back_btn") self.hotspot_back_button.setProperty("button_type", "icon") @@ -3065,7 +3015,7 @@ def _setup_hotspot_page(self) -> None: confirm_font.setWeight(75) self.hotspot_change_confirm.setFont(confirm_font) self.hotspot_change_confirm.setProperty( - "icon_pixmap", PixmapCache.get(":/dialog/media/btn_icons/yes.svg") + "icon_pixmap", BlocksPixmap.get(Icon.YES) ) self.hotspot_change_confirm.setText("Activate") @@ -3109,7 +3059,7 @@ def _setup_hidden_network_page(self) -> None: self.hidden_network_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.hidden_network_back_button.setFlat(True) self.hidden_network_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") + "icon_pixmap", BlocksPixmap.get(Icon.BACK) ) self.hidden_network_back_button.setProperty("button_type", "icon") header_layout.addWidget(self.hidden_network_back_button) @@ -3190,7 +3140,7 @@ def _setup_hidden_network_page(self) -> None: self.hidden_network_password_view.setMaximumSize(QtCore.QSize(60, 60)) self.hidden_network_password_view.setFlat(True) self.hidden_network_password_view.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/unsee.svg") + "icon_pixmap", BlocksPixmap.get(Icon.UNSEE) ) self.hidden_network_password_view.setProperty("button_type", "icon") password_frame_layout.addWidget(self.hidden_network_password_view) @@ -3216,7 +3166,7 @@ def _setup_hidden_network_page(self) -> None: self.hidden_network_connect_button.setFont(font) self.hidden_network_connect_button.setFlat(True) self.hidden_network_connect_button.setProperty( - "icon_pixmap", PixmapCache.get(":/dialog/media/btn_icons/yes.svg") + "icon_pixmap", BlocksPixmap.get(Icon.YES) ) self.hidden_network_connect_button.setText("Connect") content_layout.addWidget( @@ -3272,9 +3222,7 @@ def _setup_vlan_page(self) -> None: self.vlan_back_button.setMinimumSize(QtCore.QSize(60, 60)) self.vlan_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.vlan_back_button.setFlat(True) - self.vlan_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") - ) + self.vlan_back_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) self.vlan_back_button.setProperty("button_type", "icon") header_layout.addWidget(self.vlan_back_button) main_layout.addLayout(header_layout) @@ -3385,9 +3333,7 @@ def _make_row(label_text, field): self.vlan_apply_button.setMaximumSize(QtCore.QSize(220, 60)) self.vlan_apply_button.setFont(btn_font) self.vlan_apply_button.setText("Apply") - self.vlan_apply_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/save.svg") - ) + self.vlan_apply_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.SAVE)) btn_layout.addWidget( self.vlan_apply_button, 0, QtCore.Qt.AlignmentFlag.AlignHCenter ) @@ -3398,7 +3344,7 @@ def _make_row(label_text, field): self.vlan_delete_button.setFont(btn_font) self.vlan_delete_button.setText("Delete") self.vlan_delete_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/garbage-icon.svg") + "icon_pixmap", BlocksPixmap.get(Icon.GARBAGE_ICON) ) btn_layout.addWidget( self.vlan_delete_button, 0, QtCore.Qt.AlignmentFlag.AlignHCenter @@ -3437,7 +3383,7 @@ def _setup_wifi_static_ip_page(self) -> None: self.wifi_sip_back_button.setMaximumSize(QtCore.QSize(60, 60)) self.wifi_sip_back_button.setFlat(True) self.wifi_sip_back_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/back.svg") + "icon_pixmap", BlocksPixmap.get(Icon.BACK) ) self.wifi_sip_back_button.setProperty("button_type", "icon") header_layout.addWidget(self.wifi_sip_back_button) @@ -3514,7 +3460,7 @@ def _make_row(label_text, field): self.wifi_sip_apply_button.setFont(btn_font) self.wifi_sip_apply_button.setText("Apply") self.wifi_sip_apply_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/save.svg") + "icon_pixmap", BlocksPixmap.get(Icon.SAVE) ) btn_layout.addWidget( self.wifi_sip_apply_button, 0, QtCore.Qt.AlignmentFlag.AlignVCenter @@ -3526,7 +3472,7 @@ def _make_row(label_text, field): self.wifi_sip_dhcp_button.setFont(btn_font) self.wifi_sip_dhcp_button.setText("Reset\nDHCP") self.wifi_sip_dhcp_button.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/garbage-icon.svg") + "icon_pixmap", BlocksPixmap.get(Icon.GARBAGE_ICON) ) btn_layout.addWidget( self.wifi_sip_dhcp_button, @@ -3620,8 +3566,8 @@ def _setup_password_visibility_toggle( """Setup password visibility toggle for a button/field pair.""" view_button.setCheckable(True) - see_icon = PixmapCache.get(":/ui/media/btn_icons/see.svg") - unsee_icon = PixmapCache.get(":/ui/media/btn_icons/unsee.svg") + see_icon = BlocksPixmap.get(Icon.SEE) + unsee_icon = BlocksPixmap.get(Icon.UNSEE) view_button.toggled.connect( lambda checked: password_field.setHidden(not checked) @@ -3635,23 +3581,17 @@ def _setup_password_visibility_toggle( def _setup_icons(self) -> None: """Setup button icons.""" - self.hotspot_button.setPixmap( - PixmapCache.get(":/network/media/btn_icons/hotspot.svg") - ) - self.wifi_button.setPixmap( - PixmapCache.get(":/network/media/btn_icons/wifi_config.svg") - ) + self.hotspot_button.setPixmap(BlocksPixmap.get(Icon.HOTSPOT)) + self.wifi_button.setPixmap(BlocksPixmap.get(Icon.WIFI_CONFIG)) self.ethernet_button.setPixmap( - PixmapCache.get(":/network/media/btn_icons/network/ethernet_connected.svg"), + BlocksPixmap.get(Icon.ETHERNET_CONNECTED), ) self.network_delete_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/garbage-icon.svg") - ) - self.network_activate_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/dialog/media/btn_icons/yes.svg") + "icon_pixmap", BlocksPixmap.get(Icon.GARBAGE_ICON) ) + self.network_activate_btn.setProperty("icon_pixmap", BlocksPixmap.get(Icon.YES)) self.network_details_btn.setProperty( - "icon_pixmap", PixmapCache.get(":/ui/media/btn_icons/printer_settings.svg") + "icon_pixmap", BlocksPixmap.get(Icon.PRINTER_SETTINGS) ) def _setup_input_fields(self) -> None: diff --git a/BlocksScreen/lib/panels/widgets/filesPage.py b/BlocksScreen/lib/panels/widgets/filesPage.py index 969399ac..1f1ee7ed 100644 --- a/BlocksScreen/lib/panels/widgets/filesPage.py +++ b/BlocksScreen/lib/panels/widgets/filesPage.py @@ -1,8 +1,8 @@ import json import logging -import typing import helper_methods +from lib.utils.blocks_pixmap import BlocksPixmap, Icon from lib.utils.blocks_Scrollbar import CustomScrollBar from lib.utils.icon_button import IconButton from lib.utils.list_model import EntryDelegate, EntryListModel, ListItem @@ -30,17 +30,7 @@ class FilesPage(QtWidgets.QWidget): LEFT_FONT_SIZE = 17 RIGHT_FONT_SIZE = 12 - # Icon paths - ICON_PATHS = { - "back_folder": ":/ui/media/btn_icons/back_folder.svg", - "folder": ":/ui/media/btn_icons/folderIcon.svg", - "right_arrow": ":/arrow_icons/media/btn_icons/right_arrow.svg", - "usb": ":/ui/media/btn_icons/usb_icon.svg", - "back": ":/ui/media/btn_icons/back.svg", - "refresh": ":/ui/media/btn_icons/refresh.svg", - } - - def __init__(self, parent: typing.Optional[QtWidgets.QWidget] = None) -> None: + def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent) self._file_list: list[dict] = [] @@ -52,7 +42,6 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget] = None) -> None: self._metadata_retry_count: dict[ str, int ] = {} # Track retry count per file (max 3) - self._icons: dict[str, QtGui.QPixmap] = {} self._model = EntryListModel() self._entry_delegate = EntryDelegate() @@ -62,7 +51,6 @@ def __init__(self, parent: typing.Optional[QtWidgets.QWidget] = None) -> None: self._model.modelReset.connect(self._delayed_scrollbar_update) self._setup_ui() - self._load_icons() self._connect_signals() self.setMouseTracking(True) @@ -263,7 +251,7 @@ def _find_file_insert_position(self, modified_time: float) -> int: return insert_pos - def _find_file_key_by_display_name(self, display_name: str) -> typing.Optional[str]: + def _find_file_key_by_display_name(self, display_name: str) -> str | None: """Find the file key in _files_data by its display name.""" for key in self._files_data: if self._get_display_name(key) == display_name: @@ -308,7 +296,7 @@ def on_file_added(self, file_data: dict) -> None: item = ListItem( text=display_name, right_text="Unknown Filament - Unknown time", - right_icon=self._icons.get("right_arrow"), + right_icon=BlocksPixmap.get(Icon.RIGHT_ARROW), left_icon=None, callback=None, selected=False, @@ -426,9 +414,9 @@ def on_dir_added(self, dir_data: dict) -> None: insert_position = self._find_directory_insert_position(dirname) # Create the list item - icon = self._icons.get("folder") + icon = BlocksPixmap.get(Icon.FOLDERICON) if self._is_usb_directory(self._curr_dir, dirname): - icon = self._icons.get("usb") + icon = BlocksPixmap.get(Icon.USB_ICON) item = ListItem( text=str(dirname), @@ -677,7 +665,7 @@ def _add_file_to_list(self, file_item: dict) -> None: item = ListItem( text=display_name, right_text="Unknown Filament - Unknown time", - right_icon=self._icons.get("right_arrow"), + right_icon=BlocksPixmap.get(Icon.RIGHT_ARROW), left_icon=None, callback=None, selected=False, @@ -691,7 +679,7 @@ def _add_file_to_list(self, file_item: dict) -> None: if item: self._model.add_item(item) - def _create_file_list_item(self, filedata: dict) -> typing.Optional[ListItem]: + def _create_file_list_item(self, filedata: dict) -> ListItem | None: """Create a ListItem from file metadata.""" filename = filedata.get("filename", "") if not filename: @@ -729,7 +717,7 @@ def _create_file_list_item(self, filedata: dict) -> typing.Optional[ListItem]: return ListItem( text=display_name, right_text=f"{filament_type} - {time_str}", - right_icon=self._icons.get("right_arrow"), + right_icon=BlocksPixmap.get(Icon.RIGHT_ARROW), left_icon=None, # Files have no left icon callback=None, selected=False, @@ -747,9 +735,9 @@ def _add_directory_list_item(self, dir_data: dict) -> None: return # Choose appropriate icon - icon = self._icons.get("folder") + icon = BlocksPixmap.get(Icon.FOLDERICON) if self._is_usb_directory(self._curr_dir, dir_name): - icon = self._icons.get("usb") + icon = BlocksPixmap.get(Icon.USB_ICON) item = ListItem( text=str(dir_name), @@ -771,7 +759,7 @@ def _add_back_folder_entry(self) -> None: text="Go Back", right_text="", right_icon=None, - left_icon=self._icons.get("back_folder"), + left_icon=BlocksPixmap.get(Icon.BACK_FOLDER), callback=None, selected=False, allow_check=False, @@ -936,15 +924,6 @@ def _get_display_name(self, filename: str) -> str: return name - def _load_icons(self) -> None: - """Load all icons into cache.""" - self._icons = { - "back_folder": QtGui.QPixmap(self.ICON_PATHS["back_folder"]), - "folder": QtGui.QPixmap(self.ICON_PATHS["folder"]), - "right_arrow": QtGui.QPixmap(self.ICON_PATHS["right_arrow"]), - "usb": QtGui.QPixmap(self.ICON_PATHS["usb"]), - } - def _connect_signals(self) -> None: """Connect internal signals.""" # Button connections @@ -1015,7 +994,7 @@ def _create_header_layout(self) -> QtWidgets.QHBoxLayout: self.back_btn.setMinimumSize(QtCore.QSize(60, 60)) self.back_btn.setMaximumSize(QtCore.QSize(60, 60)) self.back_btn.setFlat(True) - self.back_btn.setProperty("icon_pixmap", QtGui.QPixmap(self.ICON_PATHS["back"])) + self.back_btn.setProperty("icon_pixmap", BlocksPixmap.get(Icon.BACK)) self.back_btn.setObjectName("back_btn") layout.addWidget(self.back_btn, 0, QtCore.Qt.AlignmentFlag.AlignLeft) @@ -1024,9 +1003,7 @@ def _create_header_layout(self) -> QtWidgets.QHBoxLayout: self._reload_button.setMinimumSize(QtCore.QSize(60, 60)) self._reload_button.setMaximumSize(QtCore.QSize(60, 60)) self._reload_button.setFlat(True) - self._reload_button.setProperty( - "icon_pixmap", QtGui.QPixmap(self.ICON_PATHS["refresh"]) - ) + self._reload_button.setProperty("icon_pixmap", BlocksPixmap.get(Icon.REFRESH)) self._reload_button.setObjectName("reload_button") layout.addWidget(self._reload_button, 0, QtCore.Qt.AlignmentFlag.AlignRight) diff --git a/BlocksScreen/lib/utils/blocks_pixmap.py b/BlocksScreen/lib/utils/blocks_pixmap.py index eace6add..419f0c11 100644 --- a/BlocksScreen/lib/utils/blocks_pixmap.py +++ b/BlocksScreen/lib/utils/blocks_pixmap.py @@ -16,6 +16,10 @@ from PyQt6 import QtCore, QtGui +# Setup-time callers cannot know their paint size, and the widgets rescale anyway; +# 128 clears the tallest button (90px hole, 0.8 fill) at 64 KiB a surface. +ICON_SIZE = QtCore.QSize(128, 128) + class Icon(StrEnum): """Every resource key the handwritten panels draw, one member per asset.""" @@ -133,7 +137,7 @@ def icon(cls, icon: Icon | str) -> QtGui.QIcon: return cached @classmethod - def get(cls, icon: Icon | str, size: QtCore.QSize) -> QtGui.QPixmap: + def get(cls, icon: Icon | str, size: QtCore.QSize = ICON_SIZE) -> QtGui.QPixmap: """Return *icon* rasterized at *size*, cached across calls.""" key = (str(icon), size.width(), size.height()) cached = cls._pixmaps.get(key) diff --git a/tests/network/conftest.py b/tests/network/conftest.py index 2825b801..e37d84df 100644 --- a/tests/network/conftest.py +++ b/tests/network/conftest.py @@ -15,6 +15,7 @@ import asyncio import enum +import importlib import sys import types from dataclasses import dataclass @@ -280,6 +281,12 @@ def set_value(self, val): sys.modules[_mod_name] = _stub sys.modules["BlocksScreen." + _mod_name] = _stub +# blocks_pixmap is a pure-PyQt6 leaf: alias the real module so the identity +# assertions in test_network_ui.py compare real cache entries, not mock children. +sys.modules["lib.utils.blocks_pixmap"] = importlib.import_module( + "BlocksScreen.lib.utils.blocks_pixmap" +) + # Mock lib.qrcode_gen (short path only) — networkWindow.py imports it as # ``from lib.qrcode_gen import generate_wifi_qrcode``. The BlocksScreen.* @@ -301,7 +308,7 @@ def set_value(self, val): sys.modules["configfile"] = _mock_configfile_mod # Now safe to import the actual network package -from BlocksScreen.lib.network.models import ( # noqa: E402 +from BlocksScreen.lib.network.models import ( ConnectionPriority, ConnectivityState, NetworkInfo, diff --git a/tests/network/test_network_ui.py b/tests/network/test_network_ui.py index 9fd4e750..b0dbf3aa 100644 --- a/tests/network/test_network_ui.py +++ b/tests/network/test_network_ui.py @@ -966,34 +966,20 @@ def _stub_init(self, *_a, **_kw): # ───────────────────────────────────────────────────────────────────────────── -# Step 4a: Helper classes — PixmapCache, WifiIconProvider, IPAddressLineEdit +# Step 4a: Helper classes — WifiIconProvider, IPAddressLineEdit # ───────────────────────────────────────────────────────────────────────────── @pytest.mark.unit class TestHelperClasses: - def test_pixmap_cache_get_loads_and_caches(self, qapp): - from BlocksScreen.lib.panels.networkWindow import PixmapCache - - PixmapCache._cache.clear() - px1 = PixmapCache.get(":/some/test/path.svg") - px2 = PixmapCache.get(":/some/test/path.svg") - assert px2 is px1 # same object from cache - - def test_pixmap_cache_preload_calls_get(self, qapp): - from BlocksScreen.lib.panels.networkWindow import PixmapCache - - PixmapCache._cache.clear() - paths = [":/path/a.svg", ":/path/b.svg"] - PixmapCache.preload(paths) - assert ":/path/a.svg" in PixmapCache._cache - assert ":/path/b.svg" in PixmapCache._cache - - def test_wifi_icon_provider_get_pixmap(self, qapp): + def test_wifi_icon_provider_picks_the_asset_for_the_signal(self, qapp): from BlocksScreen.lib.panels.networkWindow import WifiIconProvider + from BlocksScreen.lib.utils.blocks_pixmap import BlocksPixmap, Icon + # Identity, not isNull: this suite never imports the _rc blobs, so every + # pixmap here is null and only the cache key tells the assets apart. px = WifiIconProvider.get_pixmap(75, is_protected=True) - assert px is not None # returns a QPixmap (even if null in offscreen) + assert px is BlocksPixmap.get(Icon.WIFI_4BAR_PROTECTED) def test_ip_line_edit_is_valid_true(self, qapp): from BlocksScreen.lib.panels.networkWindow import IPAddressLineEdit diff --git a/tests/panels/conftest.py b/tests/panels/conftest.py index 89dd19ec..a144fa05 100644 --- a/tests/panels/conftest.py +++ b/tests/panels/conftest.py @@ -76,7 +76,6 @@ sys.modules["lib.panels.controlTab"].ControlTab = MagicMock sys.modules["lib.panels.filamentTab"].FilamentTab = MagicMock sys.modules["lib.panels.networkWindow"].NetworkControlWindow = MagicMock -sys.modules["lib.panels.networkWindow"].PixmapCache = MagicMock sys.modules["lib.panels.printTab"].PrintTab = MagicMock sys.modules["lib.panels.utilitiesTab"].UtilitiesTab = MagicMock sys.modules["lib.panels.widgets.basePopup"].BasePopup = MagicMock diff --git a/tests/ui/test_resource_keys_unit.py b/tests/ui/test_resource_keys_unit.py index 1240a28e..b3db8b08 100644 --- a/tests/ui/test_resource_keys_unit.py +++ b/tests/ui/test_resource_keys_unit.py @@ -20,15 +20,10 @@ 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. -XFAIL_KEYS = { - ":/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" - ), -} +# Known-broken keys. This dict may only ever shrink: test_xfail_keys_are_still_broken +# fails once an entry stops being broken. Emptied 2026-09-01 when Icon replaced the +# last f-string key template. +XFAIL_KEYS: dict[str, str] = {} # Text scan not AST (misses .ui XML); '/' drops ": %s", spaces admit Momcake keys. _PY_LITERAL = re.compile(r'["\'](:/?[^"\'\s][^"\']*/[^"\']*)["\']')