Improve soundpack manager UX and fix accessibility issues

- Fix confusing "Available Soundpacks" tab that only showed repository downloads
- Now shows ALL usable soundpacks: bundled, installed, and downloadable
- Add clear status indicators: (Bundled), (Built-in), (Installed), (Available for download)
- Enable keyboard text navigation in soundpack description boxes
- Add comprehensive debug logging for troubleshooting soundpack discovery
- Update CLAUDE.md with critical note about print() statements in Qt apps

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Storm Dragon
2025-08-17 02:29:02 -04:00
parent f0f761a118
commit aa65a07345
3 changed files with 132 additions and 20 deletions

View File

@@ -21,6 +21,9 @@ class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
import logging
self.logger = logging.getLogger('bifrost.settings_dialog')
self.logger.debug("SettingsDialog.__init__() called")
self.settings = SettingsManager()
self.setup_ui()
self.load_current_settings()
@@ -222,37 +225,69 @@ class SettingsDialog(QDialog):
def load_sound_packs(self):
"""Load available sound packs from the sounds directory"""
self.logger.debug("load_sound_packs() called")
self.sound_pack_combo.clear()
# Add default "None" option
self.sound_pack_combo.addItem("None (No sounds)", "none")
self.logger.debug("Added None option")
# Look for sound pack directories
sounds_dir = "sounds"
if os.path.exists(sounds_dir):
for item in os.listdir(sounds_dir):
pack_dir = os.path.join(sounds_dir, item)
if os.path.isdir(pack_dir):
pack_json = os.path.join(pack_dir, "pack.json")
if os.path.exists(pack_json):
# Valid sound pack
self.sound_pack_combo.addItem(item, item)
# Look for bundled sound pack directories
# Find sounds directory relative to the source code
import sys
from pathlib import Path
# Try to find bundled sounds directory
if hasattr(sys, '_MEIPASS'):
# Running as PyInstaller bundle
bundled_sounds_dir = Path(sys._MEIPASS) / "sounds"
else:
# Running from source - go up from src/widgets/ to project root
bundled_sounds_dir = Path(__file__).parent.parent.parent / "sounds"
self.logger.debug(f"bundled_sounds_dir = {bundled_sounds_dir}")
self.logger.debug(f"bundled_sounds_dir.exists() = {bundled_sounds_dir.exists()}")
if bundled_sounds_dir.exists():
self.logger.debug(f"bundled dir contents = {list(bundled_sounds_dir.iterdir())}")
for item in bundled_sounds_dir.iterdir():
if item.is_dir():
pack_json = item / "pack.json"
if pack_json.exists():
# Valid bundled sound pack
self.logger.debug(f"Adding bundled pack: {item.name}")
self.sound_pack_combo.addItem(f"{item.name} (Bundled)", item.name)
# Also check in XDG data directory
try:
data_sounds_dir = self.settings.get_sounds_dir()
if os.path.exists(data_sounds_dir) and data_sounds_dir != sounds_dir:
self.logger.debug(f"data_sounds_dir = {data_sounds_dir}")
self.logger.debug(f"data_sounds_dir exists = {os.path.exists(data_sounds_dir)}")
if os.path.exists(data_sounds_dir):
self.logger.debug(f"data_sounds_dir contents = {os.listdir(data_sounds_dir)}")
for item in os.listdir(data_sounds_dir):
pack_dir = os.path.join(data_sounds_dir, item)
if os.path.isdir(pack_dir):
pack_json = os.path.join(pack_dir, "pack.json")
if os.path.exists(pack_json):
# Avoid duplicates
if self.sound_pack_combo.findData(item) == -1:
existing_index = self.sound_pack_combo.findData(item)
self.logger.debug(f"Found system pack '{item}', existing_index = {existing_index}")
if existing_index == -1:
self.logger.debug(f"Adding system pack: {item}")
self.sound_pack_combo.addItem(f"{item} (System)", item)
except Exception:
else:
self.logger.debug(f"Skipping duplicate pack: {item}")
except Exception as e:
self.logger.debug(f"Exception in system pack detection: {e}")
pass # Ignore errors in system sound pack detection
# Show final combo box contents
self.logger.debug("Final combo box contents:")
for i in range(self.sound_pack_combo.count()):
text = self.sound_pack_combo.itemText(i)
data = self.sound_pack_combo.itemData(i)
self.logger.debug(f" {i}: '{text}' -> '{data}'")
def load_current_settings(self):
"""Load current settings into the dialog"""
# Audio settings

View File

