- 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>
295 lines
13 KiB
Python
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() |