mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Add dark theme support, improvements
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# v1.1.0 (pre-release)
|
||||
- Added a feature that allows selecting multiple items in book handler (in right click menu) by @jborza in #31, that fixes #28
|
||||
- Added dark theme support, allowing users to switch between light and dark themes in the settings.
|
||||
|
||||
# v1.0.9
|
||||
- Added chunking/segmenting system that fixes memory outage issues when processing large audio files.
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 585 B |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg height="800px" width="800px" version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#808080;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M502.325,307.303l-39.006-30.805c-6.215-4.908-9.665-12.429-9.668-20.348c0-0.084,0-0.168,0-0.252
|
||||
c-0.014-7.936,3.44-15.478,9.667-20.396l39.007-30.806c8.933-7.055,12.093-19.185,7.737-29.701l-17.134-41.366
|
||||
c-4.356-10.516-15.167-16.86-26.472-15.532l-49.366,5.8c-7.881,0.926-15.656-1.966-21.258-7.586
|
||||
c-0.059-0.06-0.118-0.119-0.177-0.178c-5.597-5.602-8.476-13.36-7.552-21.225l5.799-49.363
|
||||
c1.328-11.305-5.015-22.116-15.531-26.472L337.004,1.939c-10.516-4.356-22.646-1.196-29.701,7.736l-30.805,39.005
|
||||
c-4.908,6.215-12.43,9.665-20.349,9.668c-0.084,0-0.168,0-0.252,0c-7.935,0.014-15.477-3.44-20.395-9.667L204.697,9.675
|
||||
c-7.055-8.933-19.185-12.092-29.702-7.736L133.63,19.072c-10.516,4.356-16.86,15.167-15.532,26.473l5.799,49.366
|
||||
c0.926,7.881-1.964,15.656-7.585,21.257c-0.059,0.059-0.118,0.118-0.178,0.178c-5.602,5.598-13.36,8.477-21.226,7.552
|
||||
l-49.363-5.799c-11.305-1.328-22.116,5.015-26.472,15.531L1.939,174.996c-4.356,10.516-1.196,22.646,7.736,29.701l39.006,30.805
|
||||
c6.215,4.908,9.665,12.429,9.668,20.348c0,0.084,0,0.167,0,0.251c0.014,7.935-3.44,15.477-9.667,20.395L9.675,307.303
|
||||
c-8.933,7.055-12.092,19.185-7.736,29.701l17.134,41.365c4.356,10.516,15.168,16.86,26.472,15.532l49.366-5.799
|
||||
c7.882-0.926,15.656,1.965,21.258,7.586c0.059,0.059,0.118,0.119,0.178,0.178c5.597,5.603,8.476,13.36,7.552,21.226l-5.799,49.364
|
||||
c-1.328,11.305,5.015,22.116,15.532,26.472l41.366,17.134c10.516,4.356,22.646,1.196,29.701-7.736l30.804-39.005
|
||||
c4.908-6.215,12.43-9.665,20.348-9.669c0.084,0,0.168,0,0.251,0c7.936-0.014,15.478,3.44,20.396,9.667l30.806,39.007
|
||||
c7.055,8.933,19.185,12.093,29.701,7.736l41.366-17.134c10.516-4.356,16.86-15.168,15.532-26.472l-5.8-49.366
|
||||
c-0.926-7.881,1.965-15.656,7.586-21.257c0.059-0.059,0.119-0.119,0.178-0.178c5.602-5.597,13.36-8.476,21.225-7.552l49.364,5.799
|
||||
c11.305,1.328,22.117-5.015,26.472-15.531l17.134-41.365C514.418,326.488,511.258,314.358,502.325,307.303z M281.292,329.698
|
||||
c-39.68,16.436-85.172-2.407-101.607-42.087c-16.436-39.68,2.407-85.171,42.087-101.608c39.68-16.436,85.172,2.407,101.608,42.088
|
||||
C339.815,267.771,320.972,313.262,281.292,329.698z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -161,6 +161,7 @@ class HandlerDialog(QDialog):
|
||||
self.treeWidget.expandAll()
|
||||
if self.treeWidget.topLevelItemCount() > 0:
|
||||
self.treeWidget.setCurrentItem(self.treeWidget.topLevelItem(0))
|
||||
self.treeWidget.setFocus()
|
||||
|
||||
# Update checkbox states
|
||||
self._update_checkbox_states()
|
||||
|
||||
@@ -102,3 +102,19 @@ SAMPLE_VOICE_TEXTS = {
|
||||
"p": "Este é um exemplo da voz selecionada.",
|
||||
"z": "这是所选语音的示例。",
|
||||
}
|
||||
|
||||
COLORS = {
|
||||
"BLUE": "#007dff",
|
||||
"RED": "#c0392b",
|
||||
"YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)",
|
||||
"GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)",
|
||||
"RED_BACKGROUND": "rgba(232, 78, 60, 0.15)",
|
||||
# Theme palette colors
|
||||
"DARK_BG": "#202326",
|
||||
"DARK_BASE": "#141618",
|
||||
"DARK_ALT": "#2c2f31",
|
||||
"DARK_BUTTON": "#292c30",
|
||||
"DARK_DISABLED": "#535353",
|
||||
"LIGHT_BG": "#eff0f1",
|
||||
"LIGHT_DISABLED": "#9a9999",
|
||||
}
|
||||
+169
-1
@@ -39,6 +39,7 @@ from PyQt5.QtCore import (
|
||||
QIODevice,
|
||||
QSize,
|
||||
QTimer,
|
||||
QEvent,
|
||||
)
|
||||
from PyQt5.QtGui import (
|
||||
QTextCursor,
|
||||
@@ -71,6 +72,7 @@ from constants import (
|
||||
LANGUAGE_DESCRIPTIONS,
|
||||
VOICES_INTERNAL,
|
||||
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
||||
COLORS
|
||||
)
|
||||
from threading import Thread
|
||||
from voice_formula_gui import VoiceFormulaDialog
|
||||
@@ -79,7 +81,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)
|
||||
@@ -521,6 +538,7 @@ 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")
|
||||
@@ -808,7 +826,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))
|
||||
@@ -1931,10 +1949,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)
|
||||
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
import platform
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtCore import qInstallMessageHandler, QtMsgType
|
||||
|
||||
# Add the directory to Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||
@@ -24,6 +25,25 @@ if sys.stderr is None:
|
||||
if platform.system() == "Darwin" and platform.processor() == "arm":
|
||||
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
||||
|
||||
# Custom message handler to filter out specific Qt warnings
|
||||
def qt_message_handler(mode, context, message):
|
||||
if "Wayland does not support QWindow::requestActivate()" in message:
|
||||
return # Suppress this specific message
|
||||
if "setGrabPopup called with a parent, QtWaylandClient" in message:
|
||||
return
|
||||
if mode == QtMsgType.QtWarningMsg:
|
||||
print(f"Qt Warning: {message}")
|
||||
elif mode == QtMsgType.QtCriticalMsg:
|
||||
print(f"Qt Critical: {message}")
|
||||
elif mode == QtMsgType.QtFatalMsg:
|
||||
print(f"Qt Fatal: {message}")
|
||||
elif mode == QtMsgType.QtInfoMsg:
|
||||
print(f"Qt Info: {message}")
|
||||
|
||||
|
||||
# Install the custom message handler
|
||||
qInstallMessageHandler(qt_message_handler)
|
||||
|
||||
# Set application ID for Windows taskbar icon
|
||||
if platform.system() == "Windows":
|
||||
import ctypes
|
||||
|
||||
+49
-21
@@ -24,14 +24,18 @@ from PyQt5.QtWidgets import (
|
||||
QMenu,
|
||||
QAction,
|
||||
QComboBox,
|
||||
QApplication,
|
||||
)
|
||||
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QSize
|
||||
from PyQt5.QtGui import QPixmap, QIcon, QColor
|
||||
from PyQt5.QtGui import QPixmap, QIcon
|
||||
from constants import (
|
||||
VOICES_INTERNAL,
|
||||
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
||||
LANGUAGE_DESCRIPTIONS,
|
||||
COLORS,
|
||||
)
|
||||
import re
|
||||
import platform
|
||||
from utils import get_resource_path
|
||||
from voice_profiles import (
|
||||
load_profiles,
|
||||
@@ -221,6 +225,19 @@ class VoiceMixer(QWidget):
|
||||
self.slider.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
||||
self.slider.setFixedWidth(SLIDER_WIDTH)
|
||||
|
||||
# Fix slider in Windows
|
||||
if platform.system() == "Windows":
|
||||
appstyle = QApplication.instance().style().objectName().lower()
|
||||
if appstyle != "windowsvista":
|
||||
# Set custom groove color for disabled state using COLORS["GREY_BACKGROUND"]
|
||||
self.slider.setStyleSheet(f"""
|
||||
QSlider::groove:vertical:disabled {{
|
||||
background: {COLORS.get("GREY_BACKGROUND")};
|
||||
width: 4px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
""")
|
||||
|
||||
# Connect controls
|
||||
self.slider.valueChanged.connect(lambda val: self.spin_box.setValue(val / 100))
|
||||
self.spin_box.valueChanged.connect(
|
||||
@@ -271,9 +288,9 @@ class HoverLabel(QLabel):
|
||||
self.delete_button = QPushButton("×", self)
|
||||
self.delete_button.setFixedSize(16, 16)
|
||||
self.delete_button.setStyleSheet(
|
||||
"""
|
||||
QPushButton {
|
||||
background-color: #ff5555;
|
||||
f"""
|
||||
QPushButton {{
|
||||
background-color: {COLORS.get("RED")};
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
font-weight: bold;
|
||||
@@ -281,11 +298,11 @@ class HoverLabel(QLabel):
|
||||
border: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: red;
|
||||
}
|
||||
"""
|
||||
}}
|
||||
"""
|
||||
)
|
||||
# Make sure the entire button is clickable, not just the text
|
||||
self.delete_button.setFocusPolicy(Qt.NoFocus)
|
||||
@@ -350,6 +367,9 @@ class VoiceFormulaDialog(QDialog):
|
||||
profile_layout.addLayout(header_layout)
|
||||
# Profile list
|
||||
self.profile_list = QListWidget()
|
||||
self.profile_list.setSelectionMode(QListWidget.SingleSelection)
|
||||
self.profile_list.setSelectionBehavior(QListWidget.SelectRows)
|
||||
self.profile_list.setStyleSheet("QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }")
|
||||
icon = QIcon(get_resource_path("abogen.assets", "profile.png"))
|
||||
if self._virtual_new_profile:
|
||||
item = QListWidgetItem(icon, "New profile")
|
||||
@@ -738,7 +758,7 @@ class VoiceFormulaDialog(QDialog):
|
||||
percentage = weight / total * 100
|
||||
# Make the voice name bold and include percentage
|
||||
voice_label = HoverLabel(
|
||||
f'<b><span style="color:#007dff">{name}: {percentage:.1f}%</span></b>',
|
||||
f'<b><span style="color:{COLORS.get("BLUE")}">{name}: {percentage:.1f}%</span></b>',
|
||||
name,
|
||||
)
|
||||
voice_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
||||
@@ -953,6 +973,18 @@ class VoiceFormulaDialog(QDialog):
|
||||
return
|
||||
super().closeEvent(event)
|
||||
|
||||
def _parse_rgba_to_qcolor(self, rgba_str):
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QColor
|
||||
"""Helper to convert 'rgba(R,G,B,A_float)' string to QColor."""
|
||||
match = re.match(r"rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)", rgba_str)
|
||||
if match:
|
||||
r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
||||
a_float = float(match.group(4))
|
||||
a_int = int(a_float * 255)
|
||||
return QColor(r, g, b, a_int)
|
||||
return Qt.GlobalColor.transparent
|
||||
|
||||
def mark_profile_modified(self):
|
||||
item = self.profile_list.currentItem()
|
||||
if item and not item.text().startswith("*"):
|
||||
@@ -1339,23 +1371,20 @@ class VoiceFormulaDialog(QDialog):
|
||||
self.profile_list.setItemWidget(item, widget)
|
||||
|
||||
def update_profile_list_colors(self):
|
||||
from PyQt5.QtCore import Qt
|
||||
profiles = load_profiles()
|
||||
for i in range(self.profile_list.count()):
|
||||
item = self.profile_list.item(i)
|
||||
name = item.text().lstrip("*")
|
||||
if self._virtual_new_profile and name == "New profile":
|
||||
color = QColor("#ffff00") # yellow
|
||||
color.setAlpha(64) # Set transparency
|
||||
item.setBackground(color)
|
||||
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
||||
item.setData(Qt.BackgroundRole, color)
|
||||
elif item.text().startswith("*"):
|
||||
color = QColor("#ffff00") # yellow
|
||||
color.setAlpha(64) # Set transparency
|
||||
item.setBackground(color)
|
||||
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
||||
item.setData(Qt.BackgroundRole, color)
|
||||
else:
|
||||
item.setBackground(self.profile_list.palette().base().color()) # Use default list item background
|
||||
|
||||
item.setData(Qt.BackgroundRole, self.profile_list.palette().base().color())
|
||||
weights = profiles.get(name, {}).get("voices", [])
|
||||
# Defensive: only sum if weights is a list of (voice, weight) pairs
|
||||
total = 0
|
||||
if isinstance(weights, list):
|
||||
for entry in weights:
|
||||
@@ -1366,9 +1395,8 @@ class VoiceFormulaDialog(QDialog):
|
||||
):
|
||||
total += entry[1]
|
||||
if total == 0:
|
||||
color = QColor("#ff0000") # red
|
||||
color.setAlpha(64) # Set transparency
|
||||
item.setBackground(color)
|
||||
color = self._parse_rgba_to_qcolor(COLORS.get("RED_BACKGROUND"))
|
||||
item.setData(Qt.BackgroundRole, color)
|
||||
self.update_profile_save_buttons()
|
||||
|
||||
def preview_current_mix(self):
|
||||
|
||||
Reference in New Issue
Block a user