@@ -109,6 +109,13 @@ class SoundpackManagerDialog(QDialog):
header_label.setAccessibleName("Available Soundpacks")
layout.addWidget(header_label)
# Info note
info_label = QLabel("All soundpacks you can use (bundled, installed, and downloadable from repositories)")
info_label.setAccessibleName("Soundpack Information")
info_label.setWordWrap(True)
info_label.setStyleSheet("font-style: italic; color: #666; margin-bottom: 10px;")
layout.addWidget(info_label)
# Refresh button
refresh_layout = QHBoxLayout()
self.refresh_button = QPushButton("&Refresh Soundpack List")
@@ -141,6 +148,8 @@ class SoundpackManagerDialog(QDialog):
self.description_text.setAccessibleName("Soundpack description")
self.description_text.setReadOnly(True)
self.description_text.setMaximumHeight(150)
# Enable keyboard navigation for screen readers
self.description_text.setTextInteractionFlags(Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse)
details_layout.addWidget(self.description_text)
# Action buttons
@@ -307,7 +316,7 @@ class SoundpackManagerDialog(QDialog):
self.current_pack_label.setText(f"Current soundpack: {current_pack}")
def refresh_soundpacks(self):
"""Refresh available soundpacks from repositories"""
"""Refresh available soundpacks from repositories and local sources"""
if self.current_thread and self.current_thread.isRunning():
return
@@ -325,26 +334,93 @@ class SoundpackManagerDialog(QDialog):
self.refresh_button.setText("&Refresh Soundpack List")
if success:
self.soundpacks = self.manager.discover_soundpacks()
# Get repository soundpacks
repository_soundpacks = self.manager.discover_soundpacks()
# Add local/bundled soundpacks
self.soundpacks = self._get_all_available_soundpacks(repository_soundpacks)
self.update_soundpack_list()
else:
AccessibleTextDialog.show_error("Discovery Failed", "Failed to discover soundpacks", message, self)
def _get_all_available_soundpacks(self, repository_soundpacks):
"""Get all available soundpacks: bundled, installed, and from repositories"""
from pathlib import Path
import sys
import os
import json
all_soundpacks = []
repository_names = [sp.name for sp in repository_soundpacks]
# Add repository soundpacks first
all_soundpacks.extend(repository_soundpacks)
# Find bundled soundpacks
if hasattr(sys, '_MEIPASS'):
bundled_sounds_dir = Path(sys._MEIPASS) / "sounds"
else:
bundled_sounds_dir = Path(__file__).parent.parent.parent / "sounds"
if bundled_sounds_dir.exists():
for item in bundled_sounds_dir.iterdir():
if item.is_dir():
pack_json = item / "pack.json"
if pack_json.exists() and item.name not in repository_names:
try:
with open(pack_json) as f:
pack_data = json.load(f)
# Create SoundpackInfo for bundled pack
bundled_pack = SoundpackInfo(
name=item.name,
description=f"{pack_data.get('description', 'Bundled soundpack')} (Bundled)",
repository_url="Bundled with application",
download_url="",
installed=True, # Bundled packs are always "available"
version=pack_data.get('version', '1.0')
)
all_soundpacks.append(bundled_pack)
except (json.JSONDecodeError, FileNotFoundError):
pass
# Add None option as a special soundpack
none_pack = SoundpackInfo(
name="none",
description="No sounds (silence)",
repository_url="Built-in option",
download_url="",
installed=True,
version="1.0"
)
all_soundpacks.insert(0, none_pack) # Put None at the top
return all_soundpacks
def update_soundpack_list(self):
"""Update the soundpack list display"""
self.soundpack_list.clear()
# Add header item to help Orca read single-item lists
header_item = QListWidgetItem(f"Available Soundpacks ({len(self.soundpacks)} found):")
header_item = QListWidgetItem(f"Available Soundpacks ({len(self.soundpacks)} total):")
header_item.setFlags(header_item.flags() & ~Qt.ItemIsSelectable) # Make it non-selectable
header_item.setData(Qt.AccessibleDescriptionRole, "Available soundpacks list header")
self.soundpack_list.addItem(header_item)
for soundpack in self.soundpacks:
item_text = soundpack.name
if soundpack.installed:
item_text += " (Installed)"
item_text += f" - {soundpack.description}"
# Add status indicators
if soundpack.repository_url == "Bundled with application":
status = " (Bundled)"
elif soundpack.repository_url == "Built-in option":
status = " (Built-in)"
elif soundpack.installed:
status = " (Installed)"
else:
status = " (Available for download)"
item_text += status + f" - {soundpack.description}"
item = QListWidgetItem(item_text)
item.setData(Qt.UserRole, soundpack)
@@ -373,7 +449,7 @@ class SoundpackManagerDialog(QDialog):
# Update details
status = "Installed" if soundpack.installed else "Not installed"
self.details_label.setText(f"Name: {soundpack.name}\nStatus: {status}\nRepository: {soundpack.repository_url}")
self.description_text.setText(soundpack.description)
self.description_text.setText(f"Sound pack description:\n{soundpack.description}")
# Update buttons
self.install_button.setEnabled(not soundpack.installed)