diff --git a/.github/workflows/test_publish.yml b/.github/workflows/test_publish.yml
index 3be0d9d..9b6b0fe 100644
--- a/.github/workflows/test_publish.yml
+++ b/.github/workflows/test_publish.yml
@@ -2,11 +2,11 @@ name: Build multi-arch Docker Image
on:
# Build and push
- release:
- types: [published]
+ #release:
+ # types: [published]
# Build only
- push:
- branches: [main]
+ #push:
+ # branches: [main]
# TODO - enable build on pull requests if build times can be reduced
# pull_request:
workflow_dispatch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 869c195..6954003 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/abogen/assets/settings.png b/abogen/assets/settings.png
deleted file mode 100644
index bd6fb88..0000000
Binary files a/abogen/assets/settings.png and /dev/null differ
diff --git a/abogen/assets/settings.svg b/abogen/assets/settings.svg
new file mode 100644
index 0000000..f7ab139
--- /dev/null
+++ b/abogen/assets/settings.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/abogen/book_handler.py b/abogen/book_handler.py
index 55476b6..e5bf8ac 100644
--- a/abogen/book_handler.py
+++ b/abogen/book_handler.py
@@ -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()
diff --git a/abogen/constants.py b/abogen/constants.py
index a8fdfef..fca17a0 100644
--- a/abogen/constants.py
+++ b/abogen/constants.py
@@ -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",
+}
\ No newline at end of file
diff --git a/abogen/conversion.py b/abogen/conversion.py
index 5761f9c..1e6b262 100644
--- a/abogen/conversion.py
+++ b/abogen/conversion.py
@@ -693,8 +693,8 @@ class ConversionThread(QThread):
if 'ass' in subtitle_format:
# Generate ASS subtitle
- is_centered = 'centered' in subtitle_format
- is_narrow = 'narrow' in subtitle_format
+ is_centered = subtitle_format in ("ass_centered_wide", "ass_centered_narrow")
+ is_narrow = subtitle_format in ("ass_narrow", "ass_centered_narrow")
self._write_ass_subtitle(chapter_subtitle_path, chapter_subtitle_entries, is_centered, is_narrow)
else:
# Generate SRT subtitle (default)
@@ -772,9 +772,9 @@ class ConversionThread(QThread):
subtitle_path = os.path.splitext(final_out_path)[0] + f".{file_extension}"
if 'ass' in subtitle_format:
- # Generate ASS subtitle with optional centering and margin
- is_centered = 'centered' in subtitle_format
- is_narrow = 'narrow' in subtitle_format
+ # Generate ASS subtitle
+ is_centered = subtitle_format in ("ass_centered_wide", "ass_centered_narrow")
+ is_narrow = subtitle_format in ("ass_narrow", "ass_centered_narrow")
self._write_ass_subtitle(subtitle_path, subtitle_entries, is_centered, is_narrow)
else:
# Generate SRT subtitle (default)
diff --git a/abogen/gui.py b/abogen/gui.py
index f83910a..452fe8b 100644
--- a/abogen/gui.py
+++ b/abogen/gui.py
@@ -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
diff --git a/abogen/is_nvidia.py b/abogen/is_nvidia.py
index 2777f7b..78bd8cf 100644
--- a/abogen/is_nvidia.py
+++ b/abogen/is_nvidia.py
@@ -6,11 +6,15 @@ def check():
except Exception:
return False
+ nvidia_keywords = ['nvidia', 'rtx', 'gtx', 'quadro', 'tesla', 'titan', 'mx']
for gpu in stats.gpus:
- print(gpu.name)
- if 'nvidia' in gpu.name.lower():
+ name = gpu.name.lower()
+ if any(keyword in name for keyword in nvidia_keywords):
return True
return False
if __name__ == "__main__":
+ stats = gpustat.new_query()
+ for gpu in stats.gpus:
+ print(gpu.name)
print(check())
\ No newline at end of file
diff --git a/abogen/main.py b/abogen/main.py
index ad59430..2a311a9 100644
--- a/abogen/main.py
+++ b/abogen/main.py
@@ -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
diff --git a/abogen/voice_formula_gui.py b/abogen/voice_formula_gui.py
index b182b09..a9d0d70 100644
--- a/abogen/voice_formula_gui.py
+++ b/abogen/voice_formula_gui.py
@@ -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'{name}: {percentage:.1f}%',
+ f'{name}: {percentage:.1f}%',
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):