Merge branch 'denizsafak:main' into main

This commit is contained in:
Juraj Borza
2025-07-08 13:02:47 +02:00
committed by GitHub
11 changed files with 334 additions and 42 deletions
+201 -10
View File
@@ -41,6 +41,7 @@ from PyQt5.QtCore import (
QIODevice,
QSize,
QTimer,
QEvent,
)
from PyQt5.QtGui import (
QTextCursor,
@@ -73,6 +74,7 @@ from constants import (
LANGUAGE_DESCRIPTIONS,
VOICES_INTERNAL,
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
COLORS
)
from threading import Thread
from voice_formula_gui import VoiceFormulaDialog
@@ -81,7 +83,22 @@ from voice_profiles import load_profiles
# Import ctypes for Windows-specific taskbar icon
if platform.system() == "Windows":
import ctypes
import winreg
class DarkTitleBarEventFilter(QObject):
def __init__(self, is_windows, get_dark_mode_func, set_title_bar_dark_mode_func):
super().__init__()
self.is_windows = is_windows
self.get_dark_mode = get_dark_mode_func
self.set_title_bar_dark_mode = set_title_bar_dark_mode_func
def eventFilter(self, obj, event):
if event.type() == QEvent.Show:
# Only apply to QWidget windows
if isinstance(obj, QWidget) and obj.isWindow():
if self.is_windows and self.get_dark_mode():
self.set_title_bar_dark_mode(obj, True)
return False
class ShowWarningSignalEmitter(QObject): # New class to handle signal emission
show_warning_signal = pyqtSignal(str, str)
@@ -517,11 +534,26 @@ class TextboxDialog(QDialog):
self.update_char_count()
self.text_edit.setFocus()
def migrate_subtitle_format(config):
"""Convert old subtitle_format values to new internal keys."""
old_to_new = {
"srt": "srt",
"ass (wide)": "ass_wide",
"ass (narrow)": "ass_narrow",
"ass (centered wide)": "ass_centered_wide",
"ass (centered narrow)": "ass_centered_narrow",
}
val = config.get("subtitle_format")
if val in old_to_new:
config["subtitle_format"] = old_to_new[val]
save_config(config)
class abogen(QWidget):
def __init__(self):
super().__init__()
self.config = load_config()
self.apply_theme(self.config.get("theme", "system"))
migrate_subtitle_format(self.config)
self.check_updates = self.config.get("check_updates", True)
self.save_option = self.config.get("save_option", "Save next to input file")
self.selected_output_folder = self.config.get("selected_output_folder", None)
@@ -811,7 +843,7 @@ class abogen(QWidget):
gpu_layout.addLayout(gpu_checkbox_layout)
# Settings button with icon
settings_icon_path = get_resource_path("abogen.assets", "settings.png")
settings_icon_path = get_resource_path("abogen.assets", "settings.svg")
self.settings_btn = QPushButton(self)
if settings_icon_path and os.path.exists(settings_icon_path):
self.settings_btn.setIcon(QIcon(settings_icon_path))
@@ -1470,7 +1502,7 @@ class abogen(QWidget):
# Pass separate_chapters_format setting
self.conversion_thread.separate_chapters_format = self.separate_chapters_format
# Pass subtitle format setting
self.conversion_thread.subtitle_format = self.config.get("subtitle_format", "srt")
self.conversion_thread.subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow")
# Pass chapter count for EPUB or PDF files
if self.selected_file_type in ["epub", "pdf"] and hasattr(
self, "selected_chapters"
@@ -2053,10 +2085,160 @@ class abogen(QWidget):
# If dialog is rejected, cancel the conversion
self.cancel_conversion()
def apply_theme(self, theme):
from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QStyleFactory
app = QApplication.instance()
is_windows = platform.system() == "Windows"
available_styles = [s.lower() for s in QStyleFactory.keys()]
def is_windows_dark_mode():
try:
import winreg
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"
) as key:
value, _ = winreg.QueryValueEx(key, "AppsUseLightTheme")
return value == 0
except Exception:
return False
# --- Theme selection logic ---
def set_dark_palette():
palette = QPalette()
dark_bg = QColor(COLORS["DARK_BG"])
base_bg = QColor(COLORS["DARK_BASE"])
alt_bg = QColor(COLORS["DARK_ALT"])
button_bg = QColor(COLORS["DARK_BUTTON"])
disabled_fg = QColor(COLORS["DARK_DISABLED"])
palette.setColor(QPalette.ColorRole.Window, dark_bg)
palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.Base, base_bg)
palette.setColor(QPalette.ColorRole.AlternateBase, alt_bg)
palette.setColor(QPalette.ColorRole.ToolTipBase, dark_bg)
palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.Button, button_bg)
palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white)
# Disabled roles
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, dark_bg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, dark_bg)
app.setPalette(palette)
def set_light_palette():
palette = QPalette()
disabled_fg = QColor(COLORS["LIGHT_DISABLED"])
palette.setColor(QPalette.ColorRole.Window, QColor(COLORS["LIGHT_BG"]))
palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.black)
palette.setColor(QPalette.ColorRole.Base, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.AlternateBase, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.ToolTipBase, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.black)
palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.black)
palette.setColor(QPalette.ColorRole.Button, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.black)
# Disabled roles
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_fg)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, Qt.GlobalColor.white)
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Button, Qt.GlobalColor.white)
app.setPalette(palette)
# --- Dark title bar support for Windows ---
def set_title_bar_dark_mode(window, enable):
if is_windows:
try:
window.update()
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
set_window_attribute = ctypes.windll.dwmapi.DwmSetWindowAttribute
hwnd = int(window.winId())
value = ctypes.c_int(2 if enable else 0)
set_window_attribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ctypes.byref(value), ctypes.sizeof(value))
except Exception:
pass
# Main logic
dark_mode = theme == "dark" or (theme == "system" and is_windows and is_windows_dark_mode())
if dark_mode:
app.setStyle("Fusion")
set_dark_palette()
elif (theme == "light" or theme == "system") and is_windows:
if "windowsvista" in available_styles:
app.setStyle("windowsvista")
else:
app.setStyle("Fusion")
app.setPalette(QPalette())
elif theme == "light":
app.setStyle("Fusion")
set_light_palette()
else:
app.setStyle("Fusion")
app.setPalette(QPalette())
# Always set the title bar mode according to the current theme for all top-level widgets
for widget in app.topLevelWidgets():
set_title_bar_dark_mode(widget, dark_mode)
# Refresh all top-level widgets
style_name = app.style().objectName()
app.setStyle(style_name)
for widget in app.topLevelWidgets():
app.style().polish(widget)
widget.update()
# Remove old event filter if present, then install a new one for dark title bar on new windows
if hasattr(app, "_dark_titlebar_event_filter"):
app.removeEventFilter(app._dark_titlebar_event_filter)
delattr(app, "_dark_titlebar_event_filter")
def get_dark_mode():
return theme == "dark" or (theme == "system" and is_windows and is_windows_dark_mode())
app._dark_titlebar_event_filter = DarkTitleBarEventFilter(
is_windows, get_dark_mode, set_title_bar_dark_mode
)
app.installEventFilter(app._dark_titlebar_event_filter)
# Save config if changed
if self.config.get("theme", "system") != theme:
self.config["theme"] = theme
save_config(self.config)
def show_settings_menu(self):
"""Show a dropdown menu for settings options."""
menu = QMenu(self)
theme_menu = QMenu("Theme", self)
theme_menu.setToolTip("Choose the application theme")
theme_group = QActionGroup(self)
theme_group.setExclusive(True)
# Theme options: (internal_value, display_text)
theme_options = [
("system", "System"),
("light", "Light"),
("dark", "Dark"),
]
# Get current theme from config, default to "system"
current_theme = self.config.get("theme", "system")
for value, text in theme_options:
theme_action = QAction(text, self)
theme_action.setCheckable(True)
theme_action.setChecked(current_theme == value)
theme_action.triggered.connect(lambda checked, v=value: self.apply_theme(v))
theme_group.addAction(theme_action)
theme_menu.addAction(theme_action)
menu.addMenu(theme_menu)
# Add replace single newlines option
newline_action = QAction("Replace single newlines with spaces", self)
newline_action.setCheckable(True)
@@ -2072,19 +2254,28 @@ class abogen(QWidget):
# Add subtitle format option
subtitle_format_menu = QMenu("Subtitle format", self)
subtitle_format_menu.setToolTip("Choose the format for generated subtitles")
subtitle_format_group = QActionGroup(self)
subtitle_format_group.setExclusive(True)
subtitle_format = self.config.get("subtitle_format", "srt")
for format_option in ["srt", "ass (wide)", "ass (narrow)", "ass (centered wide)", "ass (centered narrow)"]:
format_action = QAction(f"{format_option}", self)
# Define mapping: (internal_value, display_text)
subtitle_formats = [
("srt", "SRT (standard)"),
("ass_wide", "ASS (wide)"),
("ass_narrow", "ASS (narrow)"),
("ass_centered_wide", "ASS (centered wide)"),
("ass_centered_narrow", "ASS (centered narrow)"),
]
subtitle_format = self.config.get("subtitle_format", "ass_centered_narrow")
for value, text in subtitle_formats:
format_action = QAction(text, self)
format_action.setCheckable(True)
format_action.setChecked(subtitle_format == format_option)
format_action.triggered.connect(lambda checked, fmt=format_option: self.set_subtitle_format(fmt))
format_action.setChecked(subtitle_format == value)
format_action.triggered.connect(lambda checked, fmt=value: self.set_subtitle_format(fmt))
subtitle_format_group.addAction(format_action)
subtitle_format_menu.addAction(format_action)
menu.addMenu(subtitle_format_menu)
# Add separate chapters format option