Compare commits
3 Commits
9c2a2cbafd
...
e481fe5033
Author | SHA1 | Date | |
---|---|---|---|
e481fe5033 | |||
9bc85d95a1 | |||
5256f016b7 |
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
series_link = f'<a href="{url}">Link To Issue</a>'
|
||||
self.lblIssueLink.setText(series_link)
|
||||
return issue
|
||||
|
@ -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()
|
||||
|
@ -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("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("'", "'")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
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 :-(")
|
||||
|
@ -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()
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user