Compare commits

...

3 Commits

Author SHA1 Message Date
e481fe5033 Fix credit window
Some checks failed
CI / lint (ubuntu-latest, 3.9) (push) Has been cancelled
CI / build-and-test (macos-13, 3.13) (push) Has been cancelled
CI / build-and-test (macos-13, 3.9) (push) Has been cancelled
CI / build-and-test (macos-14, 3.13) (push) Has been cancelled
CI / build-and-test (macos-14, 3.9) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.13) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04, 3.9) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04-arm, 3.13) (push) Has been cancelled
CI / build-and-test (ubuntu-22.04-arm, 3.9) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.13) (push) Has been cancelled
CI / build-and-test (windows-latest, 3.9) (push) Has been cancelled
2025-09-18 18:37:21 -07:00
9bc85d95a1 Fix some modal issues with dialogs 2025-09-18 16:31:24 -07:00
5256f016b7 Add links to issues and series 2025-09-18 16:30:51 -07:00
9 changed files with 159 additions and 75 deletions

View File

@ -138,6 +138,7 @@ class ComicSeries:
publisher: str
start_year: int | None
format: str | None
web_links: list[Url] = dataclasses.field(default_factory=list)
def copy(self) -> ComicSeries:
return copy.deepcopy(self)

View File

@ -18,7 +18,6 @@ from __future__ import annotations
import logging
import operator
from enum import Enum, auto
import natsort
from PyQt6 import QtCore, QtWidgets, uic
@ -32,15 +31,12 @@ from comictaggerlib.ui.qtutils import enable_widget
logger = logging.getLogger(__name__)
class EditMode(Enum):
EDIT = auto()
NEW = auto()
class CreditEditorWindow(QtWidgets.QDialog):
creditChanged = QtCore.pyqtSignal(Credit, int, EditMode)
creditChanged = QtCore.pyqtSignal(Credit, int)
def __init__(self, parent: QtWidgets.QWidget, tags: list[str], row: int, mode: EditMode, credit: Credit) -> None:
def __init__(
self, parent: QtWidgets.QWidget, tags: list[str], row: int, credit: Credit, title: str = "New Credit"
) -> None:
super().__init__(parent)
with (ui_path / "crediteditorwindow.ui").open(encoding="utf-8") as uifile:
@ -53,15 +49,11 @@ class CreditEditorWindow(QtWidgets.QDialog):
"credits.primary": self.cbPrimary,
}
self.mode = mode
self.credit = credit
self.row = row
self.tags = tags
if self.mode == EditMode.EDIT:
self.setWindowTitle("Edit Credit")
else:
self.setWindowTitle("New Credit")
self.setWindowTitle(title)
self.setModal(True)
# Add the entries to the role combobox
@ -129,4 +121,4 @@ class CreditEditorWindow(QtWidgets.QDialog):
QtWidgets.QDialog.accept(self)
new = self.get_credit()
if self.credit != new:
self.creditChanged.emit(new, self.row, self.mode)
self.creditChanged.emit(new, self.row)

View File

@ -40,18 +40,17 @@ class IssueNumberTableWidgetItem(QtWidgets.QTableWidgetItem):
class QueryThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
finish = QtCore.pyqtSignal(list)
ratelimit = QtCore.pyqtSignal(float, float)
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:
@ -59,7 +58,7 @@ class QueryThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifica
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)
self.series_id, on_rate_limit=RLCallBack(lambda x, y: self.ratelimit.emit(x, y), 10)
)
if x.issue_id is not None
]
@ -99,9 +98,9 @@ class IssueSelectionWindow(SelectionWindow):
self.querythread = QueryThread(
self.talker,
self.series_id,
self.finish,
self.ratelimit,
)
self.querythread.finish.connect(self.finish)
self.querythread.ratelimit.connect(self.ratelimit)
self.querythread.start()
def query_finished(self, issues: list[GenericMetadata]) -> None:
@ -183,4 +182,17 @@ class IssueSelectionWindow(SelectionWindow):
cover = issue._cover_image.URL if issue._cover_image else ""
self.cover_widget.set_issue_details(self.issue_id, [cover, *alt_images])
self.set_description(self.teDescription, issue.description or "")
series_link = ""
if issue.web_links:
url = (
issue.web_links[0]
.url.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("'", "&apos;")
.replace('"', "&quot;")
)
series_link = f'<a href="{url}">Link To Issue</a>'
self.lblIssueLink.setText(series_link)
return issue

View File

@ -98,6 +98,7 @@ class OptionalMessageDialog(QtWidgets.QDialog):
check_text: str = "",
) -> None:
d = OptionalMessageDialog(parent, StyleMessage, title, msg, checked=checked, check_text=check_text)
d.setModal(True)
def finished(i: int) -> None:
callback(d.theCheckBox.isChecked())
@ -117,6 +118,7 @@ class OptionalMessageDialog(QtWidgets.QDialog):
check_text: str = "",
) -> None:
d = OptionalMessageDialog(parent, StyleQuestion, title, msg, checked=checked, check_text=check_text)
d.setModal(True)
def finished(i: int) -> None:
callback(i == QtWidgets.QDialog.DialogCode.Accepted, d.theCheckBox.isChecked())
@ -130,6 +132,7 @@ class OptionalMessageDialog(QtWidgets.QDialog):
parent: QtWidgets.QWidget, title: str, msg: str, *, checked: bool = False, check_text: str = ""
) -> None:
d = OptionalMessageDialog(parent, StyleMessage, title, msg, checked=checked, check_text=check_text)
d.setModal(True)
d.theCheckBox.hide()
d.show()

