diff --git a/src/core/renderer.py b/src/core/renderer.py index f1de3dc..724b46e 100644 --- a/src/core/renderer.py +++ b/src/core/renderer.py @@ -62,7 +62,6 @@ def _draw_sprite_from_spec( sheet_name, sprite_id, dest_area, - extracted_skin_dir, transparency_color=MAGENTA_TRANSPARENCY_RGB, sprite_x=None, sprite_y=None, @@ -72,9 +71,9 @@ def _draw_sprite_from_spec( ): """Helper to draw a sprite given its sheet, sprite ID, and destination area.""" spec = self.skin_data.spec_json - sheet_path = os.path.join(extracted_skin_dir, sheet_name) - if not os.path.exists(sheet_path): - print(f"WARNING: {sheet_name} not found in {extracted_skin_dir}") + sheet_path = self.skin_data.get_path(sheet_name) + if not sheet_path or not os.path.exists(sheet_path): + print(f"WARNING: {sheet_name} not found.") return try: @@ -99,12 +98,17 @@ def _draw_sprite_from_spec( sprite_h = int(sprite_spec["h"]) # Use sprite validator to check if the sprite coordinates are valid in the actual BMP file + cache_key = (sheet_path, sprite_x, sprite_y, sprite_w, sprite_h) + if cache_key in self.sprite_manager.invalid_sprite_cache: + return + if not validate_sprite_in_bmp( sheet_path, sprite_x, sprite_y, sprite_w, sprite_h ): print( f"WARNING: Sprite '{sprite_id}' at coordinates ({sprite_x}, {sprite_y}, {sprite_w}x{sprite_h}) is out of bounds in {sheet_path}. Skipping." ) + self.sprite_manager.invalid_sprite_cache.add(cache_key) return pixmap = self.sprite_manager.load_sprite( @@ -162,9 +166,8 @@ def render(self, painter: QPainter, ui_state: "UIState"): self._render_visualization(painter) def _render_background(self, painter: QPainter): - extracted_skin_dir = self.skin_data.extracted_skin_dir - main_bmp_path = os.path.join(extracted_skin_dir, "main.bmp") - if os.path.exists(main_bmp_path): + main_bmp_path = self.skin_data.get_path("main.bmp") + if main_bmp_path and os.path.exists(main_bmp_path): background_pixmap = self.sprite_manager.load_sprite( main_bmp_path, 0, @@ -175,18 +178,16 @@ def _render_background(self, painter: QPainter): ) painter.drawPixmap(0, 0, background_pixmap) else: - print(f"WARNING: main.bmp not found in {extracted_skin_dir}") + print(f"WARNING: main.bmp not found in {self.skin_data.extracted_skin_dir}") def _render_titlebar(self, painter: QPainter): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir dest_area = main_window_spec["areas"]["titlebar"] self._draw_sprite_from_spec( - painter, "titlebar.bmp", "TITLEBAR_NORMAL", dest_area, extracted_skin_dir + painter, "titlebar.bmp", "TITLEBAR_NORMAL", dest_area ) def _render_clutterbar(self, painter: QPainter, ui_state: UIState): - extracted_skin_dir = self.skin_data.extracted_skin_dir sprite_id = "CLUTTERBAR_NORMAL" if ui_state.is_options_pressed: sprite_id = "CLUTTERBAR_OPTIONS_PRESSED" @@ -199,8 +200,8 @@ def _render_clutterbar(self, painter: QPainter, ui_state: UIState): elif ui_state.is_visualization_menu_pressed: sprite_id = "CLUTTERBAR_VISUALIZATION_PRESSED" - titlebar_bmp_path = os.path.join(extracted_skin_dir, "titlebar.bmp") - if os.path.exists(titlebar_bmp_path): + titlebar_bmp_path = self.skin_data.get_path("titlebar.bmp") + if titlebar_bmp_path and os.path.exists(titlebar_bmp_path): sprite_x, sprite_y = 304, 0 if sprite_id == "CLUTTERBAR_NORMAL": sprite_x, sprite_y = 304, 0 @@ -227,7 +228,6 @@ def _render_clutterbar(self, painter: QPainter, ui_state: UIState): def _render_transport_buttons(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir controls = main_window_spec["areas"]["controls"] for control in controls: dest_area = { @@ -252,21 +252,16 @@ def _render_transport_buttons(self, painter: QPainter, ui_state: UIState): "cbuttons.bmp", current_sprite_id, dest_area, - extracted_skin_dir, ) def _render_eject_button(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir dest_area = main_window_spec["areas"]["eject"] eject_sprite_id = "EJECT_PRESSED" if ui_state.is_eject_pressed else "EJECT" - self._draw_sprite_from_spec( - painter, "cbuttons.bmp", eject_sprite_id, dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "cbuttons.bmp", eject_sprite_id, dest_area) def _render_shuffle_repeat_eq_pl(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir dest_area = main_window_spec["areas"]["shuffle_dest"] shuffle_sprite_id = "SHUFFLE_OFF" if ui_state.shuffle_on: @@ -274,7 +269,7 @@ def _render_shuffle_repeat_eq_pl(self, painter: QPainter, ui_state: UIState): if ui_state.is_shuffle_pressed: shuffle_sprite_id += "_PRESSED" self._draw_sprite_from_spec( - painter, "shufrep.bmp", shuffle_sprite_id, dest_area, extracted_skin_dir + painter, "shufrep.bmp", shuffle_sprite_id, dest_area ) dest_area = main_window_spec["areas"]["repeat_dest"] repeat_sprite_id = "REPEAT_OFF" @@ -282,61 +277,46 @@ def _render_shuffle_repeat_eq_pl(self, painter: QPainter, ui_state: UIState): repeat_sprite_id = "REPEAT_ON" if ui_state.is_repeat_pressed: repeat_sprite_id += "_PRESSED" - self._draw_sprite_from_spec( - painter, "shufrep.bmp", repeat_sprite_id, dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "shufrep.bmp", repeat_sprite_id, dest_area) dest_area = main_window_spec["areas"]["eq_button"] eq_sprite_id = "EQ_OFF" if ui_state.eq_button_on: eq_sprite_id = "EQ_ON" if ui_state.is_eq_pressed: eq_sprite_id += "_PRESSED" - self._draw_sprite_from_spec( - painter, "shufrep.bmp", eq_sprite_id, dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "shufrep.bmp", eq_sprite_id, dest_area) dest_area = main_window_spec["areas"]["playlist_button"] pl_sprite_id = "PL_OFF" if ui_state.playlist_button_on: pl_sprite_id = "PL_ON" if ui_state.is_playlist_pressed: pl_sprite_id += "_PRESSED" - self._draw_sprite_from_spec( - painter, "shufrep.bmp", pl_sprite_id, dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "shufrep.bmp", pl_sprite_id, dest_area) def _render_mono_stereo(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir if ui_state.is_stereo: dest_area = main_window_spec["areas"]["stereo_indicator"] - self._draw_sprite_from_spec( - painter, "monoster.bmp", "STEREO_ON", dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "monoster.bmp", "STEREO_ON", dest_area) dest_area = main_window_spec["areas"]["mono_indicator"] - self._draw_sprite_from_spec( - painter, "monoster.bmp", "MONO_OFF", dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "monoster.bmp", "MONO_OFF", dest_area) else: dest_area = main_window_spec["areas"]["stereo_indicator"] self._draw_sprite_from_spec( - painter, "monoster.bmp", "STEREO_OFF", dest_area, extracted_skin_dir + painter, "monoster.bmp", "STEREO_OFF", dest_area ) dest_area = main_window_spec["areas"]["mono_indicator"] - self._draw_sprite_from_spec( - painter, "monoster.bmp", "MONO_ON", dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "monoster.bmp", "MONO_ON", dest_area) def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] spec = self.skin_data.spec_json - extracted_skin_dir = self.skin_data.extracted_skin_dir dest_area_pos_track = main_window_spec["areas"]["position_track"] self._draw_sprite_from_spec( painter, "posbar.bmp", "POSITION_TRACK", dest_area_pos_track, - extracted_skin_dir, ) position_thumb_spec = spec["sheets"]["posbar.bmp"]["sprites"]["POSITION_THUMB"] thumb_w_pos = position_thumb_spec["w"] @@ -355,7 +335,6 @@ def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): "w": thumb_w_pos, "h": thumb_h_pos, }, - extracted_skin_dir, sprite_x=position_thumb_spec["x"], sprite_y=position_thumb_spec["y"], sprite_w=thumb_w_pos, @@ -376,7 +355,6 @@ def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): "volume.bmp", "VOLUME_FRAMES", dest_area_vol, - extracted_skin_dir, sprite_x=vol_sprite_x, sprite_y=vol_sprite_y, sprite_w=vol_sprite_w, @@ -400,7 +378,6 @@ def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): "volume.bmp", volume_thumb_sprite_id, {"x": thumb_dest_x, "y": thumb_dest_y, "w": thumb_w, "h": thumb_h}, - extracted_skin_dir, sprite_x=volume_thumb_coords["x"], sprite_y=volume_thumb_coords["y"], sprite_w=thumb_w, @@ -484,7 +461,6 @@ def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): balance_sheet, balance_sprite_id, dest_area_balance, - extracted_skin_dir, sprite_x=balance_sprite_x, sprite_y=balance_sprite_y, sprite_w=balance_sprite_w, @@ -514,7 +490,6 @@ def _render_sliders_tracks(self, painter: QPainter, ui_state: UIState): "w": thumb_w, "h": thumb_h, }, - extracted_skin_dir, sprite_x=thumb_sprite_coords["x"], sprite_y=thumb_sprite_coords["y"], sprite_w=thumb_w, @@ -542,7 +517,6 @@ def _render_text_title(self, painter: QPainter, ui_state: UIState): def _render_time_display(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir current_time_seconds = ( int(ui_state.position * ui_state.duration) if ui_state.duration > 0 else 0 ) @@ -554,16 +528,11 @@ def _render_time_display(self, painter: QPainter, ui_state: UIState): sec_one = seconds % 10 # Check for nums_ex.bmp first (some skins use this name), fall back to numbers.bmp - nums_ex_bmp_path = os.path.join(extracted_skin_dir, "nums_ex.bmp") - numbers_bmp_path = os.path.join(extracted_skin_dir, "numbers.bmp") - - if os.path.exists(nums_ex_bmp_path): - # Use nums_ex.bmp if it exists - digit_sheet_name = "nums_ex.bmp" - elif os.path.exists(numbers_bmp_path): - # Fall back to numbers.bmp + digit_sheet_name = "nums_ex.bmp" + if not self.skin_data.get_path(digit_sheet_name): digit_sheet_name = "numbers.bmp" - else: + + if not self.skin_data.get_path(digit_sheet_name): # Neither file exists, skip rendering return @@ -573,7 +542,6 @@ def _render_time_display(self, painter: QPainter, ui_state: UIState): digit_sheet_name, "DIGITS", dest_area, - extracted_skin_dir, pattern_index=min_ten, ) dest_area = main_window_spec["areas"]["minute_ones"] @@ -582,7 +550,6 @@ def _render_time_display(self, painter: QPainter, ui_state: UIState): digit_sheet_name, "DIGITS", dest_area, - extracted_skin_dir, pattern_index=min_one, ) dest_area = main_window_spec["areas"]["second_tens"] @@ -591,7 +558,6 @@ def _render_time_display(self, painter: QPainter, ui_state: UIState): digit_sheet_name, "DIGITS", dest_area, - extracted_skin_dir, pattern_index=sec_ten, ) dest_area = main_window_spec["areas"]["second_ones"] @@ -600,13 +566,11 @@ def _render_time_display(self, painter: QPainter, ui_state: UIState): digit_sheet_name, "DIGITS", dest_area, - extracted_skin_dir, pattern_index=sec_one, ) def _render_work_indicator(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] - extracted_skin_dir = self.skin_data.extracted_skin_dir dest_area = main_window_spec["areas"]["play_indicator_area"] if ui_state.is_playing and not ui_state.is_paused: sprite_id = "PLAY_INDICATOR" @@ -614,9 +578,7 @@ def _render_work_indicator(self, painter: QPainter, ui_state: UIState): sprite_id = "PAUSE_INDICATOR" else: sprite_id = "STOP_INDICATOR" - self._draw_sprite_from_spec( - painter, "playpaus.bmp", sprite_id, dest_area, extracted_skin_dir - ) + self._draw_sprite_from_spec(painter, "playpaus.bmp", sprite_id, dest_area) def _render_bitrate_sample(self, painter: QPainter, ui_state: UIState): main_window_spec = self.skin_data.spec_json["destinations"]["main_window"] diff --git a/src/core/skin_data.py b/src/core/skin_data.py index a4c2691..c8a8533 100644 --- a/src/core/skin_data.py +++ b/src/core/skin_data.py @@ -15,3 +15,22 @@ class SkinData: playlist_spec_json: Dict[str, Any] = field(default_factory=dict) viscolor_data: List[Tuple[int, int, int]] = field(default_factory=list) region_data: Optional[Dict[str, Any]] = None + file_mapping: Dict[str, str] = field(default_factory=dict) + + def get_path(self, filename: str) -> str | None: + """ + Gets the full, case-correct path for a given skin file. + The lookup is case-insensitive. + """ + if not self.extracted_skin_dir: + return None + + # The keys in file_mapping are already lowercase + actual_filename = self.file_mapping.get(filename.lower()) + + if actual_filename: + import os + + return os.path.join(self.extracted_skin_dir, actual_filename) + + return None diff --git a/src/core/skin_parser.py b/src/core/skin_parser.py index 10617d8..4ca05e0 100644 --- a/src/core/skin_parser.py +++ b/src/core/skin_parser.py @@ -21,8 +21,8 @@ def parse(self) -> SkinData: self._load_viscolor_data() self._load_region_data() - main_bmp_full_path = os.path.join(self.skin_data.extracted_skin_dir, "main.bmp") - if os.path.exists(main_bmp_full_path): + main_bmp_full_path = self.skin_data.get_path("main.bmp") + if main_bmp_full_path and os.path.exists(main_bmp_full_path): self.skin_data.main_bmp_path = main_bmp_full_path else: print(f"WARNING: main.bmp not found in {self.skin_data.extracted_skin_dir}") @@ -70,11 +70,18 @@ def _extract_skin_files(self) -> bool: else: extracted_skin_dir = temp_extract_dir + self.skin_data.extracted_skin_dir = extracted_skin_dir + + # Create a case-insensitive mapping of the files. + self.skin_data.file_mapping = { + f.lower(): f for f in os.listdir(extracted_skin_dir) + } + # Validate that this directory contains essential skin files if self._validate_skin_directory(extracted_skin_dir): - self.skin_data.extracted_skin_dir = extracted_skin_dir return True else: + self.skin_data.extracted_skin_dir = None print( f"ERROR: {self.skin_path} does not contain valid Winamp skin data." ) @@ -104,17 +111,19 @@ def _validate_skin_directory(self, skin_dir: str) -> bool: # At minimum, a Winamp skin must have main.bmp (the main window background) required_files = ["main.bmp"] - # Check for required files + # Check for required files (case-insensitively) for required_file in required_files: - required_path = os.path.join(skin_dir, required_file) - if not os.path.exists(required_path): + if required_file.lower() not in self.skin_data.file_mapping: print( f"INFO: Required skin file '{required_file}' not found in {skin_dir}" ) return False # Additional check: verify that main.bmp is actually an image file - main_bmp_path = os.path.join(skin_dir, "main.bmp") + main_bmp_path = self.skin_data.get_path("main.bmp") + if not main_bmp_path: + return False + try: from PIL import Image @@ -123,7 +132,7 @@ def _validate_skin_directory(self, skin_dir: str) -> bool: img.verify() # Reopen after verify since verify() closes the file with Image.open(main_bmp_path) as img: - pass + img.load() # Read the file to be sure except Exception as e: print(f"INFO: main.bmp is not a valid image file: {e}") return False @@ -163,8 +172,8 @@ def _load_spec_files(self): print(f"WARNING: {filename} not found at {path}") def _load_viscolor_data(self): - viscolor_path = os.path.join(self.skin_data.extracted_skin_dir, "viscolor.txt") - if os.path.exists(viscolor_path): + viscolor_path = self.skin_data.get_path("viscolor.txt") + if viscolor_path and os.path.exists(viscolor_path): self.skin_data.viscolor_data = self._load_viscolor_file(viscolor_path) else: print( @@ -173,8 +182,8 @@ def _load_viscolor_data(self): self.skin_data.viscolor_data = self._get_default_viscolor_data() def _load_region_data(self): - region_path = os.path.join(self.skin_data.extracted_skin_dir, "region.txt") - if os.path.exists(region_path): + region_path = self.skin_data.get_path("region.txt") + if region_path and os.path.exists(region_path): try: with open(region_path, "r") as f: region_content = f.read() @@ -206,7 +215,10 @@ def get_sprite(self, sheet_name, sprite_id): sprite_info = sheet["sprites"][sprite_id] - sheet_path = os.path.join(self.skin_data.extracted_skin_dir, sheet_name) + sheet_path = self.skin_data.get_path(sheet_name) + if not sheet_path: + # The warning is printed by get_path if file not found + return None return { "sheet_path": sheet_path, diff --git a/src/core/sprite_manager.py b/src/core/sprite_manager.py index f268130..b526254 100644 --- a/src/core/sprite_manager.py +++ b/src/core/sprite_manager.py @@ -6,6 +6,7 @@ class SpriteManager: def __init__(self): self.cache = {} # Cache for QPixmap objects + self.invalid_sprite_cache = set() def load_sprite(self, image_path, x, y, w, h, transparency_color=None): """ @@ -100,3 +101,5 @@ def load_sprite(self, image_path, x, y, w, h, transparency_color=None): def clear_cache(self): self.cache.clear() + self.invalid_sprite_cache.clear() + self.invalid_sprite_cache.clear() diff --git a/src/ui/equalizer_window.py b/src/ui/equalizer_window.py index feba23b..7ece793 100644 --- a/src/ui/equalizer_window.py +++ b/src/ui/equalizer_window.py @@ -444,7 +444,7 @@ def paintEvent(self, event): # Draw background background_spec = self.eq_spec_json["sprites"]["EQMAIN"]["background"] background_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), background_spec["x"], background_spec["y"], background_spec["w"], @@ -459,7 +459,7 @@ def paintEvent(self, event): ] # Assuming selected for now titlebar_dest = self.eq_spec_json["destinations"]["titlebar"] titlebar_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), titlebar_spec["x"], titlebar_spec["y"], titlebar_spec["w"], @@ -502,7 +502,7 @@ def _draw_minidisplay(self, painter): "graph_background" ] graph_background_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), graph_background_spec["x"], graph_background_spec["y"], graph_background_spec["w"], @@ -519,7 +519,7 @@ def _draw_minidisplay(self, painter): "graph_line_colors" ] graph_line_colors_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), graph_line_colors_spec["x"], graph_line_colors_spec["y"], graph_line_colors_spec["w"], @@ -558,7 +558,7 @@ def _draw_minidisplay(self, painter): # Draw preamp line preamp_line_spec = self.eq_spec_json["sprites"]["EQMAIN"]["preamp_line"] preamp_line_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), preamp_line_spec["x"], preamp_line_spec["y"], preamp_line_spec["w"], @@ -648,7 +648,7 @@ def _draw_button(self, painter, button_name, state): sprite_info = self.eq_spec_json["sprites"]["EQMAIN"][sprite_name] dest = self.eq_spec_json["destinations"][button_name] pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), sprite_info["x"], sprite_info["y"], sprite_info["w"], @@ -676,7 +676,7 @@ def _draw_slider(self, painter, slider_name, value): slider_bar_index ] slider_bar_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), slider_bar_spec["x"], slider_bar_spec["y"], slider_bar_spec["w"], @@ -688,7 +688,7 @@ def _draw_slider(self, painter, slider_name, value): # Draw the slider thumb knob_sprite_info = self.eq_spec_json["sprites"]["EQMAIN"]["slider_thumb"] knob_pixmap = self.sprite_manager.load_sprite( - os.path.join(self.extracted_skin_dir, "EQMAIN.BMP"), + self.skin_data.get_path("EQMAIN.BMP"), knob_sprite_info["x"], knob_sprite_info["y"], knob_sprite_info["w"], diff --git a/src/ui/playlist_scrollbar.py b/src/ui/playlist_scrollbar.py index fc02692..6df7a8c 100644 --- a/src/ui/playlist_scrollbar.py +++ b/src/ui/playlist_scrollbar.py @@ -6,11 +6,11 @@ class ScrollbarManager: - def __init__(self, window, playlist_spec, sprite_manager, extracted_skin_dir): + def __init__(self, window, playlist_spec, sprite_manager, skin_data): self.window = window self.playlist_spec = playlist_spec self.sprite_manager = sprite_manager - self.extracted_skin_dir = extracted_skin_dir + self.skin_data = skin_data # State for scrollbar elements self.pressed_elements = {} # Stores {element_id: True/False} @@ -69,17 +69,13 @@ def get_element_rect(self, element_id): def _get_sprite_pixmap(self, sprite_id): """Helper to get a QPixmap for a given sprite ID from the spec.""" - if ( - not self.sprite_manager - or not self.extracted_skin_dir - or not self.playlist_spec - ): + if not self.sprite_manager or not self.skin_data or not self.playlist_spec: return None - pledit_bmp_path = os.path.join( - self.extracted_skin_dir, self.playlist_spec["spriteSheet"]["file"] + pledit_bmp_path = self.skin_data.get_path( + self.playlist_spec["spriteSheet"]["file"] ) - if not os.path.exists(pledit_bmp_path): + if not pledit_bmp_path or not os.path.exists(pledit_bmp_path): print(f"WARNING: {self.playlist_spec['spriteSheet']['file']} not found.") return None diff --git a/src/ui/playlist_window.py b/src/ui/playlist_window.py index c5352f2..1dec11a 100644 --- a/src/ui/playlist_window.py +++ b/src/ui/playlist_window.py @@ -75,7 +75,7 @@ def __init__( # Initialize UI component managers self.scrollbar_manager = ScrollbarManager( - self, self.playlist_spec, self.sprite_manager, self.extracted_skin_dir + self, self.playlist_spec, self.sprite_manager, self.skin_data ) self.menu_manager = MenuManager(self, self.playlist_spec) self.buttonbar_manager = ButtonBarManager(self, self.playlist_spec) @@ -468,11 +468,9 @@ def _get_scrollbar_element_rect(self, element_id): return self.scrollbar_manager.get_element_rect(element_id) def _load_pledit_colors(self): - pledit_txt_path = os.path.join(self.extracted_skin_dir, "pledit.txt") - if not os.path.exists(pledit_txt_path): - print( - f"WARNING: pledit.txt not found at {pledit_txt_path}. Using default colors." - ) + pledit_txt_path = self.skin_data.get_path("pledit.txt") + if not pledit_txt_path or not os.path.exists(pledit_txt_path): + print("WARNING: pledit.txt not found. Using default colors.") return try: @@ -495,13 +493,11 @@ def _load_pledit_colors(self): print(f"Error loading pledit.txt colors: {e}. Using default colors.") def _load_playlist_font_settings(self): - pledit_txt_path = os.path.join(self.extracted_skin_dir, "pledit.txt") + pledit_txt_path = self.skin_data.get_path("pledit.txt") # Check if pledit.txt exists - if not os.path.exists(pledit_txt_path): - print( - f"WARNING: pledit.txt not found at {pledit_txt_path}. Using default font settings." - ) + if not pledit_txt_path or not os.path.exists(pledit_txt_path): + print("WARNING: pledit.txt not found. Using default font settings.") return try: @@ -568,10 +564,10 @@ def _get_sprite_pixmap(self, sprite_id): ): return None - pledit_bmp_path = os.path.join( - self.extracted_skin_dir, self.playlist_spec["spriteSheet"]["file"] + pledit_bmp_path = self.skin_data.get_path( + self.playlist_spec["spriteSheet"]["file"] ) - if not os.path.exists(pledit_bmp_path): + if not pledit_bmp_path or not os.path.exists(pledit_bmp_path): print(f"WARNING: {self.playlist_spec['spriteSheet']['file']} not found.") return None diff --git a/src/utils/file_utils.py b/src/utils/file_utils.py new file mode 100644 index 0000000..7f013fc --- /dev/null +++ b/src/utils/file_utils.py @@ -0,0 +1,34 @@ +""" +File utilities for WimPyAmp application. + +This module provides utilities for file operations, including case-insensitive +file lookups which are important for cross-platform compatibility. +""" + +import os +from typing import Optional + + +def find_file_case_insensitive(directory: str, filename: str) -> Optional[str]: + """ + Find a file in a directory with case-insensitive matching. + + Args: + directory: The directory to search in + filename: The filename to look for (case-insensitive) + + Returns: + The actual filename with correct case if found, None otherwise + """ + if not os.path.isdir(directory): + return None + + filename_lower = filename.lower() + + for entry in os.listdir(directory): + if entry.lower() == filename_lower: + entry_path = os.path.join(directory, entry) + if os.path.isfile(entry_path): + return entry_path + + return None diff --git a/src/utils/text_renderer.py b/src/utils/text_renderer.py index 0998b8e..21a8fbf 100644 --- a/src/utils/text_renderer.py +++ b/src/utils/text_renderer.py @@ -5,7 +5,7 @@ class TextRenderer: def __init__(self, skin_data): self.skin_data = skin_data - self.text_bmp_path = os.path.join(self.skin_data.extracted_skin_dir, "TEXT.BMP") + self.text_bmp_path = self.skin_data.get_path("TEXT.BMP") self.text_bmp_image = None self.glyph_cache = {} # Cache for individual glyph QPixmaps @@ -122,7 +122,7 @@ def _load_text_bmp(self): """ Loads TEXT.BMP and converts it to an RGBA PIL Image. """ - if not os.path.exists(self.text_bmp_path): + if not self.text_bmp_path or not os.path.exists(self.text_bmp_path): print(f"WARNING: TEXT.BMP not found at {self.text_bmp_path}") return