mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 05:40:26 +02:00
Upgraded Abogen's interface from PyQt5 to PyQt6, Added tooltip indicators in queue manager
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
# 1.2.1 (pre-release)
|
# 1.2.1 (pre-release)
|
||||||
|
- Upgraded Abogen's interface from PyQt5 to PyQt6 for better compatibility and long-term support.
|
||||||
|
- Added tooltip indicators in queue manager to display book handler options (`Save chapters separately` and `Merge chapters at the end`) for queued items.
|
||||||
- Added loading gif animation to book handler window.
|
- Added loading gif animation to book handler window.
|
||||||
- Fixed subtitle word-count splitting logic for more accurate segmentation.
|
- Fixed subtitle word-count splitting logic for more accurate segmentation.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ RUN apt-get update \
|
|||||||
python3 \
|
python3 \
|
||||||
python3-venv \
|
python3-venv \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3-pyqt5 \
|
python3-pyqt6 \
|
||||||
espeak-ng \
|
espeak-ng \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|||||||
+131
-121
@@ -4,24 +4,34 @@ import base64
|
|||||||
import fitz # PyMuPDF for PDF support
|
import fitz # PyMuPDF for PDF support
|
||||||
from ebooklib import epub
|
from ebooklib import epub
|
||||||
from bs4 import BeautifulSoup, NavigableString
|
from bs4 import BeautifulSoup, NavigableString
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt6.QtGui import QAction, QMovie, QFont
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QApplication,
|
||||||
QDialog,
|
QDialog,
|
||||||
QTreeWidget,
|
QTreeWidget,
|
||||||
QTreeWidgetItem,
|
QTreeWidgetItem,
|
||||||
QDialogButtonBox,
|
QTextEdit,
|
||||||
|
QPushButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QTextEdit,
|
QDialogButtonBox,
|
||||||
QTreeWidgetItemIterator,
|
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QWidget,
|
QWidget,
|
||||||
QPushButton,
|
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QMenu,
|
QTreeWidgetItemIterator,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QMenu,
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import (
|
||||||
|
Qt,
|
||||||
|
QThread,
|
||||||
|
pyqtSignal,
|
||||||
|
QObject,
|
||||||
|
QSize,
|
||||||
|
QEvent,
|
||||||
|
QPoint,
|
||||||
|
QRect,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
|
|
||||||
from PyQt5.QtGui import QMovie
|
|
||||||
from abogen.utils import clean_text, calculate_text_length, detect_encoding, get_resource_path
|
from abogen.utils import clean_text, calculate_text_length, detect_encoding, get_resource_path
|
||||||
import os
|
import os
|
||||||
import logging # Add logging
|
import logging # Add logging
|
||||||
@@ -97,9 +107,9 @@ class HandlerDialog(QDialog):
|
|||||||
self._block_signals = False # Flag to prevent recursive signals
|
self._block_signals = False # Flag to prevent recursive signals
|
||||||
# Configure window: remove help button and allow resizing
|
# Configure window: remove help button and allow resizing
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
Qt.Window | Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint
|
Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint | Qt.WindowType.WindowMaximizeButtonHint
|
||||||
)
|
)
|
||||||
self.setWindowModality(Qt.NonModal)
|
self.setWindowModality(Qt.WindowModality.NonModal)
|
||||||
# Initialize save chapters flags from class variables
|
# Initialize save chapters flags from class variables
|
||||||
self.save_chapters_separately = HandlerDialog._save_chapters_separately
|
self.save_chapters_separately = HandlerDialog._save_chapters_separately
|
||||||
self.merge_chapters_at_end = HandlerDialog._merge_chapters_at_end
|
self.merge_chapters_at_end = HandlerDialog._merge_chapters_at_end
|
||||||
@@ -164,8 +174,8 @@ class HandlerDialog(QDialog):
|
|||||||
# Build treeview
|
# Build treeview
|
||||||
self.treeWidget = QTreeWidget(self)
|
self.treeWidget = QTreeWidget(self)
|
||||||
self.treeWidget.setHeaderHidden(True)
|
self.treeWidget.setHeaderHidden(True)
|
||||||
self.treeWidget.setSelectionMode(QTreeWidget.ExtendedSelection)
|
self.treeWidget.setSelectionMode(QTreeWidget.SelectionMode.ExtendedSelection)
|
||||||
self.treeWidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.treeWidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.treeWidget.customContextMenuRequested.connect(self.on_tree_context_menu)
|
self.treeWidget.customContextMenuRequested.connect(self.on_tree_context_menu)
|
||||||
|
|
||||||
# Initialize checked_chapters set
|
# Initialize checked_chapters set
|
||||||
@@ -177,8 +187,8 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
# Add a placeholder "Information" item so the tree isn't empty immediately
|
# Add a placeholder "Information" item so the tree isn't empty immediately
|
||||||
info_item = QTreeWidgetItem(self.treeWidget, ["Information"])
|
info_item = QTreeWidgetItem(self.treeWidget, ["Information"])
|
||||||
info_item.setData(0, Qt.UserRole, "info:bookinfo")
|
info_item.setData(0, Qt.ItemDataRole.UserRole, "info:bookinfo")
|
||||||
info_item.setFlags(info_item.flags() & ~Qt.ItemIsUserCheckable)
|
info_item.setFlags(info_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
font = info_item.font(0)
|
font = info_item.font(0)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
info_item.setFont(0, font)
|
info_item.setFont(0, font)
|
||||||
@@ -243,8 +253,8 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
# Add stretches to center the content horizontally
|
# Add stretches to center the content horizontally
|
||||||
h.addStretch(1)
|
h.addStretch(1)
|
||||||
h.addWidget(gif_label, 0, Qt.AlignVCenter)
|
h.addWidget(gif_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
h.addWidget(text_label, 0, Qt.AlignVCenter)
|
h.addWidget(text_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
h.addStretch(1)
|
h.addStretch(1)
|
||||||
|
|
||||||
# Insert at top of main layout if present, otherwise keep as child
|
# Insert at top of main layout if present, otherwise keep as child
|
||||||
@@ -1148,8 +1158,8 @@ class HandlerDialog(QDialog):
|
|||||||
self.treeWidget.clear()
|
self.treeWidget.clear()
|
||||||
|
|
||||||
info_item = QTreeWidgetItem(self.treeWidget, ["Information"])
|
info_item = QTreeWidgetItem(self.treeWidget, ["Information"])
|
||||||
info_item.setData(0, Qt.UserRole, "info:bookinfo")
|
info_item.setData(0, Qt.ItemDataRole.UserRole, "info:bookinfo")
|
||||||
info_item.setFlags(info_item.flags() & ~Qt.ItemIsUserCheckable)
|
info_item.setFlags(info_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
font = info_item.font(0)
|
font = info_item.font(0)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
info_item.setFont(0, font)
|
info_item.setFont(0, font)
|
||||||
@@ -1172,7 +1182,7 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
has_parents = False
|
has_parents = False
|
||||||
iterator = QTreeWidgetItemIterator(
|
iterator = QTreeWidgetItemIterator(
|
||||||
self.treeWidget, QTreeWidgetItemIterator.HasChildren
|
self.treeWidget, QTreeWidgetItemIterator.IteratorFlag.HasChildren
|
||||||
)
|
)
|
||||||
if iterator.value():
|
if iterator.value():
|
||||||
has_parents = True
|
has_parents = True
|
||||||
@@ -1195,7 +1205,7 @@ class HandlerDialog(QDialog):
|
|||||||
children = node.get("children", [])
|
children = node.get("children", [])
|
||||||
|
|
||||||
item = QTreeWidgetItem(parent_item, [title])
|
item = QTreeWidgetItem(parent_item, [title])
|
||||||
item.setData(0, Qt.UserRole, src)
|
item.setData(0, Qt.ItemDataRole.UserRole, src)
|
||||||
|
|
||||||
is_empty = (
|
is_empty = (
|
||||||
src
|
src
|
||||||
@@ -1211,18 +1221,18 @@ class HandlerDialog(QDialog):
|
|||||||
seen_content_hashes.add(content_hash)
|
seen_content_hashes.add(content_hash)
|
||||||
|
|
||||||
if src and not is_empty and not is_duplicate:
|
if src and not is_empty and not is_duplicate:
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
is_checked = src in self.checked_chapters
|
is_checked = src in self.checked_chapters
|
||||||
item.setCheckState(0, Qt.Checked if is_checked else Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Checked if is_checked else Qt.CheckState.Unchecked)
|
||||||
elif is_duplicate:
|
elif is_duplicate:
|
||||||
# Mark as duplicate and remove checkbox
|
# Mark as duplicate and remove checkbox
|
||||||
item.setText(0, f"{title} (Duplicate)")
|
item.setText(0, f"{title} (Duplicate)")
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
elif children:
|
elif children:
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
item.setCheckState(0, Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Unchecked)
|
||||||
else:
|
else:
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
|
|
||||||
if children:
|
if children:
|
||||||
self._build_epub_tree_from_nav(children, item, seen_content_hashes)
|
self._build_epub_tree_from_nav(children, item, seen_content_hashes)
|
||||||
@@ -1252,18 +1262,18 @@ class HandlerDialog(QDialog):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
item = QTreeWidgetItem(parent_item, [title])
|
item = QTreeWidgetItem(parent_item, [title])
|
||||||
item.setData(0, Qt.UserRole, href)
|
item.setData(0, Qt.ItemDataRole.UserRole, href)
|
||||||
|
|
||||||
has_content = (
|
has_content = (
|
||||||
href and href in self.content_texts and self.content_texts[href].strip()
|
href and href in self.content_texts and self.content_texts[href].strip()
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_content or children:
|
if has_content or children:
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
is_checked = href and href in self.checked_chapters
|
is_checked = href and href in self.checked_chapters
|
||||||
item.setCheckState(0, Qt.Checked if is_checked else Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Checked if is_checked else Qt.CheckState.Unchecked)
|
||||||
else:
|
else:
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
|
|
||||||
if children:
|
if children:
|
||||||
self._build_epub_tree_fallback(children, item)
|
self._build_epub_tree_fallback(children, item)
|
||||||
@@ -1318,27 +1328,27 @@ class HandlerDialog(QDialog):
|
|||||||
if page_num in self.bookmark_items_map:
|
if page_num in self.bookmark_items_map:
|
||||||
orig = self.bookmark_items_map[page_num]
|
orig = self.bookmark_items_map[page_num]
|
||||||
child = QTreeWidgetItem(orig, [f"{title} (Same page)"])
|
child = QTreeWidgetItem(orig, [f"{title} (Same page)"])
|
||||||
child.setData(0, Qt.UserRole, page_id)
|
child.setData(0, Qt.ItemDataRole.UserRole, page_id)
|
||||||
child.setFlags(child.flags() & ~Qt.ItemIsUserCheckable)
|
child.setFlags(child.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
continue
|
continue
|
||||||
bookmark_item = QTreeWidgetItem(parent_item, [title])
|
bookmark_item = QTreeWidgetItem(parent_item, [title])
|
||||||
bookmark_item.setData(0, Qt.UserRole, page_id)
|
bookmark_item.setData(0, Qt.ItemDataRole.UserRole, page_id)
|
||||||
# only allow checking if this chapter has content
|
# only allow checking if this chapter has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
bookmark_item.setFlags(
|
bookmark_item.setFlags(
|
||||||
bookmark_item.flags() | Qt.ItemIsUserCheckable
|
bookmark_item.flags() | Qt.ItemFlag.ItemIsUserCheckable
|
||||||
)
|
)
|
||||||
bookmark_item.setCheckState(
|
bookmark_item.setCheckState(
|
||||||
0,
|
0,
|
||||||
(
|
(
|
||||||
Qt.Checked
|
Qt.CheckState.Checked
|
||||||
if page_id in self.checked_chapters
|
if page_id in self.checked_chapters
|
||||||
else Qt.Unchecked
|
else Qt.CheckState.Unchecked
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
bookmark_item.setFlags(
|
bookmark_item.setFlags(
|
||||||
bookmark_item.flags() & ~Qt.ItemIsUserCheckable
|
bookmark_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable
|
||||||
)
|
)
|
||||||
# map for uncategorized pages
|
# map for uncategorized pages
|
||||||
self.bookmark_items_map[page_num] = bookmark_item
|
self.bookmark_items_map[page_num] = bookmark_item
|
||||||
@@ -1363,23 +1373,23 @@ class HandlerDialog(QDialog):
|
|||||||
page_title += f" - {first_line}"
|
page_title += f" - {first_line}"
|
||||||
|
|
||||||
page_item = QTreeWidgetItem(bookmark_item, [page_title])
|
page_item = QTreeWidgetItem(bookmark_item, [page_title])
|
||||||
page_item.setData(0, Qt.UserRole, page_id)
|
page_item.setData(0, Qt.ItemDataRole.UserRole, page_id)
|
||||||
# only allow checking if this sub-page has content
|
# only allow checking if this sub-page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(
|
page_item.setFlags(
|
||||||
page_item.flags() | Qt.ItemIsUserCheckable
|
page_item.flags() | Qt.ItemFlag.ItemIsUserCheckable
|
||||||
)
|
)
|
||||||
page_item.setCheckState(
|
page_item.setCheckState(
|
||||||
0,
|
0,
|
||||||
(
|
(
|
||||||
Qt.Checked
|
Qt.CheckState.Checked
|
||||||
if page_id in self.checked_chapters
|
if page_id in self.checked_chapters
|
||||||
else Qt.Unchecked
|
else Qt.CheckState.Unchecked
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(
|
page_item.setFlags(
|
||||||
page_item.flags() & ~Qt.ItemIsUserCheckable
|
page_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable
|
||||||
)
|
)
|
||||||
|
|
||||||
added_pages.add(sub_page_num)
|
added_pages.add(sub_page_num)
|
||||||
@@ -1405,15 +1415,15 @@ class HandlerDialog(QDialog):
|
|||||||
if first and len(first) < 100:
|
if first and len(first) < 100:
|
||||||
title += f" - {first}"
|
title += f" - {first}"
|
||||||
page_item = QTreeWidgetItem(parent_item, [title])
|
page_item = QTreeWidgetItem(parent_item, [title])
|
||||||
page_item.setData(0, Qt.UserRole, page_id)
|
page_item.setData(0, Qt.ItemDataRole.UserRole, page_id)
|
||||||
# only allow checking if uncategorized page has content
|
# only allow checking if uncategorized page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
page_item.setCheckState(
|
page_item.setCheckState(
|
||||||
0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked
|
0, Qt.CheckState.Checked if page_id in self.checked_chapters else Qt.CheckState.Unchecked
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
|
|
||||||
def _build_markdown_tree(self):
|
def _build_markdown_tree(self):
|
||||||
"""Build tree structure for markdown file based on parsed TOC."""
|
"""Build tree structure for markdown file based on parsed TOC."""
|
||||||
@@ -1426,13 +1436,13 @@ class HandlerDialog(QDialog):
|
|||||||
chapter_id = list(self.content_texts.keys())[0]
|
chapter_id = list(self.content_texts.keys())[0]
|
||||||
title = "Content"
|
title = "Content"
|
||||||
item = QTreeWidgetItem(self.treeWidget, [title])
|
item = QTreeWidgetItem(self.treeWidget, [title])
|
||||||
item.setData(0, Qt.UserRole, chapter_id)
|
item.setData(0, Qt.ItemDataRole.UserRole, chapter_id)
|
||||||
if self.content_lengths.get(chapter_id, 0) > 0:
|
if self.content_lengths.get(chapter_id, 0) > 0:
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
is_checked = chapter_id in self.checked_chapters
|
is_checked = chapter_id in self.checked_chapters
|
||||||
item.setCheckState(0, Qt.Checked if is_checked else Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Checked if is_checked else Qt.CheckState.Unchecked)
|
||||||
else:
|
else:
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
return
|
return
|
||||||
|
|
||||||
def build_from_toc(toc_list, parent_item):
|
def build_from_toc(toc_list, parent_item):
|
||||||
@@ -1441,17 +1451,17 @@ class HandlerDialog(QDialog):
|
|||||||
chapter_id = header['id']
|
chapter_id = header['id']
|
||||||
|
|
||||||
item = QTreeWidgetItem(parent_item, [title])
|
item = QTreeWidgetItem(parent_item, [title])
|
||||||
item.setData(0, Qt.UserRole, chapter_id)
|
item.setData(0, Qt.ItemDataRole.UserRole, chapter_id)
|
||||||
|
|
||||||
has_content = self.content_lengths.get(chapter_id, 0) > 0
|
has_content = self.content_lengths.get(chapter_id, 0) > 0
|
||||||
has_children = bool(header.get('children'))
|
has_children = bool(header.get('children'))
|
||||||
|
|
||||||
if has_content or has_children:
|
if has_content or has_children:
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
is_checked = chapter_id in self.checked_chapters
|
is_checked = chapter_id in self.checked_chapters
|
||||||
item.setCheckState(0, Qt.Checked if is_checked else Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Checked if is_checked else Qt.CheckState.Unchecked)
|
||||||
else:
|
else:
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
|
|
||||||
if has_children:
|
if has_children:
|
||||||
build_from_toc(header['children'], item)
|
build_from_toc(header['children'], item)
|
||||||
@@ -1460,7 +1470,7 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
def _build_pdf_pages_tree(self):
|
def _build_pdf_pages_tree(self):
|
||||||
pages_item = QTreeWidgetItem(self.treeWidget, ["Pages"])
|
pages_item = QTreeWidgetItem(self.treeWidget, ["Pages"])
|
||||||
pages_item.setFlags(pages_item.flags() & ~Qt.ItemIsUserCheckable)
|
pages_item.setFlags(pages_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
font = pages_item.font(0)
|
font = pages_item.font(0)
|
||||||
font.setBold(True)
|
font.setBold(True)
|
||||||
pages_item.setFont(0, font)
|
pages_item.setFont(0, font)
|
||||||
@@ -1476,15 +1486,15 @@ class HandlerDialog(QDialog):
|
|||||||
page_title += f" - {first_line}"
|
page_title += f" - {first_line}"
|
||||||
|
|
||||||
page_item = QTreeWidgetItem(pages_item, [page_title])
|
page_item = QTreeWidgetItem(pages_item, [page_title])
|
||||||
page_item.setData(0, Qt.UserRole, page_id)
|
page_item.setData(0, Qt.ItemDataRole.UserRole, page_id)
|
||||||
# only allow checking if standalone page has content
|
# only allow checking if standalone page has content
|
||||||
if self.content_lengths.get(page_id, 0) > 0:
|
if self.content_lengths.get(page_id, 0) > 0:
|
||||||
page_item.setFlags(page_item.flags() | Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
page_item.setCheckState(
|
page_item.setCheckState(
|
||||||
0, Qt.Checked if page_id in self.checked_chapters else Qt.Unchecked
|
0, Qt.CheckState.Checked if page_id in self.checked_chapters else Qt.CheckState.Unchecked
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
page_item.setFlags(page_item.flags() & ~Qt.ItemIsUserCheckable)
|
page_item.setFlags(page_item.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
|
|
||||||
def _are_provided_checks_relevant(self):
|
def _are_provided_checks_relevant(self):
|
||||||
if not self.checked_chapters:
|
if not self.checked_chapters:
|
||||||
@@ -1494,8 +1504,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if identifier:
|
if identifier:
|
||||||
all_identifiers.add(identifier)
|
all_identifiers.add(identifier)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
@@ -1525,7 +1535,7 @@ class HandlerDialog(QDialog):
|
|||||||
rightWidget = QWidget()
|
rightWidget = QWidget()
|
||||||
rightWidget.setLayout(previewLayout)
|
rightWidget.setLayout(previewLayout)
|
||||||
|
|
||||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
|
||||||
buttons.accepted.connect(self.accept)
|
buttons.accepted.connect(self.accept)
|
||||||
buttons.rejected.connect(self.reject)
|
buttons.rejected.connect(self.reject)
|
||||||
|
|
||||||
@@ -1611,7 +1621,7 @@ class HandlerDialog(QDialog):
|
|||||||
leftWidget = QWidget()
|
leftWidget = QWidget()
|
||||||
leftWidget.setLayout(leftLayout)
|
leftWidget.setLayout(leftLayout)
|
||||||
|
|
||||||
self.splitter = QSplitter(Qt.Horizontal)
|
self.splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
self.splitter.addWidget(leftWidget)
|
self.splitter.addWidget(leftWidget)
|
||||||
self.splitter.addWidget(rightWidget)
|
self.splitter.addWidget(rightWidget)
|
||||||
self.splitter.setSizes([280, 420])
|
self.splitter.setSizes([280, 420])
|
||||||
@@ -1643,8 +1653,8 @@ class HandlerDialog(QDialog):
|
|||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if (
|
if (
|
||||||
item.flags() & Qt.ItemIsUserCheckable
|
item.flags() & Qt.ItemFlag.ItemIsUserCheckable
|
||||||
and item.checkState(0) == Qt.Checked
|
and item.checkState(0) == Qt.CheckState.Checked
|
||||||
):
|
):
|
||||||
checked_count += 1
|
checked_count += 1
|
||||||
if checked_count >= 2:
|
if checked_count >= 2:
|
||||||
@@ -1658,8 +1668,8 @@ class HandlerDialog(QDialog):
|
|||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if (
|
if (
|
||||||
item.flags() & Qt.ItemIsUserCheckable
|
item.flags() & Qt.ItemFlag.ItemIsUserCheckable
|
||||||
and item.checkState(0) == Qt.Checked
|
and item.checkState(0) == Qt.CheckState.Checked
|
||||||
):
|
):
|
||||||
parent = item.parent()
|
parent = item.parent()
|
||||||
if parent and parent != self.treeWidget.invisibleRootItem():
|
if parent and parent != self.treeWidget.invisibleRootItem():
|
||||||
@@ -1683,8 +1693,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
self._block_signals = False
|
self._block_signals = False
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
@@ -1694,8 +1704,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
item.setCheckState(0, Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Unchecked)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
self._block_signals = False
|
self._block_signals = False
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
@@ -1705,8 +1715,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable and item.childCount() > 0:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable and item.childCount() > 0:
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
self._block_signals = False
|
self._block_signals = False
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
@@ -1716,8 +1726,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable and item.childCount() > 0:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable and item.childCount() > 0:
|
||||||
item.setCheckState(0, Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Unchecked)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
self._block_signals = False
|
self._block_signals = False
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
@@ -1742,30 +1752,30 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if not (item.flags() & Qt.ItemIsUserCheckable):
|
if not (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
||||||
iterator += 1
|
iterator += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
src = item.data(0, Qt.UserRole)
|
src = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
has_significant_content = src and self.content_lengths.get(src, 0) > 1000
|
has_significant_content = src and self.content_lengths.get(src, 0) > 1000
|
||||||
is_parent = item.childCount() > 0
|
is_parent = item.childCount() > 0
|
||||||
|
|
||||||
if has_significant_content or is_parent:
|
if has_significant_content or is_parent:
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
if is_parent:
|
if is_parent:
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
child = item.child(i)
|
||||||
if child.flags() & Qt.ItemIsUserCheckable:
|
if child.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
child_src = child.data(0, Qt.UserRole)
|
child_src = child.data(0, Qt.ItemDataRole.UserRole)
|
||||||
child_has_content = (
|
child_has_content = (
|
||||||
child_src and self.content_lengths.get(child_src, 0) > 0
|
child_src and self.content_lengths.get(child_src, 0) > 0
|
||||||
)
|
)
|
||||||
child_is_parent = child.childCount() > 0
|
child_is_parent = child.childCount() > 0
|
||||||
if child_has_content or child_is_parent:
|
if child_has_content or child_is_parent:
|
||||||
child.setCheckState(0, Qt.Checked)
|
child.setCheckState(0, Qt.CheckState.Checked)
|
||||||
else:
|
else:
|
||||||
item.setCheckState(0, Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Unchecked)
|
||||||
|
|
||||||
iterator += 1
|
iterator += 1
|
||||||
|
|
||||||
@@ -1774,32 +1784,32 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if not (item.flags() & Qt.ItemIsUserCheckable):
|
if not (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
||||||
iterator += 1
|
iterator += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
# Select chapters with content > 500 characters or parent items
|
# Select chapters with content > 500 characters or parent items
|
||||||
has_significant_content = identifier and self.content_lengths.get(identifier, 0) > 500
|
has_significant_content = identifier and self.content_lengths.get(identifier, 0) > 500
|
||||||
is_parent = item.childCount() > 0
|
is_parent = item.childCount() > 0
|
||||||
|
|
||||||
if has_significant_content or is_parent:
|
if has_significant_content or is_parent:
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
# Also check children if this is a parent
|
# Also check children if this is a parent
|
||||||
if is_parent:
|
if is_parent:
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
child = item.child(i)
|
||||||
if child.flags() & Qt.ItemIsUserCheckable:
|
if child.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
child_identifier = child.data(0, Qt.UserRole)
|
child_identifier = child.data(0, Qt.ItemDataRole.UserRole)
|
||||||
child_has_content = (
|
child_has_content = (
|
||||||
child_identifier and self.content_lengths.get(child_identifier, 0) > 0
|
child_identifier and self.content_lengths.get(child_identifier, 0) > 0
|
||||||
)
|
)
|
||||||
child_is_parent = child.childCount() > 0
|
child_is_parent = child.childCount() > 0
|
||||||
if child_has_content or child_is_parent:
|
if child_has_content or child_is_parent:
|
||||||
child.setCheckState(0, Qt.Checked)
|
child.setCheckState(0, Qt.CheckState.Checked)
|
||||||
else:
|
else:
|
||||||
item.setCheckState(0, Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Unchecked)
|
||||||
|
|
||||||
iterator += 1
|
iterator += 1
|
||||||
|
|
||||||
@@ -1808,19 +1818,19 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if not (item.flags() & Qt.ItemIsUserCheckable):
|
if not (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
||||||
iterator += 1
|
iterator += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
if not identifier:
|
if not identifier:
|
||||||
iterator += 1
|
iterator += 1
|
||||||
@@ -1830,7 +1840,7 @@ class HandlerDialog(QDialog):
|
|||||||
not identifier.startswith("page_")
|
not identifier.startswith("page_")
|
||||||
or self.content_lengths.get(identifier, 0) > 0
|
or self.content_lengths.get(identifier, 0) > 0
|
||||||
):
|
):
|
||||||
item.setCheckState(0, Qt.Checked)
|
item.setCheckState(0, Qt.CheckState.Checked)
|
||||||
|
|
||||||
iterator += 1
|
iterator += 1
|
||||||
|
|
||||||
@@ -1839,8 +1849,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.checkState(0) == Qt.Checked:
|
if item.checkState(0) == Qt.CheckState.Checked:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if identifier:
|
if identifier:
|
||||||
self.checked_chapters.add(identifier)
|
self.checked_chapters.add(identifier)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
@@ -1853,17 +1863,17 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
self._block_signals = True
|
self._block_signals = True
|
||||||
|
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
child = item.child(i)
|
||||||
if child.flags() & Qt.ItemIsUserCheckable:
|
if child.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
child.setCheckState(0, item.checkState(0))
|
child.setCheckState(0, item.checkState(0))
|
||||||
|
|
||||||
self._block_signals = False
|
self._block_signals = False
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
|
|
||||||
def handle_item_double_click(self, item, column=0):
|
def handle_item_double_click(self, item, column=0):
|
||||||
if item.flags() & Qt.ItemIsUserCheckable and item.childCount() == 0:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable and item.childCount() == 0:
|
||||||
rect = self.treeWidget.visualItemRect(item)
|
rect = self.treeWidget.visualItemRect(item)
|
||||||
checkbox_width = 20
|
checkbox_width = 20
|
||||||
|
|
||||||
@@ -1871,7 +1881,7 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
if mouse_pos.x() > rect.x() + checkbox_width:
|
if mouse_pos.x() > rect.x() + checkbox_width:
|
||||||
new_state = (
|
new_state = (
|
||||||
Qt.Unchecked if item.checkState(0) == Qt.Checked else Qt.Checked
|
Qt.CheckState.Unchecked if item.checkState(0) == Qt.CheckState.Checked else Qt.CheckState.Checked
|
||||||
)
|
)
|
||||||
item.setCheckState(0, new_state)
|
item.setCheckState(0, new_state)
|
||||||
|
|
||||||
@@ -1880,7 +1890,7 @@ class HandlerDialog(QDialog):
|
|||||||
self.previewEdit.clear()
|
self.previewEdit.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
identifier = current.data(0, Qt.UserRole)
|
identifier = current.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
if identifier == "info:bookinfo":
|
if identifier == "info:bookinfo":
|
||||||
self._display_book_info()
|
self._display_book_info()
|
||||||
@@ -2172,8 +2182,8 @@ class HandlerDialog(QDialog):
|
|||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
item_order_counter += 1
|
item_order_counter += 1
|
||||||
if item.checkState(0) == Qt.Checked:
|
if item.checkState(0) == Qt.CheckState.Checked:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
|
|
||||||
if identifier and identifier != "info:bookinfo":
|
if identifier and identifier != "info:bookinfo":
|
||||||
all_checked_identifiers.add(identifier)
|
all_checked_identifiers.add(identifier)
|
||||||
@@ -2208,8 +2218,8 @@ class HandlerDialog(QDialog):
|
|||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
item_order_counter += 1
|
item_order_counter += 1
|
||||||
if item.checkState(0) == Qt.Checked:
|
if item.checkState(0) == Qt.CheckState.Checked:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if identifier and identifier != "info:bookinfo":
|
if identifier and identifier != "info:bookinfo":
|
||||||
all_checked_identifiers.add(identifier)
|
all_checked_identifiers.add(identifier)
|
||||||
ordered_checked_items.append((item_order_counter, item, identifier))
|
ordered_checked_items.append((item_order_counter, item, identifier))
|
||||||
@@ -2244,8 +2254,8 @@ class HandlerDialog(QDialog):
|
|||||||
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
iterator = QTreeWidgetItemIterator(self.treeWidget)
|
||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.checkState(0) == Qt.Checked:
|
if item.checkState(0) == Qt.CheckState.Checked:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if identifier:
|
if identifier:
|
||||||
all_checked_identifiers.add(identifier)
|
all_checked_identifiers.add(identifier)
|
||||||
iterator += 1
|
iterator += 1
|
||||||
@@ -2270,15 +2280,15 @@ class HandlerDialog(QDialog):
|
|||||||
while iterator.value():
|
while iterator.value():
|
||||||
item = iterator.value()
|
item = iterator.value()
|
||||||
if item.childCount() > 0:
|
if item.childCount() > 0:
|
||||||
parent_checked = item.checkState(0) == Qt.Checked
|
parent_checked = item.checkState(0) == Qt.CheckState.Checked
|
||||||
parent_id = item.data(0, Qt.UserRole)
|
parent_id = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
parent_title = item.text(0)
|
parent_title = item.text(0)
|
||||||
checked_children = []
|
checked_children = []
|
||||||
for i in range(item.childCount()):
|
for i in range(item.childCount()):
|
||||||
child = item.child(i)
|
child = item.child(i)
|
||||||
child_id = child.data(0, Qt.UserRole)
|
child_id = child.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if (
|
if (
|
||||||
child.checkState(0) == Qt.Checked
|
child.checkState(0) == Qt.CheckState.Checked
|
||||||
and child_id
|
and child_id
|
||||||
and child_id not in included_text_ids
|
and child_id not in included_text_ids
|
||||||
):
|
):
|
||||||
@@ -2306,12 +2316,12 @@ class HandlerDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
section_titles.append((title, text))
|
section_titles.append((title, text))
|
||||||
included_text_ids.add(child_id)
|
included_text_ids.add(child_id)
|
||||||
elif item.flags() & Qt.ItemIsUserCheckable:
|
elif item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
identifier = item.data(0, Qt.UserRole)
|
identifier = item.data(0, Qt.ItemDataRole.UserRole)
|
||||||
if (
|
if (
|
||||||
identifier
|
identifier
|
||||||
and identifier not in included_text_ids
|
and identifier not in included_text_ids
|
||||||
and item.checkState(0) == Qt.Checked
|
and item.checkState(0) == Qt.CheckState.Checked
|
||||||
):
|
):
|
||||||
text = self.content_texts.get(identifier, "")
|
text = self.content_texts.get(identifier, "")
|
||||||
if text:
|
if text:
|
||||||
@@ -2363,8 +2373,8 @@ class HandlerDialog(QDialog):
|
|||||||
print(f"Checking selected items: {state}")
|
print(f"Checking selected items: {state}")
|
||||||
self.treeWidget.blockSignals(True)
|
self.treeWidget.blockSignals(True)
|
||||||
for item in self.treeWidget.selectedItems():
|
for item in self.treeWidget.selectedItems():
|
||||||
if item.flags() & Qt.ItemIsUserCheckable:
|
if item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
item.setCheckState(0, Qt.Checked if state else Qt.Unchecked)
|
item.setCheckState(0, Qt.CheckState.Checked if state else Qt.CheckState.Unchecked)
|
||||||
self.treeWidget.blockSignals(False)
|
self.treeWidget.blockSignals(False)
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
|
|
||||||
@@ -2377,30 +2387,30 @@ class HandlerDialog(QDialog):
|
|||||||
action.triggered.connect(self.check_selected_items)
|
action.triggered.connect(self.check_selected_items)
|
||||||
action = menu.addAction("Clear")
|
action = menu.addAction("Clear")
|
||||||
action.triggered.connect(self.uncheck_selected_items)
|
action.triggered.connect(self.uncheck_selected_items)
|
||||||
menu.exec_(self.treeWidget.mapToGlobal(pos))
|
menu.exec(self.treeWidget.mapToGlobal(pos))
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not item
|
not item
|
||||||
or item.childCount() == 0
|
or item.childCount() == 0
|
||||||
or not (item.flags() & Qt.ItemIsUserCheckable)
|
or not (item.flags() & Qt.ItemFlag.ItemIsUserCheckable)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
menu = QMenu(self)
|
menu = QMenu(self)
|
||||||
checked = item.checkState(0) == Qt.Checked
|
checked = item.checkState(0) == Qt.CheckState.Checked
|
||||||
text = "Unselect only this" if checked else "Select only this"
|
text = "Unselect only this" if checked else "Select only this"
|
||||||
action = menu.addAction(text)
|
action = menu.addAction(text)
|
||||||
|
|
||||||
def do_toggle():
|
def do_toggle():
|
||||||
self.treeWidget.blockSignals(True)
|
self.treeWidget.blockSignals(True)
|
||||||
new_state = Qt.Unchecked if checked else Qt.Checked
|
new_state = Qt.CheckState.Unchecked if checked else Qt.CheckState.Checked
|
||||||
item.setCheckState(0, new_state)
|
item.setCheckState(0, new_state)
|
||||||
self.treeWidget.blockSignals(False)
|
self.treeWidget.blockSignals(False)
|
||||||
self._update_checked_set_from_tree()
|
self._update_checked_set_from_tree()
|
||||||
|
|
||||||
action.triggered.connect(do_toggle)
|
action.triggered.connect(do_toggle)
|
||||||
menu.exec_(self.treeWidget.mapToGlobal(pos))
|
menu.exec(self.treeWidget.mapToGlobal(pos))
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
if self.pdf_doc is not None:
|
if self.pdf_doc is not None:
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ COLORS = {
|
|||||||
"BLUE_BORDER_HOVER": "#6ab0de",
|
"BLUE_BORDER_HOVER": "#6ab0de",
|
||||||
"YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)",
|
"YELLOW_BACKGROUND": "rgba(255, 221, 51, 0.40)",
|
||||||
"GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)",
|
"GREY_BACKGROUND": "rgba(128, 128, 128, 0.15)",
|
||||||
|
"GREY_BORDER": "#808080",
|
||||||
"RED_BACKGROUND": "rgba(232, 78, 60, 0.15)",
|
"RED_BACKGROUND": "rgba(232, 78, 60, 0.15)",
|
||||||
"RED_BG": "rgba(232, 78, 60, 0.10)",
|
"RED_BG": "rgba(232, 78, 60, 0.10)",
|
||||||
"RED_BG_HOVER": "rgba(232, 78, 60, 0.15)",
|
"RED_BG_HOVER": "rgba(232, 78, 60, 0.15)",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import re
|
|||||||
import time
|
import time
|
||||||
import hashlib # For generating unique cache filenames
|
import hashlib # For generating unique cache filenames
|
||||||
from platformdirs import user_desktop_dir
|
from platformdirs import user_desktop_dir
|
||||||
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QTimer
|
from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer
|
||||||
from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox
|
from PyQt6.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
from abogen.utils import clean_text, create_process, get_user_cache_path, detect_encoding
|
from abogen.utils import clean_text, create_process, get_user_cache_path, detect_encoding
|
||||||
from abogen.constants import (
|
from abogen.constants import (
|
||||||
@@ -96,8 +96,8 @@ class ChapterOptionsDialog(QDialog):
|
|||||||
# Prevent closing with the X button and remove the help button
|
# Prevent closing with the X button and remove the help button
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
self.windowFlags()
|
self.windowFlags()
|
||||||
& ~Qt.WindowCloseButtonHint
|
& ~Qt.WindowType.WindowCloseButtonHint
|
||||||
& ~Qt.WindowContextHelpButtonHint
|
& ~Qt.WindowType.WindowContextHelpButtonHint
|
||||||
)
|
)
|
||||||
|
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
@@ -131,7 +131,7 @@ class ChapterOptionsDialog(QDialog):
|
|||||||
layout.addWidget(self.countdown_label)
|
layout.addWidget(self.countdown_label)
|
||||||
|
|
||||||
# Add OK button
|
# Add OK button
|
||||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok)
|
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok)
|
||||||
button_box.accepted.connect(self.accept)
|
button_box.accepted.connect(self.accept)
|
||||||
layout.addWidget(button_box)
|
layout.addWidget(button_box)
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ class ChapterOptionsDialog(QDialog):
|
|||||||
|
|
||||||
# Prevent escape key from closing the dialog
|
# Prevent escape key from closing the dialog
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
if event.key() == Qt.Key_Escape:
|
if event.key() == Qt.Key.Key_Escape:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
else:
|
else:
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|||||||
+110
-97
@@ -8,28 +8,31 @@ from abogen.queue_manager_gui import QueueManager
|
|||||||
from abogen.queued_item import QueuedItem
|
from abogen.queued_item import QueuedItem
|
||||||
import abogen.hf_tracker as hf_tracker
|
import abogen.hf_tracker as hf_tracker
|
||||||
import hashlib # Added for cache path generation
|
import hashlib # Added for cache path generation
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
|
QMainWindow,
|
||||||
QWidget,
|
QWidget,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QFileDialog,
|
|
||||||
QLabel,
|
|
||||||
QProgressBar,
|
|
||||||
QSlider,
|
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QSizePolicy,
|
|
||||||
QTextEdit,
|
QTextEdit,
|
||||||
QFileIconProvider,
|
QLabel,
|
||||||
|
QSlider,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
|
QFileDialog,
|
||||||
|
QProgressBar,
|
||||||
|
QFrame,
|
||||||
|
QStyleFactory,
|
||||||
|
QInputDialog,
|
||||||
|
QFileIconProvider,
|
||||||
|
QSizePolicy,
|
||||||
QDialog,
|
QDialog,
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
QMenu,
|
QMenu,
|
||||||
QAction,
|
|
||||||
QActionGroup,
|
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import (
|
from PyQt6.QtGui import QAction, QActionGroup
|
||||||
|
from PyQt6.QtCore import (
|
||||||
Qt,
|
Qt,
|
||||||
QUrl,
|
QUrl,
|
||||||
QPoint,
|
QPoint,
|
||||||
@@ -42,8 +45,9 @@ from PyQt5.QtCore import (
|
|||||||
QSize,
|
QSize,
|
||||||
QTimer,
|
QTimer,
|
||||||
QEvent,
|
QEvent,
|
||||||
|
QProcess,
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
QTextCursor,
|
QTextCursor,
|
||||||
QDesktopServices,
|
QDesktopServices,
|
||||||
QIcon,
|
QIcon,
|
||||||
@@ -52,6 +56,7 @@ from PyQt5.QtGui import (
|
|||||||
QPolygon,
|
QPolygon,
|
||||||
QColor,
|
QColor,
|
||||||
QMovie,
|
QMovie,
|
||||||
|
QPalette,
|
||||||
)
|
)
|
||||||
from abogen.utils import (
|
from abogen.utils import (
|
||||||
load_config,
|
load_config,
|
||||||
@@ -95,12 +100,12 @@ class DarkTitleBarEventFilter(QObject):
|
|||||||
self.set_title_bar_dark_mode = set_title_bar_dark_mode_func
|
self.set_title_bar_dark_mode = set_title_bar_dark_mode_func
|
||||||
|
|
||||||
def eventFilter(self, obj, event):
|
def eventFilter(self, obj, event):
|
||||||
if event.type() == QEvent.Show:
|
if event.type() == QEvent.Type.Show:
|
||||||
# Only apply to QWidget windows
|
# Only apply to QWidget windows
|
||||||
if isinstance(obj, QWidget) and obj.isWindow():
|
if isinstance(obj, QWidget) and obj.isWindow():
|
||||||
if self.is_windows and self.get_dark_mode():
|
if self.is_windows and self.get_dark_mode():
|
||||||
self.set_title_bar_dark_mode(obj, True)
|
self.set_title_bar_dark_mode(obj, True)
|
||||||
return False
|
return super().eventFilter(obj, event)
|
||||||
|
|
||||||
|
|
||||||
class ShowWarningSignalEmitter(QObject): # New class to handle signal emission
|
class ShowWarningSignalEmitter(QObject): # New class to handle signal emission
|
||||||
@@ -154,7 +159,7 @@ class InputBox(QLabel):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setAlignment(Qt.AlignCenter)
|
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.setText(
|
self.setText(
|
||||||
"Drag and drop your file here or click to browse.\n(.txt, .epub, .pdf, .md)"
|
"Drag and drop your file here or click to browse.\n(.txt, .epub, .pdf, .md)"
|
||||||
@@ -162,8 +167,8 @@ class InputBox(QLabel):
|
|||||||
self.setStyleSheet(
|
self.setStyleSheet(
|
||||||
f"QLabel {{ {self.STYLE_DEFAULT} }} QLabel:hover {{ {self.STYLE_DEFAULT_HOVER} }}"
|
f"QLabel {{ {self.STYLE_DEFAULT} }} QLabel:hover {{ {self.STYLE_DEFAULT_HOVER} }}"
|
||||||
)
|
)
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
|
|
||||||
# Add clear button
|
# Add clear button
|
||||||
self.clear_btn = QPushButton("✕", self)
|
self.clear_btn = QPushButton("✕", self)
|
||||||
@@ -223,7 +228,7 @@ class InputBox(QLabel):
|
|||||||
pixmap = qicon.pixmap(size)
|
pixmap = qicon.pixmap(size)
|
||||||
# convert to base64 PNG
|
# convert to base64 PNG
|
||||||
buffer = QBuffer()
|
buffer = QBuffer()
|
||||||
buffer.open(QIODevice.WriteOnly)
|
buffer.open(QIODevice.OpenModeFlag.WriteOnly)
|
||||||
pixmap.save(buffer, "PNG")
|
pixmap.save(buffer, "PNG")
|
||||||
img_data = base64.b64encode(buffer.data()).decode()
|
img_data = base64.b64encode(buffer.data()).decode()
|
||||||
|
|
||||||
@@ -356,6 +361,9 @@ class InputBox(QLabel):
|
|||||||
self.window().displayed_file_path = (
|
self.window().displayed_file_path = (
|
||||||
None # Reset the displayed file path when clearing input
|
None # Reset the displayed file path when clearing input
|
||||||
)
|
)
|
||||||
|
# Reset book handler attributes
|
||||||
|
self.window().save_chapters_separately = None
|
||||||
|
self.window().merge_chapters_at_end = None
|
||||||
self.setText(
|
self.setText(
|
||||||
"Drag and drop your file here or click to browse.\n(.txt, .epub, .pdf, .md)"
|
"Drag and drop your file here or click to browse.\n(.txt, .epub, .pdf, .md)"
|
||||||
)
|
)
|
||||||
@@ -384,7 +392,7 @@ class InputBox(QLabel):
|
|||||||
return f"{size:.{decimal_places}f} PB"
|
return f"{size:.{decimal_places}f} PB"
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
if event.button() == Qt.LeftButton:
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
self.window().open_file_dialog()
|
self.window().open_file_dialog()
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
@@ -542,7 +550,7 @@ class InputBox(QLabel):
|
|||||||
|
|
||||||
menu.addAction(act_input)
|
menu.addAction(act_input)
|
||||||
# Show the menu anchored to the button
|
# Show the menu anchored to the button
|
||||||
menu.exec_(self.go_to_folder_btn.mapToGlobal(QPoint(0, self.go_to_folder_btn.height())))
|
menu.exec(self.go_to_folder_btn.mapToGlobal(QPoint(0, self.go_to_folder_btn.height())))
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
file_to_check
|
file_to_check
|
||||||
@@ -560,7 +568,7 @@ class TextboxDialog(QDialog):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("Enter Text")
|
self.setWindowTitle("Enter Text")
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
Qt.Window | Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint
|
Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint | Qt.WindowType.WindowMaximizeButtonHint
|
||||||
)
|
)
|
||||||
self.resize(700, 500)
|
self.resize(700, 500)
|
||||||
|
|
||||||
@@ -634,16 +642,16 @@ class TextboxDialog(QDialog):
|
|||||||
# Check if we need to warn about overwriting a non-temporary file
|
# Check if we need to warn about overwriting a non-temporary file
|
||||||
if hasattr(self, "is_non_cache_file") and self.is_non_cache_file:
|
if hasattr(self, "is_non_cache_file") and self.is_non_cache_file:
|
||||||
msg_box = QMessageBox(self)
|
msg_box = QMessageBox(self)
|
||||||
msg_box.setIcon(QMessageBox.Warning)
|
msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||||
msg_box.setWindowTitle("File Overwrite Warning")
|
msg_box.setWindowTitle("File Overwrite Warning")
|
||||||
msg_box.setText(
|
msg_box.setText(
|
||||||
f"You are about to overwrite the original file:\n{self.non_cache_file_path}"
|
f"You are about to overwrite the original file:\n{self.non_cache_file_path}"
|
||||||
)
|
)
|
||||||
msg_box.setInformativeText("Do you want to continue?")
|
msg_box.setInformativeText("Do you want to continue?")
|
||||||
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
msg_box.setDefaultButton(QMessageBox.No)
|
msg_box.setDefaultButton(QMessageBox.StandardButton.No)
|
||||||
|
|
||||||
if msg_box.exec_() != QMessageBox.Yes:
|
if msg_box.exec() != QMessageBox.StandardButton.Yes:
|
||||||
# User canceled, don't close the dialog
|
# User canceled, don't close the dialog
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -873,7 +881,7 @@ class abogen(QWidget):
|
|||||||
self.log_text = QTextEdit(self)
|
self.log_text = QTextEdit(self)
|
||||||
self.log_text.setReadOnly(True)
|
self.log_text.setReadOnly(True)
|
||||||
self.log_text.setUndoRedoEnabled(False)
|
self.log_text.setUndoRedoEnabled(False)
|
||||||
self.log_text.setFrameStyle(QTextEdit.NoFrame)
|
self.log_text.setFrameStyle(QFrame.Shape.NoFrame)
|
||||||
self.log_text.setStyleSheet("QTextEdit { border: none; }")
|
self.log_text.setStyleSheet("QTextEdit { border: none; }")
|
||||||
self.log_text.hide()
|
self.log_text.hide()
|
||||||
container_layout.addWidget(self.log_text, 1)
|
container_layout.addWidget(self.log_text, 1)
|
||||||
@@ -884,11 +892,11 @@ class abogen(QWidget):
|
|||||||
speed_layout = QVBoxLayout()
|
speed_layout = QVBoxLayout()
|
||||||
speed_layout.setSpacing(2)
|
speed_layout.setSpacing(2)
|
||||||
speed_layout.addWidget(QLabel("Speed:", self))
|
speed_layout.addWidget(QLabel("Speed:", self))
|
||||||
self.speed_slider = QSlider(Qt.Horizontal, self)
|
self.speed_slider = QSlider(Qt.Orientation.Horizontal, self)
|
||||||
self.speed_slider.setMinimum(10)
|
self.speed_slider.setMinimum(10)
|
||||||
self.speed_slider.setMaximum(200)
|
self.speed_slider.setMaximum(200)
|
||||||
self.speed_slider.setValue(100)
|
self.speed_slider.setValue(100)
|
||||||
self.speed_slider.setTickPosition(QSlider.TicksBelow)
|
self.speed_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
|
||||||
self.speed_slider.setTickInterval(5)
|
self.speed_slider.setTickInterval(5)
|
||||||
self.speed_slider.setSingleStep(5)
|
self.speed_slider.setSingleStep(5)
|
||||||
speed_layout.addWidget(self.speed_slider)
|
speed_layout.addWidget(self.speed_slider)
|
||||||
@@ -910,7 +918,7 @@ class abogen(QWidget):
|
|||||||
"The first character represents the language:\n"
|
"The first character represents the language:\n"
|
||||||
'"a" => American English\n"b" => British English\n"e" => Spanish\n"f" => French\n"h" => Hindi\n"i" => Italian\n"j" => Japanese\n"p" => Brazilian Portuguese\n"z" => Mandarin Chinese\nThe second character represents the gender:\n"m" => Male\n"f" => Female'
|
'"a" => American English\n"b" => British English\n"e" => Spanish\n"f" => French\n"h" => Hindi\n"i" => Italian\n"j" => Japanese\n"p" => Brazilian Portuguese\n"z" => Mandarin Chinese\nThe second character represents the gender:\n"m" => Male\n"f" => Female'
|
||||||
)
|
)
|
||||||
self.voice_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.voice_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
voice_layout.addWidget(self.voice_combo)
|
voice_layout.addWidget(self.voice_combo)
|
||||||
# Voice formula button
|
# Voice formula button
|
||||||
self.btn_voice_formula_mixer = QPushButton(self)
|
self.btn_voice_formula_mixer = QPushButton(self)
|
||||||
@@ -925,11 +933,11 @@ class abogen(QWidget):
|
|||||||
# Play/Stop icons
|
# Play/Stop icons
|
||||||
def make_icon(color, shape):
|
def make_icon(color, shape):
|
||||||
pix = QPixmap(20, 20)
|
pix = QPixmap(20, 20)
|
||||||
pix.fill(Qt.transparent)
|
pix.fill(Qt.GlobalColor.transparent)
|
||||||
p = QPainter(pix)
|
p = QPainter(pix)
|
||||||
p.setRenderHint(QPainter.Antialiasing)
|
p.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||||
p.setBrush(QColor(*color))
|
p.setBrush(QColor(*color))
|
||||||
p.setPen(Qt.NoPen)
|
p.setPen(Qt.PenStyle.NoPen)
|
||||||
if shape == "play":
|
if shape == "play":
|
||||||
pts = [
|
pts = [
|
||||||
pix.rect().topLeft() + QPoint(4, 2),
|
pix.rect().topLeft() + QPoint(4, 2),
|
||||||
@@ -983,7 +991,7 @@ class abogen(QWidget):
|
|||||||
self.subtitle_combo.setStyleSheet(
|
self.subtitle_combo.setStyleSheet(
|
||||||
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
||||||
)
|
)
|
||||||
self.subtitle_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.subtitle_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.subtitle_combo.setCurrentText(self.subtitle_mode)
|
self.subtitle_combo.setCurrentText(self.subtitle_mode)
|
||||||
self.subtitle_combo.currentTextChanged.connect(self.on_subtitle_mode_changed)
|
self.subtitle_combo.currentTextChanged.connect(self.on_subtitle_mode_changed)
|
||||||
# Enable/disable subtitle options based on selected language (profile or voice)
|
# Enable/disable subtitle options based on selected language (profile or voice)
|
||||||
@@ -1001,7 +1009,7 @@ class abogen(QWidget):
|
|||||||
self.format_combo.setStyleSheet(
|
self.format_combo.setStyleSheet(
|
||||||
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
||||||
)
|
)
|
||||||
self.format_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.format_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
# Add items with display labels and underlying keys
|
# Add items with display labels and underlying keys
|
||||||
for key, label in [
|
for key, label in [
|
||||||
("wav", "wav"),
|
("wav", "wav"),
|
||||||
@@ -1032,7 +1040,7 @@ class abogen(QWidget):
|
|||||||
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
||||||
)
|
)
|
||||||
self.subtitle_format_combo.setSizePolicy(
|
self.subtitle_format_combo.setSizePolicy(
|
||||||
QSizePolicy.Expanding, QSizePolicy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
for value, text in SUBTITLE_FORMATS:
|
for value, text in SUBTITLE_FORMATS:
|
||||||
self.subtitle_format_combo.addItem(text, value)
|
self.subtitle_format_combo.addItem(text, value)
|
||||||
@@ -1079,7 +1087,7 @@ class abogen(QWidget):
|
|||||||
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
||||||
)
|
)
|
||||||
self.replace_newlines_combo.setSizePolicy(
|
self.replace_newlines_combo.setSizePolicy(
|
||||||
QSizePolicy.Expanding, QSizePolicy.Fixed
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed
|
||||||
)
|
)
|
||||||
# Set initial value based on config
|
# Set initial value based on config
|
||||||
self.replace_newlines_combo.setCurrentIndex(
|
self.replace_newlines_combo.setCurrentIndex(
|
||||||
@@ -1106,7 +1114,7 @@ class abogen(QWidget):
|
|||||||
self.save_combo.setStyleSheet(
|
self.save_combo.setStyleSheet(
|
||||||
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
"QComboBox { min-height: 20px; padding: 6px 12px; }"
|
||||||
)
|
)
|
||||||
self.save_combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
self.save_combo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.save_combo.setCurrentText(self.save_option)
|
self.save_combo.setCurrentText(self.save_option)
|
||||||
self.save_combo.currentTextChanged.connect(self.on_save_option_changed)
|
self.save_combo.currentTextChanged.connect(self.on_save_option_changed)
|
||||||
save_layout.addWidget(self.save_combo)
|
save_layout.addWidget(self.save_combo)
|
||||||
@@ -1121,7 +1129,7 @@ class abogen(QWidget):
|
|||||||
save_path_row.addWidget(selected_folder_label)
|
save_path_row.addWidget(selected_folder_label)
|
||||||
self.save_path_label = QLabel("", self.save_path_row_widget)
|
self.save_path_label = QLabel("", self.save_path_row_widget)
|
||||||
self.save_path_label.setStyleSheet(f"QLabel {{ color: {COLORS['GREEN']}; }}")
|
self.save_path_label.setStyleSheet(f"QLabel {{ color: {COLORS['GREEN']}; }}")
|
||||||
self.save_path_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
self.save_path_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||||
save_path_row.addWidget(self.save_path_label)
|
save_path_row.addWidget(self.save_path_label)
|
||||||
self.save_path_row_widget.hide() # Hide the whole row by default
|
self.save_path_row_widget.hide() # Hide the whole row by default
|
||||||
controls_layout.addWidget(self.save_path_row_widget)
|
controls_layout.addWidget(self.save_path_row_widget)
|
||||||
@@ -1164,9 +1172,10 @@ class abogen(QWidget):
|
|||||||
self.btn_start.setFixedHeight(60)
|
self.btn_start.setFixedHeight(60)
|
||||||
self.btn_start.clicked.connect(self.start_conversion)
|
self.btn_start.clicked.connect(self.start_conversion)
|
||||||
controls_layout.addWidget(self.btn_start)
|
controls_layout.addWidget(self.btn_start)
|
||||||
|
# Add controls to a container widget
|
||||||
self.controls_widget = QWidget()
|
self.controls_widget = QWidget()
|
||||||
self.controls_widget.setLayout(controls_layout)
|
self.controls_widget.setLayout(controls_layout)
|
||||||
self.controls_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
|
self.controls_widget.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
||||||
container_layout.addWidget(self.controls_widget)
|
container_layout.addWidget(self.controls_widget)
|
||||||
# Progress bar
|
# Progress bar
|
||||||
self.progress_bar = QProgressBar(self)
|
self.progress_bar = QProgressBar(self)
|
||||||
@@ -1175,7 +1184,7 @@ class abogen(QWidget):
|
|||||||
container_layout.addWidget(self.progress_bar)
|
container_layout.addWidget(self.progress_bar)
|
||||||
# ETR Label
|
# ETR Label
|
||||||
self.etr_label = QLabel("Estimated time remaining: Calculating...", self)
|
self.etr_label = QLabel("Estimated time remaining: Calculating...", self)
|
||||||
self.etr_label.setAlignment(Qt.AlignCenter)
|
self.etr_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.etr_label.hide()
|
self.etr_label.hide()
|
||||||
container_layout.addWidget(self.etr_label)
|
container_layout.addWidget(self.etr_label)
|
||||||
# Cancel button
|
# Cancel button
|
||||||
@@ -1274,12 +1283,12 @@ class abogen(QWidget):
|
|||||||
checked_chapters=self.selected_chapters,
|
checked_chapters=self.selected_chapters,
|
||||||
parent=self
|
parent=self
|
||||||
)
|
)
|
||||||
dialog.setWindowModality(Qt.NonModal)
|
dialog.setWindowModality(Qt.WindowModality.NonModal)
|
||||||
dialog.setModal(False)
|
dialog.setModal(False)
|
||||||
dialog.show() # We'll handle the dialog result asynchronously
|
dialog.show() # We'll handle the dialog result asynchronously
|
||||||
|
|
||||||
def on_dialog_finished(result):
|
def on_dialog_finished(result):
|
||||||
if result != QDialog.Accepted:
|
if result != QDialog.DialogCode.Accepted:
|
||||||
return False
|
return False
|
||||||
chapters_text, all_checked_hrefs = dialog.get_selected_text()
|
chapters_text, all_checked_hrefs = dialog.get_selected_text()
|
||||||
if not all_checked_hrefs:
|
if not all_checked_hrefs:
|
||||||
@@ -1319,7 +1328,7 @@ class abogen(QWidget):
|
|||||||
if self.save_as_project:
|
if self.save_as_project:
|
||||||
# Get project directory from user
|
# Get project directory from user
|
||||||
project_dir = QFileDialog.getExistingDirectory(
|
project_dir = QFileDialog.getExistingDirectory(
|
||||||
self, "Select Project Folder", "", QFileDialog.ShowDirsOnly
|
self, "Select Project Folder", "", QFileDialog.Option.ShowDirsOnly
|
||||||
)
|
)
|
||||||
if not project_dir:
|
if not project_dir:
|
||||||
# User cancelled, fallback to cache
|
# User cancelled, fallback to cache
|
||||||
@@ -1445,7 +1454,7 @@ class abogen(QWidget):
|
|||||||
dialog.non_cache_file_path = edit_file
|
dialog.non_cache_file_path = edit_file
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
text = dialog.get_text()
|
text = dialog.get_text()
|
||||||
if not text.strip():
|
if not text.strip():
|
||||||
self._show_error_message_box("Textbox Error", "Text cannot be empty.")
|
self._show_error_message_box("Textbox Error", "Text cannot be empty.")
|
||||||
@@ -1605,7 +1614,7 @@ class abogen(QWidget):
|
|||||||
at_bottom = sb.value() == sb.maximum()
|
at_bottom = sb.value() == sb.maximum()
|
||||||
|
|
||||||
cursor = txt.textCursor()
|
cursor = txt.textCursor()
|
||||||
cursor.movePosition(QTextCursor.End)
|
cursor.movePosition(QTextCursor.MoveOperation.End)
|
||||||
|
|
||||||
fmt = cursor.charFormat()
|
fmt = cursor.charFormat()
|
||||||
if isinstance(message, tuple):
|
if isinstance(message, tuple):
|
||||||
@@ -1624,7 +1633,7 @@ class abogen(QWidget):
|
|||||||
end = doc.findBlockByNumber(excess).position()
|
end = doc.findBlockByNumber(excess).position()
|
||||||
trim_cursor = QTextCursor(doc)
|
trim_cursor = QTextCursor(doc)
|
||||||
trim_cursor.setPosition(start)
|
trim_cursor.setPosition(start)
|
||||||
trim_cursor.setPosition(end, QTextCursor.KeepAnchor)
|
trim_cursor.setPosition(end, QTextCursor.MoveMode.KeepAnchor)
|
||||||
trim_cursor.removeSelectedText()
|
trim_cursor.removeSelectedText()
|
||||||
|
|
||||||
if at_bottom:
|
if at_bottom:
|
||||||
@@ -1743,6 +1752,8 @@ class abogen(QWidget):
|
|||||||
total_char_count=self.char_count,
|
total_char_count=self.char_count,
|
||||||
replace_single_newlines=self.replace_single_newlines,
|
replace_single_newlines=self.replace_single_newlines,
|
||||||
save_base_path=save_base_path,
|
save_base_path=save_base_path,
|
||||||
|
save_chapters_separately=getattr(self, "save_chapters_separately", None),
|
||||||
|
merge_chapters_at_end=getattr(self, "merge_chapters_at_end", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prevent adding duplicate items to the queue
|
# Prevent adding duplicate items to the queue
|
||||||
@@ -1760,6 +1771,10 @@ class abogen(QWidget):
|
|||||||
== item_queue.replace_single_newlines
|
== item_queue.replace_single_newlines
|
||||||
and getattr(queued_item, "save_base_path", None)
|
and getattr(queued_item, "save_base_path", None)
|
||||||
== item_queue.save_base_path
|
== item_queue.save_base_path
|
||||||
|
and getattr(queued_item, "save_chapters_separately", None)
|
||||||
|
== item_queue.save_chapters_separately
|
||||||
|
and getattr(queued_item, "merge_chapters_at_end", None)
|
||||||
|
== item_queue.merge_chapters_at_end
|
||||||
):
|
):
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, "Duplicate Item", "This item is already in the queue."
|
self, "Duplicate Item", "This item is already in the queue."
|
||||||
@@ -1779,10 +1794,10 @@ class abogen(QWidget):
|
|||||||
self,
|
self,
|
||||||
"Confirm Clear Queue",
|
"Confirm Clear Queue",
|
||||||
f"Are you sure you want to clear {len(self.queued_items)} items from the queue?",
|
f"Are you sure you want to clear {len(self.queued_items)} items from the queue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
self.queued_items = []
|
self.queued_items = []
|
||||||
self.enable_disable_queue_buttons()
|
self.enable_disable_queue_buttons()
|
||||||
@@ -1790,7 +1805,7 @@ class abogen(QWidget):
|
|||||||
def manage_queue(self):
|
def manage_queue(self):
|
||||||
# show a dialog to manage the queue
|
# show a dialog to manage the queue
|
||||||
dialog = QueueManager(self, self.queued_items)
|
dialog = QueueManager(self, self.queued_items)
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
self.queued_items = dialog.get_queue()
|
self.queued_items = dialog.get_queue()
|
||||||
# re-enable/disable buttons based on queue state
|
# re-enable/disable buttons based on queue state
|
||||||
self.enable_disable_queue_buttons()
|
self.enable_disable_queue_buttons()
|
||||||
@@ -2057,7 +2072,7 @@ class abogen(QWidget):
|
|||||||
dialog.setLayout(layout)
|
dialog.setLayout(layout)
|
||||||
dialog.setMinimumSize(400, 300)
|
dialog.setMinimumSize(400, 300)
|
||||||
dialog.setSizeGripEnabled(True) # Allow resizing
|
dialog.setSizeGripEnabled(True) # Allow resizing
|
||||||
dialog.exec_()
|
dialog.exec()
|
||||||
|
|
||||||
def on_conversion_finished(self, message, output_path):
|
def on_conversion_finished(self, message, output_path):
|
||||||
prevent_sleep_end()
|
prevent_sleep_end()
|
||||||
@@ -2516,14 +2531,14 @@ class abogen(QWidget):
|
|||||||
|
|
||||||
def _show_error_message_box(self, title, message):
|
def _show_error_message_box(self, title, message):
|
||||||
box = QMessageBox(self)
|
box = QMessageBox(self)
|
||||||
box.setIcon(QMessageBox.Critical)
|
box.setIcon(QMessageBox.Icon.Critical)
|
||||||
box.setWindowTitle(title)
|
box.setWindowTitle(title)
|
||||||
box.setText(message)
|
box.setText(message)
|
||||||
copy_btn = QPushButton("Copy")
|
copy_btn = QPushButton("Copy")
|
||||||
box.addButton(copy_btn, QMessageBox.ActionRole)
|
box.addButton(copy_btn, QMessageBox.ButtonRole.ActionRole)
|
||||||
box.addButton(QMessageBox.Ok)
|
box.addButton(QMessageBox.StandardButton.Ok)
|
||||||
copy_btn.clicked.connect(lambda: QApplication.clipboard().setText(message))
|
copy_btn.clicked.connect(lambda: QApplication.clipboard().setText(message))
|
||||||
box.exec_()
|
box.exec()
|
||||||
|
|
||||||
def _show_preview_error_box(self, msg):
|
def _show_preview_error_box(self, msg):
|
||||||
self._show_error_message_box("Preview Error", f"Preview error: {msg}")
|
self._show_error_message_box("Preview Error", f"Preview error: {msg}")
|
||||||
@@ -2551,14 +2566,14 @@ class abogen(QWidget):
|
|||||||
def cancel_conversion(self):
|
def cancel_conversion(self):
|
||||||
if self.is_converting:
|
if self.is_converting:
|
||||||
box = QMessageBox(self)
|
box = QMessageBox(self)
|
||||||
box.setIcon(QMessageBox.Warning)
|
box.setIcon(QMessageBox.Icon.Warning)
|
||||||
box.setWindowTitle("Cancel Conversion")
|
box.setWindowTitle("Cancel Conversion")
|
||||||
box.setText(
|
box.setText(
|
||||||
"A conversion is currently running. Are you sure you want to cancel?"
|
"A conversion is currently running. Are you sure you want to cancel?"
|
||||||
)
|
)
|
||||||
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
box.setDefaultButton(QMessageBox.No)
|
box.setDefaultButton(QMessageBox.StandardButton.No)
|
||||||
if box.exec_() != QMessageBox.Yes:
|
if box.exec() != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if (
|
if (
|
||||||
@@ -2643,7 +2658,7 @@ class abogen(QWidget):
|
|||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
|
|
||||||
def on_gpu_setting_changed(self, state):
|
def on_gpu_setting_changed(self, state):
|
||||||
self.use_gpu = state == Qt.Checked
|
self.use_gpu = state == Qt.CheckState.Checked.value
|
||||||
self.config["use_gpu"] = self.use_gpu
|
self.config["use_gpu"] = self.use_gpu
|
||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
|
|
||||||
@@ -2660,14 +2675,14 @@ class abogen(QWidget):
|
|||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
if self.is_converting:
|
if self.is_converting:
|
||||||
box = QMessageBox(self)
|
box = QMessageBox(self)
|
||||||
box.setIcon(QMessageBox.Warning)
|
box.setIcon(QMessageBox.Icon.Warning)
|
||||||
box.setWindowTitle("Conversion in Progress")
|
box.setWindowTitle("Conversion in Progress")
|
||||||
box.setText(
|
box.setText(
|
||||||
"A conversion is currently running. Are you sure you want to exit?"
|
"A conversion is currently running. Are you sure you want to exit?"
|
||||||
)
|
)
|
||||||
box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
box.setDefaultButton(QMessageBox.No)
|
box.setDefaultButton(QMessageBox.StandardButton.No)
|
||||||
if box.exec_() == QMessageBox.Yes:
|
if box.exec() == QMessageBox.StandardButton.Yes:
|
||||||
self.cleanup_conversion_thread()
|
self.cleanup_conversion_thread()
|
||||||
event.accept()
|
event.accept()
|
||||||
else:
|
else:
|
||||||
@@ -2681,10 +2696,10 @@ class abogen(QWidget):
|
|||||||
from abogen.conversion import ChapterOptionsDialog
|
from abogen.conversion import ChapterOptionsDialog
|
||||||
|
|
||||||
dialog = ChapterOptionsDialog(chapter_count, parent=self)
|
dialog = ChapterOptionsDialog(chapter_count, parent=self)
|
||||||
dialog.setWindowModality(Qt.ApplicationModal)
|
dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
|
||||||
# If dialog is accepted, pass the options to the conversion thread
|
# If dialog is accepted, pass the options to the conversion thread
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
options = dialog.get_options()
|
options = dialog.get_options()
|
||||||
if (
|
if (
|
||||||
hasattr(self, "conversion_thread")
|
hasattr(self, "conversion_thread")
|
||||||
@@ -2696,8 +2711,7 @@ class abogen(QWidget):
|
|||||||
self.cancel_conversion()
|
self.cancel_conversion()
|
||||||
|
|
||||||
def apply_theme(self, theme):
|
def apply_theme(self, theme):
|
||||||
from PyQt5.QtGui import QPalette, QColor
|
|
||||||
from PyQt5.QtWidgets import QStyleFactory
|
|
||||||
|
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
is_windows = platform.system() == "Windows"
|
is_windows = platform.system() == "Windows"
|
||||||
@@ -2981,7 +2995,7 @@ class abogen(QWidget):
|
|||||||
about_action.triggered.connect(self.show_about_dialog)
|
about_action.triggered.connect(self.show_about_dialog)
|
||||||
menu.addAction(about_action)
|
menu.addAction(about_action)
|
||||||
|
|
||||||
menu.exec_(self.settings_btn.mapToGlobal(QPoint(0, self.settings_btn.height())))
|
menu.exec(self.settings_btn.mapToGlobal(QPoint(0, self.settings_btn.height())))
|
||||||
|
|
||||||
def toggle_replace_single_newlines(self, enabled):
|
def toggle_replace_single_newlines(self, enabled):
|
||||||
self.replace_single_newlines = enabled
|
self.replace_single_newlines = enabled
|
||||||
@@ -2989,7 +3003,7 @@ class abogen(QWidget):
|
|||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
|
|
||||||
def restart_app(self):
|
def restart_app(self):
|
||||||
from PyQt5.QtCore import QProcess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
exe = sys.executable
|
exe = sys.executable
|
||||||
@@ -3022,10 +3036,10 @@ class abogen(QWidget):
|
|||||||
self,
|
self,
|
||||||
"Restart Required",
|
"Restart Required",
|
||||||
message,
|
message,
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
self.config["disable_kokoro_internet"] = disabled
|
self.config["disable_kokoro_internet"] = disabled
|
||||||
save_config(self.config)
|
save_config(self.config)
|
||||||
try:
|
try:
|
||||||
@@ -3040,10 +3054,10 @@ class abogen(QWidget):
|
|||||||
self,
|
self,
|
||||||
"Reset Settings",
|
"Reset Settings",
|
||||||
"This will reset all settings to their default values and restart the application.\n\nDo you want to continue?",
|
"This will reset all settings to their default values and restart the application.\n\nDo you want to continue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
from abogen.utils import get_user_config_path
|
from abogen.utils import get_user_config_path
|
||||||
|
|
||||||
config_path = get_user_config_path()
|
config_path = get_user_config_path()
|
||||||
@@ -3204,9 +3218,9 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
self,
|
self,
|
||||||
"Install Application Entry",
|
"Install Application Entry",
|
||||||
"Install application entry for current user?",
|
"Install application entry for current user?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
user_app_dir = os.path.expanduser("~/.local/share/applications")
|
user_app_dir = os.path.expanduser("~/.local/share/applications")
|
||||||
@@ -3266,12 +3280,11 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
initial_state = entry.get("voices", [])
|
initial_state = entry.get("voices", [])
|
||||||
else:
|
else:
|
||||||
initial_state = entry
|
initial_state = entry
|
||||||
else:
|
self.selected_lang = entry[0][0] if entry and entry[0] else None
|
||||||
initial_state = []
|
|
||||||
dialog = VoiceFormulaDialog(
|
dialog = VoiceFormulaDialog(
|
||||||
self, initial_state=initial_state, selected_profile=selected_profile
|
self, initial_state=initial_state, selected_profile=selected_profile
|
||||||
)
|
)
|
||||||
if dialog.exec_() == QDialog.Accepted:
|
if dialog.exec() == QDialog.DialogCode.Accepted:
|
||||||
if dialog.current_profile:
|
if dialog.current_profile:
|
||||||
self.selected_profile_name = dialog.current_profile
|
self.selected_profile_name = dialog.current_profile
|
||||||
self.config["selected_profile_name"] = dialog.current_profile
|
self.config["selected_profile_name"] = dialog.current_profile
|
||||||
@@ -3292,7 +3305,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
# Create custom dialog
|
# Create custom dialog
|
||||||
dialog = QDialog(self)
|
dialog = QDialog(self)
|
||||||
dialog.setWindowTitle(f"About {PROGRAM_NAME}")
|
dialog.setWindowTitle(f"About {PROGRAM_NAME}")
|
||||||
dialog.setWindowFlags(dialog.windowFlags() & ~Qt.WindowContextHelpButtonHint)
|
dialog.setWindowFlags(dialog.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
|
||||||
dialog.setFixedSize(400, 320) # Increased height for new button
|
dialog.setFixedSize(400, 320) # Increased height for new button
|
||||||
|
|
||||||
layout = QVBoxLayout(dialog)
|
layout = QVBoxLayout(dialog)
|
||||||
@@ -3314,7 +3327,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
title_label = QLabel(
|
title_label = QLabel(
|
||||||
f"<h1 style='margin-bottom: 0;'>{PROGRAM_NAME} <span style='font-size: 12px; font-weight: normal; color: #666;'>v{VERSION}</span></h1><h3 style='margin-top: 5px;'>Audiobook Generator</h3>"
|
f"<h1 style='margin-bottom: 0;'>{PROGRAM_NAME} <span style='font-size: 12px; font-weight: normal; color: #666;'>v{VERSION}</span></h1><h3 style='margin-top: 5px;'>Audiobook Generator</h3>"
|
||||||
)
|
)
|
||||||
title_label.setTextFormat(Qt.RichText)
|
title_label.setTextFormat(Qt.TextFormat.RichText)
|
||||||
header_layout.addWidget(title_label, 1)
|
header_layout.addWidget(title_label, 1)
|
||||||
layout.addLayout(header_layout)
|
layout.addLayout(header_layout)
|
||||||
|
|
||||||
@@ -3323,7 +3336,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
f"<p>{PROGRAM_DESCRIPTION}</p>"
|
f"<p>{PROGRAM_DESCRIPTION}</p>"
|
||||||
"<p>Visit the GitHub repository for updates, documentation, and to report issues.</p>"
|
"<p>Visit the GitHub repository for updates, documentation, and to report issues.</p>"
|
||||||
)
|
)
|
||||||
desc_label.setTextFormat(Qt.RichText)
|
desc_label.setTextFormat(Qt.TextFormat.RichText)
|
||||||
desc_label.setWordWrap(True)
|
desc_label.setWordWrap(True)
|
||||||
layout.addWidget(desc_label)
|
layout.addWidget(desc_label)
|
||||||
|
|
||||||
@@ -3346,7 +3359,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
close_btn.setFixedHeight(32)
|
close_btn.setFixedHeight(32)
|
||||||
layout.addWidget(close_btn)
|
layout.addWidget(close_btn)
|
||||||
|
|
||||||
dialog.exec_()
|
dialog.exec()
|
||||||
|
|
||||||
def manual_check_for_updates(self):
|
def manual_check_for_updates(self):
|
||||||
"""Manually check for updates and always show result"""
|
"""Manually check for updates and always show result"""
|
||||||
@@ -3359,7 +3372,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
|
|
||||||
def show_update_message(remote_version, local_version):
|
def show_update_message(remote_version, local_version):
|
||||||
msg_box = QMessageBox(self)
|
msg_box = QMessageBox(self)
|
||||||
msg_box.setIcon(QMessageBox.Information)
|
msg_box.setIcon(QMessageBox.Icon.Information)
|
||||||
msg_box.setWindowTitle("Update Available")
|
msg_box.setWindowTitle("Update Available")
|
||||||
msg_box.setText(
|
msg_box.setText(
|
||||||
f"A new version of {PROGRAM_NAME} is available! ({local_version} > {remote_version})"
|
f"A new version of {PROGRAM_NAME} is available! ({local_version} > {remote_version})"
|
||||||
@@ -3371,9 +3384,9 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
"Alternatively, visit the GitHub repository for more information. "
|
"Alternatively, visit the GitHub repository for more information. "
|
||||||
"Would you like to view the changelog?"
|
"Would you like to view the changelog?"
|
||||||
)
|
)
|
||||||
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
msg_box.setDefaultButton(QMessageBox.Yes)
|
msg_box.setDefaultButton(QMessageBox.StandardButton.Yes)
|
||||||
if msg_box.exec_() == QMessageBox.Yes:
|
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
||||||
try:
|
try:
|
||||||
QDesktopServices.openUrl(QUrl(GITHUB_URL + "/releases/latest"))
|
QDesktopServices.openUrl(QUrl(GITHUB_URL + "/releases/latest"))
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -3455,7 +3468,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
|
|
||||||
# Create a custom message box with checkbox
|
# Create a custom message box with checkbox
|
||||||
msg_box = QMessageBox(self)
|
msg_box = QMessageBox(self)
|
||||||
msg_box.setIcon(QMessageBox.Question)
|
msg_box.setIcon(QMessageBox.Icon.Question)
|
||||||
msg_box.setWindowTitle("Clear Cache Files")
|
msg_box.setWindowTitle("Clear Cache Files")
|
||||||
|
|
||||||
msg_text = f"Found {file_count} cache file{'s' if file_count != 1 else ''} in the {PROGRAM_NAME} cache folder."
|
msg_text = f"Found {file_count} cache file{'s' if file_count != 1 else ''} in the {PROGRAM_NAME} cache folder."
|
||||||
@@ -3474,10 +3487,10 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
msg_box.setCheckBox(preview_cache_checkbox)
|
msg_box.setCheckBox(preview_cache_checkbox)
|
||||||
|
|
||||||
# Add buttons
|
# Add buttons
|
||||||
msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
msg_box.setDefaultButton(QMessageBox.Yes)
|
msg_box.setDefaultButton(QMessageBox.StandardButton.Yes)
|
||||||
|
|
||||||
if msg_box.exec_() != QMessageBox.Yes:
|
if msg_box.exec() != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Delete the text files
|
# Delete the text files
|
||||||
@@ -3522,7 +3535,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
|
|
||||||
def set_max_log_lines(self):
|
def set_max_log_lines(self):
|
||||||
"""Open a dialog to set the maximum lines in the log window."""
|
"""Open a dialog to set the maximum lines in the log window."""
|
||||||
from PyQt5.QtWidgets import QInputDialog
|
from PyQt6.QtWidgets import QInputDialog
|
||||||
|
|
||||||
value, ok = QInputDialog.getInt(
|
value, ok = QInputDialog.getInt(
|
||||||
self,
|
self,
|
||||||
@@ -3545,7 +3558,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
|
|
||||||
def set_max_subtitle_words(self):
|
def set_max_subtitle_words(self):
|
||||||
"""Open a dialog to set the maximum words per subtitle"""
|
"""Open a dialog to set the maximum words per subtitle"""
|
||||||
from PyQt5.QtWidgets import QInputDialog
|
from PyQt6.QtWidgets import QInputDialog
|
||||||
|
|
||||||
current_value = self.config.get("max_subtitle_words", 50)
|
current_value = self.config.get("max_subtitle_words", 50)
|
||||||
|
|
||||||
@@ -3574,7 +3587,7 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
|
|
||||||
def set_silence_between_chapters(self):
|
def set_silence_between_chapters(self):
|
||||||
"""Open a dialog to set the silence duration between chapters"""
|
"""Open a dialog to set the silence duration between chapters"""
|
||||||
from PyQt5.QtWidgets import QInputDialog, QDialog
|
|
||||||
|
|
||||||
current_value = self.config.get("silence_duration", 2.0)
|
current_value = self.config.get("silence_duration", 2.0)
|
||||||
|
|
||||||
@@ -3583,14 +3596,14 @@ Categories=AudioVideo;Audio;Utility;
|
|||||||
dlg.setLabelText(
|
dlg.setLabelText(
|
||||||
"Enter the duration of silence\nbetween chapters (in seconds):"
|
"Enter the duration of silence\nbetween chapters (in seconds):"
|
||||||
)
|
)
|
||||||
dlg.setInputMode(QInputDialog.DoubleInput)
|
dlg.setInputMode(QInputDialog.InputMode.DoubleInput)
|
||||||
dlg.setDoubleDecimals(1)
|
dlg.setDoubleDecimals(1)
|
||||||
dlg.setDoubleMinimum(0.0)
|
dlg.setDoubleMinimum(0.0)
|
||||||
dlg.setDoubleMaximum(60.0)
|
dlg.setDoubleMaximum(60.0)
|
||||||
dlg.setDoubleValue(current_value)
|
dlg.setDoubleValue(current_value)
|
||||||
dlg.setDoubleStep(0.1) # <-- set step to 0.1
|
dlg.setDoubleStep(0.1) # <-- set step to 0.1
|
||||||
|
|
||||||
if dlg.exec_() == QDialog.Accepted:
|
if dlg.exec() == QDialog.DialogCode.Accepted:
|
||||||
value = dlg.doubleValue()
|
value = dlg.doubleValue()
|
||||||
# Round to one decimal to avoid floating-point representation noise
|
# Round to one decimal to avoid floating-point representation noise
|
||||||
value = round(value, 1)
|
value = round(value, 1)
|
||||||
|
|||||||
+22
-8
@@ -7,19 +7,24 @@ import signal
|
|||||||
|
|
||||||
# Qt platform plugin detection (fixes #59)
|
# Qt platform plugin detection (fixes #59)
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtCore import QLibraryInfo
|
from PyQt6.QtCore import QLibraryInfo
|
||||||
plugins = QLibraryInfo.location(QLibraryInfo.PluginsPath)
|
|
||||||
|
# Get the path to the plugins directory
|
||||||
|
plugins = QLibraryInfo.path(QLibraryInfo.LibraryPath.PluginsPath)
|
||||||
|
|
||||||
# Normalize path to use the OS-native separators and absolute path
|
# Normalize path to use the OS-native separators and absolute path
|
||||||
platform_dir = os.path.normpath(os.path.join(plugins, "platforms"))
|
platform_dir = os.path.normpath(os.path.join(plugins, "platforms"))
|
||||||
|
|
||||||
# Ensure we work with an absolute path for clarity
|
# Ensure we work with an absolute path for clarity
|
||||||
platform_dir = os.path.abspath(platform_dir)
|
platform_dir = os.path.abspath(platform_dir)
|
||||||
|
|
||||||
if os.path.isdir(platform_dir):
|
if os.path.isdir(platform_dir):
|
||||||
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = platform_dir
|
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = platform_dir
|
||||||
print("QT_QPA_PLATFORM_PLUGIN_PATH set to:", platform_dir)
|
print("QT_QPA_PLATFORM_PLUGIN_PATH set to:", platform_dir)
|
||||||
else:
|
else:
|
||||||
print("PyQt5 platform plugins not found at", platform_dir)
|
print("PyQt6 platform plugins not found at", platform_dir)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("PyQt5 not installed.")
|
print("PyQt6 not installed.")
|
||||||
|
|
||||||
# Set application ID for Windows taskbar icon
|
# Set application ID for Windows taskbar icon
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
@@ -31,9 +36,16 @@ if platform.system() == "Windows":
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Warning: failed to set AppUserModelID:", e)
|
print("Warning: failed to set AppUserModelID:", e)
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt6.QtGui import QIcon
|
||||||
from PyQt5.QtCore import qInstallMessageHandler, QtMsgType
|
from PyQt6.QtCore import (
|
||||||
|
Qt,
|
||||||
|
QLibraryInfo,
|
||||||
|
qInstallMessageHandler,
|
||||||
|
QStandardPaths,
|
||||||
|
QSettings,
|
||||||
|
QtMsgType,
|
||||||
|
)
|
||||||
|
|
||||||
# Add the directory to Python path
|
# Add the directory to Python path
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
|
||||||
@@ -82,10 +94,12 @@ if platform.system() == "Darwin" and platform.processor() == "arm":
|
|||||||
|
|
||||||
# Custom message handler to filter out specific Qt warnings
|
# Custom message handler to filter out specific Qt warnings
|
||||||
def qt_message_handler(mode, context, message):
|
def qt_message_handler(mode, context, message):
|
||||||
|
# In PyQt6, the mode is an enum, so we compare with the enum members
|
||||||
if "Wayland does not support QWindow::requestActivate()" in message:
|
if "Wayland does not support QWindow::requestActivate()" in message:
|
||||||
return # Suppress this specific message
|
return # Suppress this specific message
|
||||||
if "setGrabPopup called with a parent, QtWaylandClient" in message:
|
if "setGrabPopup called with a parent, QtWaylandClient" in message:
|
||||||
return
|
return
|
||||||
|
|
||||||
if mode == QtMsgType.QtWarningMsg:
|
if mode == QtMsgType.QtWarningMsg:
|
||||||
print(f"Qt Warning: {message}")
|
print(f"Qt Warning: {message}")
|
||||||
elif mode == QtMsgType.QtCriticalMsg:
|
elif mode == QtMsgType.QtCriticalMsg:
|
||||||
@@ -129,7 +143,7 @@ def main():
|
|||||||
|
|
||||||
ex = abogen()
|
ex = abogen()
|
||||||
ex.show()
|
ex.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+66
-44
@@ -2,7 +2,7 @@
|
|||||||
# button to remove an item from the queue
|
# button to remove an item from the queue
|
||||||
# button to clear the queue
|
# button to clear the queue
|
||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
@@ -14,18 +14,20 @@ from PyQt5.QtWidgets import (
|
|||||||
QLabel,
|
QLabel,
|
||||||
QWidget,
|
QWidget,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
|
QAbstractItemView,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import QFileInfo, Qt
|
from PyQt6.QtCore import QFileInfo, Qt
|
||||||
from abogen.constants import COLORS
|
from abogen.constants import COLORS
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from PyQt5.QtGui import QFontMetrics
|
from PyQt6.QtGui import QFontMetrics
|
||||||
|
|
||||||
|
|
||||||
class ElidedLabel(QLabel):
|
class ElidedLabel(QLabel):
|
||||||
def __init__(self, text, parent=None):
|
def __init__(self, text):
|
||||||
super().__init__(text, parent)
|
super().__init__(text)
|
||||||
self._full_text = text
|
self._full_text = text
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||||
|
self.setTextFormat(Qt.TextFormat.PlainText)
|
||||||
|
|
||||||
def setText(self, text):
|
def setText(self, text):
|
||||||
self._full_text = text
|
self._full_text = text
|
||||||
@@ -34,7 +36,7 @@ class ElidedLabel(QLabel):
|
|||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
metrics = QFontMetrics(self.font())
|
metrics = QFontMetrics(self.font())
|
||||||
elided = metrics.elidedText(self._full_text, Qt.ElideRight, self.width())
|
elided = metrics.elidedText(self._full_text, Qt.TextElideMode.ElideRight, self.width())
|
||||||
super().setText(elided)
|
super().setText(elided)
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
|
|
||||||
@@ -53,8 +55,8 @@ class QueueListItemWidget(QWidget):
|
|||||||
name_label = ElidedLabel(os.path.basename(file_name))
|
name_label = ElidedLabel(os.path.basename(file_name))
|
||||||
char_label = QLabel(f"Chars: {char_count}")
|
char_label = QLabel(f"Chars: {char_count}")
|
||||||
char_label.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']};")
|
char_label.setStyleSheet(f"color: {COLORS['LIGHT_DISABLED']};")
|
||||||
char_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
char_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||||
char_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
char_label.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||||
layout.addWidget(name_label, 1)
|
layout.addWidget(name_label, 1)
|
||||||
layout.addWidget(char_label, 0)
|
layout.addWidget(char_label, 0)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
@@ -67,12 +69,12 @@ class DroppableQueueListWidget(QListWidget):
|
|||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
# Overlay for drag hover
|
# Overlay for drag hover
|
||||||
self.drag_overlay = QLabel("", self)
|
self.drag_overlay = QLabel("", self)
|
||||||
self.drag_overlay.setAlignment(Qt.AlignCenter)
|
self.drag_overlay.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.drag_overlay.setStyleSheet(
|
self.drag_overlay.setStyleSheet(
|
||||||
f"border:2px dashed {COLORS['BLUE_BORDER_HOVER']}; border-radius:5px; padding:20px; background:{COLORS['BLUE_BG_HOVER']};"
|
f"border:2px dashed {COLORS['BLUE_BORDER_HOVER']}; border-radius:5px; padding:20px; background:{COLORS['BLUE_BG_HOVER']};"
|
||||||
)
|
)
|
||||||
self.drag_overlay.setVisible(False)
|
self.drag_overlay.setVisible(False)
|
||||||
self.drag_overlay.setAttribute(Qt.WA_TransparentForMouseEvents, True)
|
self.drag_overlay.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
|
||||||
|
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
if event.mimeData().hasUrls():
|
if event.mimeData().hasUrls():
|
||||||
@@ -132,9 +134,9 @@ class QueueManager(QDialog):
|
|||||||
layout.setSpacing(12) # set spacing between widgets in main layout
|
layout.setSpacing(12) # set spacing between widgets in main layout
|
||||||
# list of queued items
|
# list of queued items
|
||||||
self.listwidget = DroppableQueueListWidget(self)
|
self.listwidget = DroppableQueueListWidget(self)
|
||||||
self.listwidget.setSelectionMode(QListWidget.ExtendedSelection)
|
self.listwidget.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
self.listwidget.setAlternatingRowColors(True)
|
self.listwidget.setAlternatingRowColors(True)
|
||||||
self.listwidget.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.listwidget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.listwidget.customContextMenuRequested.connect(self.show_context_menu)
|
self.listwidget.customContextMenuRequested.connect(self.show_context_menu)
|
||||||
# Add informative instructions at the top
|
# Add informative instructions at the top
|
||||||
instructions = QLabel(
|
instructions = QLabel(
|
||||||
@@ -145,7 +147,7 @@ class QueueManager(QDialog):
|
|||||||
"Changing the main window configuration afterward <b>does not</b> affect files already in the queue. "
|
"Changing the main window configuration afterward <b>does not</b> affect files already in the queue. "
|
||||||
"You can view each file's configuration by hovering over them."
|
"You can view each file's configuration by hovering over them."
|
||||||
)
|
)
|
||||||
instructions.setAlignment(Qt.AlignLeft)
|
instructions.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
instructions.setWordWrap(True)
|
instructions.setWordWrap(True)
|
||||||
instructions.setStyleSheet("margin-bottom: 8px;")
|
instructions.setStyleSheet("margin-bottom: 8px;")
|
||||||
layout.addWidget(instructions)
|
layout.addWidget(instructions)
|
||||||
@@ -154,12 +156,12 @@ class QueueManager(QDialog):
|
|||||||
"Drag and drop your text files here or use the 'Add files' button.",
|
"Drag and drop your text files here or use the 'Add files' button.",
|
||||||
self.listwidget,
|
self.listwidget,
|
||||||
)
|
)
|
||||||
self.empty_overlay.setAlignment(Qt.AlignCenter)
|
self.empty_overlay.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.empty_overlay.setStyleSheet(
|
self.empty_overlay.setStyleSheet(
|
||||||
f"color: {COLORS['LIGHT_DISABLED']}; background: transparent; padding: 20px;"
|
f"color: {COLORS['LIGHT_DISABLED']}; background: transparent; padding: 20px;"
|
||||||
)
|
)
|
||||||
self.empty_overlay.setWordWrap(True)
|
self.empty_overlay.setWordWrap(True)
|
||||||
self.empty_overlay.setAttribute(Qt.WA_TransparentForMouseEvents, True)
|
self.empty_overlay.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
|
||||||
self.empty_overlay.hide()
|
self.empty_overlay.hide()
|
||||||
# add queue items to the list
|
# add queue items to the list
|
||||||
self.process_queue()
|
self.process_queue()
|
||||||
@@ -192,7 +194,7 @@ class QueueManager(QDialog):
|
|||||||
self.listwidget.currentItemChanged.connect(self.update_button_states)
|
self.listwidget.currentItemChanged.connect(self.update_button_states)
|
||||||
self.listwidget.itemSelectionChanged.connect(self.update_button_states)
|
self.listwidget.itemSelectionChanged.connect(self.update_button_states)
|
||||||
|
|
||||||
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
|
||||||
buttons.accepted.connect(self.accept)
|
buttons.accepted.connect(self.accept)
|
||||||
buttons.rejected.connect(self.reject)
|
buttons.rejected.connect(self.reject)
|
||||||
|
|
||||||
@@ -211,7 +213,6 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
self.listwidget.clear()
|
self.listwidget.clear()
|
||||||
if not self.queue:
|
if not self.queue:
|
||||||
self.empty_overlay.resize(self.listwidget.size())
|
|
||||||
self.empty_overlay.show()
|
self.empty_overlay.show()
|
||||||
self.update_button_states()
|
self.update_button_states()
|
||||||
return
|
return
|
||||||
@@ -262,10 +263,18 @@ class QueueManager(QDialog):
|
|||||||
f"<b>Characters:</b> {getattr(item, 'total_char_count', '')}<br>"
|
f"<b>Characters:</b> {getattr(item, 'total_char_count', '')}<br>"
|
||||||
f"<b>Replace Single Newlines:</b> {getattr(item, 'replace_single_newlines', False)}"
|
f"<b>Replace Single Newlines:</b> {getattr(item, 'replace_single_newlines', False)}"
|
||||||
)
|
)
|
||||||
|
# Add book handler options if present
|
||||||
|
save_chapters_separately = getattr(item, 'save_chapters_separately', None)
|
||||||
|
merge_chapters_at_end = getattr(item, 'merge_chapters_at_end', None)
|
||||||
|
if save_chapters_separately is not None:
|
||||||
|
tooltip += f"<br><b>Save chapters separately:</b> {'Yes' if save_chapters_separately else 'No'}"
|
||||||
|
# Only show merge option if saving chapters separately
|
||||||
|
if save_chapters_separately and merge_chapters_at_end is not None:
|
||||||
|
tooltip += f"<br><b>Merge chapters at the end:</b> {'Yes' if merge_chapters_at_end else 'No'}"
|
||||||
list_item.setToolTip(tooltip)
|
list_item.setToolTip(tooltip)
|
||||||
list_item.setIcon(icon)
|
list_item.setIcon(icon)
|
||||||
# Store both paths for context menu
|
# Store both paths for context menu
|
||||||
list_item.setData(Qt.UserRole, {
|
list_item.setData(Qt.ItemDataRole.UserRole, {
|
||||||
'display_path': display_file_path,
|
'display_path': display_file_path,
|
||||||
'processing_path': processing_file_path
|
'processing_path': processing_file_path
|
||||||
})
|
})
|
||||||
@@ -280,7 +289,7 @@ class QueueManager(QDialog):
|
|||||||
items = self.listwidget.selectedItems()
|
items = self.listwidget.selectedItems()
|
||||||
if not items:
|
if not items:
|
||||||
return
|
return
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
# Remove by index to ensure correct mapping
|
# Remove by index to ensure correct mapping
|
||||||
rows = sorted([self.listwidget.row(item) for item in items], reverse=True)
|
rows = sorted([self.listwidget.row(item) for item in items], reverse=True)
|
||||||
@@ -290,10 +299,10 @@ class QueueManager(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Confirm Remove",
|
"Confirm Remove",
|
||||||
f"Are you sure you want to remove {len(rows)} selected items from the queue?",
|
f"Are you sure you want to remove {len(rows)} selected items from the queue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if 0 <= row < len(self.queue):
|
if 0 <= row < len(self.queue):
|
||||||
@@ -302,17 +311,17 @@ class QueueManager(QDialog):
|
|||||||
self.update_button_states()
|
self.update_button_states()
|
||||||
|
|
||||||
def clear_queue(self):
|
def clear_queue(self):
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
if len(self.queue) > 1:
|
if len(self.queue) > 1:
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Confirm Clear Queue",
|
"Confirm Clear Queue",
|
||||||
f"Are you sure you want to clear {len(self.queue)} items from the queue?",
|
f"Are you sure you want to clear {len(self.queue)} items from the queue?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
self.queue.clear()
|
self.queue.clear()
|
||||||
self.listwidget.clear()
|
self.listwidget.clear()
|
||||||
@@ -362,6 +371,13 @@ class QueueManager(QDialog):
|
|||||||
attrs["replace_single_newlines"] = getattr(
|
attrs["replace_single_newlines"] = getattr(
|
||||||
parent, "replace_single_newlines", False
|
parent, "replace_single_newlines", False
|
||||||
)
|
)
|
||||||
|
# book handler options
|
||||||
|
attrs["save_chapters_separately"] = getattr(
|
||||||
|
parent, "save_chapters_separately", None
|
||||||
|
)
|
||||||
|
attrs["merge_chapters_at_end"] = getattr(
|
||||||
|
parent, "merge_chapters_at_end", None
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# fallback: empty values
|
# fallback: empty values
|
||||||
attrs = {
|
attrs = {
|
||||||
@@ -378,11 +394,13 @@ class QueueManager(QDialog):
|
|||||||
"replace_single_newlines",
|
"replace_single_newlines",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
attrs["save_chapters_separately"] = None
|
||||||
|
attrs["merge_chapters_at_end"] = None
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def add_files_from_paths(self, file_paths):
|
def add_files_from_paths(self, file_paths):
|
||||||
from abogen.utils import calculate_text_length
|
from abogen.utils import calculate_text_length
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
import os
|
import os
|
||||||
|
|
||||||
current_attrs = self.get_current_attributes()
|
current_attrs = self.get_current_attributes()
|
||||||
@@ -430,6 +448,10 @@ class QueueManager(QDialog):
|
|||||||
== getattr(item, "replace_single_newlines", False)
|
== getattr(item, "replace_single_newlines", False)
|
||||||
and getattr(queued_item, "save_base_path", None)
|
and getattr(queued_item, "save_base_path", None)
|
||||||
== getattr(item, "save_base_path", None)
|
== getattr(item, "save_base_path", None)
|
||||||
|
and getattr(queued_item, "save_chapters_separately", None)
|
||||||
|
== getattr(item, "save_chapters_separately", None)
|
||||||
|
and getattr(queued_item, "merge_chapters_at_end", None)
|
||||||
|
== getattr(item, "merge_chapters_at_end", None)
|
||||||
):
|
):
|
||||||
is_duplicate = True
|
is_duplicate = True
|
||||||
break
|
break
|
||||||
@@ -447,7 +469,7 @@ class QueueManager(QDialog):
|
|||||||
self.update_button_states()
|
self.update_button_states()
|
||||||
|
|
||||||
def add_more_files(self):
|
def add_more_files(self):
|
||||||
from PyQt5.QtWidgets import QFileDialog
|
from PyQt6.QtWidgets import QFileDialog
|
||||||
from abogen.utils import calculate_text_length # import the function
|
from abogen.utils import calculate_text_length # import the function
|
||||||
|
|
||||||
# Only allow .txt files
|
# Only allow .txt files
|
||||||
@@ -477,9 +499,9 @@ class QueueManager(QDialog):
|
|||||||
self.clear_button.setEnabled(bool(self.queue))
|
self.clear_button.setEnabled(bool(self.queue))
|
||||||
|
|
||||||
def show_context_menu(self, pos):
|
def show_context_menu(self, pos):
|
||||||
from PyQt5.QtWidgets import QMenu, QAction
|
from PyQt6.QtWidgets import QMenu
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt6.QtGui import QAction, QDesktopServices
|
||||||
from PyQt5.QtCore import QUrl
|
from PyQt6.QtCore import QUrl
|
||||||
import os
|
import os
|
||||||
|
|
||||||
global_pos = self.listwidget.viewport().mapToGlobal(pos)
|
global_pos = self.listwidget.viewport().mapToGlobal(pos)
|
||||||
@@ -495,10 +517,10 @@ class QueueManager(QDialog):
|
|||||||
open_file_action = QAction("Open file", self)
|
open_file_action = QAction("Open file", self)
|
||||||
|
|
||||||
def open_file():
|
def open_file():
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
item = selected_items[0]
|
item = selected_items[0]
|
||||||
paths = item.data(Qt.UserRole)
|
paths = item.data(Qt.ItemDataRole.UserRole)
|
||||||
if isinstance(paths, dict):
|
if isinstance(paths, dict):
|
||||||
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
||||||
else:
|
else:
|
||||||
@@ -524,7 +546,7 @@ class QueueManager(QDialog):
|
|||||||
# If the queued item represents a converted document (markdown, pdf, epub)
|
# If the queued item represents a converted document (markdown, pdf, epub)
|
||||||
# show two actions: Go to processed file (the cached .txt) and Go to input file (original source)
|
# show two actions: Go to processed file (the cached .txt) and Go to input file (original source)
|
||||||
item = selected_items[0]
|
item = selected_items[0]
|
||||||
paths = item.data(Qt.UserRole)
|
paths = item.data(Qt.ItemDataRole.UserRole)
|
||||||
if isinstance(paths, dict):
|
if isinstance(paths, dict):
|
||||||
display_path = paths.get('display_path', '')
|
display_path = paths.get('display_path', '')
|
||||||
processing_path = paths.get('processing_path', '')
|
processing_path = paths.get('processing_path', '')
|
||||||
@@ -539,7 +561,7 @@ class QueueManager(QDialog):
|
|||||||
isinstance(processing_path, str) and processing_path.lower().endswith(doc_exts)
|
isinstance(processing_path, str) and processing_path.lower().endswith(doc_exts)
|
||||||
)
|
)
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
def open_folder_for(path_label: str):
|
def open_folder_for(path_label: str):
|
||||||
# path_label should be either 'display' or 'processing'
|
# path_label should be either 'display' or 'processing'
|
||||||
@@ -588,7 +610,7 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
def go_to_folder():
|
def go_to_folder():
|
||||||
item = selected_items[0]
|
item = selected_items[0]
|
||||||
paths = item.data(Qt.UserRole)
|
paths = item.data(Qt.ItemDataRole.UserRole)
|
||||||
if isinstance(paths, dict):
|
if isinstance(paths, dict):
|
||||||
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
file_path = paths.get('display_path', paths.get('processing_path', ''))
|
||||||
else:
|
else:
|
||||||
@@ -618,7 +640,7 @@ class QueueManager(QDialog):
|
|||||||
clear_action = QAction("Clear Queue", self)
|
clear_action = QAction("Clear Queue", self)
|
||||||
clear_action.triggered.connect(self.clear_queue)
|
clear_action.triggered.connect(self.clear_queue)
|
||||||
menu.addAction(clear_action)
|
menu.addAction(clear_action)
|
||||||
menu.exec_(global_pos)
|
menu.exec(global_pos)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
# Accept: keep changes
|
# Accept: keep changes
|
||||||
@@ -626,7 +648,7 @@ class QueueManager(QDialog):
|
|||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
# Cancel: restore original queue
|
# Cancel: restore original queue
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt6.QtWidgets import QMessageBox
|
||||||
|
|
||||||
# Warn if user changed a lot (e.g., more than 1 items difference)
|
# Warn if user changed a lot (e.g., more than 1 items difference)
|
||||||
original_count = len(self._original_queue)
|
original_count = len(self._original_queue)
|
||||||
@@ -636,19 +658,19 @@ class QueueManager(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Confirm Cancel",
|
"Confirm Cancel",
|
||||||
f"Are you sure you want to cancel and discard all changes?",
|
f"Are you sure you want to cancel and discard all changes?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
QMessageBox.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
self.queue.clear()
|
self.queue.clear()
|
||||||
self.queue.extend(deepcopy(self._original_queue))
|
self.queue.extend(deepcopy(self._original_queue))
|
||||||
super().reject()
|
super().reject()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
if event.key() == Qt.Key_Delete:
|
if event.key() == Qt.Key.Key_Delete:
|
||||||
self.remove_item()
|
self.remove_item()
|
||||||
else:
|
else:
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ class QueuedItem:
|
|||||||
total_char_count: int
|
total_char_count: int
|
||||||
replace_single_newlines: bool = False
|
replace_single_newlines: bool = False
|
||||||
save_base_path: str = None
|
save_base_path: str = None
|
||||||
|
save_chapters_separately: bool = None
|
||||||
|
merge_chapters_at_end: bool = None
|
||||||
|
|||||||
+64
-62
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QDialog,
|
QDialog,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QCheckBox,
|
QCheckBox,
|
||||||
@@ -22,12 +22,11 @@ from PyQt5.QtWidgets import (
|
|||||||
QFileDialog,
|
QFileDialog,
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QMenu,
|
QMenu,
|
||||||
QAction,
|
|
||||||
QComboBox,
|
|
||||||
QApplication,
|
QApplication,
|
||||||
|
QComboBox,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QTimer, QPoint, QRect, QSize
|
from PyQt6.QtCore import Qt, QTimer, QPoint, QRect, QSize
|
||||||
from PyQt5.QtGui import QPixmap, QIcon
|
from PyQt6.QtGui import QPixmap, QIcon, QAction
|
||||||
from abogen.constants import (
|
from abogen.constants import (
|
||||||
VOICES_INTERNAL,
|
VOICES_INTERNAL,
|
||||||
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
|
||||||
@@ -91,7 +90,7 @@ class FlowLayout(QLayout):
|
|||||||
return len(self._item_list)
|
return len(self._item_list)
|
||||||
|
|
||||||
def expandingDirections(self):
|
def expandingDirections(self):
|
||||||
return Qt.Orientations(Qt.Orientation(0))
|
return Qt.Orientation(0)
|
||||||
|
|
||||||
def hasHeightForWidth(self):
|
def hasHeightForWidth(self):
|
||||||
return True
|
return True
|
||||||
@@ -132,10 +131,10 @@ class FlowLayout(QLayout):
|
|||||||
for item in self._item_list:
|
for item in self._item_list:
|
||||||
style = self.parentWidget().style() if self.parentWidget() else QStyle()
|
style = self.parentWidget().style() if self.parentWidget() else QStyle()
|
||||||
layout_spacing_x = style.layoutSpacing(
|
layout_spacing_x = style.layoutSpacing(
|
||||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Horizontal
|
||||||
)
|
)
|
||||||
layout_spacing_y = style.layoutSpacing(
|
layout_spacing_y = style.layoutSpacing(
|
||||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Vertical
|
||||||
)
|
)
|
||||||
space_x = spacing if spacing >= 0 else layout_spacing_x
|
space_x = spacing if spacing >= 0 else layout_spacing_x
|
||||||
space_y = spacing if spacing >= 0 else layout_spacing_y
|
space_y = spacing if spacing >= 0 else layout_spacing_y
|
||||||
@@ -163,7 +162,7 @@ class VoiceMixer(QWidget):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.voice_name = voice_name
|
self.voice_name = voice_name
|
||||||
self.setFixedWidth(VOICE_MIXER_WIDTH)
|
self.setFixedWidth(VOICE_MIXER_WIDTH)
|
||||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
|
|
||||||
# TODO Set CSS for rounded corners
|
# TODO Set CSS for rounded corners
|
||||||
# self.setObjectName("VoiceMixer")
|
# self.setObjectName("VoiceMixer")
|
||||||
@@ -173,7 +172,7 @@ class VoiceMixer(QWidget):
|
|||||||
|
|
||||||
# Name label at the top
|
# Name label at the top
|
||||||
name = voice_name
|
name = voice_name
|
||||||
layout.addWidget(QLabel(name), alignment=Qt.AlignCenter)
|
layout.addWidget(QLabel(name), alignment=Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
# Voice name label with gender icon
|
# Voice name label with gender icon
|
||||||
is_female = self.voice_name in VOICES_INTERNAL and self.voice_name[1] == "f"
|
is_female = self.voice_name in VOICES_INTERNAL and self.voice_name[1] == "f"
|
||||||
@@ -181,7 +180,7 @@ class VoiceMixer(QWidget):
|
|||||||
# Icons layout (flag and gender)
|
# Icons layout (flag and gender)
|
||||||
icons_layout = QHBoxLayout()
|
icons_layout = QHBoxLayout()
|
||||||
icons_layout.setSpacing(3)
|
icons_layout.setSpacing(3)
|
||||||
icons_layout.setAlignment(Qt.AlignCenter) # Center the icons horizontally
|
icons_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the icons horizontally
|
||||||
|
|
||||||
# Flag icon
|
# Flag icon
|
||||||
flag_icon_path = get_resource_path(
|
flag_icon_path = get_resource_path(
|
||||||
@@ -194,11 +193,11 @@ class VoiceMixer(QWidget):
|
|||||||
gender_label = QLabel()
|
gender_label = QLabel()
|
||||||
flag_pixmap = QPixmap(flag_icon_path)
|
flag_pixmap = QPixmap(flag_icon_path)
|
||||||
flag_label.setPixmap(
|
flag_label.setPixmap(
|
||||||
flag_pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
flag_pixmap.scaled(16, 16, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
||||||
)
|
)
|
||||||
gender_pixmap = QPixmap(gender_icon_path)
|
gender_pixmap = QPixmap(gender_icon_path)
|
||||||
gender_label.setPixmap(
|
gender_label.setPixmap(
|
||||||
gender_pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
gender_pixmap.scaled(16, 16, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
||||||
)
|
)
|
||||||
icons_layout.addWidget(flag_label)
|
icons_layout.addWidget(flag_label)
|
||||||
icons_layout.addWidget(gender_label)
|
icons_layout.addWidget(gender_label)
|
||||||
@@ -210,7 +209,7 @@ class VoiceMixer(QWidget):
|
|||||||
self.checkbox = QCheckBox()
|
self.checkbox = QCheckBox()
|
||||||
self.checkbox.setChecked(initial_status)
|
self.checkbox.setChecked(initial_status)
|
||||||
self.checkbox.stateChanged.connect(self.toggle_inputs)
|
self.checkbox.stateChanged.connect(self.toggle_inputs)
|
||||||
layout.addWidget(self.checkbox, alignment=Qt.AlignCenter)
|
layout.addWidget(self.checkbox, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
# Spinbox and slider
|
# Spinbox and slider
|
||||||
self.spin_box = QDoubleSpinBox()
|
self.spin_box = QDoubleSpinBox()
|
||||||
@@ -219,10 +218,10 @@ class VoiceMixer(QWidget):
|
|||||||
self.spin_box.setDecimals(2)
|
self.spin_box.setDecimals(2)
|
||||||
self.spin_box.setValue(initial_weight)
|
self.spin_box.setValue(initial_weight)
|
||||||
|
|
||||||
self.slider = QSlider(Qt.Vertical)
|
self.slider = QSlider(Qt.Orientation.Vertical)
|
||||||
self.slider.setRange(0, 100)
|
self.slider.setRange(0, 100)
|
||||||
self.slider.setValue(int(initial_weight * 100))
|
self.slider.setValue(int(initial_weight * 100))
|
||||||
self.slider.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
self.slider.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
|
||||||
self.slider.setFixedWidth(SLIDER_WIDTH)
|
self.slider.setFixedWidth(SLIDER_WIDTH)
|
||||||
|
|
||||||
# Fix slider in Windows
|
# Fix slider in Windows
|
||||||
@@ -249,17 +248,17 @@ class VoiceMixer(QWidget):
|
|||||||
# Layout for slider and labels
|
# Layout for slider and labels
|
||||||
slider_layout = QVBoxLayout()
|
slider_layout = QVBoxLayout()
|
||||||
slider_layout.addWidget(self.spin_box)
|
slider_layout.addWidget(self.spin_box)
|
||||||
slider_layout.addWidget(QLabel("1", alignment=Qt.AlignCenter))
|
slider_layout.addWidget(QLabel("1", alignment=Qt.AlignmentFlag.AlignCenter))
|
||||||
|
|
||||||
slider_center_layout = QHBoxLayout()
|
slider_center_layout = QHBoxLayout()
|
||||||
slider_center_layout.addWidget(self.slider, alignment=Qt.AlignHCenter)
|
slider_center_layout.addWidget(self.slider, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||||
slider_center_layout.setContentsMargins(0, 0, 0, 0)
|
slider_center_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
slider_center_widget = QWidget()
|
slider_center_widget = QWidget()
|
||||||
slider_center_widget.setLayout(slider_center_layout)
|
slider_center_widget.setLayout(slider_center_layout)
|
||||||
|
|
||||||
slider_layout.addWidget(slider_center_widget, stretch=1)
|
slider_layout.addWidget(slider_center_widget, stretch=1)
|
||||||
slider_layout.addWidget(QLabel("0", alignment=Qt.AlignCenter))
|
slider_layout.addWidget(QLabel("0", alignment=Qt.AlignmentFlag.AlignCenter))
|
||||||
slider_layout.setStretch(2, 1)
|
slider_layout.setStretch(2, 1)
|
||||||
|
|
||||||
layout.addLayout(slider_layout, stretch=1)
|
layout.addLayout(slider_layout, stretch=1)
|
||||||
@@ -307,9 +306,9 @@ class HoverLabel(QLabel):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
# Make sure the entire button is clickable, not just the text
|
# Make sure the entire button is clickable, not just the text
|
||||||
self.delete_button.setFocusPolicy(Qt.NoFocus)
|
self.delete_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||||
self.delete_button.setAttribute(Qt.WA_TransparentForMouseEvents, False)
|
self.delete_button.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False)
|
||||||
self.delete_button.setCursor(Qt.PointingHandCursor)
|
self.delete_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
self.delete_button.hide()
|
self.delete_button.hide()
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event):
|
||||||
@@ -357,7 +356,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
if parent is not None and hasattr(parent, "subtitle_combo"):
|
if parent is not None and hasattr(parent, "subtitle_combo"):
|
||||||
self.subtitle_combo = parent.subtitle_combo
|
self.subtitle_combo = parent.subtitle_combo
|
||||||
# Create main container layout with profile section and mixer section
|
# Create main container layout with profile section and mixer section
|
||||||
splitter = QSplitter(Qt.Horizontal)
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
# Profile section
|
# Profile section
|
||||||
profile_widget = QWidget()
|
profile_widget = QWidget()
|
||||||
profile_layout = QVBoxLayout(profile_widget)
|
profile_layout = QVBoxLayout(profile_widget)
|
||||||
@@ -371,8 +370,8 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
profile_layout.addLayout(header_layout)
|
profile_layout.addLayout(header_layout)
|
||||||
# Profile list
|
# Profile list
|
||||||
self.profile_list = QListWidget()
|
self.profile_list = QListWidget()
|
||||||
self.profile_list.setSelectionMode(QListWidget.SingleSelection)
|
self.profile_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
|
||||||
self.profile_list.setSelectionBehavior(QListWidget.SelectRows)
|
self.profile_list.setSelectionBehavior(QListWidget.SelectionBehavior.SelectRows)
|
||||||
self.profile_list.setStyleSheet(
|
self.profile_list.setStyleSheet(
|
||||||
"QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }"
|
"QListWidget::item:selected { background: palette(highlight); color: palette(highlighted-text); }"
|
||||||
)
|
)
|
||||||
@@ -388,7 +387,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
idx = list(profiles.keys()).index(self.current_profile)
|
idx = list(profiles.keys()).index(self.current_profile)
|
||||||
self.profile_list.setCurrentRow(idx)
|
self.profile_list.setCurrentRow(idx)
|
||||||
profile_layout.addWidget(self.profile_list)
|
profile_layout.addWidget(self.profile_list)
|
||||||
self.profile_list.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.profile_list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
self.profile_list.customContextMenuRequested.connect(
|
self.profile_list.customContextMenuRequested.connect(
|
||||||
self.show_profile_context_menu
|
self.show_profile_context_menu
|
||||||
)
|
)
|
||||||
@@ -409,7 +408,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
|
|
||||||
self.setWindowTitle("Voice Mixer")
|
self.setWindowTitle("Voice Mixer")
|
||||||
self.setWindowFlags(
|
self.setWindowFlags(
|
||||||
Qt.Window | Qt.WindowCloseButtonHint | Qt.WindowMaximizeButtonHint
|
Qt.WindowType.Window | Qt.WindowType.WindowCloseButtonHint | Qt.WindowType.WindowMaximizeButtonHint
|
||||||
)
|
)
|
||||||
self.setMinimumSize(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
|
self.setMinimumSize(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
|
||||||
self.resize(INITIAL_WINDOW_WIDTH, INITIAL_WINDOW_HEIGHT)
|
self.resize(INITIAL_WINDOW_WIDTH, INITIAL_WINDOW_HEIGHT)
|
||||||
@@ -468,21 +467,21 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
separator = QFrame()
|
separator = QFrame()
|
||||||
separator.setFrameShadow(QFrame.Sunken)
|
separator.setFrameShadow(QFrame.Shadow.Sunken)
|
||||||
mixer_layout.addWidget(separator)
|
mixer_layout.addWidget(separator)
|
||||||
|
|
||||||
# Voice list scroll area
|
# Voice list scroll area
|
||||||
self.scroll_area = QScrollArea()
|
self.scroll_area = QScrollArea()
|
||||||
self.scroll_area.setWidgetResizable(True)
|
self.scroll_area.setWidgetResizable(True)
|
||||||
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
self.scroll_area.viewport().installEventFilter(self)
|
self.scroll_area.viewport().installEventFilter(self)
|
||||||
|
|
||||||
self.voice_list_widget = QWidget()
|
self.voice_list_widget = QWidget()
|
||||||
self.voice_list_layout = QHBoxLayout()
|
self.voice_list_layout = QHBoxLayout()
|
||||||
self.voice_list_widget.setLayout(self.voice_list_layout)
|
self.voice_list_widget.setLayout(self.voice_list_layout)
|
||||||
self.voice_list_widget.setSizePolicy(
|
self.voice_list_widget.setSizePolicy(
|
||||||
QSizePolicy.Expanding, QSizePolicy.Expanding
|
QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding
|
||||||
)
|
)
|
||||||
self.scroll_area.setWidget(self.voice_list_widget)
|
self.scroll_area.setWidget(self.voice_list_widget)
|
||||||
mixer_layout.addWidget(self.scroll_area, stretch=1)
|
mixer_layout.addWidget(self.scroll_area, stretch=1)
|
||||||
@@ -497,6 +496,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
ok_button.setDefault(True)
|
ok_button.setDefault(True)
|
||||||
ok_button.setFocus()
|
ok_button.setFocus()
|
||||||
|
|
||||||
|
# Connect buttons
|
||||||
|
clear_all_button.clicked.connect(self.clear_all_voices)
|
||||||
|
ok_button.clicked.connect(self.accept)
|
||||||
# Connect buttons
|
# Connect buttons
|
||||||
clear_all_button.clicked.connect(self.clear_all_voices)
|
clear_all_button.clicked.connect(self.clear_all_voices)
|
||||||
ok_button.clicked.connect(self.accept)
|
ok_button.clicked.connect(self.accept)
|
||||||
@@ -533,7 +535,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
# Bind Delete key to delete_profile when a profile is selected
|
# Bind Delete key to delete_profile when a profile is selected
|
||||||
if event.key() == Qt.Key_Delete and self.profile_list.hasFocus():
|
if event.key() == Qt.Key.Key_Delete and self.profile_list.hasFocus():
|
||||||
item = self.profile_list.currentItem()
|
item = self.profile_list.currentItem()
|
||||||
if item:
|
if item:
|
||||||
self.delete_profile(item)
|
self.delete_profile(item)
|
||||||
@@ -562,10 +564,10 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Unsaved Changes",
|
"Unsaved Changes",
|
||||||
msg,
|
msg,
|
||||||
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
|
QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel,
|
||||||
QMessageBox.Save,
|
QMessageBox.StandardButton.Save,
|
||||||
)
|
)
|
||||||
if ret == QMessageBox.Save:
|
if ret == QMessageBox.StandardButton.Save:
|
||||||
# Save all using stored states
|
# Save all using stored states
|
||||||
profiles = load_profiles()
|
profiles = load_profiles()
|
||||||
for i in dirty_indices:
|
for i in dirty_indices:
|
||||||
@@ -590,7 +592,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self.update_profile_save_buttons()
|
self.update_profile_save_buttons()
|
||||||
self.update_profile_list_colors()
|
self.update_profile_list_colors()
|
||||||
return True
|
return True
|
||||||
elif ret == QMessageBox.Discard:
|
elif ret == QMessageBox.StandardButton.Discard:
|
||||||
# Discard all modifications
|
# Discard all modifications
|
||||||
self._profile_states.clear()
|
self._profile_states.clear()
|
||||||
for i in dirty_indices:
|
for i in dirty_indices:
|
||||||
@@ -612,17 +614,17 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
else:
|
else:
|
||||||
# Fallback to original logic for 0 or 1 dirty profile
|
# Fallback to original logic for 0 or 1 dirty profile
|
||||||
box = QMessageBox(self)
|
box = QMessageBox(self)
|
||||||
box.setIcon(QMessageBox.Warning)
|
box.setIcon(QMessageBox.Icon.Warning)
|
||||||
box.setWindowTitle("Unsaved Changes")
|
box.setWindowTitle("Unsaved Changes")
|
||||||
box.setText(
|
box.setText(
|
||||||
"You have unsaved changes in your profile. Do you want to save the changes?"
|
"You have unsaved changes in your profile. Do you want to save the changes?"
|
||||||
)
|
)
|
||||||
box.setStandardButtons(
|
box.setStandardButtons(
|
||||||
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
|
QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel
|
||||||
)
|
)
|
||||||
box.setDefaultButton(QMessageBox.Save)
|
box.setDefaultButton(QMessageBox.StandardButton.Save)
|
||||||
ret = box.exec_()
|
ret = box.exec()
|
||||||
if ret == QMessageBox.Save:
|
if ret == QMessageBox.StandardButton.Save:
|
||||||
for i in range(self.profile_list.count()):
|
for i in range(self.profile_list.count()):
|
||||||
item = self.profile_list.item(i)
|
item = self.profile_list.item(i)
|
||||||
name = item.text().lstrip("*")
|
name = item.text().lstrip("*")
|
||||||
@@ -636,7 +638,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
if hasattr(parent, "populate_profiles_in_voice_combo"):
|
if hasattr(parent, "populate_profiles_in_voice_combo"):
|
||||||
parent.populate_profiles_in_voice_combo()
|
parent.populate_profiles_in_voice_combo()
|
||||||
return True
|
return True
|
||||||
elif ret == QMessageBox.Discard:
|
elif ret == QMessageBox.StandardButton.Discard:
|
||||||
profiles = load_profiles()
|
profiles = load_profiles()
|
||||||
for i in range(self.profile_list.count()):
|
for i in range(self.profile_list.count()):
|
||||||
item = self.profile_list.item(i)
|
item = self.profile_list.item(i)
|
||||||
@@ -717,7 +719,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
return voice_mixer
|
return voice_mixer
|
||||||
|
|
||||||
def handle_voice_checkbox(self, voice_mixer, state):
|
def handle_voice_checkbox(self, voice_mixer, state):
|
||||||
if state == Qt.Checked:
|
if state == Qt.CheckState.Checked.value:
|
||||||
self.last_enabled_voice = voice_mixer.voice_name
|
self.last_enabled_voice = voice_mixer.voice_name
|
||||||
self.update_weighted_sums()
|
self.update_weighted_sums()
|
||||||
|
|
||||||
@@ -767,7 +769,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
f'<b><span style="color:{COLORS.get("BLUE")}">{name}: {percentage:.1f}%</span></b>',
|
f'<b><span style="color:{COLORS.get("BLUE")}">{name}: {percentage:.1f}%</span></b>',
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
voice_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
voice_label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
|
||||||
voice_label.delete_button.clicked.connect(
|
voice_label.delete_button.clicked.connect(
|
||||||
lambda _, vn=name: self.disable_voice_by_name(vn)
|
lambda _, vn=name: self.disable_voice_by_name(vn)
|
||||||
)
|
)
|
||||||
@@ -787,7 +789,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
mixer.checkbox.setChecked(False)
|
mixer.checkbox.setChecked(False)
|
||||||
|
|
||||||
def eventFilter(self, source, event):
|
def eventFilter(self, source, event):
|
||||||
if source is self.scroll_area.viewport() and event.type() == event.Wheel:
|
if source is self.scroll_area.viewport() and event.type() == event.Type.Wheel:
|
||||||
# Skip if over an enabled slider
|
# Skip if over an enabled slider
|
||||||
if any(
|
if any(
|
||||||
mixer.slider.underMouse() and mixer.slider.isEnabled()
|
mixer.slider.underMouse() and mixer.slider.isEnabled()
|
||||||
@@ -890,10 +892,10 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Invalid Profiles",
|
"Invalid Profiles",
|
||||||
msg,
|
msg,
|
||||||
QMessageBox.Yes | QMessageBox.Cancel,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
|
||||||
QMessageBox.Yes,
|
QMessageBox.StandardButton.Yes,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
for i, name in reversed(zero):
|
for i, name in reversed(zero):
|
||||||
self.profile_list.takeItem(i)
|
self.profile_list.takeItem(i)
|
||||||
delete_profile(name)
|
delete_profile(name)
|
||||||
@@ -980,8 +982,8 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
def _parse_rgba_to_qcolor(self, rgba_str):
|
def _parse_rgba_to_qcolor(self, rgba_str):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt5.QtGui import QColor
|
from PyQt6.QtGui import QColor
|
||||||
|
|
||||||
"""Helper to convert 'rgba(R,G,B,A_float)' string to 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)
|
match = re.match(r"rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)", rgba_str)
|
||||||
@@ -1136,9 +1138,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
msg += f"\nThis will overwrite an existing profile."
|
msg += f"\nThis will overwrite an existing profile."
|
||||||
msg += "\nContinue?"
|
msg += "\nContinue?"
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self, "Import Profile", msg, QMessageBox.Yes | QMessageBox.No
|
self, "Import Profile", msg, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
profiles.update(imported_profiles)
|
profiles.update(imported_profiles)
|
||||||
save_profiles(profiles)
|
save_profiles(profiles)
|
||||||
@@ -1153,9 +1155,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
msg += f"\n{len(collisions)} profile(s) will be overwritten."
|
msg += f"\n{len(collisions)} profile(s) will be overwritten."
|
||||||
msg += "\nContinue?"
|
msg += "\nContinue?"
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self, "Import Profiles", msg, QMessageBox.Yes | QMessageBox.No
|
self, "Import Profiles", msg, QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
||||||
)
|
)
|
||||||
if reply != QMessageBox.Yes:
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
return
|
return
|
||||||
profiles.update(imported_profiles)
|
profiles.update(imported_profiles)
|
||||||
save_profiles(profiles)
|
save_profiles(profiles)
|
||||||
@@ -1196,7 +1198,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
menu.addAction(dup_act)
|
menu.addAction(dup_act)
|
||||||
menu.addAction(export_act)
|
menu.addAction(export_act)
|
||||||
menu.addAction(delete_act)
|
menu.addAction(delete_act)
|
||||||
act = menu.exec_(self.profile_list.viewport().mapToGlobal(pos))
|
act = menu.exec(self.profile_list.viewport().mapToGlobal(pos))
|
||||||
if act == rename_act:
|
if act == rename_act:
|
||||||
self.rename_profile(item)
|
self.rename_profile(item)
|
||||||
elif act == delete_act:
|
elif act == delete_act:
|
||||||
@@ -1323,9 +1325,9 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self,
|
self,
|
||||||
"Delete Profile",
|
"Delete Profile",
|
||||||
f"Delete profile '{name}'?",
|
f"Delete profile '{name}'?",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
delete_profile(name)
|
delete_profile(name)
|
||||||
row = self.profile_list.row(item)
|
row = self.profile_list.row(item)
|
||||||
self.profile_list.takeItem(row)
|
self.profile_list.takeItem(row)
|
||||||
@@ -1378,7 +1380,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
self.profile_list.setItemWidget(item, widget)
|
self.profile_list.setItemWidget(item, widget)
|
||||||
|
|
||||||
def update_profile_list_colors(self):
|
def update_profile_list_colors(self):
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
profiles = load_profiles()
|
profiles = load_profiles()
|
||||||
for i in range(self.profile_list.count()):
|
for i in range(self.profile_list.count()):
|
||||||
@@ -1386,13 +1388,13 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
name = item.text().lstrip("*")
|
name = item.text().lstrip("*")
|
||||||
if self._virtual_new_profile and name == "New profile":
|
if self._virtual_new_profile and name == "New profile":
|
||||||
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
||||||
item.setData(Qt.BackgroundRole, color)
|
item.setData(Qt.ItemDataRole.BackgroundRole, color)
|
||||||
elif item.text().startswith("*"):
|
elif item.text().startswith("*"):
|
||||||
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
color = self._parse_rgba_to_qcolor(COLORS.get("YELLOW_BACKGROUND"))
|
||||||
item.setData(Qt.BackgroundRole, color)
|
item.setData(Qt.ItemDataRole.BackgroundRole, color)
|
||||||
else:
|
else:
|
||||||
item.setData(
|
item.setData(
|
||||||
Qt.BackgroundRole, self.profile_list.palette().base().color()
|
Qt.ItemDataRole.BackgroundRole, self.profile_list.palette().base().color()
|
||||||
)
|
)
|
||||||
weights = profiles.get(name, {}).get("voices", [])
|
weights = profiles.get(name, {}).get("voices", [])
|
||||||
total = 0
|
total = 0
|
||||||
@@ -1406,7 +1408,7 @@ class VoiceFormulaDialog(QDialog):
|
|||||||
total += entry[1]
|
total += entry[1]
|
||||||
if total == 0:
|
if total == 0:
|
||||||
color = self._parse_rgba_to_qcolor(COLORS.get("RED_BACKGROUND"))
|
color = self._parse_rgba_to_qcolor(COLORS.get("RED_BACKGROUND"))
|
||||||
item.setData(Qt.BackgroundRole, color)
|
item.setData(Qt.ItemDataRole.BackgroundRole, color)
|
||||||
self.update_profile_save_buttons()
|
self.update_profile_save_buttons()
|
||||||
|
|
||||||
def preview_current_mix(self):
|
def preview_current_mix(self):
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ license = "MIT"
|
|||||||
requires-python = ">=3.10, <3.13"
|
requires-python = ">=3.10, <3.13"
|
||||||
keywords = ["audiobook", "epub", "pdf", "text-to-speech", "subtitle", "tts", "kokoro", "accessibility", "book-converter", "voice-synthesis", "multilingual", "chapter-management", "subtitles", "content-creation", "media-generation"]
|
keywords = ["audiobook", "epub", "pdf", "text-to-speech", "subtitle", "tts", "kokoro", "accessibility", "book-converter", "voice-synthesis", "multilingual", "chapter-management", "subtitles", "content-creation", "media-generation"]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"PyQt5>=5.15.11",
|
"PyQt6>=6.10.0",
|
||||||
"kokoro>=0.9.4",
|
"kokoro>=0.9.4",
|
||||||
"misaki[zh]>=0.9.4",
|
"misaki[zh]>=0.9.4",
|
||||||
"ebooklib>=0.19",
|
"ebooklib>=0.19",
|
||||||
|
|||||||
Reference in New Issue
Block a user