Files
bifrost/src/widgets/settings_dialog.py
Storm Dragon ed7de5dafa Simplify sound system with pack-based approach
- Remove complex per-event sound enable/disable settings
- Replace dual volume controls with single "Sound Pack Volume"
- Add "None" sound pack option to disable all sounds
- Fix boolean settings parsing in settings dialog
- Update documentation to reflect simplified sound system

Sound system is now intuitive: select a sound pack (or "None")
and set one volume level. Much cleaner than the previous
per-event checkboxes and confusing dual volume controls.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-21 09:17:32 -04:00

295 lines
13 KiB
Python

"""
Settings dialog for Bifrost configuration
"""
import os
from PySide6.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout,
QLabel, QComboBox, QPushButton, QDialogButtonBox,
QGroupBox, QCheckBox, QSpinBox, QTabWidget, QWidget
)
from PySide6.QtCore import Qt, Signal
from config.settings import SettingsManager
from accessibility.accessible_combo import AccessibleComboBox
class SettingsDialog(QDialog):
"""Main settings dialog for Bifrost"""
settings_changed = Signal() # Emitted when settings are saved
def __init__(self, parent=None):
super().__init__(parent)
self.settings = SettingsManager()
self.setup_ui()
self.load_current_settings()
def setup_ui(self):
"""Initialize the settings dialog UI"""
self.setWindowTitle("Bifrost Settings")
self.setMinimumSize(500, 400)
self.setModal(True)
layout = QVBoxLayout(self)
# Create tab widget for organized settings
self.tabs = QTabWidget()
layout.addWidget(self.tabs)
# Audio settings tab
self.setup_audio_tab()
# Desktop notifications tab
self.setup_notifications_tab()
# Accessibility settings tab
self.setup_accessibility_tab()
# Button box
button_box = QDialogButtonBox(
QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply
)
button_box.accepted.connect(self.save_and_close)
button_box.rejected.connect(self.reject)
button_box.button(QDialogButtonBox.Apply).clicked.connect(self.apply_settings)
layout.addWidget(button_box)
def setup_audio_tab(self):
"""Set up the audio settings tab"""
audio_widget = QWidget()
layout = QVBoxLayout(audio_widget)
# Sound pack selection
sound_group = QGroupBox("Sound Pack")
sound_layout = QFormLayout(sound_group)
self.sound_pack_combo = AccessibleComboBox()
self.sound_pack_combo.setAccessibleName("Sound Pack Selection")
self.sound_pack_combo.setAccessibleDescription("Choose which sound pack to use for audio feedback")
# Populate sound packs
self.load_sound_packs()
sound_layout.addRow("Sound Pack:", self.sound_pack_combo)
layout.addWidget(sound_group)
# Audio volume settings
volume_group = QGroupBox("Volume Settings")
volume_layout = QFormLayout(volume_group)
self.sound_pack_volume = QSpinBox()
self.sound_pack_volume.setRange(0, 100)
self.sound_pack_volume.setSuffix("%")
self.sound_pack_volume.setAccessibleName("Sound Pack Volume")
self.sound_pack_volume.setAccessibleDescription("Volume for all sounds from the selected sound pack")
volume_layout.addRow("Sound Pack Volume:", self.sound_pack_volume)
layout.addWidget(volume_group)
# Note about sound pack selection
note_label = QLabel("To disable sounds, select 'None' as the sound pack above.")
note_label.setStyleSheet("font-style: italic; color: #666;")
layout.addWidget(note_label)
layout.addStretch()
self.tabs.addTab(audio_widget, "&Audio")
def setup_notifications_tab(self):
"""Set up the desktop notifications settings tab"""
notifications_widget = QWidget()
layout = QVBoxLayout(notifications_widget)
# Desktop notifications group
desktop_group = QGroupBox("Desktop Notifications")
desktop_layout = QVBoxLayout(desktop_group)
self.enable_desktop_notifications = QCheckBox("Enable desktop notifications")
self.enable_desktop_notifications.setAccessibleName("Enable Desktop Notifications")
self.enable_desktop_notifications.setAccessibleDescription("Show desktop notifications for various events")
desktop_layout.addWidget(self.enable_desktop_notifications)
# Notification types
types_group = QGroupBox("Notification Types")
types_layout = QVBoxLayout(types_group)
self.notify_direct_messages = QCheckBox("Direct/Private messages")
self.notify_direct_messages.setAccessibleName("Direct Message Notifications")
self.notify_direct_messages.setAccessibleDescription("Show notifications for direct messages")
types_layout.addWidget(self.notify_direct_messages)
self.notify_mentions = QCheckBox("Mentions")
self.notify_mentions.setAccessibleName("Mention Notifications")
self.notify_mentions.setAccessibleDescription("Show notifications when you are mentioned")
types_layout.addWidget(self.notify_mentions)
self.notify_boosts = QCheckBox("Boosts/Reblogs")
self.notify_boosts.setAccessibleName("Boost Notifications")
self.notify_boosts.setAccessibleDescription("Show notifications when your posts are boosted")
types_layout.addWidget(self.notify_boosts)
self.notify_favorites = QCheckBox("Favorites")
self.notify_favorites.setAccessibleName("Favorite Notifications")
self.notify_favorites.setAccessibleDescription("Show notifications when your posts are favorited")
types_layout.addWidget(self.notify_favorites)
self.notify_follows = QCheckBox("New followers")
self.notify_follows.setAccessibleName("Follow Notifications")
self.notify_follows.setAccessibleDescription("Show notifications for new followers")
types_layout.addWidget(self.notify_follows)
self.notify_timeline_updates = QCheckBox("Timeline updates")
self.notify_timeline_updates.setAccessibleName("Timeline Update Notifications")
self.notify_timeline_updates.setAccessibleDescription("Show notifications for new posts in timeline")
types_layout.addWidget(self.notify_timeline_updates)
layout.addWidget(desktop_group)
layout.addWidget(types_group)
layout.addStretch()
self.tabs.addTab(notifications_widget, "&Notifications")
def setup_accessibility_tab(self):
"""Set up the accessibility settings tab"""
accessibility_widget = QWidget()
layout = QVBoxLayout(accessibility_widget)
# Navigation settings
nav_group = QGroupBox("Navigation Settings")
nav_layout = QFormLayout(nav_group)
self.page_step_size = QSpinBox()
self.page_step_size.setRange(1, 20)
self.page_step_size.setAccessibleName("Page Step Size")
self.page_step_size.setAccessibleDescription("Number of posts to jump when using Page Up/Down")
nav_layout.addRow("Page Step Size:", self.page_step_size)
layout.addWidget(nav_group)
# Screen reader options
sr_group = QGroupBox("Screen Reader Options")
sr_layout = QVBoxLayout(sr_group)
self.verbose_announcements = QCheckBox("Verbose announcements")
self.verbose_announcements.setAccessibleName("Verbose Announcements")
self.verbose_announcements.setAccessibleDescription("Provide detailed descriptions for screen readers")
sr_layout.addWidget(self.verbose_announcements)
self.announce_thread_state = QCheckBox("Announce thread expand/collapse state")
self.announce_thread_state.setAccessibleName("Thread State Announcements")
self.announce_thread_state.setAccessibleDescription("Announce when threads are expanded or collapsed")
sr_layout.addWidget(self.announce_thread_state)
layout.addWidget(sr_group)
# Timeline settings
timeline_group = QGroupBox("Timeline Settings")
timeline_layout = QFormLayout(timeline_group)
self.posts_per_page = QSpinBox()
self.posts_per_page.setRange(10, 200)
self.posts_per_page.setAccessibleName("Posts Per Page")
self.posts_per_page.setAccessibleDescription("Number of posts to load at once in timeline")
timeline_layout.addRow("Posts per page:", self.posts_per_page)
layout.addWidget(timeline_group)
layout.addStretch()
self.tabs.addTab(accessibility_widget, "A&ccessibility")
def load_sound_packs(self):
"""Load available sound packs from the sounds directory"""
self.sound_pack_combo.clear()
# Add default "None" option
self.sound_pack_combo.addItem("None (No sounds)", "none")
# 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)
# 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:
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:
self.sound_pack_combo.addItem(f"{item} (System)", item)
except Exception:
pass # Ignore errors in system sound pack detection
def load_current_settings(self):
"""Load current settings into the dialog"""
# Audio settings
current_pack = self.settings.get('audio', 'sound_pack', 'none')
index = self.sound_pack_combo.findData(current_pack)
if index >= 0:
self.sound_pack_combo.setCurrentIndex(index)
self.sound_pack_volume.setValue(int(self.settings.get('audio', 'volume', 100) or 100))
# Desktop notification settings (defaults: DMs, mentions, follows ON; others OFF)
self.enable_desktop_notifications.setChecked(self.settings.get_bool('notifications', 'enabled', True))
self.notify_direct_messages.setChecked(self.settings.get_bool('notifications', 'direct_messages', True))
self.notify_mentions.setChecked(self.settings.get_bool('notifications', 'mentions', True))
self.notify_boosts.setChecked(self.settings.get_bool('notifications', 'boosts', False))
self.notify_favorites.setChecked(self.settings.get_bool('notifications', 'favorites', False))
self.notify_follows.setChecked(self.settings.get_bool('notifications', 'follows', True))
self.notify_timeline_updates.setChecked(self.settings.get_bool('notifications', 'timeline_updates', False))
# Accessibility settings
self.page_step_size.setValue(int(self.settings.get('accessibility', 'page_step_size', 5) or 5))
self.verbose_announcements.setChecked(self.settings.get_bool('accessibility', 'verbose_announcements', True))
self.announce_thread_state.setChecked(self.settings.get_bool('accessibility', 'announce_thread_state', True))
# Timeline settings
self.posts_per_page.setValue(int(self.settings.get('timeline', 'posts_per_page', 40) or 40))
def apply_settings(self):
"""Apply the current settings without closing the dialog"""
# Audio settings
selected_pack = self.sound_pack_combo.currentData()
self.settings.set('audio', 'sound_pack', selected_pack)
self.settings.set('audio', 'volume', self.sound_pack_volume.value())
# Desktop notification settings
self.settings.set('notifications', 'enabled', self.enable_desktop_notifications.isChecked())
self.settings.set('notifications', 'direct_messages', self.notify_direct_messages.isChecked())
self.settings.set('notifications', 'mentions', self.notify_mentions.isChecked())
self.settings.set('notifications', 'boosts', self.notify_boosts.isChecked())
self.settings.set('notifications', 'favorites', self.notify_favorites.isChecked())
self.settings.set('notifications', 'follows', self.notify_follows.isChecked())
self.settings.set('notifications', 'timeline_updates', self.notify_timeline_updates.isChecked())
# Accessibility settings
self.settings.set('accessibility', 'page_step_size', self.page_step_size.value())
self.settings.set('accessibility', 'verbose_announcements', self.verbose_announcements.isChecked())
self.settings.set('accessibility', 'announce_thread_state', self.announce_thread_state.isChecked())
# Timeline settings
self.settings.set('timeline', 'posts_per_page', self.posts_per_page.value())
# Save to file
self.settings.save_settings()
# Emit signal so other components can update
self.settings_changed.emit()
def save_and_close(self):
"""Save settings and close the dialog"""
self.apply_settings()
self.accept()