Add unified font control system with auto-regeneration
- Add open file button for direct audio file selection - Implement clustered FontControlWidget with font selector, size slider, and refresh controls - Add auto-regeneration of matplotlib plots when fonts change - Enhance FontManager with system font discovery and startup selection logic - Replace individual font selector with comprehensive font control interface - Support both custom fonts and system font candidates with proper prioritization 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
"""
|
||||
Unified font control widget clustering all font-related manipulators.
|
||||
Self-contained widget for easy layout management.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Optional
|
||||
from PyQt5.QtWidgets import (QWidget, QComboBox, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QSlider, QPushButton, QGroupBox)
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
|
||||
from font_manager import get_font_manager
|
||||
|
||||
|
||||
class FontControlWidget(QWidget):
|
||||
"""
|
||||
Unified font control widget containing all font-related manipulators.
|
||||
|
||||
Provides clustered interface for:
|
||||
- Font selection (unified for Qt and matplotlib)
|
||||
- Qt font size adjustment
|
||||
- Plot regeneration controls
|
||||
"""
|
||||
|
||||
# Signals
|
||||
fontChanged = pyqtSignal(str, str) # (font_name, font_type)
|
||||
fontSizeChanged = pyqtSignal(int) # font_size
|
||||
plotRefreshRequested = pyqtSignal() # manual refresh request
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize the font control widget."""
|
||||
super().__init__(parent)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.font_manager = get_font_manager()
|
||||
self.available_fonts: Dict[str, str] = {} # display_name -> actual_font_name
|
||||
|
||||
# Font size bounds (reasonable range for GUI fonts)
|
||||
self.min_font_size = 8
|
||||
self.max_font_size = 12
|
||||
self.default_font_size = 10
|
||||
|
||||
self.initUI()
|
||||
self.refresh_font_list()
|
||||
|
||||
def initUI(self):
|
||||
"""Initialize the user interface."""
|
||||
# Main layout
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(5, 5, 5, 5) # Minimal margins
|
||||
|
||||
# Group box for visual clustering
|
||||
group_box = QGroupBox("Font Settings")
|
||||
group_layout = QVBoxLayout(group_box)
|
||||
|
||||
# Font selector section
|
||||
font_section = self._create_font_selector_section()
|
||||
group_layout.addWidget(font_section)
|
||||
|
||||
# Font size section
|
||||
size_section = self._create_font_size_section()
|
||||
group_layout.addWidget(size_section)
|
||||
|
||||
# Control buttons section
|
||||
button_section = self._create_button_section()
|
||||
group_layout.addWidget(button_section)
|
||||
|
||||
layout.addWidget(group_box)
|
||||
|
||||
def _create_font_selector_section(self) -> QWidget:
|
||||
"""Create the font selector section."""
|
||||
section = QWidget()
|
||||
layout = QVBoxLayout(section)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Font label and dropdown
|
||||
font_label = QLabel("Font:")
|
||||
layout.addWidget(font_label)
|
||||
|
||||
self.font_combo = QComboBox()
|
||||
self.font_combo.currentTextChanged.connect(self.on_font_changed)
|
||||
layout.addWidget(self.font_combo)
|
||||
|
||||
return section
|
||||
|
||||
def _create_font_size_section(self) -> QWidget:
|
||||
"""Create the font size control section."""
|
||||
section = QWidget()
|
||||
layout = QVBoxLayout(section)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Size label
|
||||
self.size_label = QLabel(f"Qt Font Size: {self.default_font_size}pt")
|
||||
layout.addWidget(self.size_label)
|
||||
|
||||
# Size slider with value labels
|
||||
slider_layout = QHBoxLayout()
|
||||
|
||||
# Min label
|
||||
min_label = QLabel(str(self.min_font_size))
|
||||
min_label.setFixedWidth(20)
|
||||
slider_layout.addWidget(min_label)
|
||||
|
||||
# Slider
|
||||
self.size_slider = QSlider(Qt.Horizontal)
|
||||
self.size_slider.setMinimum(self.min_font_size)
|
||||
self.size_slider.setMaximum(self.max_font_size)
|
||||
self.size_slider.setValue(self.default_font_size)
|
||||
self.size_slider.setTickPosition(QSlider.TicksBelow)
|
||||
self.size_slider.setTickInterval(2)
|
||||
self.size_slider.valueChanged.connect(self.on_font_size_changed)
|
||||
slider_layout.addWidget(self.size_slider)
|
||||
|
||||
# Max label
|
||||
max_label = QLabel(str(self.max_font_size))
|
||||
max_label.setFixedWidth(20)
|
||||
slider_layout.addWidget(max_label)
|
||||
|
||||
layout.addLayout(slider_layout)
|
||||
|
||||
return section
|
||||
|
||||
def _create_button_section(self) -> QWidget:
|
||||
"""Create the control buttons section."""
|
||||
section = QWidget()
|
||||
layout = QHBoxLayout(section)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Refresh plot button
|
||||
self.refresh_button = QPushButton("Refresh Plot")
|
||||
self.refresh_button.setToolTip("Regenerate current plot with new font settings")
|
||||
self.refresh_button.clicked.connect(self.on_refresh_plot_clicked)
|
||||
layout.addWidget(self.refresh_button)
|
||||
|
||||
return section
|
||||
|
||||
def refresh_font_list(self):
|
||||
"""Refresh the list of available fonts."""
|
||||
self.logger.debug("Refreshing font list...")
|
||||
self.available_fonts.clear()
|
||||
|
||||
try:
|
||||
# Get custom fonts from font manager
|
||||
custom_fonts = self.font_manager.loaded_fonts
|
||||
|
||||
# Get system font candidates
|
||||
system_fonts = self.font_manager.get_available_system_fonts()[:10]
|
||||
|
||||
# Clear combo box
|
||||
self.font_combo.clear()
|
||||
|
||||
# Add custom fonts first (highest priority)
|
||||
if custom_fonts:
|
||||
for font_family, font_path in custom_fonts.items():
|
||||
display_name = f"{font_family} (Custom)"
|
||||
self.available_fonts[display_name] = font_family
|
||||
self.font_combo.addItem(display_name)
|
||||
self.logger.debug(f"Added custom font: {display_name}")
|
||||
|
||||
# Add system fonts
|
||||
for font_name in system_fonts:
|
||||
display_name = f"{font_name} (System)"
|
||||
self.available_fonts[display_name] = font_name
|
||||
self.font_combo.addItem(display_name)
|
||||
self.logger.debug(f"Added system font: {display_name}")
|
||||
|
||||
# Add default option with actual system font name
|
||||
system_font_name = self.font_manager.get_default_system_font_name()
|
||||
default_name = f"Default ({system_font_name})"
|
||||
self.available_fonts[default_name] = "default"
|
||||
self.font_combo.addItem(default_name)
|
||||
|
||||
# Select startup font
|
||||
self._select_startup_font()
|
||||
|
||||
self.logger.info(f"Font list refreshed: {len(self.available_fonts)} fonts available")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error refreshing font list: {e}")
|
||||
# Fallback: add default option only
|
||||
self.font_combo.clear()
|
||||
self.font_combo.addItem("Default (System)")
|
||||
self.available_fonts = {"Default (System)": "default"}
|
||||
|
||||
def _select_startup_font(self):
|
||||
"""Select the appropriate font on startup."""
|
||||
# Use font manager's startup font selection logic
|
||||
startup_font_name, startup_font_type = self.font_manager.select_startup_font()
|
||||
|
||||
# Find the corresponding display name in our combo box
|
||||
target_display_name = None
|
||||
for display_name, actual_name in self.available_fonts.items():
|
||||
if actual_name == startup_font_name:
|
||||
target_display_name = display_name
|
||||
break
|
||||
|
||||
# If we found the font, select it
|
||||
if target_display_name:
|
||||
index = self.font_combo.findText(target_display_name)
|
||||
if index >= 0:
|
||||
self.font_combo.setCurrentIndex(index)
|
||||
self.logger.info(f"Startup font selected: {target_display_name}")
|
||||
return
|
||||
|
||||
# Fallback to default if we couldn't find the startup font
|
||||
for i in range(self.font_combo.count()):
|
||||
item_text = self.font_combo.itemText(i)
|
||||
if item_text.startswith("Default ("):
|
||||
self.font_combo.setCurrentIndex(i)
|
||||
self.logger.info(f"Startup font fallback: {item_text}")
|
||||
return
|
||||
|
||||
def on_font_changed(self, display_name: str):
|
||||
"""Handle font selection change."""
|
||||
if not display_name or display_name not in self.available_fonts:
|
||||
return
|
||||
|
||||
actual_font_name = self.available_fonts[display_name]
|
||||
|
||||
# Determine font type
|
||||
if "(Custom)" in display_name:
|
||||
font_type = "custom"
|
||||
elif "(System)" in display_name:
|
||||
font_type = "system"
|
||||
else:
|
||||
font_type = "default"
|
||||
|
||||
self.logger.info(f"Font changed: {display_name} -> {actual_font_name} ({font_type})")
|
||||
|
||||
# Apply the font change
|
||||
self._apply_font_change(actual_font_name, font_type)
|
||||
|
||||
# Emit signal for external listeners (including auto-regeneration)
|
||||
self.fontChanged.emit(actual_font_name, font_type)
|
||||
|
||||
def on_font_size_changed(self, size: int):
|
||||
"""Handle font size change."""
|
||||
self.size_label.setText(f"Qt Font Size: {size}pt")
|
||||
self.logger.info(f"Qt font size changed: {size}pt")
|
||||
|
||||
# Apply Qt font size change
|
||||
self._apply_font_size_change(size)
|
||||
|
||||
# Emit signal for external listeners
|
||||
self.fontSizeChanged.emit(size)
|
||||
|
||||
def on_refresh_plot_clicked(self):
|
||||
"""Handle manual plot refresh button click."""
|
||||
self.logger.info("Manual plot refresh requested")
|
||||
self.plotRefreshRequested.emit()
|
||||
|
||||
def _apply_font_change(self, font_name: str, font_type: str):
|
||||
"""Apply the font change to both Qt and matplotlib."""
|
||||
try:
|
||||
# Use font manager's centralized font application
|
||||
success = self.font_manager.apply_font_selection(font_name, font_type)
|
||||
if not success:
|
||||
self.logger.warning(f"Font application may have failed: {font_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error applying font change: {e}")
|
||||
|
||||
def _apply_font_size_change(self, size: int):
|
||||
"""Apply Qt font size change."""
|
||||
try:
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
app = QCoreApplication.instance()
|
||||
if app:
|
||||
current_font = app.font()
|
||||
current_font.setPointSize(size)
|
||||
app.setFont(current_font)
|
||||
self.logger.debug(f"Qt font size set to: {size}pt")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error applying font size change: {e}")
|
||||
|
||||
def get_current_font(self) -> tuple[str, str]:
|
||||
"""
|
||||
Get currently selected font.
|
||||
|
||||
Returns:
|
||||
tuple: (font_name, font_type)
|
||||
"""
|
||||
display_name = self.font_combo.currentText()
|
||||
if display_name in self.available_fonts:
|
||||
actual_font_name = self.available_fonts[display_name]
|
||||
|
||||
if "(Custom)" in display_name:
|
||||
font_type = "custom"
|
||||
elif "(System)" in display_name:
|
||||
font_type = "system"
|
||||
else:
|
||||
font_type = "default"
|
||||
|
||||
return actual_font_name, font_type
|
||||
|
||||
return "default", "default"
|
||||
|
||||
def get_current_font_size(self) -> int:
|
||||
"""
|
||||
Get current Qt font size.
|
||||
|
||||
Returns:
|
||||
int: Current font size in points
|
||||
"""
|
||||
return self.size_slider.value()
|
||||
|
||||
def set_font(self, font_name: str):
|
||||
"""
|
||||
Programmatically set the font selection.
|
||||
|
||||
Args:
|
||||
font_name: Name of font to select
|
||||
"""
|
||||
# Find matching display name
|
||||
for display_name, actual_name in self.available_fonts.items():
|
||||
if actual_name == font_name:
|
||||
index = self.font_combo.findText(display_name)
|
||||
if index >= 0:
|
||||
self.font_combo.setCurrentIndex(index)
|
||||
return
|
||||
|
||||
self.logger.warning(f"Font not found in selector: {font_name}")
|
||||
|
||||
def set_font_size(self, size: int):
|
||||
"""
|
||||
Programmatically set the font size.
|
||||
|
||||
Args:
|
||||
size: Font size in points (will be clamped to valid range)
|
||||
"""
|
||||
clamped_size = max(self.min_font_size, min(self.max_font_size, size))
|
||||
self.size_slider.setValue(clamped_size)
|
||||
if clamped_size != size:
|
||||
self.logger.warning(f"Font size clamped: {size} -> {clamped_size}")
|
||||
+146
@@ -127,7 +127,15 @@ class FontManager:
|
||||
|
||||
if fonts_loaded > 0:
|
||||
# Clear matplotlib's font cache to recognize new fonts
|
||||
try:
|
||||
# Try the common method for refreshing font cache
|
||||
if hasattr(fm.fontManager, '_load_fontmanager'):
|
||||
fm.fontManager._load_fontmanager(try_read_cache=False)
|
||||
else:
|
||||
# For newer matplotlib versions, just reinitialize
|
||||
fm.fontManager.__init__()
|
||||
except Exception as font_cache_error:
|
||||
self.logger.debug(f"Font cache refresh failed (non-critical): {font_cache_error}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading custom fonts: {e}")
|
||||
@@ -296,6 +304,138 @@ Recommended fonts: {', '.join(self.system_font_fallbacks.get(platform.system(),
|
||||
"""
|
||||
return instructions
|
||||
|
||||
def get_available_system_fonts(self) -> List[str]:
|
||||
"""
|
||||
Get list of available system fonts that are good candidates for selection.
|
||||
|
||||
Returns:
|
||||
List[str]: List of available system font names
|
||||
"""
|
||||
current_system = platform.system()
|
||||
fallback_fonts = self.system_font_fallbacks.get(current_system,
|
||||
self.system_font_fallbacks['Linux'])
|
||||
|
||||
# Combine all font categories
|
||||
preferred_fonts = []
|
||||
for category in ['sans-serif', 'serif', 'monospace']:
|
||||
preferred_fonts.extend(fallback_fonts.get(category, []))
|
||||
|
||||
# Get actually available fonts on the system
|
||||
available_fonts = set(fm.get_font_names())
|
||||
|
||||
# Filter to only fonts that are actually available
|
||||
system_candidates = []
|
||||
for font_name in preferred_fonts:
|
||||
if font_name in available_fonts:
|
||||
system_candidates.append(font_name)
|
||||
|
||||
# Remove duplicates while preserving order
|
||||
seen = set()
|
||||
unique_candidates = []
|
||||
for font in system_candidates:
|
||||
if font not in seen:
|
||||
seen.add(font)
|
||||
unique_candidates.append(font)
|
||||
|
||||
return unique_candidates
|
||||
|
||||
def get_default_system_font_name(self) -> str:
|
||||
"""
|
||||
Get the name of the default system font for display purposes.
|
||||
|
||||
Returns:
|
||||
str: Human-readable name of the default system font
|
||||
"""
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
# Try to get the actual default font name from Qt
|
||||
app = QCoreApplication.instance()
|
||||
if app:
|
||||
default_font = QFont()
|
||||
return default_font.family()
|
||||
|
||||
# Fallback to platform-specific defaults
|
||||
current_system = platform.system()
|
||||
if current_system == 'Windows':
|
||||
return 'Segoe UI'
|
||||
elif current_system == 'Darwin':
|
||||
return 'SF Pro Display'
|
||||
else:
|
||||
return 'DejaVu Sans'
|
||||
|
||||
def select_startup_font(self) -> tuple[str, str]:
|
||||
"""
|
||||
Select the appropriate font for application startup.
|
||||
Priority: First custom font alphabetically, then first system font, then default.
|
||||
|
||||
Returns:
|
||||
tuple: (font_name, font_type) where font_type is 'custom', 'system', or 'default'
|
||||
"""
|
||||
# Check for custom fonts first
|
||||
if self.loaded_fonts:
|
||||
custom_font_names = sorted(self.loaded_fonts.keys())
|
||||
selected_font = custom_font_names[0]
|
||||
self.logger.info(f"Startup font selected (custom): {selected_font}")
|
||||
return selected_font, 'custom'
|
||||
|
||||
# Check for system fonts
|
||||
system_fonts = self.get_available_system_fonts()
|
||||
if system_fonts:
|
||||
selected_font = system_fonts[0]
|
||||
self.logger.info(f"Startup font selected (system): {selected_font}")
|
||||
return selected_font, 'system'
|
||||
|
||||
# Default fallback
|
||||
default_font = self.get_default_system_font_name()
|
||||
self.logger.info(f"Startup font selected (default): {default_font}")
|
||||
return default_font, 'default'
|
||||
|
||||
def apply_font_selection(self, font_name: str, font_type: str) -> bool:
|
||||
"""
|
||||
Apply a font selection to both matplotlib and Qt.
|
||||
|
||||
Args:
|
||||
font_name: Name of the font to apply
|
||||
font_type: Type of font ('custom', 'system', or 'default')
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
"""
|
||||
try:
|
||||
if font_type == 'default':
|
||||
# Reset to default configuration
|
||||
self._configure_matplotlib()
|
||||
self._configure_qt()
|
||||
else:
|
||||
# Apply specific font
|
||||
self._set_specific_font(font_name)
|
||||
|
||||
self.logger.info(f"Font applied successfully: {font_name} ({font_type})")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error applying font selection: {e}")
|
||||
return False
|
||||
|
||||
def _set_specific_font(self, font_name: str):
|
||||
"""Set a specific font for both matplotlib and Qt."""
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
from PyQt5.QtGui import QFont
|
||||
|
||||
# Update Qt font
|
||||
app = QCoreApplication.instance()
|
||||
if app:
|
||||
font = QFont(font_name)
|
||||
app.setFont(font)
|
||||
|
||||
# Update matplotlib font (insert at front of font list)
|
||||
current_fonts = plt.rcParams['font.sans-serif'].copy()
|
||||
if font_name in current_fonts:
|
||||
current_fonts.remove(font_name)
|
||||
current_fonts.insert(0, font_name)
|
||||
plt.rcParams['font.sans-serif'] = current_fonts
|
||||
|
||||
def get_status_report(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a status report of the font system.
|
||||
@@ -303,11 +443,17 @@ Recommended fonts: {', '.join(self.system_font_fallbacks.get(platform.system(),
|
||||
Returns:
|
||||
dict: Status information
|
||||
"""
|
||||
startup_font, startup_type = self.select_startup_font()
|
||||
|
||||
return {
|
||||
'matplotlib_configured': self._matplotlib_configured,
|
||||
'qt_configured': self._qt_configured,
|
||||
'custom_fonts_loaded': len(self.loaded_fonts),
|
||||
'custom_font_families': list(self.loaded_fonts.keys()),
|
||||
'available_system_fonts': self.get_available_system_fonts(),
|
||||
'default_system_font': self.get_default_system_font_name(),
|
||||
'startup_font': startup_font,
|
||||
'startup_font_type': startup_type,
|
||||
'fonts_directory_exists': self.fonts_dir.exists(),
|
||||
'fonts_directory_path': str(self.fonts_dir.absolute()),
|
||||
'current_matplotlib_fonts': plt.rcParams.get('font.sans-serif', [])[:5],
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
Font selector widget providing GUI interface to font management system.
|
||||
Self-contained widget that can be placed anywhere in the layout.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Optional
|
||||
from PyQt5.QtWidgets import QWidget, QComboBox, QVBoxLayout, QLabel
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
from font_manager import get_font_manager
|
||||
|
||||
|
||||
class FontSelectorWidget(QWidget):
|
||||
"""
|
||||
Self-contained font selector widget.
|
||||
|
||||
Provides a dropdown interface to select fonts from available
|
||||
custom fonts and system fonts. Integrates with the FontManager
|
||||
backend for font discovery and application.
|
||||
"""
|
||||
|
||||
# Signal emitted when font selection changes
|
||||
fontChanged = pyqtSignal(str, str) # (font_name, font_type)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize the font selector widget."""
|
||||
super().__init__(parent)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.font_manager = get_font_manager()
|
||||
self.available_fonts: Dict[str, str] = {} # display_name -> actual_font_name
|
||||
|
||||
self.initUI()
|
||||
self.refresh_font_list()
|
||||
|
||||
def initUI(self):
|
||||
"""Initialize the user interface."""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0) # Minimal margins for embedding
|
||||
|
||||
# Label
|
||||
self.label = QLabel("Font:")
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Font selector dropdown
|
||||
self.font_combo = QComboBox()
|
||||
self.font_combo.currentTextChanged.connect(self.on_font_changed)
|
||||
layout.addWidget(self.font_combo)
|
||||
|
||||
def refresh_font_list(self):
|
||||
"""Refresh the list of available fonts."""
|
||||
self.logger.debug("Refreshing font list...")
|
||||
self.available_fonts.clear()
|
||||
|
||||
try:
|
||||
# Get custom fonts from font manager
|
||||
custom_fonts = self.font_manager.loaded_fonts
|
||||
|
||||
# Get system font candidates
|
||||
system_fonts = self._get_system_font_candidates()
|
||||
|
||||
# Clear combo box
|
||||
self.font_combo.clear()
|
||||
|
||||
# Add custom fonts first (highest priority)
|
||||
if custom_fonts:
|
||||
for font_family, font_path in custom_fonts.items():
|
||||
display_name = f"{font_family} (Custom)"
|
||||
self.available_fonts[display_name] = font_family
|
||||
self.font_combo.addItem(display_name)
|
||||
self.logger.debug(f"Added custom font: {display_name}")
|
||||
|
||||
# Add system fonts
|
||||
for font_name in system_fonts:
|
||||
display_name = f"{font_name} (System)"
|
||||
self.available_fonts[display_name] = font_name
|
||||
self.font_combo.addItem(display_name)
|
||||
self.logger.debug(f"Added system font: {display_name}")
|
||||
|
||||
# Add default option with actual system font name
|
||||
system_font_name = self.font_manager.get_default_system_font_name()
|
||||
default_name = f"Default ({system_font_name})"
|
||||
self.available_fonts[default_name] = "default"
|
||||
self.font_combo.addItem(default_name)
|
||||
|
||||
# Select startup font
|
||||
self._select_startup_font()
|
||||
|
||||
self.logger.info(f"Font list refreshed: {len(self.available_fonts)} fonts available")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error refreshing font list: {e}")
|
||||
# Fallback: add default option only
|
||||
self.font_combo.clear()
|
||||
self.font_combo.addItem("Default (System)")
|
||||
self.available_fonts = {"Default (System)": "default"}
|
||||
|
||||
def _get_system_font_candidates(self) -> List[str]:
|
||||
"""
|
||||
Get list of available system fonts that are good candidates.
|
||||
|
||||
Returns:
|
||||
List[str]: List of available system font names
|
||||
"""
|
||||
# Use font manager's enhanced system font discovery
|
||||
return self.font_manager.get_available_system_fonts()[:10] # Limit to reasonable number
|
||||
|
||||
def _select_startup_font(self):
|
||||
"""Select the appropriate font on startup."""
|
||||
# Use font manager's startup font selection logic
|
||||
startup_font_name, startup_font_type = self.font_manager.select_startup_font()
|
||||
|
||||
# Find the corresponding display name in our combo box
|
||||
target_display_name = None
|
||||
for display_name, actual_name in self.available_fonts.items():
|
||||
if actual_name == startup_font_name:
|
||||
target_display_name = display_name
|
||||
break
|
||||
|
||||
# If we found the font, select it
|
||||
if target_display_name:
|
||||
index = self.font_combo.findText(target_display_name)
|
||||
if index >= 0:
|
||||
self.font_combo.setCurrentIndex(index)
|
||||
self.logger.info(f"Startup font selected: {target_display_name}")
|
||||
return
|
||||
|
||||
# Fallback to default if we couldn't find the startup font
|
||||
# Find any item that starts with "Default ("
|
||||
for i in range(self.font_combo.count()):
|
||||
item_text = self.font_combo.itemText(i)
|
||||
if item_text.startswith("Default ("):
|
||||
self.font_combo.setCurrentIndex(i)
|
||||
self.logger.info(f"Startup font fallback: {item_text}")
|
||||
return
|
||||
|
||||
def on_font_changed(self, display_name: str):
|
||||
"""Handle font selection change."""
|
||||
if not display_name or display_name not in self.available_fonts:
|
||||
return
|
||||
|
||||
actual_font_name = self.available_fonts[display_name]
|
||||
|
||||
# Determine font type
|
||||
if "(Custom)" in display_name:
|
||||
font_type = "custom"
|
||||
elif "(System)" in display_name:
|
||||
font_type = "system"
|
||||
else:
|
||||
font_type = "default"
|
||||
|
||||
self.logger.info(f"Font changed: {display_name} -> {actual_font_name} ({font_type})")
|
||||
|
||||
# Apply the font change
|
||||
self._apply_font_change(actual_font_name, font_type)
|
||||
|
||||
# Emit signal for any external listeners
|
||||
self.fontChanged.emit(actual_font_name, font_type)
|
||||
|
||||
def _apply_font_change(self, font_name: str, font_type: str):
|
||||
"""Apply the font change to the application."""
|
||||
try:
|
||||
# Use font manager's centralized font application
|
||||
success = self.font_manager.apply_font_selection(font_name, font_type)
|
||||
if not success:
|
||||
self.logger.warning(f"Font application may have failed: {font_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error applying font change: {e}")
|
||||
|
||||
def get_current_font(self) -> tuple[str, str]:
|
||||
"""
|
||||
Get currently selected font.
|
||||
|
||||
Returns:
|
||||
tuple: (font_name, font_type)
|
||||
"""
|
||||
display_name = self.font_combo.currentText()
|
||||
if display_name in self.available_fonts:
|
||||
actual_font_name = self.available_fonts[display_name]
|
||||
|
||||
if "(Custom)" in display_name:
|
||||
font_type = "custom"
|
||||
elif "(System)" in display_name:
|
||||
font_type = "system"
|
||||
else:
|
||||
font_type = "default"
|
||||
|
||||
return actual_font_name, font_type
|
||||
|
||||
return "default", "default"
|
||||
|
||||
def set_font(self, font_name: str):
|
||||
"""
|
||||
Programmatically set the font selection.
|
||||
|
||||
Args:
|
||||
font_name: Name of font to select
|
||||
"""
|
||||
# Find matching display name
|
||||
for display_name, actual_name in self.available_fonts.items():
|
||||
if actual_name == font_name:
|
||||
index = self.font_combo.findText(display_name)
|
||||
if index >= 0:
|
||||
self.font_combo.setCurrentIndex(index)
|
||||
return
|
||||
|
||||
self.logger.warning(f"Font not found in selector: {font_name}")
|
||||
@@ -3,13 +3,14 @@ import os
|
||||
import logging
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QSplitter, QLabel, QListWidget,
|
||||
QTextEdit, QListWidgetItem)
|
||||
QTextEdit, QListWidgetItem, QPushButton, QFileDialog)
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
from audio_visualization_widget import AudioVisualizationWidget
|
||||
from analysis_results_manager import AnalysisResultsManager
|
||||
from logger_setup import setup_logging, parse_log_args
|
||||
from font_manager import initialize_fonts, get_font_manager
|
||||
from font_control_widget import FontControlWidget
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
@@ -53,6 +54,18 @@ class MainWindow(QMainWindow):
|
||||
panel = QWidget()
|
||||
layout = QVBoxLayout(panel)
|
||||
|
||||
# Open File button
|
||||
self.open_file_button = QPushButton("Open Audio File...")
|
||||
self.open_file_button.clicked.connect(self.open_file_dialog)
|
||||
layout.addWidget(self.open_file_button)
|
||||
|
||||
# Font control cluster
|
||||
self.font_control = FontControlWidget()
|
||||
self.font_control.fontChanged.connect(self.on_font_changed)
|
||||
self.font_control.fontSizeChanged.connect(self.on_font_size_changed)
|
||||
self.font_control.plotRefreshRequested.connect(self.on_plot_refresh_requested)
|
||||
layout.addWidget(self.font_control)
|
||||
|
||||
# File list
|
||||
self.file_list_label = QLabel("Analyzed Files:")
|
||||
layout.addWidget(self.file_list_label)
|
||||
@@ -116,6 +129,19 @@ class MainWindow(QMainWindow):
|
||||
self.visualization_widget.set_status("No audio files detected in drop")
|
||||
self.logger.warning("No supported audio files found in drop")
|
||||
|
||||
def open_file_dialog(self):
|
||||
"""Open file dialog to select audio files for analysis."""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Select Audio File",
|
||||
"", # Default directory (empty = current directory)
|
||||
"Audio Files (*.mp3 *.wav *.flac);;All Files (*)"
|
||||
)
|
||||
|
||||
if file_path: # User selected a file (didn't cancel)
|
||||
self.logger.info(f"File selected via dialog: {os.path.basename(file_path)}")
|
||||
self.analysis_manager.analyze_file(file_path)
|
||||
|
||||
def on_analysis_started(self, file_path):
|
||||
"""Called when analysis starts."""
|
||||
filename = os.path.basename(file_path)
|
||||
@@ -173,6 +199,45 @@ class MainWindow(QMainWindow):
|
||||
metadata_text = self.analysis_manager.get_metadata_text(file_path)
|
||||
self.metadata_display.setText(metadata_text)
|
||||
|
||||
def on_font_changed(self, font_name: str, font_type: str):
|
||||
"""Called when font selection changes."""
|
||||
self.logger.info(f"Font changed via GUI: {font_name} ({font_type})")
|
||||
# Auto-regenerate current plot with new font
|
||||
self._regenerate_current_plot()
|
||||
|
||||
def on_font_size_changed(self, font_size: int):
|
||||
"""Called when Qt font size changes."""
|
||||
self.logger.info(f"Qt font size changed via GUI: {font_size}pt")
|
||||
# Qt font size doesn't affect matplotlib plots, so no regeneration needed
|
||||
|
||||
def on_plot_refresh_requested(self):
|
||||
"""Called when manual plot refresh is requested."""
|
||||
self.logger.info("Manual plot refresh requested via GUI")
|
||||
self._regenerate_current_plot()
|
||||
|
||||
def _regenerate_current_plot(self):
|
||||
"""Regenerate the current plot with updated font settings."""
|
||||
try:
|
||||
# Get the currently selected file
|
||||
current_item = self.file_list.currentItem()
|
||||
if not current_item:
|
||||
self.logger.debug("No file selected for plot regeneration")
|
||||
return
|
||||
|
||||
file_path = current_item.data(Qt.UserRole)
|
||||
if not file_path:
|
||||
self.logger.debug("No file path found for current selection")
|
||||
return
|
||||
|
||||
self.logger.info(f"Regenerating plot for: {os.path.basename(file_path)}")
|
||||
|
||||
# Re-analyze the file to regenerate plots with new font
|
||||
self.analysis_manager.analyze_file(file_path)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error regenerating plot: {e}")
|
||||
self.visualization_widget.set_status(f"Error regenerating plot: {e}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Parse logging arguments before creating QApplication
|
||||
|
||||
Reference in New Issue
Block a user