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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user