Use signals a little better and avoid QDialog.exec

This commit is contained in:
Timmy Welch 2025-01-14 17:41:00 -08:00
parent a9da87bff3
commit 13ee9e9ad8
3 changed files with 202 additions and 89 deletions

View File

@ -24,7 +24,7 @@ from comicapi.genericmetadata import GenericMetadata
from comicapi.issuestring import IssueString
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.ctsettings import ct_ns
from comictaggerlib.seriesselectionwindow import SeriesSelectionWindow
from comictaggerlib.seriesselectionwindow import SelectionWindow
from comictaggerlib.ui import ui_path
from comictalker.comictalker import ComicTalker, RLCallBack, TalkerError
@ -39,9 +39,44 @@ class IssueNumberTableWidgetItem(QtWidgets.QTableWidgetItem):
return (IssueString(self_str).as_float() or 0) < (IssueString(other_str).as_float() or 0)
class IssueSelectionWindow(SeriesSelectionWindow):
class QueryThread(QtCore.QThread):
def __init__(
self,
talker: ComicTalker,
series_id: str,
finish: QtCore.pyqtSignal,
on_ratelimit: QtCore.pyqtSignal,
) -> None:
super().__init__()
self.series_id = series_id
self.talker = talker
self.finish = finish
self.on_ratelimit = on_ratelimit
def run(self) -> None:
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
issue_list = [
x
for x in self.talker.fetch_issues_in_series(
self.series_id, on_rate_limit=RLCallBack(lambda x, y: self.on_ratelimit.emit(x, y), 10)
)
if x.issue_id is not None
]
except TalkerError as e:
QtWidgets.QApplication.restoreOverrideCursor()
QtWidgets.QMessageBox.critical(None, f"{e.source} {e.code_name} Error", f"{e}")
return
QtWidgets.QApplication.restoreOverrideCursor()
self.finish.emit(issue_list)
class IssueSelectionWindow(SelectionWindow):
ui_file = ui_path / "issueselectionwindow.ui"
CoverImageMode = CoverImageWidget.AltCoverMode
finish = QtCore.pyqtSignal(list)
def __init__(
self,
@ -60,42 +95,28 @@ class IssueSelectionWindow(SeriesSelectionWindow):
self.issue_number = "1"
self.initial_id: str = ""
self.perform_query()
# now that the list has been sorted, find the initial record, and
# select it
if self.initial_id:
for r in range(0, self.twList.rowCount()):
issue_id = self.twList.item(r, 0).data(QtCore.Qt.ItemDataRole.UserRole)
if issue_id == self.initial_id:
self.twList.selectRow(r)
break
self.leFilter.textChanged.connect(self.filter)
self.finish.connect(self.query_finished)
def showEvent(self, event: QtGui.QShowEvent) -> None:
return
self.perform_query()
def perform_query(self) -> None: # type: ignore[override]
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
try:
self.issue_list = {
x.issue_id: x
for x in self.talker.fetch_issues_in_series(
self.series_id, on_rate_limit=RLCallBack(self.on_ratelimit, 10)
)
if x.issue_id is not None
}
except TalkerError as e:
QtWidgets.QApplication.restoreOverrideCursor()
QtWidgets.QMessageBox.critical(self, f"{e.source} {e.code_name} Error", f"{e}")
return
self.querythread = QueryThread(
self.talker,
self.series_id,
self.finish,
self.ratelimit,
)
self.querythread.start()
def query_finished(self, issues: list[GenericMetadata]) -> None:
self.twList.setRowCount(0)
self.twList.setSortingEnabled(False)
for row, issue in enumerate(self.issue_list.values()):
self.issue_list = {i.issue_id: i for i in issues if i.issue_id is not None}
self.twList.clear()
for row, issue in enumerate(issues):
self.twList.insertRow(row)
self.twList.setItem(row, 0, IssueNumberTableWidgetItem())
self.twList.setItem(row, 1, QtWidgets.QTableWidgetItem())
@ -108,8 +129,15 @@ class IssueSelectionWindow(SeriesSelectionWindow):
self.twList.setSortingEnabled(True)
self.twList.sortItems(0, QtCore.Qt.SortOrder.AscendingOrder)
QtWidgets.QApplication.restoreOverrideCursor()
self.twList: QtWidgets.QTableWidget
if self.initial_id:
for r in range(0, self.twList.rowCount()):
item = self.twList.item(r, 0)
issue_id = item.data(QtCore.Qt.ItemDataRole.UserRole)
if issue_id == self.initial_id:
self.twList.selectRow(r)
self.twList.scrollToItem(item, QtWidgets.QAbstractItemView.ScrollHint.EnsureVisible)
break
def cell_double_clicked(self, r: int, c: int) -> None:
self.accept()

View File

@ -18,6 +18,7 @@ from __future__ import annotations
import itertools
import logging
from abc import ABCMeta, abstractmethod
from collections import deque
import natsort
@ -128,9 +129,11 @@ class IdentifyThread(QtCore.QThread):
self.ratelimit.emit(full_time, sleep_time)
class SeriesSelectionWindow(QtWidgets.QDialog):
class SelectionWindow(QtWidgets.QDialog):
__metaclass__ = ABCMeta
ui_file = ui_path / "seriesselectionwindow.ui"
CoverImageMode = CoverImageWidget.URLMode
ratelimit = pyqtSignal(float, float)
def __init__(
self,
@ -203,31 +206,107 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
self.leFilter.textChanged.connect(self.filter)
self.twList.selectRow(0)
if series_name:
self.series_name = series_name
self.issue_number = issue_number
self.year = year
self.issue_count = issue_count
self.series_id: str = ""
self.comic_archive = comic_archive
self.immediate_autoselect = autoselect
self.series_list: dict[str, ComicSeries] = {}
self.literal = literal
self.iddialog: IDProgressWindow | None = None
self.id_thread: IdentifyThread | None = None
self.progdialog: QtWidgets.QProgressDialog | None = None
self.search_thread: SearchThread | None = None
@abstractmethod
def perform_query(self, refresh: bool = False) -> None: ...
self.use_publisher_filter = self.config.Auto_Tag__use_publisher_filter
@abstractmethod
def cell_double_clicked(self, r: int, c: int) -> None: ...
self.btnRequery.clicked.connect(self.requery)
self.btnIssues.clicked.connect(self.show_issues)
self.btnAutoSelect.clicked.connect(self.auto_select)
@abstractmethod
def update_row(self, row: int, series: ComicSeries) -> None: ...
self.cbxPublisherFilter.setChecked(self.use_publisher_filter)
self.cbxPublisherFilter.toggled.connect(self.publisher_filter_toggled)
def set_description(self, widget: QtWidgets.QWidget, text: str) -> None:
if isinstance(widget, QtWidgets.QTextEdit):
widget.setText(text.replace("</figure>", "</div>").replace("<figure", "<div"))
else:
html = text
widget.setHtml(html, QUrl(self.talker.website))
self.update_buttons()
def filter(self, text: str) -> None:
rows = set(range(self.twList.rowCount()))
for r in rows:
self.twList.showRow(r)
if text.strip():
shown_rows = {x.row() for x in self.twList.findItems(text, QtCore.Qt.MatchFlag.MatchContains)}
for r in rows - shown_rows:
self.twList.hideRow(r)
@abstractmethod
def _fetch(self, row: int) -> ComicSeries: ...
def on_ratelimit(self, full_time: float, sleep_time: float) -> None:
self.ratelimit.emit(full_time, sleep_time)
def current_item_changed(self, curr: QtCore.QModelIndex | None, prev: QtCore.QModelIndex | None) -> None:
if curr is None:
return
if prev is not None and prev.row() == curr.row():
return
row = curr.row()
item = self._fetch(row)
QtWidgets.QApplication.restoreOverrideCursor()
# Update current record information
self.update_row(row, item)
class SeriesSelectionWindow(SelectionWindow):
ui_file = ui_path / "seriesselectionwindow.ui"
CoverImageMode = CoverImageWidget.URLMode
def __init__(
self,
parent: QtWidgets.QWidget,
config: ct_ns,
talker: ComicTalker,
series_name: str = "",
issue_number: str = "",
comic_archive: ComicArchive | None = None,
year: int | None = None,
issue_count: int | None = None,
autoselect: bool = False,
literal: bool = False,
) -> None:
super().__init__(
parent,
config,
talker,
series_name,
issue_number,
comic_archive,
year,
issue_count,
autoselect,
literal,
)
self.series_name = series_name
self.issue_number = issue_number
self.year = year
self.issue_count = issue_count
self.series_id: str = ""
self.comic_archive = comic_archive
self.immediate_autoselect = autoselect
self.series_list: dict[str, ComicSeries] = {}
self.literal = literal
self.iddialog: IDProgressWindow | None = None
self.id_thread: IdentifyThread | None = None
self.progdialog: QtWidgets.QProgressDialog | None = None
self.search_thread: SearchThread | None = None
self.use_publisher_filter = self.config.Auto_Tag__use_publisher_filter
self.btnRequery.clicked.connect(self.requery)
self.btnIssues.clicked.connect(self.show_issues)
self.btnAutoSelect.clicked.connect(self.auto_select)
self.cbxPublisherFilter.setChecked(self.use_publisher_filter)
self.cbxPublisherFilter.toggled.connect(self.publisher_filter_toggled)
self.ratelimit.connect(self.ratelimit_message)
self.update_buttons()
def showEvent(self, event: QtGui.QShowEvent) -> None:
self.perform_query()
@ -251,8 +330,7 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
)
self.search_thread.searchComplete.connect(self.search_complete)
self.search_thread.progressUpdate.connect(self.search_progress_update)
self.search_thread.ratelimit.connect(self.on_ratelimit)
self.search_thread.ratelimit.connect(self.parent().on_ratelimit)
self.search_thread.ratelimit.connect(self.ratelimit)
self.search_thread.start()
self.progdialog = QtWidgets.QProgressDialog("Searching Online", "Cancel", 0, 100, self)
@ -269,11 +347,6 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
def cell_double_clicked(self, r: int, c: int) -> None:
self.show_issues()
def on_ratelimit(self, full_time: float, sleep_time: float) -> None:
self.log_output(
f"Rate limit reached: {full_time:.0f}s until next request. Waiting {sleep_time:.0f}s for ratelimit"
)
def update_row(self, row: int, series: ComicSeries) -> None:
item_text = series.name
item = self.twList.item(row, 0)
@ -380,21 +453,13 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
self.id_thread.identifyComplete.connect(self.identify_complete)
self.id_thread.identifyLogMsg.connect(self.log_output)
self.id_thread.identifyProgress.connect(self.identify_progress)
self.id_thread.ratelimit.connect(self.on_ratelimit)
self.id_thread.ratelimit.connect(self.parent().on_ratelimit)
self.id_thread.ratelimit.connect(self.ratelimit)
self.iddialog.rejected.connect(self.id_thread.cancel)
self.id_thread.start()
self.iddialog.exec()
def log_output(self, text: str) -> None:
if self.iddialog is not None:
self.iddialog.textEdit.append(text.rstrip())
self.iddialog.textEdit.ensureCursorVisible()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
self.selector = None
def identify_progress(self, cur: int, total: int) -> None:
if self.iddialog is not None:
@ -449,20 +514,24 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
def show_issues(self) -> None:
from comictaggerlib.issueselectionwindow import IssueSelectionWindow
selector = IssueSelectionWindow(self, self.config, self.talker, self.series_id, self.issue_number)
self.selector = IssueSelectionWindow(self, self.config, self.talker, self.series_id, self.issue_number)
self.selector.ratelimit.connect(self.ratelimit)
title = ""
for series in self.series_list.values():
if series.id == self.series_id:
title = f"{series.name} ({series.start_year:04}) - " if series.start_year else f"{series.name} - "
break
selector.setWindowTitle(title + "Select Issue")
selector.setModal(True)
selector.exec()
if selector.result():
self.selector.setWindowTitle(title + "Select Issue")
self.selector.setModal(True)
self.selector.finished.connect(self.issue_selected)
self.selector.open()
def issue_selected(self, result) -> None:
if result and self.selector:
# we should now have a series ID
self.issue_number = selector.issue_number
self.issue_id = selector.issue_id
self.issue_number = self.selector.issue_number
self.issue_id = self.selector.issue_id
self.accept()
else:
self.cover_widget.update_content()
@ -615,3 +684,16 @@ class SeriesSelectionWindow(QtWidgets.QDialog):
# Update current record information
self.update_row(row, item)
def ratelimit_message(self, full_time: float, sleep_time: float) -> None:
self.log_output(
f"Rate limit reached: {full_time:.0f}s until next request. Waiting {sleep_time:.0f}s for ratelimit"
)
def log_output(self, text: str) -> None:
if self.iddialog is not None:
self.iddialog.textEdit.append(text.rstrip())
self.iddialog.textEdit.ensureCursorVisible()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()
QtCore.QCoreApplication.processEvents()

View File

@ -77,7 +77,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
appName = "ComicTagger"
version = ctversion.version
ratelimit = QtCore.pyqtSignal(float, float)
finish = QtCore.pyqtSignal(GenericMetadata, str)
query_finished = QtCore.pyqtSignal(GenericMetadata, str)
def __init__(
self,
@ -290,7 +290,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.page_list_editor.set_blur(self.config[0].General__blur)
self.ratelimit.connect(self.on_ratelimit)
self.finish.connect(self.finish_query)
self.query_finished.connect(self.apply_query_metadata)
def _sync_blur(*args: Any) -> None:
self.config[0].General__blur = self.page_list_editor.blur
@ -1073,7 +1073,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
issue_count = utils.xlate_int(self.leIssueCount.text())
selector = SeriesSelectionWindow(
self.selector = SeriesSelectionWindow(
self,
self.config[0],
self.current_talker(),
@ -1085,13 +1085,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
autoselect,
literal,
)
self.selector.ratelimit.connect(self.on_ratelimit)
selector.setWindowTitle(f"Search: '{series_name}' - Select Series")
self.selector.setWindowTitle(f"Search: '{series_name}' - Select Series")
self.selector.finished.connect(self.finish_query)
selector.setModal(True)
selector.exec()
self.selector.setModal(True)
self.selector.open()
if selector.result():
def finish_query(self, result) -> None:
if result and self.selector:
class QueryThread(QtCore.QThread):
def __init__(
@ -1126,15 +1129,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.querythread = QueryThread(
self.current_talker(),
selector.issue_id,
selector.series_id,
selector.issue_number,
self.finish,
self.selector.issue_id,
self.selector.series_id,
self.selector.issue_number,
self.query_finished,
self.ratelimit,
)
self.querythread.start()
def finish_query(self, new_metadata: GenericMetadata, issue_number: str) -> None:
def apply_query_metadata(self, new_metadata: GenericMetadata, issue_number: str) -> None:
# we should now have a series ID
# QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
QtWidgets.QApplication.restoreOverrideCursor()