Compare commits
2 Commits
98387cac5e
...
9864159407
Author | SHA1 | Date | |
---|---|---|---|
9864159407 | |||
194c381c7f |
@ -26,7 +26,7 @@ from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.md import prepare_metadata
|
||||
from comictaggerlib.resulttypes import IssueResult, Result
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictalker.comictalker import ComicTalker, TalkerError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -209,17 +209,15 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
self.update_data()
|
||||
|
||||
def reject(self) -> None:
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Cancel Matching",
|
||||
"Are you sure you wish to cancel the matching process?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.No:
|
||||
return
|
||||
qmsg = QtWidgets.QMessageBox(self)
|
||||
qmsg.setIcon(qmsg.Icon.Question)
|
||||
qmsg.setText("Cancel Matching")
|
||||
qmsg.setInformativeText("Are you sure you wish to cancel the matching process?")
|
||||
qmsg.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No)
|
||||
qmsg.rejected.connect(self._cancel)
|
||||
qmsg.show()
|
||||
|
||||
def _cancel(self) -> None:
|
||||
QtWidgets.QDialog.reject(self)
|
||||
|
||||
def save_match(self) -> None:
|
||||
@ -230,13 +228,13 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
md, error = self.parent().read_selected_tags(self._tags, ca)
|
||||
if error is not None:
|
||||
logger.error("Failed to load tags for %s: %s", ca.path, error)
|
||||
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
QtWidgets.QMessageBox.critical(
|
||||
return qtutils.critical(
|
||||
self,
|
||||
"Read Failed!",
|
||||
f"One or more of the read tags failed to load for {ca.path}, check log for details",
|
||||
)
|
||||
return
|
||||
|
||||
if md.is_empty:
|
||||
md = ca.metadata_from_filename(
|
||||
@ -252,12 +250,11 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
self.current_match_set.md = ct_md = self.talker.fetch_comic_data(issue_id=match.issue_id)
|
||||
except TalkerError as e:
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
QtWidgets.QMessageBox.critical(self, f"{e.source} {e.code_name} Error", f"{e}")
|
||||
qtutils.critical(self, f"{e.source} {e.code_name} Error", str(e))
|
||||
return
|
||||
|
||||
if ct_md is None or ct_md.is_empty:
|
||||
QtWidgets.QMessageBox.critical(self, "Network Issue", "Could not retrieve issue details!")
|
||||
return
|
||||
return qtutils.critical(self, "Network Issue", "Could not retrieve issue details!")
|
||||
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
md = prepare_metadata(md, ct_md, self.config)
|
||||
@ -265,7 +262,7 @@ class AutoTagMatchWindow(QtWidgets.QDialog):
|
||||
success = ca.write_tags(md, tag_id)
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
if not success:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
qtutils.warning(
|
||||
self,
|
||||
"Write Error",
|
||||
f"Saving {tags[tag_id].name()} the tags to the archive seemed to fail!",
|
||||
|
@ -31,13 +31,13 @@ from comictaggerlib.issueidentifier import IssueIdentifierCancelled
|
||||
from comictaggerlib.md import read_selected_tags
|
||||
from comictaggerlib.resulttypes import Action, OnlineMatchResults, Result, Status
|
||||
from comictaggerlib.tag import identify_comic
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictalker.comictalker import ComicTalker, RLCallBack
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutoTagThread(QtCore.QThread):
|
||||
class AutoTagThread(QtCore.QThread): # TODO: re-check thread semantics. Specifically with signals
|
||||
autoTagComplete = QtCore.pyqtSignal(OnlineMatchResults, list)
|
||||
autoTagLogMsg = QtCore.pyqtSignal(str)
|
||||
autoTagProgress = QtCore.pyqtSignal(object, object, object, bytes, bytes) # see progress_callback
|
||||
@ -108,7 +108,7 @@ class AutoTagThread(QtCore.QThread):
|
||||
# read in tags, and parse file name if not there
|
||||
md, tags_used, error = read_selected_tags(self.config.internal__read_tags, ca)
|
||||
if error is not None:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
qtutils.critical(
|
||||
None,
|
||||
"Aborting...",
|
||||
f"One or more of the read tags failed to load for {ca.path}. Aborting to prevent any possible further damage. Check log for details.",
|
||||
|
@ -17,16 +17,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import NamedTuple
|
||||
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib.ctsettings import ct_ns, settngs_namespace
|
||||
from comictaggerlib.ui import ui_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutoTagSettings(NamedTuple):
|
||||
settings: settngs_namespace.Auto_Tag
|
||||
remove_after_success: bool
|
||||
series_match_identify_thresh: bool
|
||||
split_words: bool
|
||||
search_string: str
|
||||
|
||||
|
||||
class AutoTagStartWindow(QtWidgets.QDialog):
|
||||
startAutoTag = QtCore.pyqtSignal(AutoTagSettings)
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget, config: ct_ns, msg: str) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
@ -77,6 +89,8 @@ class AutoTagStartWindow(QtWidgets.QDialog):
|
||||
self.search_string = ""
|
||||
self.name_length_match_tolerance = self.config.Issue_Identifier__series_match_search_thresh
|
||||
self.split_words = self.cbxSplitWords.isChecked()
|
||||
self.adjustSize()
|
||||
self.setModal(True)
|
||||
|
||||
def search_string_toggle(self) -> None:
|
||||
enable = self.cbxSpecifySearchString.isChecked()
|
||||
@ -85,20 +99,26 @@ class AutoTagStartWindow(QtWidgets.QDialog):
|
||||
def accept(self) -> None:
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
self.auto_save_on_low = self.cbxSaveOnLowConfidence.isChecked()
|
||||
self.dont_use_year = self.cbxDontUseYear.isChecked()
|
||||
self.assume_issue_one = self.cbxAssumeIssueOne.isChecked()
|
||||
self.ignore_leading_digits_in_filename = self.cbxIgnoreLeadingDigitsInFilename.isChecked()
|
||||
self.remove_after_success = self.cbxRemoveAfterSuccess.isChecked()
|
||||
self.name_length_match_tolerance = self.sbNameMatchSearchThresh.value()
|
||||
self.split_words = self.cbxSplitWords.isChecked()
|
||||
|
||||
# persist some settings
|
||||
self.config.Auto_Tag__save_on_low_confidence = self.auto_save_on_low
|
||||
self.config.Auto_Tag__use_year_when_identifying = not self.dont_use_year
|
||||
self.config.Auto_Tag__assume_issue_one = self.assume_issue_one
|
||||
self.config.Auto_Tag__ignore_leading_numbers_in_filename = self.ignore_leading_digits_in_filename
|
||||
self.config.internal__remove_archive_after_successful_match = self.remove_after_success
|
||||
|
||||
if self.cbxSpecifySearchString.isChecked():
|
||||
self.search_string = self.leSearchString.text()
|
||||
self.startAutoTag.emit(
|
||||
AutoTagSettings(
|
||||
settings=settngs_namespace.Auto_Tag(
|
||||
online=self.config.Auto_Tag__online,
|
||||
save_on_low_confidence=self.cbxSaveOnLowConfidence.isChecked(),
|
||||
use_year_when_identifying=not self.cbxDontUseYear.isChecked(),
|
||||
assume_issue_one=self.cbxAssumeIssueOne.isChecked(),
|
||||
ignore_leading_numbers_in_filename=self.cbxIgnoreLeadingDigitsInFilename.isChecked(),
|
||||
parse_filename=self.config.Auto_Tag__parse_filename,
|
||||
prefer_filename=self.config.Auto_Tag__prefer_filename,
|
||||
issue_id=None,
|
||||
metadata=GenericMetadata(),
|
||||
clear_tags=self.config.Auto_Tag__clear_tags,
|
||||
publisher_filter=self.config.Auto_Tag__publisher_filter,
|
||||
use_publisher_filter=self.config.Auto_Tag__use_publisher_filter,
|
||||
auto_imprint=self.cbxAutoImprint.isChecked(),
|
||||
),
|
||||
remove_after_success=self.cbxRemoveAfterSuccess.isChecked(),
|
||||
series_match_identify_thresh=self.sbNameMatchSearchThresh.value(),
|
||||
split_words=self.cbxSplitWords.isChecked(),
|
||||
search_string=self.leSearchString.text(),
|
||||
)
|
||||
)
|
||||
|
@ -249,12 +249,13 @@ class CoverImageWidget(QtWidgets.QWidget):
|
||||
self.set_display_pixmap()
|
||||
|
||||
def load_page(self) -> None:
|
||||
if self.comic_archive is not None:
|
||||
if self.page_loader is not None:
|
||||
self.page_loader.abandoned = True
|
||||
self.page_loader = PageLoader(self.comic_archive, self.imageIndex)
|
||||
self.page_loader.loadComplete.connect(self.page_load_complete)
|
||||
self.page_loader.start()
|
||||
if self.comic_archive is None:
|
||||
return
|
||||
if self.page_loader is not None:
|
||||
self.page_loader.abandoned = True
|
||||
self.page_loader = PageLoader(self.comic_archive, self.imageIndex)
|
||||
self.page_loader.loadComplete.connect(self.page_load_complete)
|
||||
self.page_loader.start()
|
||||
|
||||
def page_load_complete(self, image_data: bytes) -> None:
|
||||
img = get_qimage_from_data(image_data)
|
||||
|
@ -18,33 +18,51 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import operator
|
||||
from enum import Enum, auto
|
||||
|
||||
import natsort
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import tags
|
||||
from comicapi.genericmetadata import Credit
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictaggerlib.ui.qtutils import enable_widget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreditEditorWindow(QtWidgets.QDialog):
|
||||
ModeEdit = 0
|
||||
ModeNew = 1
|
||||
class EditMode(Enum):
|
||||
EDIT = auto()
|
||||
NEW = auto()
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget, mode: int, credit: Credit) -> None:
|
||||
|
||||
class CreditEditorWindow(QtWidgets.QDialog):
|
||||
creditChanged = QtCore.pyqtSignal(Credit, int, EditMode)
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget, tags: list[str], row: int, mode: EditMode, credit: Credit) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
with (ui_path / "crediteditorwindow.ui").open(encoding="utf-8") as uifile:
|
||||
uic.loadUi(uifile, self)
|
||||
|
||||
self.mode = mode
|
||||
self.md_attributes = {
|
||||
"credits.person": self.leName,
|
||||
"credits.language": self.cbLanguage,
|
||||
"credits.role": self.cbRole,
|
||||
"credits.primary": self.cbPrimary,
|
||||
}
|
||||
|
||||
if self.mode == self.ModeEdit:
|
||||
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.setModal(True)
|
||||
|
||||
# Add the entries to the role combobox
|
||||
self.cbRole.addItem("")
|
||||
@ -86,13 +104,29 @@ class CreditEditorWindow(QtWidgets.QDialog):
|
||||
self.cbLanguage.setCurrentIndex(i)
|
||||
|
||||
self.cbPrimary.setChecked(credit.primary)
|
||||
self.update_tag_tweaks()
|
||||
|
||||
def get_credit(self) -> Credit:
|
||||
lang = self.cbLanguage.currentData() or self.cbLanguage.currentText()
|
||||
return Credit(self.leName.text(), self.cbRole.currentText(), self.cbPrimary.isChecked(), lang)
|
||||
|
||||
def update_tag_tweaks(self) -> None:
|
||||
# depending on the current data tag, certain fields are disabled
|
||||
enabled_widgets = set()
|
||||
for tag_id in self.tags:
|
||||
if not tags[tag_id].enabled:
|
||||
continue
|
||||
enabled_widgets.update(tags[tag_id].supported_attributes)
|
||||
|
||||
for md_field, widget in self.md_attributes.items():
|
||||
if widget is not None and not isinstance(widget, (int)):
|
||||
enable_widget(widget, md_field in enabled_widgets)
|
||||
|
||||
def accept(self) -> None:
|
||||
if self.leName.text() == "":
|
||||
QtWidgets.QMessageBox.warning(self, "Whoops", "You need to enter a name for a credit.")
|
||||
else:
|
||||
QtWidgets.QDialog.accept(self)
|
||||
return qtutils.warning(self, "Whoops", "You need to enter a name for a credit.")
|
||||
|
||||
QtWidgets.QDialog.accept(self)
|
||||
new = self.get_credit()
|
||||
if self.credit != new:
|
||||
self.creditChanged.emit(new, self.row, self.mode)
|
||||
|
@ -17,6 +17,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from enum import Enum, auto
|
||||
from typing import NamedTuple
|
||||
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
|
||||
@ -25,19 +27,35 @@ from comictaggerlib.ui import ui_path
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExportConflictOpts:
|
||||
dontCreate = 1
|
||||
overwrite = 2
|
||||
createUnique = 3
|
||||
class ExportConflictOpts(Enum):
|
||||
DONT_CREATE = auto()
|
||||
OVERWRITE = auto()
|
||||
CREATE_UNIQUE = auto()
|
||||
|
||||
|
||||
class ExportConfig(NamedTuple):
|
||||
conflict: ExportConflictOpts
|
||||
add_to_list: bool
|
||||
delete_original: bool
|
||||
|
||||
|
||||
class ExportWindow(QtWidgets.QDialog):
|
||||
def __init__(self, parent: QtWidgets.QWidget, msg: str) -> None:
|
||||
export = QtCore.pyqtSignal(ExportConfig)
|
||||
|
||||
def __init__(self, parent: QtWidgets.QWidget) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
with (ui_path / "exportwindow.ui").open(encoding="utf-8") as uifile:
|
||||
uic.loadUi(uifile, self)
|
||||
self.label.setText(msg)
|
||||
self.label: QtWidgets.QLabel
|
||||
self.cbxDeleteOriginal: QtWidgets.QCheckBox
|
||||
self.cbxAddToList: QtWidgets.QCheckBox
|
||||
self.radioDontCreate: QtWidgets.QRadioButton
|
||||
self.radioCreateNew: QtWidgets.QRadioButton
|
||||
self.msg = """You have selected {count} archive(s) to export to Zip format. New archives will be created in the same folder as the original.
|
||||
|
||||
Please choose config below, and select OK.
|
||||
"""
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowType(self.windowFlags() & ~QtCore.Qt.WindowType.WindowContextHelpButtonHint)
|
||||
@ -46,17 +64,20 @@ class ExportWindow(QtWidgets.QDialog):
|
||||
self.cbxDeleteOriginal.setChecked(False)
|
||||
self.cbxAddToList.setChecked(True)
|
||||
self.radioDontCreate.setChecked(True)
|
||||
self.setModal(True)
|
||||
|
||||
self.deleteOriginal = False
|
||||
self.addToList = True
|
||||
self.fileConflictBehavior = ExportConflictOpts.dontCreate
|
||||
def show(self, count: int) -> None:
|
||||
self.label.setText(self.msg.format(count=count))
|
||||
self.adjustSize()
|
||||
QtWidgets.QDialog.show(self)
|
||||
|
||||
def accept(self) -> None:
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
self.deleteOriginal = self.cbxDeleteOriginal.isChecked()
|
||||
self.addToList = self.cbxAddToList.isChecked()
|
||||
conflict = ExportConflictOpts.DONT_CREATE
|
||||
if self.radioDontCreate.isChecked():
|
||||
self.fileConflictBehavior = ExportConflictOpts.dontCreate
|
||||
conflict = ExportConflictOpts.DONT_CREATE
|
||||
elif self.radioCreateNew.isChecked():
|
||||
self.fileConflictBehavior = ExportConflictOpts.createUnique
|
||||
conflict = ExportConflictOpts.CREATE_UNIQUE
|
||||
|
||||
QtWidgets.QDialog.accept(self)
|
||||
self.export.emit(ExportConfig(conflict, self.cbxAddToList.isChecked(), self.cbxDeleteOriginal.isChecked()))
|
||||
|
@ -30,7 +30,7 @@ from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.graphics import graphics_path
|
||||
from comictaggerlib.optionalmsgdialog import OptionalMessageDialog
|
||||
from comictaggerlib.settingswindow import linuxRarHelp, macRarHelp, windowsRarHelp
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictaggerlib.ui.qtutils import center_window_on_parent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -56,7 +56,7 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
uic.loadUi(uifile, self)
|
||||
|
||||
self.config = config
|
||||
|
||||
self.twList: QtWidgets.QTableWidget
|
||||
self.twList.horizontalHeader().setMinimumSectionSize(50)
|
||||
self.twList.currentItemChanged.connect(self.current_item_changed_cb)
|
||||
|
||||
@ -108,6 +108,17 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
QtWidgets.QTableWidgetSelectionRange(0, 0, self.twList.rowCount() - 1, self.twList.columnCount() - 1), False
|
||||
)
|
||||
|
||||
def remove_deleted(self) -> None:
|
||||
deleted = []
|
||||
for row in range(self.twList.rowCount()):
|
||||
row_ca = self.get_archive_by_row(row)
|
||||
if not row_ca:
|
||||
continue
|
||||
if not row_ca.path.exists():
|
||||
deleted.append(row_ca)
|
||||
|
||||
self.remove_archive_list(deleted)
|
||||
|
||||
def remove_archive_list(self, ca_list: list[ComicArchive]) -> None:
|
||||
self.twList.setSortingEnabled(False)
|
||||
current_removed = False
|
||||
@ -219,11 +230,8 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
self.twList.selectRow(first_added)
|
||||
else:
|
||||
if len(pathlist) == 1 and os.path.isfile(pathlist[0]):
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "File Open", "Selected file doesn't seem to be a comic archive."
|
||||
)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "File/Folder Open", "No readable comic archives were found.")
|
||||
return qtutils.information(self, "File Open", "Selected file doesn't seem to be a comic archive.")
|
||||
return qtutils.information(self, "File/Folder Open", "No readable comic archives were found.")
|
||||
|
||||
if rar_added_ro:
|
||||
self.rar_ro_message()
|
||||
@ -339,10 +347,15 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
ca: ComicArchive = self.twList.item(row, FileSelectionList.dataColNum).data(QtCore.Qt.ItemDataRole.UserRole)
|
||||
|
||||
filename_item = self.twList.item(row, FileSelectionList.fileColNum)
|
||||
assert filename_item
|
||||
folder_item = self.twList.item(row, FileSelectionList.folderColNum)
|
||||
assert folder_item
|
||||
md_item = self.twList.item(row, FileSelectionList.MDFlagColNum)
|
||||
assert md_item
|
||||
type_item = self.twList.item(row, FileSelectionList.typeColNum)
|
||||
assert type_item
|
||||
readonly_item = self.twList.item(row, FileSelectionList.readonlyColNum)
|
||||
assert readonly_item
|
||||
|
||||
item_text = os.path.split(ca.path)[1]
|
||||
filename_item.setText(item_text)
|
||||
@ -398,6 +411,10 @@ class FileSelectionList(QtWidgets.QWidget):
|
||||
|
||||
if old_idx == new_idx:
|
||||
return
|
||||
ca = self.get_archive_by_row(new_idx)
|
||||
if not ca or not ca.path.exists():
|
||||
self.remove_deleted()
|
||||
return
|
||||
|
||||
# don't allow change if modified
|
||||
if prev is not None and new_idx != old_idx:
|
||||
|
@ -23,15 +23,14 @@ try:
|
||||
If unavailable (non-console application), log an additional notice.
|
||||
"""
|
||||
if QtWidgets.QApplication.instance() is not None:
|
||||
errorbox = QtWidgets.QMessageBox()
|
||||
errorbox = QtWidgets.QMessageBox(QtWidgets.QApplication.activeWindow())
|
||||
errorbox.setStandardButtons(
|
||||
QtWidgets.QMessageBox.StandardButton.Abort | QtWidgets.QMessageBox.StandardButton.Ignore
|
||||
)
|
||||
errorbox.setText(log_msg)
|
||||
if errorbox.exec() == QtWidgets.QMessageBox.StandardButton.Abort:
|
||||
QtWidgets.QApplication.exit(1)
|
||||
else:
|
||||
logger.warning("Exception ignored")
|
||||
errorbox.rejected.connect(lambda: QtWidgets.QApplication.exit(1))
|
||||
errorbox.accepted.connect(lambda: logger.warning("Exception ignored"))
|
||||
errorbox.show()
|
||||
else:
|
||||
logger.debug("No QApplication instance available.")
|
||||
|
||||
|
@ -39,7 +39,7 @@ class IssueNumberTableWidgetItem(QtWidgets.QTableWidgetItem):
|
||||
return (IssueString(self_str).as_float() or 0) < (IssueString(other_str).as_float() or 0)
|
||||
|
||||
|
||||
class QueryThread(QtCore.QThread):
|
||||
class QueryThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
|
||||
def __init__(
|
||||
self,
|
||||
talker: ComicTalker,
|
||||
@ -54,7 +54,6 @@ class QueryThread(QtCore.QThread):
|
||||
self.on_ratelimit = on_ratelimit
|
||||
|
||||
def run(self) -> None:
|
||||
# QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
|
||||
try:
|
||||
issue_list = [
|
||||
@ -66,11 +65,8 @@ class QueryThread(QtCore.QThread):
|
||||
]
|
||||
except TalkerError as e:
|
||||
logger.exception("Failed to retrieve issue list: %s", 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)
|
||||
|
||||
|
||||
|
@ -32,6 +32,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MatchSelectionWindow(QtWidgets.QDialog):
|
||||
match_selected = QtCore.pyqtSignal(IssueResult)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: QtWidgets.QWidget,
|
||||
@ -70,6 +72,7 @@ class MatchSelectionWindow(QtWidgets.QDialog):
|
||||
|
||||
self.twList.currentItemChanged.connect(self.current_item_changed)
|
||||
self.twList.cellDoubleClicked.connect(self.cell_double_clicked)
|
||||
self.accepted.connect(self.select)
|
||||
|
||||
self.update_data()
|
||||
|
||||
@ -160,3 +163,6 @@ class MatchSelectionWindow(QtWidgets.QDialog):
|
||||
row = self.twList.currentRow()
|
||||
match: IssueResult = self.twList.item(row, 0).data(QtCore.Qt.ItemDataRole.UserRole)[0]
|
||||
return match
|
||||
|
||||
def selected(self) -> None:
|
||||
self.match_selected.emit(self.current_match())
|
||||
|
@ -1,15 +1,4 @@
|
||||
"""A PyQt6 dialog to show a message and let the user check a box
|
||||
|
||||
Example usage:
|
||||
|
||||
checked = OptionalMessageDialog.msg(self, "Disclaimer",
|
||||
"This is beta software, and you are using it at your own risk!",
|
||||
)
|
||||
|
||||
said_yes, checked = OptionalMessageDialog.question(self, "QtWidgets.Question",
|
||||
"Are you sure you wish to do this?",
|
||||
)
|
||||
"""
|
||||
"""A PyQt6 dialog to show a message and let the user check a box"""
|
||||
|
||||
#
|
||||
# Copyright 2012-2014 ComicTagger Authors
|
||||
@ -28,6 +17,7 @@ said_yes, checked = OptionalMessageDialog.question(self, "QtWidgets.Question",
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
from PyQt6 import QtCore, QtWidgets
|
||||
|
||||
@ -39,7 +29,14 @@ StyleQuestion = 1
|
||||
|
||||
class OptionalMessageDialog(QtWidgets.QDialog):
|
||||
def __init__(
|
||||
self, parent: QtWidgets.QWidget, style: int, title: str, msg: str, checked: bool = False, check_text: str = ""
|
||||
self,
|
||||
parent: QtWidgets.QWidget,
|
||||
style: int,
|
||||
title: str,
|
||||
msg: str,
|
||||
*,
|
||||
checked: bool = False,
|
||||
check_text: str = "",
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
@ -85,36 +82,54 @@ class OptionalMessageDialog(QtWidgets.QDialog):
|
||||
layout.addWidget(self.theButtonBox)
|
||||
|
||||
def accept(self) -> None:
|
||||
self.was_accepted = True
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
def reject(self) -> None:
|
||||
self.was_accepted = False
|
||||
QtWidgets.QDialog.reject(self)
|
||||
|
||||
@staticmethod
|
||||
def msg(parent: QtWidgets.QWidget, title: str, msg: str, checked: bool = False, check_text: str = "") -> bool:
|
||||
def msg(
|
||||
parent: QtWidgets.QWidget,
|
||||
title: str,
|
||||
msg: str,
|
||||
*,
|
||||
callback: Callable[[bool], None],
|
||||
checked: bool = False,
|
||||
check_text: str = "",
|
||||
) -> None:
|
||||
d = OptionalMessageDialog(parent, StyleMessage, title, msg, checked=checked, check_text=check_text)
|
||||
|
||||
d.exec()
|
||||
return d.theCheckBox.isChecked()
|
||||
def finished(i: int) -> None:
|
||||
callback(d.theCheckBox.isChecked())
|
||||
|
||||
d.finished.connect(finished)
|
||||
|
||||
d.show()
|
||||
|
||||
@staticmethod
|
||||
def question(
|
||||
parent: QtWidgets.QWidget, title: str, msg: str, checked: bool = False, check_text: str = ""
|
||||
) -> tuple[bool, bool]:
|
||||
parent: QtWidgets.QWidget,
|
||||
title: str,
|
||||
msg: str,
|
||||
*,
|
||||
callback: Callable[[bool, bool], None],
|
||||
checked: bool = False,
|
||||
check_text: str = "",
|
||||
) -> None:
|
||||
d = OptionalMessageDialog(parent, StyleQuestion, title, msg, checked=checked, check_text=check_text)
|
||||
|
||||
d.exec()
|
||||
def finished(i: int) -> None:
|
||||
callback(i == QtWidgets.QDialog.DialogCode.Accepted, d.theCheckBox.isChecked())
|
||||
|
||||
return d.was_accepted, d.theCheckBox.isChecked()
|
||||
d.finished.connect(finished)
|
||||
|
||||
d.show()
|
||||
|
||||
@staticmethod
|
||||
def msg_no_checkbox(
|
||||
parent: QtWidgets.QWidget, title: str, msg: str, checked: bool = False, check_text: str = ""
|
||||
) -> bool:
|
||||
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.theCheckBox.hide()
|
||||
|
||||
d.exec()
|
||||
return d.theCheckBox.isChecked()
|
||||
d.show()
|
||||
|
@ -351,7 +351,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.comic_archive = comic_archive
|
||||
self.pages_list = pages_list
|
||||
if pages_list:
|
||||
self.select_read_tags(self.tag_ids)
|
||||
self.select_write_tags(self.tag_ids)
|
||||
else:
|
||||
self.cbPageType.setEnabled(False)
|
||||
self.chkDoublePage.setEnabled(False)
|
||||
@ -396,7 +396,7 @@ class PageListEditor(QtWidgets.QWidget):
|
||||
self.first_front_page = self.get_first_front_cover()
|
||||
self.firstFrontCoverChanged.emit(self.first_front_page)
|
||||
|
||||
def select_read_tags(self, tag_ids: list[str]) -> None:
|
||||
def select_write_tags(self, tag_ids: list[str]) -> None:
|
||||
# depending on the current tags, certain fields are disabled
|
||||
if not tag_ids:
|
||||
return
|
||||
|
@ -25,7 +25,7 @@ from comicapi.comicarchive import ComicArchive
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PageLoader(QtCore.QThread):
|
||||
class PageLoader(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
|
||||
"""
|
||||
This class holds onto a reference of each instance in a list since
|
||||
problems occur if the ref count goes to zero and the GC tries to reap
|
||||
|
@ -20,6 +20,7 @@ import logging
|
||||
|
||||
import settngs
|
||||
from PyQt6 import QtCore, QtWidgets, uic
|
||||
from PyQt6.QtGui import QColorConstants
|
||||
|
||||
from comicapi import utils
|
||||
from comicapi.comicarchive import ComicArchive, tags
|
||||
@ -27,7 +28,7 @@ from comicapi.genericmetadata import GenericMetadata
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.filerenamer import FileRenamer, get_rename_dir
|
||||
from comictaggerlib.settingswindow import SettingsWindow
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictaggerlib.ui.qtutils import center_window_on_parent
|
||||
from comictalker.comictalker import ComicTalker
|
||||
|
||||
@ -70,12 +71,13 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
|
||||
self.do_preview()
|
||||
|
||||
def config_renamer(self, ca: ComicArchive, md: GenericMetadata = GenericMetadata()) -> str:
|
||||
def config_renamer(self, ca: ComicArchive, md: GenericMetadata = GenericMetadata()) -> tuple[str, Exception | None]:
|
||||
self.renamer.set_template(self.config[0].File_Rename__template)
|
||||
self.renamer.set_issue_zero_padding(self.config[0].File_Rename__issue_number_padding)
|
||||
self.renamer.set_smart_cleanup(self.config[0].File_Rename__use_smart_string_cleanup)
|
||||
self.renamer.replacements = self.config[0].File_Rename__replacements
|
||||
self.renamer.move_only = self.config[0].File_Rename__only_move
|
||||
error = None
|
||||
|
||||
new_ext = ca.path.suffix # default
|
||||
if self.config[0].File_Rename__auto_extension:
|
||||
@ -85,11 +87,6 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
md, _, error = self.parent().read_selected_tags(self.read_tag_ids, ca)
|
||||
if error is not None:
|
||||
logger.error("Failed to load tags from %s: %s", ca.path, error)
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Read Failed!",
|
||||
f"One or more of the read tags failed to load for {ca.path}, check log for details",
|
||||
)
|
||||
|
||||
if md.is_empty:
|
||||
md = ca.metadata_from_filename(
|
||||
@ -100,20 +97,22 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
)
|
||||
self.renamer.set_metadata(md, ca.path.name)
|
||||
self.renamer.move = self.config[0].File_Rename__move
|
||||
return new_ext
|
||||
return new_ext, error
|
||||
|
||||
def do_preview(self) -> None:
|
||||
self.twList.setRowCount(0)
|
||||
|
||||
self.twList.setSortingEnabled(False)
|
||||
|
||||
errors = False
|
||||
for ca in self.comic_archive_list:
|
||||
new_ext = self.config_renamer(ca)
|
||||
new_ext, error = self.config_renamer(ca)
|
||||
errors = errors or error is not None
|
||||
try:
|
||||
new_name = self.renamer.determine_name(new_ext)
|
||||
except ValueError as e:
|
||||
logger.exception("Invalid format string: %s", self.config[0].File_Rename__template)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
qtutils.critical(
|
||||
self,
|
||||
"Invalid format string!",
|
||||
"Your rename template is invalid!"
|
||||
@ -128,7 +127,7 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
logger.exception(
|
||||
"Formatter failure: %s metadata: %s", self.config[0].File_Rename__template, self.renamer.metadata
|
||||
)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
qtutils.critical(
|
||||
self,
|
||||
"The formatter had an issue!",
|
||||
"The formatter has experienced an unexpected error!"
|
||||
@ -164,7 +163,11 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
|
||||
new_name_item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled)
|
||||
self.twList.setItem(row, 2, new_name_item)
|
||||
new_name_item.setText(new_name)
|
||||
if error is not None:
|
||||
new_name_item.setText(f"Error reading tags: {error}")
|
||||
new_name_item.setBackground(QColorConstants.Red)
|
||||
else:
|
||||
new_name_item.setText(new_name)
|
||||
new_name_item.setData(QtCore.Qt.ItemDataRole.ToolTipRole, new_name)
|
||||
|
||||
self.rename_list.append(new_name)
|
||||
@ -177,14 +180,18 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
self.twList.setColumnWidth(0, 200)
|
||||
|
||||
self.twList.setSortingEnabled(True)
|
||||
if errors:
|
||||
qtutils.warning(self, "Read Failed!", "One or more of the read tags failed to load, check log for details")
|
||||
|
||||
def modify_settings(self) -> None:
|
||||
settingswin = SettingsWindow(self, self.config, self.talkers)
|
||||
settingswin.setModal(True)
|
||||
settingswin.show_rename_tab()
|
||||
settingswin.exec()
|
||||
if settingswin.result():
|
||||
self.do_preview()
|
||||
settingswin.accepted.connect(self.settings_closed)
|
||||
settingswin.show()
|
||||
|
||||
def settings_closed(self) -> None:
|
||||
self.do_preview()
|
||||
|
||||
def accept(self) -> None:
|
||||
prog_dialog = QtWidgets.QProgressDialog("", "Cancel", 0, len(self.rename_list), self)
|
||||
@ -234,22 +241,23 @@ class RenameWindow(QtWidgets.QDialog):
|
||||
except Exception as e:
|
||||
assert comic
|
||||
logger.exception("Failed to rename comic archive: %s", comic[0].path)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
qtutils.critical(
|
||||
self,
|
||||
"There was an issue when renaming!",
|
||||
f"Renaming failed!<br/><br/>{type(e).__name__}: {e}<br/><br/>",
|
||||
)
|
||||
|
||||
if failed_renames:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
qtutils.critical(
|
||||
self,
|
||||
f"Failed to rename {len(failed_renames)} files!",
|
||||
f"Renaming failed for {len(failed_renames)} files!<br/><br/>"
|
||||
+ "<br/>".join([f"{x[0]!r} -> {x[1]!r}: {x[2]}" for x in failed_renames])
|
||||
+ "<br/><br/>",
|
||||
"Renaming failed for {} files!<br/><br/>{}<br/><br/>".format(
|
||||
len(failed_renames),
|
||||
"<br/>".join([f"{x[0]!r} -> {x[1]!r}: {x[2]}" for x in failed_renames]),
|
||||
),
|
||||
)
|
||||
|
||||
prog_dialog.hide()
|
||||
prog_dialog.close()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
@ -42,7 +42,7 @@ from comictalker.comictalker import ComicTalker, RLCallBack, TalkerError
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SearchThread(QtCore.QThread):
|
||||
class SearchThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
|
||||
searchComplete = pyqtSignal()
|
||||
progressUpdate = pyqtSignal(int, int)
|
||||
ratelimit = pyqtSignal(float, float)
|
||||
@ -86,7 +86,7 @@ class SearchThread(QtCore.QThread):
|
||||
self.ratelimit.emit(full_time, sleep_time)
|
||||
|
||||
|
||||
class IdentifyThread(QtCore.QThread):
|
||||
class IdentifyThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
|
||||
ratelimit = pyqtSignal(float, float)
|
||||
identifyComplete = pyqtSignal(IIResult, list)
|
||||
identifyLogMsg = pyqtSignal(str)
|
||||
@ -336,7 +336,7 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
self.progdialog.setMinimumDuration(300)
|
||||
|
||||
if refresh or self.search_thread.isRunning():
|
||||
self.progdialog.exec()
|
||||
self.progdialog.open()
|
||||
else:
|
||||
self.progdialog = None
|
||||
|
||||
@ -428,15 +428,13 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
|
||||
def auto_select(self) -> None:
|
||||
if self.comic_archive is None:
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Select", "You need to load a comic first!")
|
||||
qtutils.information(self, "Auto-Select", "You need to load a comic first!")
|
||||
return
|
||||
|
||||
if self.issue_number is None or self.issue_number == "":
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Select", "Can't auto-select without an issue number (yet!)")
|
||||
qtutils.information(self, "Auto-Select", "Can't auto-select without an issue number (yet!)")
|
||||
return
|
||||
self.iddialog = IDProgressWindow(self)
|
||||
self.iddialog.setModal(True)
|
||||
self.iddialog.show()
|
||||
|
||||
md = GenericMetadata()
|
||||
md.series = self.series_name
|
||||
@ -453,7 +451,7 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
|
||||
self.id_thread.start()
|
||||
|
||||
self.iddialog.exec()
|
||||
self.iddialog.open()
|
||||
|
||||
def log_output(self, text: str) -> None:
|
||||
if self.iddialog is None:
|
||||
@ -469,47 +467,46 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
self.iddialog.progressBar.setValue(cur)
|
||||
|
||||
def identify_complete(self, result: IIResult, issues: list[IssueResult]) -> None:
|
||||
if not (self.iddialog is not None and self.comic_archive is not None):
|
||||
if self.iddialog is None or self.comic_archive is None:
|
||||
return
|
||||
|
||||
found_match = None
|
||||
choices = False
|
||||
if result == IIResult.single_good_match:
|
||||
return self.update_match(issues[0])
|
||||
|
||||
qmsg = QtWidgets.QMessageBox(parent=self)
|
||||
qmsg.setIcon(qmsg.Icon.Information)
|
||||
qmsg.setText("Auto-Select Result")
|
||||
qmsg.setInformativeText(" Manual interaction needed :-(")
|
||||
qmsg.finished.connect(self.iddialog.close)
|
||||
|
||||
if result == IIResult.no_matches:
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Select Result", " No issues found :-(")
|
||||
elif result == IIResult.single_bad_cover_score:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
"Auto-Select Result",
|
||||
" Found a match, but cover doesn't seem the same. Verify before committing!",
|
||||
)
|
||||
found_match = issues[0]
|
||||
elif result == IIResult.multiple_bad_cover_scores:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Auto-Select Result", " Found some possibilities, but no confidence. Proceed manually."
|
||||
)
|
||||
choices = True
|
||||
elif result == IIResult.single_good_match:
|
||||
found_match = issues[0]
|
||||
qmsg.setInformativeText(" No matches found :-(")
|
||||
return qmsg.show()
|
||||
|
||||
if result == IIResult.single_bad_cover_score:
|
||||
qmsg.setInformativeText(" Found a match, but cover doesn't seem the same. Verify before committing!")
|
||||
qmsg.finished.connect(lambda: self.update_match(issues[0]))
|
||||
return qmsg.show()
|
||||
|
||||
selector = MatchSelectionWindow(self, issues, self.comic_archive, talker=self.talker, config=self.config)
|
||||
selector.match_selected.connect(self.update_match)
|
||||
qmsg.finished.connect(selector.open)
|
||||
|
||||
if result == IIResult.multiple_bad_cover_scores:
|
||||
qmsg.setInformativeText(" Found some possibilities, but no confidence. Proceed manually.")
|
||||
elif result == IIResult.multiple_good_matches:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "Auto-Select Result", " Found multiple likely matches. Please select."
|
||||
)
|
||||
choices = True
|
||||
qmsg.setInformativeText(" Found multiple likely matches. Please select.")
|
||||
|
||||
if choices:
|
||||
selector = MatchSelectionWindow(self, issues, self.comic_archive, talker=self.talker, config=self.config)
|
||||
selector.exec()
|
||||
if selector.result():
|
||||
# we should now have a list index
|
||||
found_match = selector.current_match()
|
||||
qmsg.show()
|
||||
|
||||
if found_match is not None:
|
||||
self.iddialog.accept()
|
||||
def update_match(self, match: IssueResult) -> None:
|
||||
if self.iddialog is not None:
|
||||
self.iddialog.close()
|
||||
|
||||
self.series_id = utils.xlate(found_match.series_id) or ""
|
||||
self.issue_number = found_match.issue_number
|
||||
self.select_by_id()
|
||||
self.show_issues()
|
||||
self.series_id = utils.xlate(match.series_id) or ""
|
||||
self.issue_number = match.issue_number
|
||||
self.select_by_id()
|
||||
self.show_issues()
|
||||
|
||||
def show_issues(self) -> None:
|
||||
title = ""
|
||||
@ -567,13 +564,14 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
self.progdialog.accept()
|
||||
self.progdialog = None
|
||||
if self.search_thread is not None and self.search_thread.ct_error:
|
||||
# TODO Currently still opens the window
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
parent = self.parent()
|
||||
if not isinstance(parent, QtWidgets.QWidget):
|
||||
parent = None
|
||||
return qtutils.critical(
|
||||
parent,
|
||||
f"{self.search_thread.error_e.source} {self.search_thread.error_e.code_name} Error",
|
||||
f"{self.search_thread.error_e}",
|
||||
)
|
||||
return
|
||||
|
||||
tmp_list = self.search_thread.ct_search_results if self.search_thread is not None else []
|
||||
self.series_list = {x.id: x for x in tmp_list}
|
||||
@ -674,8 +672,7 @@ class SeriesSelectionWindow(SelectionWindow):
|
||||
self.twList.resizeRowsToContents()
|
||||
|
||||
if not self.series_list:
|
||||
QtWidgets.QMessageBox.information(self, "Search Result", "No matches found!\nSeriesSelectionWindow")
|
||||
QtCore.QTimer.singleShot(200, self.close_me)
|
||||
return qtutils.information(self, "Search Result", "No matches found!\nSeriesSelectionWindow")
|
||||
|
||||
elif self.immediate_autoselect:
|
||||
# defer the immediate autoselect so this dialog has time to pop up
|
||||
|
@ -37,7 +37,7 @@ from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.ctsettings.plugin import group_for_plugin
|
||||
from comictaggerlib.filerenamer import FileRenamer, Replacement, Replacements
|
||||
from comictaggerlib.imagefetcher import ImageFetcher
|
||||
from comictaggerlib.ui import ui_path
|
||||
from comictaggerlib.ui import qtutils, ui_path
|
||||
from comictalker.comiccacher import ComicCacher
|
||||
from comictalker.comictalker import ComicTalker
|
||||
|
||||
@ -146,6 +146,8 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
with (ui_path / "settingswindow.ui").open(encoding="utf-8") as uifile:
|
||||
uic.loadUi(uifile, self)
|
||||
|
||||
self.leRarExePath: QtWidgets.QLineEdit
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowType(self.windowFlags() & ~QtCore.Qt.WindowType.WindowContextHelpButtonHint)
|
||||
)
|
||||
@ -154,6 +156,8 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
self.talkers = talkers
|
||||
self.name = "Settings"
|
||||
|
||||
self.setModal(True)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
self.lblRarHelp.setText(windowsRarHelp)
|
||||
|
||||
@ -526,7 +530,7 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
logger.error(
|
||||
"Invalid format string: %s", self.config[0].File_Rename__template, exc_info=self.rename_error
|
||||
)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
return qtutils.critical(
|
||||
self,
|
||||
"Invalid format string!",
|
||||
"Your rename template is invalid!"
|
||||
@ -536,23 +540,21 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
+ "<a href='https://docs.python.org/3/library/string.html#format-string-syntax'>"
|
||||
+ "https://docs.python.org/3/library/string.html#format-string-syntax</a>",
|
||||
)
|
||||
return
|
||||
else:
|
||||
logger.error(
|
||||
"Formatter failure: %s metadata: %s",
|
||||
self.config[0].File_Rename__template,
|
||||
self.renamer.metadata,
|
||||
exc_info=self.rename_error,
|
||||
)
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"The formatter had an issue!",
|
||||
"The formatter has experienced an unexpected error!"
|
||||
+ f"<br/><br/>{type(self.rename_error).__name__}: {self.rename_error}<br/><br/>"
|
||||
+ "Please open an issue at "
|
||||
+ "<a href='https://github.com/comictagger/comictagger'>"
|
||||
+ "https://github.com/comictagger/comictagger</a>",
|
||||
)
|
||||
logger.error(
|
||||
"Formatter failure: %s metadata: %s",
|
||||
self.config[0].File_Rename__template,
|
||||
self.renamer.metadata,
|
||||
exc_info=self.rename_error,
|
||||
)
|
||||
return qtutils.critical(
|
||||
self,
|
||||
"The formatter had an issue!",
|
||||
"The formatter has experienced an unexpected error!"
|
||||
+ f"<br/><br/>{type(self.rename_error).__name__}: {self.rename_error}<br/><br/>"
|
||||
+ "Please open an issue at "
|
||||
+ "<a href='https://github.com/comictagger/comictagger'>"
|
||||
+ "https://github.com/comictagger/comictagger</a>",
|
||||
)
|
||||
|
||||
# Copy values from form to settings and save
|
||||
archive_group = group_for_plugin(Archiver)
|
||||
@ -645,12 +647,12 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
cache_folder.mkdir(parents=True, exist_ok=True)
|
||||
ComicCacher(cache_folder, "0")
|
||||
ImageFetcher(cache_folder)
|
||||
QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.")
|
||||
qtutils.information(self, self.name, "Cache has been cleared.")
|
||||
|
||||
def reset_settings(self) -> None:
|
||||
self.config = cast(settngs.Config[ct_ns], settngs.get_namespace(settngs.defaults(self.config[1])))
|
||||
self.settings_to_form()
|
||||
QtWidgets.QMessageBox.information(self, self.name, self.name + " have been returned to default values.")
|
||||
qtutils.information(self, self.name, self.name + " have been returned to default values.")
|
||||
|
||||
def select_file(self, control: QtWidgets.QLineEdit, name: str) -> None:
|
||||
dialog = QtWidgets.QFileDialog(self)
|
||||
@ -671,9 +673,11 @@ class SettingsWindow(QtWidgets.QDialog):
|
||||
else:
|
||||
dialog.setWindowTitle(f"Find {name} library")
|
||||
|
||||
if dialog.exec():
|
||||
file_list = dialog.selectedFiles()
|
||||
control.setText(str(file_list[0]))
|
||||
dialog.fileSelected.connect(self.set_rar_path)
|
||||
dialog.open()
|
||||
|
||||
def set_rar_path(self, path: str) -> None:
|
||||
self.leRarExePath.setText(str(path))
|
||||
|
||||
def show_rename_tab(self) -> None:
|
||||
self.tabWidget.setCurrentIndex(5)
|
||||
|
@ -44,12 +44,12 @@ from comictaggerlib import ctsettings, ctversion
|
||||
from comictaggerlib.applicationlogwindow import ApplicationLogWindow, QTextEditLogger
|
||||
from comictaggerlib.autotagmatchwindow import AutoTagMatchWindow
|
||||
from comictaggerlib.autotagprogresswindow import AutoTagProgressWindow, AutoTagThread
|
||||
from comictaggerlib.autotagstartwindow import AutoTagStartWindow
|
||||
from comictaggerlib.autotagstartwindow import AutoTagSettings, AutoTagStartWindow
|
||||
from comictaggerlib.cbltransformer import CBLTransformer
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.crediteditorwindow import CreditEditorWindow
|
||||
from comictaggerlib.crediteditorwindow import CreditEditorWindow, EditMode
|
||||
from comictaggerlib.ctsettings import ct_ns
|
||||
from comictaggerlib.exportwindow import ExportConflictOpts, ExportWindow
|
||||
from comictaggerlib.exportwindow import ExportConfig, ExportConflictOpts, ExportWindow
|
||||
from comictaggerlib.fileselectionlist import FileSelectionList
|
||||
from comictaggerlib.graphics import graphics_path
|
||||
from comictaggerlib.gtinvalidator import is_valid_gtin
|
||||
@ -75,7 +75,7 @@ def execute(f: Callable[[], Any]) -> None:
|
||||
f()
|
||||
|
||||
|
||||
class QueryThread(QtCore.QThread):
|
||||
class QueryThread(QtCore.QThread): # TODO: Evaluate thread semantics. Specifically with signals
|
||||
def __init__(
|
||||
self,
|
||||
talker: ComicTalker,
|
||||
@ -102,8 +102,7 @@ class QueryThread(QtCore.QThread):
|
||||
on_rate_limit=RLCallBack(lambda x, y: self.on_rate_limit.emit(x, y), 60),
|
||||
)
|
||||
except TalkerError as e:
|
||||
QtWidgets.QMessageBox.critical(None, f"{e.source} {e.code_name} Error", f"{e}")
|
||||
return
|
||||
return qtutils.critical(None, f"{e.source} {e.code_name} Error", f"{e}")
|
||||
self.finish.emit(new_metadata, self.issue_number)
|
||||
|
||||
|
||||
@ -359,7 +358,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.fileSelectionList.add_path_list(file_list)
|
||||
|
||||
if self.config[0].Dialog_Flags__show_disclaimer:
|
||||
checked = OptionalMessageDialog.msg(
|
||||
|
||||
def set_checked(checked: bool) -> None:
|
||||
self.config[0].Dialog_Flags__show_disclaimer = not checked
|
||||
|
||||
OptionalMessageDialog.msg(
|
||||
self,
|
||||
"Welcome!",
|
||||
"""
|
||||
@ -378,10 +381,14 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
for more information.<br><br>
|
||||
Have fun!
|
||||
""",
|
||||
callback=set_checked,
|
||||
)
|
||||
self.config[0].Dialog_Flags__show_disclaimer = not checked
|
||||
if self.config[0].Dialog_Flags__notify_plugin_changes and getattr(sys, "frozen", False):
|
||||
checked = OptionalMessageDialog.msg(
|
||||
if self.config[0].Dialog_Flags__notify_plugin_changes and True:
|
||||
|
||||
def set_checked(checked: bool) -> None:
|
||||
self.config[0].Dialog_Flags__notify_plugin_changes = not checked
|
||||
|
||||
OptionalMessageDialog.msg(
|
||||
self,
|
||||
"Plugins Have moved!",
|
||||
f"""
|
||||
@ -391,23 +398,19 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
Metron: <a href="https://github.com/comictagger/metron_talker/releases">https://github.com/comictagger/metron_talker/releases</a><br/><br/>
|
||||
For more information on installing plugins see the wiki page:<br/><a href="https://github.com/comictagger/comictagger/wiki/Installing-plugins">https://github.com/comictagger/comictagger/wiki/Installing-plugins</a>
|
||||
""",
|
||||
callback=set_checked,
|
||||
)
|
||||
self.config[0].Dialog_Flags__notify_plugin_changes = not checked
|
||||
if self.enabled_tags():
|
||||
# This should never be false
|
||||
self.selected_write_tags = [self.enabled_tags()[0]]
|
||||
self.selected_read_tags = [self.enabled_tags()[0]]
|
||||
else:
|
||||
checked = OptionalMessageDialog.msg_no_checkbox(
|
||||
self,
|
||||
"No tags enabled",
|
||||
"""
|
||||
There are no tags enabled!<br/><br/>
|
||||
Go to the "Metadata Options" tab in settings to enable the builtin "Comic Rack" tags
|
||||
""",
|
||||
)
|
||||
|
||||
if self.config[0].General__check_for_new_version:
|
||||
self.check_latest_version_online()
|
||||
|
||||
self.export_window = ExportWindow(self)
|
||||
self.export_window.export.connect(self._repackage_archive)
|
||||
|
||||
def enabled_tags(self) -> Sequence[str]:
|
||||
return [tag.id for tag in tags.values() if tag.enabled]
|
||||
|
||||
@ -568,20 +571,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def repackage_archive(self) -> None:
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
non_zip_count = 0
|
||||
to_zip = []
|
||||
largest_page_size = 0
|
||||
for ca in ca_list:
|
||||
largest_page_size = max(largest_page_size, len(ca.get_page_name_list()))
|
||||
if not ca.is_zip():
|
||||
to_zip.append(ca)
|
||||
to_zip = [ca for ca in ca_list if not ca.is_zip()]
|
||||
|
||||
if not to_zip:
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, self.tr("Export as Zip Archive"), self.tr("Only ZIP archives are selected!")
|
||||
)
|
||||
logger.warning("Export as Zip Archive. Only ZIP archives are selected")
|
||||
return
|
||||
return qtutils.information(self, "Export as Zip Archive", "Only ZIP archives are selected!")
|
||||
|
||||
if not self.dirty_flag_verification(
|
||||
"Export as Zip Archive",
|
||||
@ -589,98 +583,90 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
):
|
||||
return
|
||||
|
||||
if to_zip:
|
||||
EW = ExportWindow(
|
||||
self,
|
||||
(
|
||||
f"You have selected {len(to_zip)} archive(s) to export to Zip format. "
|
||||
""" New archives will be created in the same folder as the original.
|
||||
self.export_window.show(len(to_zip))
|
||||
|
||||
Please choose config below, and select OK.
|
||||
"""
|
||||
),
|
||||
)
|
||||
EW.adjustSize()
|
||||
EW.setModal(True)
|
||||
if not EW.exec():
|
||||
return
|
||||
def _repackage_archive(self, export_config: ExportConfig) -> None:
|
||||
largest_page_size = 0
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
to_zip = []
|
||||
for ca in ca_list:
|
||||
if not ca.is_zip():
|
||||
to_zip.append(ca)
|
||||
if ca.get_number_of_pages() > largest_page_size:
|
||||
largest_page_size = ca.get_number_of_pages()
|
||||
|
||||
prog_dialog = None
|
||||
if len(to_zip) > 3 or largest_page_size > 24:
|
||||
prog_dialog = QtWidgets.QProgressDialog("", "Cancel", 0, non_zip_count, self)
|
||||
prog_dialog.setWindowTitle("Exporting as ZIP")
|
||||
prog_dialog.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
|
||||
prog_dialog.setMinimumDuration(300)
|
||||
center_window_on_parent(prog_dialog)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
prog_dialog = None
|
||||
if len(to_zip) > 3 or largest_page_size > 24:
|
||||
prog_dialog = QtWidgets.QProgressDialog("", "Cancel", 0, len(to_zip), self)
|
||||
prog_dialog.setWindowTitle("Exporting as ZIP")
|
||||
prog_dialog.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
|
||||
prog_dialog.setMinimumDuration(300)
|
||||
center_window_on_parent(prog_dialog)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
new_archives_to_add = []
|
||||
archives_to_remove = []
|
||||
skipped_list = []
|
||||
failed_list = []
|
||||
success_count = 0
|
||||
logger.debug("Exporting %d comics to zip", len(to_zip))
|
||||
new_archives_to_add = []
|
||||
archives_to_remove = []
|
||||
skipped_list = []
|
||||
failed_list = []
|
||||
success_count = 0
|
||||
logger.debug("Exporting %d comics to zip", len(to_zip))
|
||||
|
||||
for prog_idx, ca in enumerate(to_zip, 1):
|
||||
logger.debug("Exporting comic %d: %s", prog_idx, ca.path)
|
||||
if prog_idx % 10 == 0:
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
if prog_dialog is not None:
|
||||
if prog_dialog.wasCanceled():
|
||||
break
|
||||
for prog_idx, ca in enumerate(to_zip, 1):
|
||||
logger.debug("Exporting comic %d: %s", prog_idx, ca.path)
|
||||
if prog_dialog is not None:
|
||||
if prog_dialog.wasCanceled():
|
||||
break
|
||||
if prog_idx % 10 == 0 or len(ca_list) < 50:
|
||||
prog_dialog.setValue(prog_idx)
|
||||
prog_dialog.setLabelText(str(ca.path))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
export_name = ca.path.with_suffix(".cbz")
|
||||
export = True
|
||||
export_name = ca.path.with_suffix(".cbz")
|
||||
export = True
|
||||
|
||||
if export_name.exists():
|
||||
if EW.fileConflictBehavior == ExportConflictOpts.dontCreate:
|
||||
export = False
|
||||
skipped_list.append(ca.path)
|
||||
elif EW.fileConflictBehavior == ExportConflictOpts.createUnique:
|
||||
export_name = utils.unique_file(export_name)
|
||||
if export_name.exists():
|
||||
if export_config.conflict == ExportConflictOpts.DONT_CREATE:
|
||||
export = False
|
||||
skipped_list.append(ca.path)
|
||||
elif export_config.conflict == ExportConflictOpts.CREATE_UNIQUE:
|
||||
export_name = utils.unique_file(export_name)
|
||||
|
||||
if export:
|
||||
logger.debug("Exporting %s to %s", ca.path, export_name)
|
||||
if ca.export_as_zip(export_name):
|
||||
success_count += 1
|
||||
if EW.addToList:
|
||||
new_archives_to_add.append(str(export_name))
|
||||
if EW.deleteOriginal:
|
||||
archives_to_remove.append(ca)
|
||||
ca.path.unlink(missing_ok=True)
|
||||
if export:
|
||||
logger.debug("Exporting %s to %s", ca.path, export_name)
|
||||
if ca.export_as_zip(export_name):
|
||||
success_count += 1
|
||||
if export_config.add_to_list:
|
||||
new_archives_to_add.append(str(export_name))
|
||||
if export_config.delete_original:
|
||||
archives_to_remove.append(ca)
|
||||
ca.path.unlink(missing_ok=True)
|
||||
|
||||
else:
|
||||
# last export failed, so remove the zip, if it exists
|
||||
failed_list.append(ca.path)
|
||||
if export_name.exists():
|
||||
export_name.unlink(missing_ok=True)
|
||||
else:
|
||||
# last export failed, so remove the zip, if it exists
|
||||
failed_list.append(ca.path)
|
||||
if export_name.exists():
|
||||
export_name.unlink(missing_ok=True)
|
||||
|
||||
if prog_dialog is not None:
|
||||
prog_dialog.hide()
|
||||
self.fileSelectionList.remove_archive_list(archives_to_remove)
|
||||
if prog_dialog is not None:
|
||||
prog_dialog.hide()
|
||||
self.fileSelectionList.remove_archive_list(archives_to_remove)
|
||||
|
||||
summary = f"Successfully created {success_count} Zip archive(s)."
|
||||
if skipped_list:
|
||||
summary += (
|
||||
f"\n\nThe following {len(skipped_list)} archive(s) were skipped due to file name conflicts:\n"
|
||||
)
|
||||
for f in skipped_list:
|
||||
summary += f"\t{f}\n"
|
||||
if failed_list:
|
||||
summary += (
|
||||
f"\n\nThe following {len(failed_list)} archive(s) failed to export due to read/write errors:\n"
|
||||
)
|
||||
for f in failed_list:
|
||||
summary += f"\t{f}\n"
|
||||
summary = f"Successfully created {success_count} Zip archive(s)."
|
||||
if skipped_list:
|
||||
summary += f"\n\nThe following {len(skipped_list)} archive(s) were skipped due to file name conflicts:\n"
|
||||
for f in skipped_list:
|
||||
summary += f"\t{f}\n"
|
||||
if failed_list:
|
||||
summary += f"\n\nThe following {len(failed_list)} archive(s) failed to export due to read/write errors:\n"
|
||||
for f in failed_list:
|
||||
summary += f"\t{f}\n"
|
||||
|
||||
logger.info(summary)
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(summary)
|
||||
dlg.setWindowTitle("Archive Export to Zip Summary")
|
||||
dlg.exec()
|
||||
self.fileSelectionList.add_path_list(new_archives_to_add)
|
||||
logger.info(summary)
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(summary)
|
||||
dlg.setWindowTitle("Archive Export to Zip Summary")
|
||||
dlg.show()
|
||||
self.fileSelectionList.add_path_list(new_archives_to_add)
|
||||
|
||||
def about_app(self) -> None:
|
||||
website = "https://github.com/comictagger/comictagger"
|
||||
@ -688,7 +674,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
license_link = "http://www.apache.org/licenses/LICENSE-2.0"
|
||||
license_name = "Apache License 2.0"
|
||||
|
||||
msg_box = QtWidgets.QMessageBox()
|
||||
msg_box = QtWidgets.QMessageBox(self)
|
||||
msg_box.setWindowTitle("About " + self.appName)
|
||||
msg_box.setTextFormat(QtCore.Qt.TextFormat.RichText)
|
||||
msg_box.setIconPixmap(QtGui.QPixmap(":/graphics/about.png"))
|
||||
@ -704,7 +690,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
)
|
||||
|
||||
msg_box.setStandardButtons(QtWidgets.QMessageBox.StandardButton.Ok)
|
||||
msg_box.exec()
|
||||
msg_box.show()
|
||||
|
||||
def dragEnterEvent(self, event: QtGui.QDragEnterEvent) -> None:
|
||||
self.droppedFiles = []
|
||||
@ -1107,17 +1093,23 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.select_file(folder_mode=True)
|
||||
|
||||
def select_folder_archive(self) -> None:
|
||||
dialog = self.file_dialog(folder_mode=True)
|
||||
if dialog.exec():
|
||||
file_list = dialog.selectedFiles()
|
||||
if file_list:
|
||||
self.fileSelectionList.twList.selectRow(self.fileSelectionList.add_path_item(file_list[0])[0])
|
||||
self.select_file(folder_mode=True, recursive=True)
|
||||
|
||||
def select_file(self, folder_mode: bool = False) -> None:
|
||||
def select_file(self, folder_mode: bool = False, recursive: bool = True) -> None:
|
||||
dialog = self.file_dialog(folder_mode=folder_mode)
|
||||
if dialog.exec():
|
||||
file_list = dialog.selectedFiles()
|
||||
self.fileSelectionList.add_path_list(file_list)
|
||||
if recursive:
|
||||
dialog.filesSelected.connect(self._load_files)
|
||||
else:
|
||||
dialog.fileSelected.connect(self._load_single_file)
|
||||
dialog.open()
|
||||
|
||||
def _load_single_file(self, file: str) -> None:
|
||||
if file:
|
||||
self.fileSelectionList.twList.selectRow(self.fileSelectionList.add_path_item(file)[0])
|
||||
|
||||
def _load_files(self, files: list[str]) -> None:
|
||||
if files:
|
||||
self.fileSelectionList.add_path_list(files)
|
||||
|
||||
def file_dialog(self, folder_mode: bool = False) -> QtWidgets.QFileDialog:
|
||||
dialog = QtWidgets.QFileDialog(self)
|
||||
@ -1138,8 +1130,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def auto_identify_search(self) -> None:
|
||||
if self.comic_archive is None:
|
||||
QtWidgets.QMessageBox.warning(self, "Automatic Identify Search", "You need to load a comic first!")
|
||||
return
|
||||
return qtutils.warning(self, "Automatic Identify Search", "You need to load a comic first!")
|
||||
|
||||
self.query_online(autoselect=True)
|
||||
|
||||
@ -1151,18 +1142,16 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# Only need this check is the source has issue level data.
|
||||
if autoselect and issue_number == "":
|
||||
QtWidgets.QMessageBox.information(
|
||||
return qtutils.information(
|
||||
self,
|
||||
"Automatic Identify Search",
|
||||
"Can't auto-identify without an issue number. The auto-tag function has the 'If no issue number, assume \"1\"' option if desired.",
|
||||
)
|
||||
return
|
||||
|
||||
if str(self.leSeries.text()).strip() != "":
|
||||
series_name = str(self.leSeries.text()).strip()
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "Online Search", "Need to enter a series name to search.")
|
||||
return
|
||||
return qtutils.information(self, "Online Search", "Need to enter a series name to search.")
|
||||
|
||||
year = utils.xlate_int(self.lePubYear.text())
|
||||
|
||||
@ -1210,8 +1199,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.form_to_metadata()
|
||||
|
||||
if new_metadata is None or new_metadata.is_empty:
|
||||
QtWidgets.QMessageBox.critical(None, "Search", f"Could not find an issue {new_metadata} for that series")
|
||||
return
|
||||
return qtutils.critical(self, "Search", f"Could not find an issue {new_metadata} for that series")
|
||||
|
||||
self.metadata = prepare_metadata(self.metadata, new_metadata, self.config[0])
|
||||
# Now push the new combined data into the edit controls
|
||||
@ -1236,59 +1224,59 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.toast.show()
|
||||
|
||||
def write_tags(self) -> None:
|
||||
if self.metadata is not None and self.comic_archive is not None:
|
||||
if self.config[0].General__prompt_on_save:
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Save Tags",
|
||||
f"Are you sure you wish to save {', '.join([tags[tag_id].name() for tag_id in self.selected_write_tags])} tags to this archive?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
else:
|
||||
reply = QtWidgets.QMessageBox.StandardButton.Yes
|
||||
if self.metadata is None or self.comic_archive is None:
|
||||
return qtutils.information(self, "Whoops!", "No data to write!")
|
||||
|
||||
if reply != QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
self.form_to_metadata()
|
||||
|
||||
failed_tag: str = ""
|
||||
# Save each tag
|
||||
for tag_id in self.selected_write_tags:
|
||||
success = self.comic_archive.write_tags(self.metadata, tag_id)
|
||||
if not success:
|
||||
failed_tag = tags[tag_id].name()
|
||||
break
|
||||
|
||||
self.comic_archive.load_cache(set(tags))
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if failed_tag:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Save failed",
|
||||
f"The tag save operation seemed to fail for: {failed_tag}",
|
||||
)
|
||||
else:
|
||||
self.clear_dirty_flag()
|
||||
self.update_info_box()
|
||||
self.update_menus()
|
||||
|
||||
# Only try to read if write was successful
|
||||
self.metadata, _, error = self.read_selected_tags(self.selected_read_tags, self.comic_archive)
|
||||
if error is not None:
|
||||
QtWidgets.QMessageBox.warning(
|
||||
self,
|
||||
"Read Failed!",
|
||||
f"One or more of the selected read tags failed to load for {self.comic_archive.path}, check log for details",
|
||||
)
|
||||
logger.error("Failed to load metadata for %s: %s", self.ca.path, error)
|
||||
|
||||
self.fileSelectionList.update_current_row()
|
||||
self.update_ui_for_archive()
|
||||
if self.config[0].General__prompt_on_save:
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Save Tags",
|
||||
f"Are you sure you wish to save {', '.join([tags[tag_id].name() for tag_id in self.selected_write_tags])} tags to this archive?",
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, "Whoops!", "No data to write!")
|
||||
reply = QtWidgets.QMessageBox.StandardButton.Yes
|
||||
|
||||
if reply != QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
return
|
||||
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CursorShape.WaitCursor))
|
||||
self.form_to_metadata()
|
||||
|
||||
failed_tag: str = ""
|
||||
# Save each tag
|
||||
for tag_id in self.selected_write_tags:
|
||||
success = self.comic_archive.write_tags(self.metadata, tag_id)
|
||||
if not success:
|
||||
failed_tag = tags[tag_id].name()
|
||||
break
|
||||
|
||||
self.comic_archive.load_cache(set(tags))
|
||||
QtWidgets.QApplication.restoreOverrideCursor()
|
||||
|
||||
if failed_tag:
|
||||
qtutils.warning(
|
||||
self,
|
||||
"Save failed",
|
||||
f"The tag save operation seemed to fail for: {failed_tag}",
|
||||
)
|
||||
else:
|
||||
self.clear_dirty_flag()
|
||||
self.update_info_box()
|
||||
self.update_menus()
|
||||
|
||||
# Only try to read if write was successful
|
||||
self.metadata, _, error = self.read_selected_tags(self.selected_read_tags, self.comic_archive)
|
||||
if error is not None:
|
||||
logger.error("Failed to load metadata for %s: %s", self.ca.path, error)
|
||||
qtutils.warning(
|
||||
self,
|
||||
"Read Failed!",
|
||||
f"One or more of the selected read tags failed to load for {self.comic_archive.path}, check log for details",
|
||||
)
|
||||
|
||||
self.fileSelectionList.update_current_row()
|
||||
self.update_ui_for_archive()
|
||||
|
||||
def select_read_tags(self, tag_ids: list[str]) -> None:
|
||||
"""Should only be called from the combobox signal"""
|
||||
@ -1346,7 +1334,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
enable_widget(widget, md_field in enabled_widgets)
|
||||
|
||||
self.update_credit_colors()
|
||||
self.page_list_editor.select_read_tags(self.selected_write_tags)
|
||||
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:
|
||||
@ -1381,7 +1369,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
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)
|
||||
@ -1394,55 +1384,51 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
lang,
|
||||
)
|
||||
|
||||
editor = CreditEditorWindow(self, CreditEditorWindow.ModeEdit, old)
|
||||
editor.setModal(True)
|
||||
editor.exec()
|
||||
if editor.result():
|
||||
new = editor.get_credit()
|
||||
editor = CreditEditorWindow(self, self.selected_write_tags, row, mode, old)
|
||||
editor.creditChanged.connect(self._credit_changed)
|
||||
editor.show()
|
||||
|
||||
if new == old:
|
||||
# nothing has changed, just quit
|
||||
return
|
||||
def _edit_credit(self, credit: Credit, row: int) -> None:
|
||||
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)
|
||||
self.twCredits.item(row, self.md_attributes["credits.language"]).setText(lang)
|
||||
self.twCredits.item(row, self.md_attributes["credits.language"]).setData(
|
||||
QtCore.Qt.ItemDataRole.UserRole, credit.language
|
||||
)
|
||||
self.update_credit_primary_flag(row, credit.primary)
|
||||
|
||||
# check for dupes
|
||||
ok_to_mod = True
|
||||
if self.is_dupe_credit(row, new.role, new.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)
|
||||
self.update_credit_colors()
|
||||
self.set_dirty_flag()
|
||||
|
||||
if qmsg.exec() == 0:
|
||||
# merge
|
||||
if edit:
|
||||
# just remove the row that would be same
|
||||
self.twCredits.removeRow(row)
|
||||
# TODO -- need to find the row of the dupe, and possible change the primary flag
|
||||
def _add_credit(self, credit: Credit) -> None:
|
||||
# add new entry
|
||||
row = self.twCredits.rowCount()
|
||||
self.add_new_credit_entry(row, credit)
|
||||
|
||||
ok_to_mod = False
|
||||
self.update_credit_colors()
|
||||
self.set_dirty_flag()
|
||||
|
||||
if ok_to_mod:
|
||||
# modify it
|
||||
if edit:
|
||||
lang = utils.get_language_from_iso(new.language) or new.language
|
||||
self.twCredits.item(row, self.md_attributes["credits.role"]).setText(new.role)
|
||||
self.twCredits.item(row, self.md_attributes["credits.person"]).setText(new.person)
|
||||
self.twCredits.item(row, self.md_attributes["credits.language"]).setText(lang)
|
||||
self.twCredits.item(row, self.md_attributes["credits.language"]).setData(
|
||||
QtCore.Qt.ItemDataRole.UserRole, new.language
|
||||
)
|
||||
self.update_credit_primary_flag(row, new.primary)
|
||||
else:
|
||||
# add new entry
|
||||
row = self.twCredits.rowCount()
|
||||
self.add_new_credit_entry(row, new)
|
||||
def _credit_changed(self, credit: Credit, row: int, mode: EditMode) -> None:
|
||||
|
||||
self.update_credit_colors()
|
||||
self.set_dirty_flag()
|
||||
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)
|
||||
|
||||
if qmsg.exec() == 0 and mode == EditMode.EDIT:
|
||||
# just remove the row that would be same
|
||||
self.twCredits.removeRow(row)
|
||||
mode = EditMode.NEW
|
||||
|
||||
if mode == EditMode.EDIT:
|
||||
return self._edit_credit(credit, row)
|
||||
self._add_credit(credit)
|
||||
|
||||
def remove_credit(self) -> None:
|
||||
row = self.twCredits.currentRow()
|
||||
@ -1461,13 +1447,12 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
utils.parse_url(web_link)
|
||||
webbrowser.open_new_tab(web_link)
|
||||
except utils.LocationParseError:
|
||||
QtWidgets.QMessageBox.warning(self, "Web Link", "Web Link is invalid.")
|
||||
qtutils.warning(self, "Web Link", "Web Link is invalid.")
|
||||
|
||||
def show_settings(self) -> None:
|
||||
settingswin = SettingsWindow(self, self.config, self.talkers)
|
||||
settingswin.exec()
|
||||
settingswin.result()
|
||||
self.adjust_source_combo()
|
||||
settingswin.finished.connect(self.adjust_source_combo)
|
||||
settingswin.show()
|
||||
|
||||
def set_app_position(self) -> None:
|
||||
if self.config[0].internal__window_width != 0:
|
||||
@ -1640,12 +1625,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
file_md_count[tag_id] += 1
|
||||
|
||||
if has_md_count == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
qtutils.information(
|
||||
self,
|
||||
"Remove Tags",
|
||||
f"No archives with {', '.join([tags[tag_id].name() for tag_id in tag_ids])} tags selected!",
|
||||
)
|
||||
return
|
||||
|
||||
if has_md_count != 0 and not self.dirty_flag_verification(
|
||||
"Remove Tags", "If you remove tags now, unsaved data in the form will be lost. Are you sure?"
|
||||
@ -1690,7 +1674,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
progdialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
self._reload_page()
|
||||
self.update_info_box()
|
||||
self.update_menus()
|
||||
|
||||
@ -1703,7 +1687,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(summary)
|
||||
dlg.setWindowTitle("Tag Remove Summary")
|
||||
dlg.exec()
|
||||
dlg.show()
|
||||
|
||||
def copy_tags(self) -> None:
|
||||
# copy the indicated tags in the archive
|
||||
@ -1718,10 +1702,9 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
dest_tag_ids.remove(src_tag_ids[0])
|
||||
|
||||
if not dest_tag_ids:
|
||||
QtWidgets.QMessageBox.information(
|
||||
return qtutils.information(
|
||||
self, "Copy Tags", "Can't copy tag tag onto itself. Read tag and modify tag must be different."
|
||||
)
|
||||
return
|
||||
|
||||
for ca in ca_list:
|
||||
for tag_id in src_tag_ids:
|
||||
@ -1730,12 +1713,11 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
continue
|
||||
|
||||
if has_src_count == 0:
|
||||
QtWidgets.QMessageBox.information(
|
||||
return qtutils.information(
|
||||
self,
|
||||
"Copy Tags",
|
||||
f"No archives with {', '.join([tags[tag_id].name() for tag_id in src_tag_ids])} tags selected!",
|
||||
)
|
||||
return
|
||||
|
||||
if has_src_count != 0 and not self.dirty_flag_verification(
|
||||
"Copy Tags", "If you copy tags now, unsaved data in the form may be lost. Are you sure?"
|
||||
@ -1799,7 +1781,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
prog_dialog.hide()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
self._reload_page()
|
||||
self.update_info_box()
|
||||
self.update_menus()
|
||||
|
||||
@ -1812,7 +1794,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(summary)
|
||||
dlg.setWindowTitle("Tag Copy Summary")
|
||||
dlg.exec()
|
||||
dlg.show()
|
||||
|
||||
def auto_tag_log(self, text: str) -> None:
|
||||
if self.atprogdialog is not None:
|
||||
@ -1825,8 +1807,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
tag_names = ", ".join([tags[tag_id].name() for tag_id in self.selected_write_tags])
|
||||
|
||||
if not ca_list:
|
||||
QtWidgets.QMessageBox.information(self, "Auto-Tag", "No archives selected!")
|
||||
return
|
||||
return qtutils.information(self, "Auto-Tag", "No archives selected!")
|
||||
|
||||
if not self.dirty_flag_verification(
|
||||
"Auto-Tag", "If you auto-tag now, unsaved data in the form will be lost. Are you sure?"
|
||||
@ -1842,28 +1823,35 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
),
|
||||
)
|
||||
|
||||
atstartdlg.adjustSize()
|
||||
atstartdlg.setModal(True)
|
||||
if not atstartdlg.exec():
|
||||
return
|
||||
atstartdlg.startAutoTag.connect(self._start_auto_tag)
|
||||
|
||||
atstartdlg.open()
|
||||
|
||||
def _start_auto_tag(self, auto_tag: AutoTagSettings) -> None:
|
||||
# persist some settings because it's probably going to be used next time
|
||||
self.config[0].Auto_Tag__save_on_low_confidence = auto_tag.settings["save_on_low_confidence"]
|
||||
self.config[0].Auto_Tag__use_year_when_identifying = auto_tag.settings["use_year_when_identifying"]
|
||||
self.config[0].Auto_Tag__assume_issue_one = auto_tag.settings["assume_issue_one"]
|
||||
self.config[0].Auto_Tag__ignore_leading_numbers_in_filename = auto_tag.settings[
|
||||
"ignore_leading_numbers_in_filename"
|
||||
]
|
||||
self.config[0].internal__remove_archive_after_successful_match = auto_tag.remove_after_success
|
||||
|
||||
ca_list = self.fileSelectionList.get_selected_archive_list()
|
||||
self.atprogdialog = AutoTagProgressWindow(self, self.current_talker())
|
||||
self.atprogdialog.open()
|
||||
self.atprogdialog.progressBar.setMaximum(len(ca_list))
|
||||
self.atprogdialog.setWindowTitle("Auto-Tagging")
|
||||
|
||||
center_window_on_parent(self.atprogdialog)
|
||||
temp_opts = cast(ct_ns, settngs.get_namespace(self.config, True, True, True, False)[0])
|
||||
temp_opts.Auto_Tag__clear_tags = atstartdlg.cbxClearMetadata.isChecked()
|
||||
temp_opts.Issue_Identifier__series_match_identify_thresh = atstartdlg.name_length_match_tolerance
|
||||
temp_opts.Auto_Tag__ignore_leading_numbers_in_filename = atstartdlg.ignore_leading_digits_in_filename
|
||||
temp_opts.Auto_Tag__use_year_when_identifying = not atstartdlg.dont_use_year
|
||||
temp_opts.Auto_Tag__assume_issue_one = atstartdlg.assume_issue_one
|
||||
temp_opts.internal__remove_archive_after_successful_match = atstartdlg.remove_after_success
|
||||
temp_opts.Auto_Tag__clear_tags = auto_tag.settings["clear_tags"]
|
||||
|
||||
temp_opts.Issue_Identifier__series_match_identify_thresh = auto_tag.series_match_identify_thresh
|
||||
|
||||
temp_opts.Runtime_Options__tags_read = self.selected_read_tags
|
||||
temp_opts.Runtime_Options__tags_write = self.selected_write_tags
|
||||
|
||||
self.autotagthread = AutoTagThread(atstartdlg.search_string, ca_list, self.config[0], self.current_talker())
|
||||
self.autotagthread = AutoTagThread(auto_tag.search_string, ca_list, self.config[0], self.current_talker())
|
||||
|
||||
self.autotagthread.autoTagComplete.connect(self.auto_tag_finished)
|
||||
self.autotagthread.autoTagLogMsg.connect(self.auto_tag_log)
|
||||
@ -1875,6 +1863,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.auto_tag_log("==========================================================================\n")
|
||||
self.auto_tag_log(f"Auto-Tagging Started for {len(ca_list)} items\n")
|
||||
self.autotagthread.start()
|
||||
self.atprogdialog.open()
|
||||
|
||||
def auto_tag_finished(self, match_results: OnlineMatchResults, archives_to_remove: list[ComicArchive]) -> None:
|
||||
tag_names = ", ".join([tags[tag_id].name() for tag_id in self.selected_write_tags])
|
||||
@ -1882,11 +1871,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.atprogdialog.close()
|
||||
|
||||
self.fileSelectionList.remove_archive_list(archives_to_remove)
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
|
||||
new_ca = self.fileSelectionList.get_current_archive()
|
||||
if new_ca is not None:
|
||||
self.load_archive(new_ca)
|
||||
self._reload_page()
|
||||
self.atprogdialog = None
|
||||
|
||||
summary = f"<p>{self.current_talker().attribution}</p>"
|
||||
@ -1908,42 +1893,51 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
self.auto_tag_log(summary)
|
||||
|
||||
selectable = match_results.multiple_matches or match_results.low_confidence_matches
|
||||
if selectable:
|
||||
summary += (
|
||||
"\n\nDo you want to manually select the ones with multiple matches and/or low-confidence matches now?"
|
||||
)
|
||||
if not selectable:
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(summary)
|
||||
dlg.setWindowTitle("Tag Remove Summary")
|
||||
dlg.show()
|
||||
logger.info(summary)
|
||||
return
|
||||
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Auto-Tag Summary",
|
||||
summary,
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
match_results.multiple_matches.extend(match_results.low_confidence_matches)
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
matchdlg = AutoTagMatchWindow(
|
||||
self,
|
||||
match_results.multiple_matches,
|
||||
self.selected_write_tags,
|
||||
self.config[0],
|
||||
self.current_talker(),
|
||||
)
|
||||
matchdlg.exec()
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
new_ca = self.fileSelectionList.get_current_archive()
|
||||
if new_ca is not None:
|
||||
self.load_archive(new_ca)
|
||||
|
||||
else:
|
||||
QtWidgets.QMessageBox.information(self, self.tr("Auto-Tag Summary"), self.tr(summary))
|
||||
summary += (
|
||||
"\n\nDo you want to manually select the ones with multiple matches and/or low-confidence matches now?"
|
||||
)
|
||||
logger.info(summary)
|
||||
|
||||
reply = QtWidgets.QMessageBox.question(
|
||||
self,
|
||||
"Auto-Tag Summary",
|
||||
summary,
|
||||
QtWidgets.QMessageBox.StandardButton.Yes,
|
||||
QtWidgets.QMessageBox.StandardButton.No,
|
||||
)
|
||||
|
||||
match_results.multiple_matches.extend(match_results.low_confidence_matches)
|
||||
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
||||
matchdlg = AutoTagMatchWindow(
|
||||
self,
|
||||
match_results.multiple_matches,
|
||||
self.selected_write_tags,
|
||||
self.config[0],
|
||||
self.current_talker(),
|
||||
)
|
||||
|
||||
matchdlg.open()
|
||||
matchdlg.finished.connect(self._reload_page)
|
||||
return
|
||||
|
||||
def _reload_page(self) -> None:
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
new_ca = self.fileSelectionList.get_current_archive()
|
||||
if new_ca is not None:
|
||||
self.load_archive(new_ca)
|
||||
|
||||
def exception(self, message: str) -> None:
|
||||
errorbox = QtWidgets.QMessageBox()
|
||||
errorbox.setText(message)
|
||||
errorbox.exec()
|
||||
errorbox.open()
|
||||
|
||||
def dirty_flag_verification(self, title: str, desc: str) -> bool:
|
||||
if self.dirty_flag:
|
||||
@ -2004,7 +1998,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
dlg = LogWindow(self)
|
||||
dlg.set_text(self.comic_archive.read_raw_tags(tag.id))
|
||||
dlg.setWindowTitle(f"Raw {tag.name()} Tag View")
|
||||
dlg.exec()
|
||||
dlg.open()
|
||||
|
||||
def show_wiki(self) -> None:
|
||||
webbrowser.open("https://github.com/comictagger/comictagger/wiki")
|
||||
@ -2057,9 +2051,8 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
"File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?"
|
||||
):
|
||||
dlg = RenameWindow(self, ca_list, self.selected_read_tags, self.config, self.talkers)
|
||||
if dlg.exec() and self.comic_archive is not None:
|
||||
self.fileSelectionList.update_selected_rows()
|
||||
self.load_archive(self.comic_archive)
|
||||
dlg.finished.connect(self._reload_page)
|
||||
dlg.open()
|
||||
|
||||
def load_archive(self, comic_archive: ComicArchive) -> None:
|
||||
self.comic_archive = None
|
||||
@ -2068,8 +2061,7 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
|
||||
if not os.path.exists(comic_archive.path):
|
||||
self.fileSelectionList.dirty_flag = False
|
||||
self.fileSelectionList.remove_archive_list([comic_archive])
|
||||
QtCore.QTimer.singleShot(1, self.fileSelectionList.revert_selection)
|
||||
self.fileSelectionList.remove_deleted()
|
||||
return
|
||||
|
||||
self.config[0].internal__last_opened_folder = os.path.abspath(os.path.split(comic_archive.path)[0])
|
||||
@ -2123,16 +2115,20 @@ class TaggerWindow(QtWidgets.QMainWindow):
|
||||
def version_check_complete(self, new_version: tuple[str, str]) -> None:
|
||||
if new_version[0] not in (self.version, self.config[0].Dialog_Flags__dont_notify_about_this_version):
|
||||
website = "https://github.com/comictagger/comictagger"
|
||||
checked = OptionalMessageDialog.msg(
|
||||
|
||||
def set_checked(checked: bool) -> None:
|
||||
if checked:
|
||||
self.config[0].Dialog_Flags__dont_notify_about_this_version = new_version[0]
|
||||
|
||||
OptionalMessageDialog.msg(
|
||||
self,
|
||||
"New version available!",
|
||||
f"New version ({new_version[1]}) available!<br>(You are currently running {self.version})<br><br>"
|
||||
f"Visit <a href='{website}/releases/latest'>{website}/releases/latest</a> for more info.<br><br>",
|
||||
False,
|
||||
"Don't tell me about this version again",
|
||||
callback=set_checked,
|
||||
checked=False,
|
||||
check_text="Don't tell me about this version again",
|
||||
)
|
||||
if checked:
|
||||
self.config[0].Dialog_Flags__dont_notify_about_this_version = new_version[0]
|
||||
|
||||
def on_incoming_socket_connection(self) -> None:
|
||||
# Accept connection from other instance.
|
||||
|
@ -8,21 +8,16 @@ import traceback
|
||||
import webbrowser
|
||||
from collections.abc import Collection, Sequence
|
||||
|
||||
from PyQt6.QtCore import QUrl
|
||||
from PyQt6.QtGui import QGuiApplication, QPalette
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from PyQt6 import QtGui, QtWidgets
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtCore import Qt, QUrl
|
||||
from PyQt6.QtGui import QGuiApplication, QPalette
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
qt_available = True
|
||||
except ImportError:
|
||||
qt_available = False
|
||||
|
||||
if qt_available:
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
@ -30,6 +25,7 @@ if qt_available:
|
||||
except ImportError:
|
||||
pil_available = False
|
||||
active_palette: QPalette | None = None
|
||||
|
||||
try:
|
||||
from PyQt6.QtWebEngineCore import QWebEnginePage
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
@ -156,7 +152,7 @@ if qt_available:
|
||||
if e:
|
||||
trace = "\n".join(traceback.format_exception(type(e), e, e.__traceback__))
|
||||
|
||||
QtWidgets.QMessageBox.critical(QtWidgets.QMainWindow(), "Error", msg + trace)
|
||||
return critical(QtWidgets.QMainWindow(), "Error", msg + trace)
|
||||
|
||||
def enable_widget(widget: QtWidgets.QWidget | Collection[QtWidgets.QWidget], enable: bool) -> None:
|
||||
if isinstance(widget, Sequence):
|
||||
@ -240,3 +236,27 @@ if qt_available:
|
||||
# QSplitter has issues with replacing a widget before it's been first shown. Assume it should be visible
|
||||
new_widget.show()
|
||||
return new_widget
|
||||
|
||||
def critical(parent: QWidget | None, title: str, text: str) -> None:
|
||||
qmsg = QtWidgets.QMessageBox(parent)
|
||||
qmsg.setIcon(qmsg.Icon.Critical)
|
||||
qmsg.setText(title)
|
||||
qmsg.setInformativeText(text)
|
||||
return qmsg.show()
|
||||
|
||||
def warning(parent: QWidget | None, title: str, text: str) -> None:
|
||||
qmsg = QtWidgets.QMessageBox(parent)
|
||||
qmsg.setIcon(qmsg.Icon.Warning)
|
||||
qmsg.setText(title)
|
||||
qmsg.setInformativeText(text)
|
||||
return qmsg.show()
|
||||
|
||||
def information(parent: QWidget | None, title: str, text: str) -> None:
|
||||
qmsg = QtWidgets.QMessageBox(parent)
|
||||
qmsg.setIcon(qmsg.Icon.Information)
|
||||
qmsg.setText(title)
|
||||
qmsg.setInformativeText(text)
|
||||
return qmsg.show()
|
||||
|
||||
except ImportError:
|
||||
qt_available = False
|
||||
|
@ -10,6 +10,7 @@ from PyQt6 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from comictaggerlib.coverimagewidget import CoverImageWidget
|
||||
from comictaggerlib.ctsettings import ct_ns, group_for_plugin
|
||||
from comictaggerlib.ui import qtutils
|
||||
from comictalker.comictalker import ComicTalker
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -76,9 +77,9 @@ def generate_api_widgets(
|
||||
def call_check_api(*args: Any, tab: TalkerTab, talker: ComicTalker, definitions: settngs.Definitions) -> None:
|
||||
check_text, check_bool = talker.check_status(get_config_from_tab(tab, definitions[group_for_plugin(talker)]))
|
||||
if check_bool:
|
||||
QtWidgets.QMessageBox.information(None, "API Test Success", check_text)
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(None, "API Test Failed", check_text)
|
||||
return qtutils.information(TalkerTab.tab, "API Test Success", check_text)
|
||||
|
||||
qtutils.warning(TalkerTab.tab, "API Test Failed", check_text)
|
||||
|
||||
# get the actual config objects in case they have overwritten the default
|
||||
btn_test_row = None
|
||||
|
Reference in New Issue
Block a user