View File

@ -405,6 +405,19 @@ class SeriesSelectionWindow(SelectionWindow):
except TalkerError:
pass
self.set_description(self.teDescription, series.description or "")
series_link = ""
if series.web_links:
url = (
series.web_links[0]
.url.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("'", "&apos;")
.replace('"', "&quot;")
)
series_link = f'<a href="{url}">Link To Series</a>'
self.lblSeriesLink.setText(series_link)
self.cover_widget.set_url(series.image_url)
return series
@ -473,7 +486,8 @@ class SeriesSelectionWindow(SelectionWindow):
if result == IIResult.single_good_match:
return self.update_match(issues[0])
qmsg = QtWidgets.QMessageBox(parent=self)
qmsg = QtWidgets.QMessageBox(parent=self.iddialog)
qmsg.setModal(False)
qmsg.setIcon(qmsg.Icon.Information)
qmsg.setText("Auto-Select Result")
qmsg.setInformativeText(" Manual interaction needed :-(")

View File

@ -47,7 +47,7 @@ from comictaggerlib.autotagprogresswindow import AutoTagProgressWindow, AutoTagT
from comictaggerlib.autotagstartwindow import AutoTagSettings, AutoTagStartWindow
from comictaggerlib.cbltransformer import CBLTransformer
from comictaggerlib.coverimagewidget import CoverImageWidget
from comictaggerlib.crediteditorwindow import CreditEditorWindow, EditMode
from comictaggerlib.crediteditorwindow import CreditEditorWindow
from comictaggerlib.ctsettings import ct_ns
from comictaggerlib.exportwindow import ExportConfig, ExportConflictOpts, ExportWindow
from comictaggerlib.fileselectionlist import FileSelectionList
@ -937,7 +937,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
for row, credit in enumerate(md.credits):
# if the role-person pair already exists, just skip adding it to the list
if self.is_dupe_credit(None, credit.role.title(), credit.person):
if self.get_dupe_credit(None, credit.role.title(), credit.person):
continue
self.add_new_credit_entry(row, credit)
@ -973,7 +973,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.twCredits.setItem(row, self.md_attributes["credits.primary"], item)
self.update_credit_primary_flag(row, credit.primary)
def is_dupe_credit(self, row: int | None, role: str, name: str) -> bool:
def get_dupe_credit(self, row: int | None, role: str, name: str) -> int:
for r in range(self.twCredits.rowCount()):
if r == row:
continue
@ -982,9 +982,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.twCredits.item(r, self.md_attributes["credits.role"]).text() == role
and self.twCredits.item(r, self.md_attributes["credits.person"]).text() == name
):
return True
return r
return False
return -1
def form_to_metadata(self) -> None:
# copy the data from the form into the metadata
@ -1337,15 +1337,29 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.page_list_editor.select_write_tags(self.selected_write_tags)
self.toggle_enable_embedding_hashes()
def cell_double_clicked(self, r: int, c: int) -> None:
self.edit_credit()
def add_credit(self) -> None:
self.modify_credits(False)
row = self.twCredits.rowCount()
editor = CreditEditorWindow(self, self.selected_write_tags, row, Credit())
editor.creditChanged.connect(self._credit_added)
editor.show()
def edit_credit(self) -> None:
if self.twCredits.currentRow() > -1:
self.modify_credits(True)
if self.twCredits.currentRow() < 0:
return
row = self.twCredits.currentRow()
lang = str(
self.twCredits.item(row, self.md_attributes["credits.language"]).data(QtCore.Qt.ItemDataRole.UserRole)
or utils.get_language_iso(self.twCredits.item(row, self.md_attributes["credits.language"]).text())
)
old = Credit(
self.twCredits.item(row, self.md_attributes["credits.person"]).text(),
self.twCredits.item(row, self.md_attributes["credits.role"]).text(),
self.twCredits.item(row, self.md_attributes["credits.primary"]).text() != "",
lang,
)
editor = CreditEditorWindow(self, self.selected_write_tags, row, old, "Edit Credit")
editor.creditChanged.connect(self._credit_changed)
editor.show()
def update_credit_primary_flag(self, row: int, primary: bool) -> None:
# if we're clearing a flag do it and quit
@ -1366,29 +1380,8 @@ class TaggerWindow(QtWidgets.QMainWindow):
# Now set our new primary
self.twCredits.item(row, self.md_attributes["credits.primary"]).setText("Yes")
def modify_credits(self, edit: bool) -> None:
row = self.twCredits.rowCount()
old = Credit()
mode = EditMode.NEW
if edit:
mode = EditMode.EDIT
row = self.twCredits.currentRow()
lang = str(
self.twCredits.item(row, self.md_attributes["credits.language"]).data(QtCore.Qt.ItemDataRole.UserRole)
or utils.get_language_iso(self.twCredits.item(row, self.md_attributes["credits.language"]).text())
)
old = Credit(
self.twCredits.item(row, self.md_attributes["credits.person"]).text(),
self.twCredits.item(row, self.md_attributes["credits.role"]).text(),
self.twCredits.item(row, self.md_attributes["credits.primary"]).text() != "",
lang,
)
editor = CreditEditorWindow(self, self.selected_write_tags, row, mode, old)
editor.creditChanged.connect(self._credit_changed)
editor.show()
def _edit_credit(self, credit: Credit, row: int) -> None:
def _update_credit(self, credit: Credit, row: int) -> None:
assert isinstance(row, int)
lang = utils.get_language_from_iso(credit.language) or credit.language
self.twCredits.item(row, self.md_attributes["credits.role"]).setText(credit.role)
self.twCredits.item(row, self.md_attributes["credits.person"]).setText(credit.person)
@ -1409,26 +1402,44 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.update_credit_colors()
self.set_dirty_flag()
def _credit_changed(self, credit: Credit, row: int, mode: EditMode) -> None:
def _credit_changed(self, credit: Credit, row: int) -> None:
dupe_index = self.get_dupe_credit(row, credit.role, credit.person)
if dupe_index < 0:
return self._update_credit(credit, row)
# delete the dupe credit from list
qmsg = QtWidgets.QMessageBox(parent=self)
qmsg.setText("Duplicate Credit!")
qmsg.setInformativeText(
"This will create a duplicate credit entry. Would you like to merge the entries, or create a duplicate?"
)
qmsg.addButton("Merge", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
qmsg.addButton("Duplicate", QtWidgets.QMessageBox.ButtonRole.RejectRole)
if self.is_dupe_credit(row, credit.role, credit.person):
# delete the dupe credit from list
qmsg = QtWidgets.QMessageBox()
qmsg.setText("Duplicate Credit!")
qmsg.setInformativeText(
"This will create a duplicate credit entry. Would you like to merge the entries, or create a duplicate?"
)
qmsg.addButton("Merge", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
qmsg.addButton("Duplicate", QtWidgets.QMessageBox.ButtonRole.NoRole)
def _merge(credit: Credit, row: int, existing: int) -> None:
self.twCredits.removeRow(row)
self._update_credit(credit, existing)
if qmsg.exec() == 0 and mode == EditMode.EDIT:
# just remove the row that would be same
self.twCredits.removeRow(row)
mode = EditMode.NEW
qmsg.accepted.connect(functools.partial(_merge, credit, row, dupe_index))
qmsg.rejected.connect(functools.partial(self._update_credit, credit, row))
if mode == EditMode.EDIT:
return self._edit_credit(credit, row)
self._add_credit(credit)
qmsg.show()
def _credit_added(self, credit: Credit) -> None:
dupe_index = self.get_dupe_credit(None, credit.role, credit.person)
if dupe_index < 0:
self._add_credit(credit)
return
# delete the dupe credit from list
qmsg = QtWidgets.QMessageBox(parent=self)
qmsg.setText("Duplicate Credit!")
qmsg.setInformativeText(
"This will create a duplicate credit entry. Would you like to merge the entries, or create a duplicate?"
)
qmsg.addButton("Merge", QtWidgets.QMessageBox.ButtonRole.AcceptRole)
qmsg.addButton("Duplicate", QtWidgets.QMessageBox.ButtonRole.RejectRole)
qmsg.accepted.connect(functools.partial(self._update_credit, credit, dupe_index))
qmsg.rejected.connect(functools.partial(self._add_credit, credit))
qmsg.show()
def remove_credit(self) -> None:
row = self.twCredits.currentRow()

View File

@ -161,6 +161,25 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblIssueLink">
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Issue Link:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="imageSourceLogo" native="true">
<property name="minimumSize">

View File

@ -90,6 +90,31 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblSeriesLink">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Series Link:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="imageSourceLogo" native="true">
<property name="minimumSize">

View File

@ -302,7 +302,7 @@ class ComicVineTalker(ComicTalker):
"format": "json",
"resources": "volume",
"query": search_series_name,
"field_list": "volume,name,id,start_year,publisher,image,description,count_of_issues,aliases",
"field_list": "volume,name,id,start_year,publisher,image,description,count_of_issues,aliases,site_detail_url",
"page": 1,
"limit": 100,
}
@ -801,7 +801,7 @@ class ComicVineTalker(ComicTalker):
aliases = record.get("aliases") or ""
return ComicSeries(
series = ComicSeries(
aliases=set(utils.split(aliases, "\n")),
count_of_issues=record.get("count_of_issues"),
count_of_volumes=None,
@ -813,6 +813,13 @@ class ComicVineTalker(ComicTalker):
start_year=start_year,
format=None,
)
url = utils.xlate(record.get("site_detail_url"))
if url:
try:
series.web_links = [parse_url(url)]
except LocationParseError:
...
return series
def _fetch_issues_in_series(
self,